From 6507e4409e723f15f4746cc956f6caae3a753524 Mon Sep 17 00:00:00 2001 From: "Laszlo Boszormenyi (GCS)" Date: Sat, 21 Mar 2026 08:16:43 +0100 Subject: [PATCH] Import fuse3_3.18.2.orig.tar.gz [dgit import orig fuse3_3.18.2.orig.tar.gz] --- .ackrc | 2 + .clang-format | 119 + .codespellrc | 13 + .dir-locals.el | 64 + AUTHORS | 287 + ChangeLog.rst | 950 ++ GPL2.txt | 339 + LGPL2.txt | 502 ++ LICENSE | 13 + README.md | 162 + SECURITY.md | 20 + checkpatch.pl | 7820 +++++++++++++++++ dev-docs/extend-authors.sh | 35 + dev-docs/release-process.md | 61 + doc/Doxyfile | 2695 ++++++ doc/README.NFS | 44 + doc/README.fuse-io-uring | 38 + doc/README.fuse_reply_errors | 5 + doc/README.notifications | 37 + doc/fast17-vangoor.pdf | Bin 0 -> 599157 bytes doc/fusermount3.1 | 44 + doc/html/annotated.html | 76 + doc/html/bc_s.png | Bin 0 -> 676 bytes doc/html/bc_sd.png | Bin 0 -> 635 bytes doc/html/classes.html | 60 + doc/html/closed.png | Bin 0 -> 132 bytes .../dir_042d006301a0f6339943f03c22d70dab.html | 55 + .../dir_13e138d54eb8818da29c3992edef070a.html | 81 + .../dir_151531ba243f27a508f84a018a5044f9.html | 102 + .../dir_2379d157e40387655d5d09cb33050082.html | 69 + .../dir_23ec12649285f9fabf3a6b7380226c28.html | 63 + .../dir_3141c10b43d1ded6ee3c902c2fe1c48b.html | 81 + .../dir_4d35850f380bf2b0e1e23612de44a403.html | 69 + .../dir_567612448af13c8fdbe428427cb913c5.html | 75 + .../dir_5cbcf3cc0c552e5807bf52f5541bdba8.html | 75 + .../dir_5dc95f8183277f5c6f5c244c43efe208.html | 99 + .../dir_5fb9f92dc673d37c57217e3feec2be83.html | 108 + .../dir_7015ddbf9bcaeb01169723f1ad3dc4f8.html | 81 + .../dir_73f848c5a0a9108732ea2e7217c7aea5.html | 63 + .../dir_97aefd0d527b934f1d99a682da8fe6a9.html | 108 + .../dir_98164a96c6a79e43c7a77c344d27e55d.html | 63 + .../dir_a1e8bb6aacb6fb222eb29390226a8aec.html | 69 + .../dir_aa785a52b370a0eb18aa670afa0403e9.html | 63 + .../dir_bea620c7bc056d32632a5444abf88a37.html | 108 + .../dir_cfafba98a580ce4b62f8a6fa96d7cbb0.html | 99 + .../dir_d44c64559bbebec7f509842c48db8b23.html | 75 + .../dir_de6a3d84c7bdc16a0560151553ce19f4.html | 63 + .../dir_de8f9914a0c6d3fefb7eee74461f97ba.html | 99 + .../dir_e1dbc8ba94a86723d4c32227b7c46099.html | 63 + .../dir_e68e8157741866f444e17edd764ebbae.html | 55 + .../dir_e7cc7929bd1ffaa820e10e01d9c95abd.html | 77 + .../dir_ee79e556e893a265551a6396eb768ff4.html | 75 + .../dir_ef89b861fb26f7108388f05c496b561a.html | 63 + .../dir_f353b27f473de27f9d371ee54fafa676.html | 63 + .../dir_f77c7566d5b54b3a8f6688c1c1718be3.html | 79 + .../dir_ff8734a6ed1553e4cdaef764f37a71c4.html | 55 + doc/html/doc.svg | 12 + doc/html/docd.svg | 12 + doc/html/doxygen.css | 2045 +++++ doc/html/doxygen.svg | 28 + doc/html/dynsections.js | 199 + doc/html/example_2cuse_8c.html | 409 + doc/html/example_2cuse_8c_source.html | 395 + doc/html/example_2cuse__client_8c.html | 214 + doc/html/example_2cuse__client_8c_source.html | 188 + doc/html/example_2hello_8c.html | 265 + doc/html/example_2hello_8c_source.html | 254 + doc/html/example_2hello__ll_8c.html | 389 + doc/html/example_2hello__ll_8c_source.html | 376 + doc/html/example_2hello__ll__uds_8c.html | 391 + .../example_2hello__ll__uds_8c_source.html | 442 + doc/html/example_2invalidate__path_8c.html | 390 + .../example_2invalidate__path_8c_source.html | 370 + doc/html/example_2ioctl_8c.html | 294 + doc/html/example_2ioctl_8c_source.html | 283 + doc/html/example_2ioctl_8h.html | 96 + doc/html/example_2ioctl_8h_source.html | 92 + doc/html/example_2ioctl__client_8c.html | 136 + .../example_2ioctl__client_8c_source.html | 123 + .../example_2notify__inval__entry_8c.html | 497 ++ ...ample_2notify__inval__entry_8c_source.html | 446 + .../example_2notify__inval__inode_8c.html | 483 + ...ample_2notify__inval__inode_8c_source.html | 443 + .../example_2notify__store__retrieve_8c.html | 560 ++ ...le_2notify__store__retrieve_8c_source.html | 522 ++ doc/html/example_2null_8c.html | 779 ++ doc/html/example_2null_8c_source.html | 196 + doc/html/example_2passthrough_8c.html | 679 ++ doc/html/example_2passthrough_8c_source.html | 665 ++ doc/html/example_2passthrough__fh_8c.html | 783 ++ .../example_2passthrough__fh_8c_source.html | 767 ++ ...ample_2passthrough__helpers_8h_source.html | 182 + doc/html/example_2passthrough__ll_8c.html | 1546 ++++ .../example_2passthrough__ll_8c_source.html | 1525 ++++ doc/html/example_2poll_8c.html | 379 + doc/html/example_2poll_8c_source.html | 364 + doc/html/example_2poll__client_8c.html | 145 + doc/html/example_2poll__client_8c_source.html | 131 + doc/html/example_2printcap_8c.html | 240 + doc/html/example_2printcap_8c_source.html | 231 + doc/html/fast17-vangoor.pdf | Bin 0 -> 599157 bytes doc/html/files.html | 255 + doc/html/folderclosed.svg | 11 + doc/html/folderclosedd.svg | 11 + doc/html/folderopen.svg | 17 + doc/html/folderopend.svg | 12 + doc/html/functions.html | 258 + doc/html/functions_vars.html | 258 + ..._2example_2notify__store__retrieve_8c.html | 560 ++ ...le_2notify__store__retrieve_8c_source.html | 522 ++ doc/html/fuse-3_817_84_2example_2null_8c.html | 779 ++ ...use-3_817_84_2example_2null_8c_source.html | 196 + ...use-3_817_84_2example_2passthrough_8c.html | 679 ++ ...17_84_2example_2passthrough_8c_source.html | 649 ++ ...3_817_84_2example_2passthrough__fh_8c.html | 782 ++ ...4_2example_2passthrough__fh_8c_source.html | 751 ++ ...ample_2passthrough__helpers_8h_source.html | 136 + ...3_817_84_2example_2passthrough__ll_8c.html | 1546 ++++ ...4_2example_2passthrough__ll_8c_source.html | 1509 ++++ doc/html/fuse-3_817_84_2example_2poll_8c.html | 379 + ...use-3_817_84_2example_2poll_8c_source.html | 364 + ...se-3_817_84_2example_2poll__client_8c.html | 145 + ...7_84_2example_2poll__client_8c_source.html | 131 + .../fuse-3_817_84_2example_2printcap_8c.html | 240 + ...3_817_84_2example_2printcap_8c_source.html | 231 + ...84_2include_2cuse__lowlevel_8h_source.html | 152 + doc/html/fuse-3_817_84_2include_2fuse_8h.html | 838 ++ ...use-3_817_84_2include_2fuse_8h_source.html | 647 ++ ...se-3_817_84_2include_2fuse__common_8h.html | 1284 +++ ...7_84_2include_2fuse__common_8h_source.html | 502 ++ ...7_84_2include_2fuse__kernel_8h_source.html | 1097 +++ .../fuse-3_817_84_2include_2fuse__log_8h.html | 268 + ..._817_84_2include_2fuse__log_8h_source.html | 112 + ...-3_817_84_2include_2fuse__lowlevel_8h.html | 2363 +++++ ...84_2include_2fuse__lowlevel_8h_source.html | 683 ++ ...nclude_2fuse__mount__compat_8h_source.html | 109 + .../fuse-3_817_84_2include_2fuse__opt_8h.html | 587 ++ ..._817_84_2include_2fuse__opt_8h_source.html | 149 + .../fuse-3_817_84_2lib_2buffer_8c_source.html | 410 + .../fuse-3_817_84_2lib_2compat_8c_source.html | 148 + ...817_84_2lib_2cuse__lowlevel_8c_source.html | 451 + .../fuse-3_817_84_2lib_2fuse_8c_source.html | 5433 ++++++++++++ ...fuse-3_817_84_2lib_2fuse__i_8h_source.html | 264 + ...se-3_817_84_2lib_2fuse__log_8c_source.html | 172 + ...e-3_817_84_2lib_2fuse__loop_8c_source.html | 114 + ...817_84_2lib_2fuse__loop__mt_8c_source.html | 607 ++ ...817_84_2lib_2fuse__lowlevel_8c_source.html | 3916 +++++++++ ...e-3_817_84_2lib_2fuse__misc_8h_source.html | 111 + ...se-3_817_84_2lib_2fuse__opt_8c_source.html | 516 ++ ..._817_84_2lib_2fuse__signals_8c_source.html | 276 + .../fuse-3_817_84_2lib_2helper_8c_source.html | 620 ++ ...817_84_2lib_2modules_2iconv_8c_source.html | 816 ++ ...17_84_2lib_2modules_2subdir_8c_source.html | 767 ++ .../fuse-3_817_84_2lib_2mount_8c_source.html | 792 ++ ...e-3_817_84_2lib_2mount__bsd_8c_source.html | 333 + ...-3_817_84_2lib_2mount__util_8c_source.html | 437 + ...-3_817_84_2lib_2mount__util_8h_source.html | 78 + .../fuse-3_817_84_2lib_2util_8c_source.html | 95 + .../fuse-3_817_84_2lib_2util_8h_source.html | 97 + doc/html/fuse-3_817_84_2test_2hello_8c.html | 265 + .../fuse-3_817_84_2test_2hello_8c_source.html | 250 + ...17_84_2test_2readdir__inode_8c_source.html | 117 + ...test_2release__unlink__race_8c_source.html | 183 + ..._817_84_2test_2stracedecode_8c_source.html | 259 + ...e-3_817_84_2test_2test__abi_8c_source.html | 80 + ...817_84_2test_2test__setattr_8c_source.html | 272 + ...17_84_2test_2test__syscalls_8c_source.html | 2258 +++++ ...est_2test__want__conversion_8c_source.html | 264 + ...4_2test_2test__write__cache_8c_source.html | 404 + ...17_84_2test_2wrong__command_8c_source.html | 76 + ...-3_817_84_2util_2fusermount_8c_source.html | 1796 ++++ ...3_817_84_2util_2mount_8fuse_8c_source.html | 515 ++ doc/html/fuse-3_818_81_2example_2cuse_8c.html | 409 + ...use-3_818_81_2example_2cuse_8c_source.html | 395 + ...se-3_818_81_2example_2cuse__client_8c.html | 214 + ...8_81_2example_2cuse__client_8c_source.html | 188 + .../fuse-3_818_81_2example_2hello_8c.html | 265 + ...se-3_818_81_2example_2hello_8c_source.html | 254 + .../fuse-3_818_81_2example_2hello__ll_8c.html | 389 + ..._818_81_2example_2hello__ll_8c_source.html | 376 + ...-3_818_81_2example_2hello__ll__uds_8c.html | 391 + ...81_2example_2hello__ll__uds_8c_source.html | 442 + ..._818_81_2example_2invalidate__path_8c.html | 390 + ..._2example_2invalidate__path_8c_source.html | 370 + .../fuse-3_818_81_2example_2ioctl_8c.html | 294 + ...se-3_818_81_2example_2ioctl_8c_source.html | 283 + .../fuse-3_818_81_2example_2ioctl_8h.html | 96 + ...se-3_818_81_2example_2ioctl_8h_source.html | 92 + ...e-3_818_81_2example_2ioctl__client_8c.html | 136 + ..._81_2example_2ioctl__client_8c_source.html | 123 + ..._81_2example_2notify__inval__entry_8c.html | 497 ++ ...ample_2notify__inval__entry_8c_source.html | 446 + ..._81_2example_2notify__inval__inode_8c.html | 483 + ...ample_2notify__inval__inode_8c_source.html | 443 + ..._2example_2notify__store__retrieve_8c.html | 560 ++ ...le_2notify__store__retrieve_8c_source.html | 522 ++ doc/html/fuse-3_818_81_2example_2null_8c.html | 779 ++ ...use-3_818_81_2example_2null_8c_source.html | 196 + ...use-3_818_81_2example_2passthrough_8c.html | 679 ++ ...18_81_2example_2passthrough_8c_source.html | 665 ++ ...3_818_81_2example_2passthrough__fh_8c.html | 783 ++ ...1_2example_2passthrough__fh_8c_source.html | 767 ++ ...ample_2passthrough__helpers_8h_source.html | 182 + ...3_818_81_2example_2passthrough__ll_8c.html | 1546 ++++ ...1_2example_2passthrough__ll_8c_source.html | 1525 ++++ doc/html/fuse-3_818_81_2example_2poll_8c.html | 379 + ...use-3_818_81_2example_2poll_8c_source.html | 364 + ...se-3_818_81_2example_2poll__client_8c.html | 145 + ...8_81_2example_2poll__client_8c_source.html | 131 + .../fuse-3_818_81_2example_2printcap_8c.html | 240 + ...3_818_81_2example_2printcap_8c_source.html | 231 + ...81_2include_2cuse__lowlevel_8h_source.html | 152 + doc/html/fuse-3_818_81_2include_2fuse_8h.html | 839 ++ ...use-3_818_81_2include_2fuse_8h_source.html | 650 ++ ...se-3_818_81_2include_2fuse__common_8h.html | 1303 +++ ...8_81_2include_2fuse__common_8h_source.html | 470 + ...8_81_2include_2fuse__kernel_8h_source.html | 1184 +++ .../fuse-3_818_81_2include_2fuse__log_8h.html | 268 + ..._818_81_2include_2fuse__log_8h_source.html | 113 + ...-3_818_81_2include_2fuse__lowlevel_8h.html | 2530 ++++++ ...81_2include_2fuse__lowlevel_8h_source.html | 693 ++ ...nclude_2fuse__mount__compat_8h_source.html | 109 + .../fuse-3_818_81_2include_2fuse__opt_8h.html | 587 ++ ..._818_81_2include_2fuse__opt_8h_source.html | 145 + .../fuse-3_818_81_2lib_2buffer_8c_source.html | 406 + .../fuse-3_818_81_2lib_2compat_8c_source.html | 148 + ...818_81_2lib_2cuse__lowlevel_8c_source.html | 458 + .../fuse-3_818_81_2lib_2fuse_8c_source.html | 5449 ++++++++++++ ...fuse-3_818_81_2lib_2fuse__i_8h_source.html | 292 + ...se-3_818_81_2lib_2fuse__log_8c_source.html | 125 + ...e-3_818_81_2lib_2fuse__loop_8c_source.html | 113 + ...818_81_2lib_2fuse__loop__mt_8c_source.html | 604 ++ ...818_81_2lib_2fuse__lowlevel_8c_source.html | 4675 ++++++++++ ...e-3_818_81_2lib_2fuse__misc_8h_source.html | 111 + ...se-3_818_81_2lib_2fuse__opt_8c_source.html | 504 ++ ..._818_81_2lib_2fuse__signals_8c_source.html | 281 + ...-3_818_81_2lib_2fuse__uring_8c_source.html | 1015 +++ ...818_81_2lib_2fuse__uring__i_8h_source.html | 149 + .../fuse-3_818_81_2lib_2helper_8c_source.html | 606 ++ ...818_81_2lib_2modules_2iconv_8c_source.html | 835 ++ ...18_81_2lib_2modules_2subdir_8c_source.html | 786 ++ .../fuse-3_818_81_2lib_2mount_8c_source.html | 794 ++ ...e-3_818_81_2lib_2mount__bsd_8c_source.html | 336 + ...-3_818_81_2lib_2mount__util_8c_source.html | 437 + ...-3_818_81_2lib_2mount__util_8h_source.html | 78 + .../fuse-3_818_81_2lib_2usdt_8h_source.html | 595 ++ .../fuse-3_818_81_2lib_2util_8c_source.html | 113 + .../fuse-3_818_81_2lib_2util_8h_source.html | 104 + doc/html/fuse-3_818_81_2test_2hello_8c.html | 265 + .../fuse-3_818_81_2test_2hello_8c_source.html | 250 + ...18_81_2test_2readdir__inode_8c_source.html | 117 + ...test_2release__unlink__race_8c_source.html | 183 + ..._818_81_2test_2stracedecode_8c_source.html | 259 + ...e-3_818_81_2test_2test__abi_8c_source.html | 80 + ...818_81_2test_2test__setattr_8c_source.html | 272 + ...818_81_2test_2test__signals_8c_source.html | 279 + ...18_81_2test_2test__syscalls_8c_source.html | 2307 +++++ ...est_2test__want__conversion_8c_source.html | 264 + ...1_2test_2test__write__cache_8c_source.html | 412 + ...18_81_2test_2wrong__command_8c_source.html | 76 + ...-3_818_81_2util_2fusermount_8c_source.html | 1843 ++++ ...3_818_81_2util_2mount_8fuse_8c_source.html | 515 ++ doc/html/fuse-3_818_82_2example_2cuse_8c.html | 409 + ...use-3_818_82_2example_2cuse_8c_source.html | 395 + ...se-3_818_82_2example_2cuse__client_8c.html | 214 + ...8_82_2example_2cuse__client_8c_source.html | 188 + .../fuse-3_818_82_2example_2hello_8c.html | 265 + ...se-3_818_82_2example_2hello_8c_source.html | 254 + .../fuse-3_818_82_2example_2hello__ll_8c.html | 389 + ..._818_82_2example_2hello__ll_8c_source.html | 376 + ...-3_818_82_2example_2hello__ll__uds_8c.html | 391 + ...82_2example_2hello__ll__uds_8c_source.html | 442 + ..._818_82_2example_2invalidate__path_8c.html | 390 + ..._2example_2invalidate__path_8c_source.html | 370 + .../fuse-3_818_82_2example_2ioctl_8c.html | 294 + ...se-3_818_82_2example_2ioctl_8c_source.html | 283 + .../fuse-3_818_82_2example_2ioctl_8h.html | 96 + ...se-3_818_82_2example_2ioctl_8h_source.html | 92 + ...e-3_818_82_2example_2ioctl__client_8c.html | 136 + ..._82_2example_2ioctl__client_8c_source.html | 123 + ..._82_2example_2notify__inval__entry_8c.html | 497 ++ ...ample_2notify__inval__entry_8c_source.html | 446 + ..._82_2example_2notify__inval__inode_8c.html | 483 + ...ample_2notify__inval__inode_8c_source.html | 443 + ..._2example_2notify__store__retrieve_8c.html | 560 ++ ...le_2notify__store__retrieve_8c_source.html | 522 ++ doc/html/fuse-3_818_82_2example_2null_8c.html | 779 ++ ...use-3_818_82_2example_2null_8c_source.html | 196 + ...use-3_818_82_2example_2passthrough_8c.html | 679 ++ ...18_82_2example_2passthrough_8c_source.html | 665 ++ ...3_818_82_2example_2passthrough__fh_8c.html | 783 ++ ...2_2example_2passthrough__fh_8c_source.html | 767 ++ ...ample_2passthrough__helpers_8h_source.html | 182 + ...3_818_82_2example_2passthrough__ll_8c.html | 1546 ++++ ...2_2example_2passthrough__ll_8c_source.html | 1525 ++++ doc/html/fuse-3_818_82_2example_2poll_8c.html | 379 + ...use-3_818_82_2example_2poll_8c_source.html | 364 + ...se-3_818_82_2example_2poll__client_8c.html | 145 + ...8_82_2example_2poll__client_8c_source.html | 131 + .../fuse-3_818_82_2example_2printcap_8c.html | 240 + ...3_818_82_2example_2printcap_8c_source.html | 231 + ...82_2include_2cuse__lowlevel_8h_source.html | 152 + doc/html/fuse-3_818_82_2include_2fuse_8h.html | 839 ++ ...use-3_818_82_2include_2fuse_8h_source.html | 650 ++ ...se-3_818_82_2include_2fuse__common_8h.html | 1303 +++ ...8_82_2include_2fuse__common_8h_source.html | 469 + ...8_82_2include_2fuse__kernel_8h_source.html | 1180 +++ .../fuse-3_818_82_2include_2fuse__log_8h.html | 268 + ..._818_82_2include_2fuse__log_8h_source.html | 113 + ...-3_818_82_2include_2fuse__lowlevel_8h.html | 2530 ++++++ ...82_2include_2fuse__lowlevel_8h_source.html | 691 ++ ...nclude_2fuse__mount__compat_8h_source.html | 109 + .../fuse-3_818_82_2include_2fuse__opt_8h.html | 587 ++ ..._818_82_2include_2fuse__opt_8h_source.html | 145 + .../fuse-3_818_82_2lib_2buffer_8c_source.html | 406 + .../fuse-3_818_82_2lib_2compat_8c_source.html | 148 + ...818_82_2lib_2cuse__lowlevel_8c_source.html | 458 + .../fuse-3_818_82_2lib_2fuse_8c_source.html | 5449 ++++++++++++ ...fuse-3_818_82_2lib_2fuse__i_8h_source.html | 292 + ...se-3_818_82_2lib_2fuse__log_8c_source.html | 125 + ...e-3_818_82_2lib_2fuse__loop_8c_source.html | 113 + ...818_82_2lib_2fuse__loop__mt_8c_source.html | 604 ++ ...818_82_2lib_2fuse__lowlevel_8c_source.html | 4667 ++++++++++ ...e-3_818_82_2lib_2fuse__misc_8h_source.html | 111 + ...se-3_818_82_2lib_2fuse__opt_8c_source.html | 504 ++ ..._818_82_2lib_2fuse__signals_8c_source.html | 281 + ...-3_818_82_2lib_2fuse__uring_8c_source.html | 1015 +++ ...818_82_2lib_2fuse__uring__i_8h_source.html | 149 + .../fuse-3_818_82_2lib_2helper_8c_source.html | 606 ++ ...818_82_2lib_2modules_2iconv_8c_source.html | 835 ++ ...18_82_2lib_2modules_2subdir_8c_source.html | 786 ++ .../fuse-3_818_82_2lib_2mount_8c_source.html | 794 ++ ...e-3_818_82_2lib_2mount__bsd_8c_source.html | 336 + ...-3_818_82_2lib_2mount__util_8c_source.html | 437 + ...-3_818_82_2lib_2mount__util_8h_source.html | 78 + .../fuse-3_818_82_2lib_2usdt_8h_source.html | 595 ++ .../fuse-3_818_82_2lib_2util_8c_source.html | 113 + .../fuse-3_818_82_2lib_2util_8h_source.html | 104 + doc/html/fuse-3_818_82_2test_2hello_8c.html | 265 + .../fuse-3_818_82_2test_2hello_8c_source.html | 250 + ...18_82_2test_2readdir__inode_8c_source.html | 117 + ...test_2release__unlink__race_8c_source.html | 183 + ..._818_82_2test_2stracedecode_8c_source.html | 259 + ...e-3_818_82_2test_2test__abi_8c_source.html | 80 + ...818_82_2test_2test__setattr_8c_source.html | 272 + ...818_82_2test_2test__signals_8c_source.html | 279 + ...18_82_2test_2test__syscalls_8c_source.html | 2307 +++++ ...est_2test__want__conversion_8c_source.html | 264 + ...2_2test_2test__write__cache_8c_source.html | 412 + ...18_82_2test_2wrong__command_8c_source.html | 76 + ...-3_818_82_2util_2fusermount_8c_source.html | 1843 ++++ ...3_818_82_2util_2mount_8fuse_8c_source.html | 515 ++ doc/html/globals.html | 210 + doc/html/globals_defs.html | 92 + doc/html/globals_enum.html | 56 + doc/html/globals_eval.html | 59 + doc/html/globals_func.html | 148 + doc/html/globals_type.html | 57 + .../include_2cuse__lowlevel_8h_source.html | 152 + doc/html/include_2fuse_8h.html | 839 ++ doc/html/include_2fuse_8h_source.html | 650 ++ doc/html/include_2fuse__common_8h.html | 1303 +++ doc/html/include_2fuse__common_8h_source.html | 469 + doc/html/include_2fuse__kernel_8h_source.html | 1180 +++ doc/html/include_2fuse__log_8h.html | 268 + doc/html/include_2fuse__log_8h_source.html | 113 + doc/html/include_2fuse__lowlevel_8h.html | 2530 ++++++ .../include_2fuse__lowlevel_8h_source.html | 691 ++ ...nclude_2fuse__mount__compat_8h_source.html | 109 + doc/html/include_2fuse__opt_8h.html | 587 ++ doc/html/include_2fuse__opt_8h_source.html | 145 + doc/html/index.html | 69 + doc/html/jquery.js | 34 + doc/html/lib_2buffer_8c_source.html | 406 + doc/html/lib_2compat_8c_source.html | 148 + doc/html/lib_2cuse__lowlevel_8c_source.html | 458 + doc/html/lib_2fuse_8c_source.html | 5449 ++++++++++++ doc/html/lib_2fuse__i_8h_source.html | 292 + doc/html/lib_2fuse__log_8c_source.html | 125 + doc/html/lib_2fuse__loop_8c_source.html | 113 + doc/html/lib_2fuse__loop__mt_8c_source.html | 604 ++ doc/html/lib_2fuse__lowlevel_8c_source.html | 4667 ++++++++++ doc/html/lib_2fuse__misc_8h_source.html | 111 + doc/html/lib_2fuse__opt_8c_source.html | 504 ++ doc/html/lib_2fuse__signals_8c_source.html | 281 + doc/html/lib_2fuse__uring_8c_source.html | 1015 +++ doc/html/lib_2fuse__uring__i_8h_source.html | 149 + doc/html/lib_2helper_8c_source.html | 606 ++ doc/html/lib_2modules_2iconv_8c_source.html | 835 ++ doc/html/lib_2modules_2subdir_8c_source.html | 786 ++ doc/html/lib_2mount_8c_source.html | 794 ++ doc/html/lib_2mount__bsd_8c_source.html | 336 + doc/html/lib_2mount__util_8c_source.html | 437 + doc/html/lib_2mount__util_8h_source.html | 78 + doc/html/lib_2usdt_8h_source.html | 595 ++ doc/html/lib_2util_8c_source.html | 113 + doc/html/lib_2util_8h_source.html | 104 + doc/html/menu.js | 136 + doc/html/menudata.js | 85 + doc/html/minus.svg | 8 + doc/html/minusd.svg | 8 + doc/html/nav_f.png | Bin 0 -> 153 bytes doc/html/nav_fd.png | Bin 0 -> 169 bytes doc/html/nav_g.png | Bin 0 -> 95 bytes doc/html/nav_h.png | Bin 0 -> 98 bytes doc/html/nav_hd.png | Bin 0 -> 114 bytes doc/html/open.png | Bin 0 -> 123 bytes doc/html/plus.svg | 9 + doc/html/plusd.svg | 9 + doc/html/splitbar.png | Bin 0 -> 314 bytes doc/html/splitbard.png | Bin 0 -> 282 bytes doc/html/structfuse__args.html | 126 + doc/html/structfuse__buf.html | 188 + doc/html/structfuse__bufvec.html | 147 + doc/html/structfuse__cmdline__opts.html | 62 + doc/html/structfuse__config.html | 502 ++ doc/html/structfuse__conn__info.html | 389 + doc/html/structfuse__context.html | 184 + doc/html/structfuse__ctx.html | 146 + doc/html/structfuse__entry__param.html | 167 + doc/html/structfuse__ext__header.html | 63 + doc/html/structfuse__file__info.html | 357 + doc/html/structfuse__loop__config.html | 154 + doc/html/structfuse__lowlevel__ops.html | 1530 ++++ doc/html/structfuse__module.html | 63 + doc/html/structfuse__operations.html | 969 ++ doc/html/structfuse__opt.html | 147 + doc/html/structfuse__ring__pool.html | 59 + doc/html/structfuse__supp__groups.html | 62 + doc/html/structfuse__uring__cmd__req.html | 61 + doc/html/structfuse__uring__req__header.html | 61 + doc/html/structlibfuse__version.html | 62 + doc/html/sync_off.png | Bin 0 -> 853 bytes doc/html/sync_on.png | Bin 0 -> 845 bytes doc/html/tab_a.png | Bin 0 -> 142 bytes doc/html/tab_ad.png | Bin 0 -> 135 bytes doc/html/tab_b.png | Bin 0 -> 169 bytes doc/html/tab_bd.png | Bin 0 -> 173 bytes doc/html/tab_h.png | Bin 0 -> 177 bytes doc/html/tab_hd.png | Bin 0 -> 180 bytes doc/html/tab_s.png | Bin 0 -> 184 bytes doc/html/tab_sd.png | Bin 0 -> 188 bytes doc/html/tabs.css | 1 + doc/html/test_2hello_8c.html | 265 + doc/html/test_2hello_8c_source.html | 250 + doc/html/test_2readdir__inode_8c_source.html | 117 + ...test_2release__unlink__race_8c_source.html | 183 + doc/html/test_2stracedecode_8c_source.html | 259 + doc/html/test_2test__abi_8c_source.html | 80 + doc/html/test_2test__setattr_8c_source.html | 272 + doc/html/test_2test__signals_8c_source.html | 279 + doc/html/test_2test__syscalls_8c_source.html | 2307 +++++ ...est_2test__want__conversion_8c_source.html | 264 + .../test_2test__write__cache_8c_source.html | 412 + doc/html/test_2wrong__command_8c_source.html | 76 + doc/html/util_2fusermount_8c_source.html | 1843 ++++ doc/html/util_2mount_8fuse_8c_source.html | 515 ++ doc/kernel.txt | 380 + doc/libfuse-operations.txt | 426 + doc/mainpage.dox | 54 + doc/meson.build | 4 + doc/mount.fuse3.8 | 273 + example/README.compile | 4 + example/cuse.c | 335 + example/cuse_client.c | 157 + example/cxxopts.hpp | 2114 +++++ example/hello.c | 184 + example/hello_ll.c | 297 + example/hello_ll_uds.c | 367 + example/invalidate_path.c | 292 + example/ioctl.c | 230 + example/ioctl.h | 42 + example/ioctl_client.c | 74 + example/memfs_ll.cc | 1139 +++ example/meson.build | 44 + example/notify_inval_entry.c | 427 + example/notify_inval_inode.c | 402 + example/notify_store_retrieve.c | 475 + example/null.c | 143 + example/passthrough.c | 604 ++ example/passthrough_fh.c | 692 ++ example/passthrough_helpers.h | 122 + example/passthrough_hp.cc | 1666 ++++ example/passthrough_ll.c | 1416 +++ example/poll.c | 304 + example/poll_client.c | 84 + example/printcap.c | 136 + example/usdt.bt | 19 + include/cuse_lowlevel.h | 87 + include/fuse.h | 1434 +++ include/fuse_common.h | 1161 +++ include/fuse_kernel.h | 1303 +++ include/fuse_log.h | 95 + include/fuse_lowlevel.h | 2391 +++++ include/fuse_mount_compat.h | 49 + include/fuse_opt.h | 271 + include/meson.build | 4 + lib/buffer.c | 324 + lib/compat.c | 86 + lib/cuse_lowlevel.c | 375 + lib/fuse.c | 5264 +++++++++++ lib/fuse_i.h | 266 + lib/fuse_log.c | 59 + lib/fuse_loop.c | 48 + lib/fuse_loop_mt.c | 535 ++ lib/fuse_lowlevel.c | 4491 ++++++++++ lib/fuse_misc.h | 51 + lib/fuse_opt.c | 423 + lib/fuse_signals.c | 216 + lib/fuse_uring.c | 954 ++ lib/fuse_uring_i.h | 86 + lib/fuse_versionscript | 227 + lib/helper.c | 498 ++ lib/meson.build | 66 + lib/modules/iconv.c | 756 ++ lib/modules/subdir.c | 708 ++ lib/mount.c | 725 ++ lib/mount_bsd.c | 269 + lib/mount_util.c | 377 + lib/mount_util.h | 18 + lib/usdt.h | 540 ++ lib/util.c | 53 + lib/util.h | 49 + meson.build | 332 + meson_options.txt | 29 + requirements.txt | 7 + signify/fuse-3.15.pub | 2 + signify/fuse-3.16.pub | 2 + signify/fuse-3.17.pub | 2 + signify/fuse-3.18.pub | 2 + signify/fuse-3.19.pub | 2 + test/ci-build.sh | 168 + test/conftest.py | 100 + test/hello.c | 180 + test/lsan_suppress.txt | 1 + test/meson.build | 45 + test/pytest.ini | 6 + test/readdir_inode.c | 57 + test/release_unlink_race.c | 111 + test/stracedecode.c | 199 + test/test_abi.c | 18 + test/test_ctests.py | 161 + test/test_custom_io.py | 81 + test/test_examples.py | 980 +++ test/test_setattr.c | 194 + test/test_signals.c | 202 + test/test_syscalls.c | 2247 +++++ test/test_want_conversion.c | 181 + test/test_write_cache.c | 323 + test/util.py | 211 + test/wrong_command.c | 16 + tsan_suppressions.txt | 5 + util/fuse.conf | 17 + util/fusermount.c | 1783 ++++ util/init_script | 92 + util/install_helper.sh | 55 + util/meson.build | 34 + util/mount.fuse.c | 454 + util/parse-backtrace.sh | 117 + util/udev.rules | 1 + xfstests/README.md | 18 + xfstests/local.config | 21 + xfstests/mount.fuse.passthrough | 40 + 563 files changed, 268042 insertions(+) create mode 100644 .ackrc create mode 100644 .clang-format create mode 100644 .codespellrc create mode 100644 .dir-locals.el create mode 100644 AUTHORS create mode 100644 ChangeLog.rst create mode 100644 GPL2.txt create mode 100644 LGPL2.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 SECURITY.md create mode 100755 checkpatch.pl create mode 100755 dev-docs/extend-authors.sh create mode 100644 dev-docs/release-process.md create mode 100644 doc/Doxyfile create mode 100644 doc/README.NFS create mode 100644 doc/README.fuse-io-uring create mode 100644 doc/README.fuse_reply_errors create mode 100644 doc/README.notifications create mode 100644 doc/fast17-vangoor.pdf create mode 100644 doc/fusermount3.1 create mode 100644 doc/html/annotated.html create mode 100644 doc/html/bc_s.png create mode 100644 doc/html/bc_sd.png create mode 100644 doc/html/classes.html create mode 100644 doc/html/closed.png create mode 100644 doc/html/dir_042d006301a0f6339943f03c22d70dab.html create mode 100644 doc/html/dir_13e138d54eb8818da29c3992edef070a.html create mode 100644 doc/html/dir_151531ba243f27a508f84a018a5044f9.html create mode 100644 doc/html/dir_2379d157e40387655d5d09cb33050082.html create mode 100644 doc/html/dir_23ec12649285f9fabf3a6b7380226c28.html create mode 100644 doc/html/dir_3141c10b43d1ded6ee3c902c2fe1c48b.html create mode 100644 doc/html/dir_4d35850f380bf2b0e1e23612de44a403.html create mode 100644 doc/html/dir_567612448af13c8fdbe428427cb913c5.html create mode 100644 doc/html/dir_5cbcf3cc0c552e5807bf52f5541bdba8.html create mode 100644 doc/html/dir_5dc95f8183277f5c6f5c244c43efe208.html create mode 100644 doc/html/dir_5fb9f92dc673d37c57217e3feec2be83.html create mode 100644 doc/html/dir_7015ddbf9bcaeb01169723f1ad3dc4f8.html create mode 100644 doc/html/dir_73f848c5a0a9108732ea2e7217c7aea5.html create mode 100644 doc/html/dir_97aefd0d527b934f1d99a682da8fe6a9.html create mode 100644 doc/html/dir_98164a96c6a79e43c7a77c344d27e55d.html create mode 100644 doc/html/dir_a1e8bb6aacb6fb222eb29390226a8aec.html create mode 100644 doc/html/dir_aa785a52b370a0eb18aa670afa0403e9.html create mode 100644 doc/html/dir_bea620c7bc056d32632a5444abf88a37.html create mode 100644 doc/html/dir_cfafba98a580ce4b62f8a6fa96d7cbb0.html create mode 100644 doc/html/dir_d44c64559bbebec7f509842c48db8b23.html create mode 100644 doc/html/dir_de6a3d84c7bdc16a0560151553ce19f4.html create mode 100644 doc/html/dir_de8f9914a0c6d3fefb7eee74461f97ba.html create mode 100644 doc/html/dir_e1dbc8ba94a86723d4c32227b7c46099.html create mode 100644 doc/html/dir_e68e8157741866f444e17edd764ebbae.html create mode 100644 doc/html/dir_e7cc7929bd1ffaa820e10e01d9c95abd.html create mode 100644 doc/html/dir_ee79e556e893a265551a6396eb768ff4.html create mode 100644 doc/html/dir_ef89b861fb26f7108388f05c496b561a.html create mode 100644 doc/html/dir_f353b27f473de27f9d371ee54fafa676.html create mode 100644 doc/html/dir_f77c7566d5b54b3a8f6688c1c1718be3.html create mode 100644 doc/html/dir_ff8734a6ed1553e4cdaef764f37a71c4.html create mode 100644 doc/html/doc.svg create mode 100644 doc/html/docd.svg create mode 100644 doc/html/doxygen.css create mode 100644 doc/html/doxygen.svg create mode 100644 doc/html/dynsections.js create mode 100644 doc/html/example_2cuse_8c.html create mode 100644 doc/html/example_2cuse_8c_source.html create mode 100644 doc/html/example_2cuse__client_8c.html create mode 100644 doc/html/example_2cuse__client_8c_source.html create mode 100644 doc/html/example_2hello_8c.html create mode 100644 doc/html/example_2hello_8c_source.html create mode 100644 doc/html/example_2hello__ll_8c.html create mode 100644 doc/html/example_2hello__ll_8c_source.html create mode 100644 doc/html/example_2hello__ll__uds_8c.html create mode 100644 doc/html/example_2hello__ll__uds_8c_source.html create mode 100644 doc/html/example_2invalidate__path_8c.html create mode 100644 doc/html/example_2invalidate__path_8c_source.html create mode 100644 doc/html/example_2ioctl_8c.html create mode 100644 doc/html/example_2ioctl_8c_source.html create mode 100644 doc/html/example_2ioctl_8h.html create mode 100644 doc/html/example_2ioctl_8h_source.html create mode 100644 doc/html/example_2ioctl__client_8c.html create mode 100644 doc/html/example_2ioctl__client_8c_source.html create mode 100644 doc/html/example_2notify__inval__entry_8c.html create mode 100644 doc/html/example_2notify__inval__entry_8c_source.html create mode 100644 doc/html/example_2notify__inval__inode_8c.html create mode 100644 doc/html/example_2notify__inval__inode_8c_source.html create mode 100644 doc/html/example_2notify__store__retrieve_8c.html create mode 100644 doc/html/example_2notify__store__retrieve_8c_source.html create mode 100644 doc/html/example_2null_8c.html create mode 100644 doc/html/example_2null_8c_source.html create mode 100644 doc/html/example_2passthrough_8c.html create mode 100644 doc/html/example_2passthrough_8c_source.html create mode 100644 doc/html/example_2passthrough__fh_8c.html create mode 100644 doc/html/example_2passthrough__fh_8c_source.html create mode 100644 doc/html/example_2passthrough__helpers_8h_source.html create mode 100644 doc/html/example_2passthrough__ll_8c.html create mode 100644 doc/html/example_2passthrough__ll_8c_source.html create mode 100644 doc/html/example_2poll_8c.html create mode 100644 doc/html/example_2poll_8c_source.html create mode 100644 doc/html/example_2poll__client_8c.html create mode 100644 doc/html/example_2poll__client_8c_source.html create mode 100644 doc/html/example_2printcap_8c.html create mode 100644 doc/html/example_2printcap_8c_source.html create mode 100644 doc/html/fast17-vangoor.pdf create mode 100644 doc/html/files.html create mode 100644 doc/html/folderclosed.svg create mode 100644 doc/html/folderclosedd.svg create mode 100644 doc/html/folderopen.svg create mode 100644 doc/html/folderopend.svg create mode 100644 doc/html/functions.html create mode 100644 doc/html/functions_vars.html create mode 100644 doc/html/fuse-3_817_84_2example_2notify__store__retrieve_8c.html create mode 100644 doc/html/fuse-3_817_84_2example_2notify__store__retrieve_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2example_2null_8c.html create mode 100644 doc/html/fuse-3_817_84_2example_2null_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2example_2passthrough_8c.html create mode 100644 doc/html/fuse-3_817_84_2example_2passthrough_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2example_2passthrough__fh_8c.html create mode 100644 doc/html/fuse-3_817_84_2example_2passthrough__fh_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2example_2passthrough__helpers_8h_source.html create mode 100644 doc/html/fuse-3_817_84_2example_2passthrough__ll_8c.html create mode 100644 doc/html/fuse-3_817_84_2example_2passthrough__ll_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2example_2poll_8c.html create mode 100644 doc/html/fuse-3_817_84_2example_2poll_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2example_2poll__client_8c.html create mode 100644 doc/html/fuse-3_817_84_2example_2poll__client_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2example_2printcap_8c.html create mode 100644 doc/html/fuse-3_817_84_2example_2printcap_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2include_2cuse__lowlevel_8h_source.html create mode 100644 doc/html/fuse-3_817_84_2include_2fuse_8h.html create mode 100644 doc/html/fuse-3_817_84_2include_2fuse_8h_source.html create mode 100644 doc/html/fuse-3_817_84_2include_2fuse__common_8h.html create mode 100644 doc/html/fuse-3_817_84_2include_2fuse__common_8h_source.html create mode 100644 doc/html/fuse-3_817_84_2include_2fuse__kernel_8h_source.html create mode 100644 doc/html/fuse-3_817_84_2include_2fuse__log_8h.html create mode 100644 doc/html/fuse-3_817_84_2include_2fuse__log_8h_source.html create mode 100644 doc/html/fuse-3_817_84_2include_2fuse__lowlevel_8h.html create mode 100644 doc/html/fuse-3_817_84_2include_2fuse__lowlevel_8h_source.html create mode 100644 doc/html/fuse-3_817_84_2include_2fuse__mount__compat_8h_source.html create mode 100644 doc/html/fuse-3_817_84_2include_2fuse__opt_8h.html create mode 100644 doc/html/fuse-3_817_84_2include_2fuse__opt_8h_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2buffer_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2compat_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2cuse__lowlevel_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2fuse_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2fuse__i_8h_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2fuse__log_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2fuse__loop_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2fuse__loop__mt_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2fuse__lowlevel_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2fuse__misc_8h_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2fuse__opt_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2fuse__signals_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2helper_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2modules_2iconv_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2modules_2subdir_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2mount_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2mount__bsd_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2mount__util_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2mount__util_8h_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2util_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2lib_2util_8h_source.html create mode 100644 doc/html/fuse-3_817_84_2test_2hello_8c.html create mode 100644 doc/html/fuse-3_817_84_2test_2hello_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2test_2readdir__inode_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2test_2release__unlink__race_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2test_2stracedecode_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2test_2test__abi_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2test_2test__setattr_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2test_2test__syscalls_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2test_2test__want__conversion_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2test_2test__write__cache_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2test_2wrong__command_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2util_2fusermount_8c_source.html create mode 100644 doc/html/fuse-3_817_84_2util_2mount_8fuse_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2cuse_8c.html create mode 100644 doc/html/fuse-3_818_81_2example_2cuse_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2cuse__client_8c.html create mode 100644 doc/html/fuse-3_818_81_2example_2cuse__client_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2hello_8c.html create mode 100644 doc/html/fuse-3_818_81_2example_2hello_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2hello__ll_8c.html create mode 100644 doc/html/fuse-3_818_81_2example_2hello__ll_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2hello__ll__uds_8c.html create mode 100644 doc/html/fuse-3_818_81_2example_2hello__ll__uds_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2invalidate__path_8c.html create mode 100644 doc/html/fuse-3_818_81_2example_2invalidate__path_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2ioctl_8c.html create mode 100644 doc/html/fuse-3_818_81_2example_2ioctl_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2ioctl_8h.html create mode 100644 doc/html/fuse-3_818_81_2example_2ioctl_8h_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2ioctl__client_8c.html create mode 100644 doc/html/fuse-3_818_81_2example_2ioctl__client_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2notify__inval__entry_8c.html create mode 100644 doc/html/fuse-3_818_81_2example_2notify__inval__entry_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2notify__inval__inode_8c.html create mode 100644 doc/html/fuse-3_818_81_2example_2notify__inval__inode_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2notify__store__retrieve_8c.html create mode 100644 doc/html/fuse-3_818_81_2example_2notify__store__retrieve_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2null_8c.html create mode 100644 doc/html/fuse-3_818_81_2example_2null_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2passthrough_8c.html create mode 100644 doc/html/fuse-3_818_81_2example_2passthrough_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2passthrough__fh_8c.html create mode 100644 doc/html/fuse-3_818_81_2example_2passthrough__fh_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2passthrough__helpers_8h_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2passthrough__ll_8c.html create mode 100644 doc/html/fuse-3_818_81_2example_2passthrough__ll_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2poll_8c.html create mode 100644 doc/html/fuse-3_818_81_2example_2poll_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2poll__client_8c.html create mode 100644 doc/html/fuse-3_818_81_2example_2poll__client_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2example_2printcap_8c.html create mode 100644 doc/html/fuse-3_818_81_2example_2printcap_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2include_2cuse__lowlevel_8h_source.html create mode 100644 doc/html/fuse-3_818_81_2include_2fuse_8h.html create mode 100644 doc/html/fuse-3_818_81_2include_2fuse_8h_source.html create mode 100644 doc/html/fuse-3_818_81_2include_2fuse__common_8h.html create mode 100644 doc/html/fuse-3_818_81_2include_2fuse__common_8h_source.html create mode 100644 doc/html/fuse-3_818_81_2include_2fuse__kernel_8h_source.html create mode 100644 doc/html/fuse-3_818_81_2include_2fuse__log_8h.html create mode 100644 doc/html/fuse-3_818_81_2include_2fuse__log_8h_source.html create mode 100644 doc/html/fuse-3_818_81_2include_2fuse__lowlevel_8h.html create mode 100644 doc/html/fuse-3_818_81_2include_2fuse__lowlevel_8h_source.html create mode 100644 doc/html/fuse-3_818_81_2include_2fuse__mount__compat_8h_source.html create mode 100644 doc/html/fuse-3_818_81_2include_2fuse__opt_8h.html create mode 100644 doc/html/fuse-3_818_81_2include_2fuse__opt_8h_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2buffer_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2compat_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2cuse__lowlevel_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2fuse_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2fuse__i_8h_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2fuse__log_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2fuse__loop_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2fuse__loop__mt_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2fuse__lowlevel_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2fuse__misc_8h_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2fuse__opt_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2fuse__signals_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2fuse__uring_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2fuse__uring__i_8h_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2helper_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2modules_2iconv_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2modules_2subdir_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2mount_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2mount__bsd_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2mount__util_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2mount__util_8h_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2usdt_8h_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2util_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2lib_2util_8h_source.html create mode 100644 doc/html/fuse-3_818_81_2test_2hello_8c.html create mode 100644 doc/html/fuse-3_818_81_2test_2hello_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2test_2readdir__inode_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2test_2release__unlink__race_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2test_2stracedecode_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2test_2test__abi_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2test_2test__setattr_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2test_2test__signals_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2test_2test__syscalls_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2test_2test__want__conversion_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2test_2test__write__cache_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2test_2wrong__command_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2util_2fusermount_8c_source.html create mode 100644 doc/html/fuse-3_818_81_2util_2mount_8fuse_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2cuse_8c.html create mode 100644 doc/html/fuse-3_818_82_2example_2cuse_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2cuse__client_8c.html create mode 100644 doc/html/fuse-3_818_82_2example_2cuse__client_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2hello_8c.html create mode 100644 doc/html/fuse-3_818_82_2example_2hello_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2hello__ll_8c.html create mode 100644 doc/html/fuse-3_818_82_2example_2hello__ll_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2hello__ll__uds_8c.html create mode 100644 doc/html/fuse-3_818_82_2example_2hello__ll__uds_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2invalidate__path_8c.html create mode 100644 doc/html/fuse-3_818_82_2example_2invalidate__path_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2ioctl_8c.html create mode 100644 doc/html/fuse-3_818_82_2example_2ioctl_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2ioctl_8h.html create mode 100644 doc/html/fuse-3_818_82_2example_2ioctl_8h_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2ioctl__client_8c.html create mode 100644 doc/html/fuse-3_818_82_2example_2ioctl__client_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2notify__inval__entry_8c.html create mode 100644 doc/html/fuse-3_818_82_2example_2notify__inval__entry_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2notify__inval__inode_8c.html create mode 100644 doc/html/fuse-3_818_82_2example_2notify__inval__inode_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2notify__store__retrieve_8c.html create mode 100644 doc/html/fuse-3_818_82_2example_2notify__store__retrieve_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2null_8c.html create mode 100644 doc/html/fuse-3_818_82_2example_2null_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2passthrough_8c.html create mode 100644 doc/html/fuse-3_818_82_2example_2passthrough_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2passthrough__fh_8c.html create mode 100644 doc/html/fuse-3_818_82_2example_2passthrough__fh_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2passthrough__helpers_8h_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2passthrough__ll_8c.html create mode 100644 doc/html/fuse-3_818_82_2example_2passthrough__ll_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2poll_8c.html create mode 100644 doc/html/fuse-3_818_82_2example_2poll_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2poll__client_8c.html create mode 100644 doc/html/fuse-3_818_82_2example_2poll__client_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2example_2printcap_8c.html create mode 100644 doc/html/fuse-3_818_82_2example_2printcap_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2include_2cuse__lowlevel_8h_source.html create mode 100644 doc/html/fuse-3_818_82_2include_2fuse_8h.html create mode 100644 doc/html/fuse-3_818_82_2include_2fuse_8h_source.html create mode 100644 doc/html/fuse-3_818_82_2include_2fuse__common_8h.html create mode 100644 doc/html/fuse-3_818_82_2include_2fuse__common_8h_source.html create mode 100644 doc/html/fuse-3_818_82_2include_2fuse__kernel_8h_source.html create mode 100644 doc/html/fuse-3_818_82_2include_2fuse__log_8h.html create mode 100644 doc/html/fuse-3_818_82_2include_2fuse__log_8h_source.html create mode 100644 doc/html/fuse-3_818_82_2include_2fuse__lowlevel_8h.html create mode 100644 doc/html/fuse-3_818_82_2include_2fuse__lowlevel_8h_source.html create mode 100644 doc/html/fuse-3_818_82_2include_2fuse__mount__compat_8h_source.html create mode 100644 doc/html/fuse-3_818_82_2include_2fuse__opt_8h.html create mode 100644 doc/html/fuse-3_818_82_2include_2fuse__opt_8h_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2buffer_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2compat_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2cuse__lowlevel_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2fuse_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2fuse__i_8h_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2fuse__log_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2fuse__loop_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2fuse__loop__mt_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2fuse__lowlevel_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2fuse__misc_8h_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2fuse__opt_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2fuse__signals_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2fuse__uring_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2fuse__uring__i_8h_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2helper_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2modules_2iconv_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2modules_2subdir_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2mount_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2mount__bsd_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2mount__util_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2mount__util_8h_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2usdt_8h_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2util_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2lib_2util_8h_source.html create mode 100644 doc/html/fuse-3_818_82_2test_2hello_8c.html create mode 100644 doc/html/fuse-3_818_82_2test_2hello_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2test_2readdir__inode_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2test_2release__unlink__race_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2test_2stracedecode_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2test_2test__abi_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2test_2test__setattr_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2test_2test__signals_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2test_2test__syscalls_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2test_2test__want__conversion_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2test_2test__write__cache_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2test_2wrong__command_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2util_2fusermount_8c_source.html create mode 100644 doc/html/fuse-3_818_82_2util_2mount_8fuse_8c_source.html create mode 100644 doc/html/globals.html create mode 100644 doc/html/globals_defs.html create mode 100644 doc/html/globals_enum.html create mode 100644 doc/html/globals_eval.html create mode 100644 doc/html/globals_func.html create mode 100644 doc/html/globals_type.html create mode 100644 doc/html/include_2cuse__lowlevel_8h_source.html create mode 100644 doc/html/include_2fuse_8h.html create mode 100644 doc/html/include_2fuse_8h_source.html create mode 100644 doc/html/include_2fuse__common_8h.html create mode 100644 doc/html/include_2fuse__common_8h_source.html create mode 100644 doc/html/include_2fuse__kernel_8h_source.html create mode 100644 doc/html/include_2fuse__log_8h.html create mode 100644 doc/html/include_2fuse__log_8h_source.html create mode 100644 doc/html/include_2fuse__lowlevel_8h.html create mode 100644 doc/html/include_2fuse__lowlevel_8h_source.html create mode 100644 doc/html/include_2fuse__mount__compat_8h_source.html create mode 100644 doc/html/include_2fuse__opt_8h.html create mode 100644 doc/html/include_2fuse__opt_8h_source.html create mode 100644 doc/html/index.html create mode 100644 doc/html/jquery.js create mode 100644 doc/html/lib_2buffer_8c_source.html create mode 100644 doc/html/lib_2compat_8c_source.html create mode 100644 doc/html/lib_2cuse__lowlevel_8c_source.html create mode 100644 doc/html/lib_2fuse_8c_source.html create mode 100644 doc/html/lib_2fuse__i_8h_source.html create mode 100644 doc/html/lib_2fuse__log_8c_source.html create mode 100644 doc/html/lib_2fuse__loop_8c_source.html create mode 100644 doc/html/lib_2fuse__loop__mt_8c_source.html create mode 100644 doc/html/lib_2fuse__lowlevel_8c_source.html create mode 100644 doc/html/lib_2fuse__misc_8h_source.html create mode 100644 doc/html/lib_2fuse__opt_8c_source.html create mode 100644 doc/html/lib_2fuse__signals_8c_source.html create mode 100644 doc/html/lib_2fuse__uring_8c_source.html create mode 100644 doc/html/lib_2fuse__uring__i_8h_source.html create mode 100644 doc/html/lib_2helper_8c_source.html create mode 100644 doc/html/lib_2modules_2iconv_8c_source.html create mode 100644 doc/html/lib_2modules_2subdir_8c_source.html create mode 100644 doc/html/lib_2mount_8c_source.html create mode 100644 doc/html/lib_2mount__bsd_8c_source.html create mode 100644 doc/html/lib_2mount__util_8c_source.html create mode 100644 doc/html/lib_2mount__util_8h_source.html create mode 100644 doc/html/lib_2usdt_8h_source.html create mode 100644 doc/html/lib_2util_8c_source.html create mode 100644 doc/html/lib_2util_8h_source.html create mode 100644 doc/html/menu.js create mode 100644 doc/html/menudata.js create mode 100644 doc/html/minus.svg create mode 100644 doc/html/minusd.svg create mode 100644 doc/html/nav_f.png create mode 100644 doc/html/nav_fd.png create mode 100644 doc/html/nav_g.png create mode 100644 doc/html/nav_h.png create mode 100644 doc/html/nav_hd.png create mode 100644 doc/html/open.png create mode 100644 doc/html/plus.svg create mode 100644 doc/html/plusd.svg create mode 100644 doc/html/splitbar.png create mode 100644 doc/html/splitbard.png create mode 100644 doc/html/structfuse__args.html create mode 100644 doc/html/structfuse__buf.html create mode 100644 doc/html/structfuse__bufvec.html create mode 100644 doc/html/structfuse__cmdline__opts.html create mode 100644 doc/html/structfuse__config.html create mode 100644 doc/html/structfuse__conn__info.html create mode 100644 doc/html/structfuse__context.html create mode 100644 doc/html/structfuse__ctx.html create mode 100644 doc/html/structfuse__entry__param.html create mode 100644 doc/html/structfuse__ext__header.html create mode 100644 doc/html/structfuse__file__info.html create mode 100644 doc/html/structfuse__loop__config.html create mode 100644 doc/html/structfuse__lowlevel__ops.html create mode 100644 doc/html/structfuse__module.html create mode 100644 doc/html/structfuse__operations.html create mode 100644 doc/html/structfuse__opt.html create mode 100644 doc/html/structfuse__ring__pool.html create mode 100644 doc/html/structfuse__supp__groups.html create mode 100644 doc/html/structfuse__uring__cmd__req.html create mode 100644 doc/html/structfuse__uring__req__header.html create mode 100644 doc/html/structlibfuse__version.html create mode 100644 doc/html/sync_off.png create mode 100644 doc/html/sync_on.png create mode 100644 doc/html/tab_a.png create mode 100644 doc/html/tab_ad.png create mode 100644 doc/html/tab_b.png create mode 100644 doc/html/tab_bd.png create mode 100644 doc/html/tab_h.png create mode 100644 doc/html/tab_hd.png create mode 100644 doc/html/tab_s.png create mode 100644 doc/html/tab_sd.png create mode 100644 doc/html/tabs.css create mode 100644 doc/html/test_2hello_8c.html create mode 100644 doc/html/test_2hello_8c_source.html create mode 100644 doc/html/test_2readdir__inode_8c_source.html create mode 100644 doc/html/test_2release__unlink__race_8c_source.html create mode 100644 doc/html/test_2stracedecode_8c_source.html create mode 100644 doc/html/test_2test__abi_8c_source.html create mode 100644 doc/html/test_2test__setattr_8c_source.html create mode 100644 doc/html/test_2test__signals_8c_source.html create mode 100644 doc/html/test_2test__syscalls_8c_source.html create mode 100644 doc/html/test_2test__want__conversion_8c_source.html create mode 100644 doc/html/test_2test__write__cache_8c_source.html create mode 100644 doc/html/test_2wrong__command_8c_source.html create mode 100644 doc/html/util_2fusermount_8c_source.html create mode 100644 doc/html/util_2mount_8fuse_8c_source.html create mode 100644 doc/kernel.txt create mode 100644 doc/libfuse-operations.txt create mode 100644 doc/mainpage.dox create mode 100644 doc/meson.build create mode 100644 doc/mount.fuse3.8 create mode 100644 example/README.compile create mode 100644 example/cuse.c create mode 100644 example/cuse_client.c create mode 100644 example/cxxopts.hpp create mode 100644 example/hello.c create mode 100644 example/hello_ll.c create mode 100644 example/hello_ll_uds.c create mode 100644 example/invalidate_path.c create mode 100644 example/ioctl.c create mode 100644 example/ioctl.h create mode 100644 example/ioctl_client.c create mode 100644 example/memfs_ll.cc create mode 100644 example/meson.build create mode 100644 example/notify_inval_entry.c create mode 100644 example/notify_inval_inode.c create mode 100644 example/notify_store_retrieve.c create mode 100644 example/null.c create mode 100644 example/passthrough.c create mode 100644 example/passthrough_fh.c create mode 100644 example/passthrough_helpers.h create mode 100644 example/passthrough_hp.cc create mode 100644 example/passthrough_ll.c create mode 100644 example/poll.c create mode 100644 example/poll_client.c create mode 100644 example/printcap.c create mode 100644 example/usdt.bt create mode 100644 include/cuse_lowlevel.h create mode 100644 include/fuse.h create mode 100644 include/fuse_common.h create mode 100644 include/fuse_kernel.h create mode 100644 include/fuse_log.h create mode 100644 include/fuse_lowlevel.h create mode 100644 include/fuse_mount_compat.h create mode 100644 include/fuse_opt.h create mode 100644 include/meson.build create mode 100644 lib/buffer.c create mode 100644 lib/compat.c create mode 100644 lib/cuse_lowlevel.c create mode 100644 lib/fuse.c create mode 100644 lib/fuse_i.h create mode 100644 lib/fuse_log.c create mode 100644 lib/fuse_loop.c create mode 100644 lib/fuse_loop_mt.c create mode 100644 lib/fuse_lowlevel.c create mode 100644 lib/fuse_misc.h create mode 100644 lib/fuse_opt.c create mode 100644 lib/fuse_signals.c create mode 100644 lib/fuse_uring.c create mode 100644 lib/fuse_uring_i.h create mode 100644 lib/fuse_versionscript create mode 100644 lib/helper.c create mode 100644 lib/meson.build create mode 100644 lib/modules/iconv.c create mode 100644 lib/modules/subdir.c create mode 100644 lib/mount.c create mode 100644 lib/mount_bsd.c create mode 100644 lib/mount_util.c create mode 100644 lib/mount_util.h create mode 100644 lib/usdt.h create mode 100644 lib/util.c create mode 100644 lib/util.h create mode 100644 meson.build create mode 100644 meson_options.txt create mode 100644 requirements.txt create mode 100644 signify/fuse-3.15.pub create mode 100644 signify/fuse-3.16.pub create mode 100644 signify/fuse-3.17.pub create mode 100644 signify/fuse-3.18.pub create mode 100644 signify/fuse-3.19.pub create mode 100755 test/ci-build.sh create mode 100644 test/conftest.py create mode 100644 test/hello.c create mode 100644 test/lsan_suppress.txt create mode 100644 test/meson.build create mode 100644 test/pytest.ini create mode 100644 test/readdir_inode.c create mode 100644 test/release_unlink_race.c create mode 100644 test/stracedecode.c create mode 100644 test/test_abi.c create mode 100644 test/test_ctests.py create mode 100644 test/test_custom_io.py create mode 100755 test/test_examples.py create mode 100644 test/test_setattr.c create mode 100644 test/test_signals.c create mode 100644 test/test_syscalls.c create mode 100644 test/test_want_conversion.c create mode 100644 test/test_write_cache.c create mode 100644 test/util.py create mode 100644 test/wrong_command.c create mode 100644 tsan_suppressions.txt create mode 100644 util/fuse.conf create mode 100644 util/fusermount.c create mode 100755 util/init_script create mode 100755 util/install_helper.sh create mode 100644 util/meson.build create mode 100644 util/mount.fuse.c create mode 100755 util/parse-backtrace.sh create mode 100644 util/udev.rules create mode 100644 xfstests/README.md create mode 100644 xfstests/local.config create mode 100755 xfstests/mount.fuse.passthrough diff --git a/.ackrc b/.ackrc new file mode 100644 index 0000000..c8f5d0a --- /dev/null +++ b/.ackrc @@ -0,0 +1,2 @@ +--ignore-dir=build +--ignore-dir=doc/html diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..3ebac7c --- /dev/null +++ b/.clang-format @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# clang-format configuration file. Intended for clang-format >= 11. +# +# For more information, see: +# +# Documentation/process/clang-format.rst +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false + +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentGotoLabels: false +IndentPPDirectives: None +IndentWidth: 8 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 8 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +# Taken from git's rules +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 + +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatementsExceptForEachMacros +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp03 +TabWidth: 8 +UseTab: Always +... diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000..cbd941f --- /dev/null +++ b/.codespellrc @@ -0,0 +1,13 @@ +[codespell] +skip = .git,*.pdf,*.svg,AUTHORS + +# The following strings shouldn't actually be accepted, but they're wrongly +# identified as words and there is currently no way to exclude them on +# a by-line basis (https://github.com/codespell-project/codespell/pull/2400). +# Therefore, pretend that they are correctly spelled words: +# - alse: used in regex +# - siz: wanted short +# - fiter: variable +# - re-used: intentional hyphenation +# - re-using: intentional hyphenation +ignore-words-list = alse,siz,fiter,re-used,re-using diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..4616d40 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,64 @@ +((python-mode . ((indent-tabs-mode . nil))) + (c++-mode . ((c-file-style . "linux-kernel") + (c-basic-offset . 8) + (c-label-minimum-indentation . 0) + (c-offsets-alist . ( + (arglist-close . c-lineup-arglist-tabs-only) + (arglist-cont-nonempty . + (c-lineup-gcc-asm-reg c-lineup-arglist-tabs-only)) + (arglist-intro . +) + (brace-list-intro . +) + (c . c-lineup-C-comments) + (case-label . 0) + (comment-intro . c-lineup-comment) + (cpp-define-intro . +) + (cpp-macro . -1000) + (cpp-macro-cont . +) + (defun-block-intro . +) + (else-clause . 0) + (func-decl-cont . +) + (inclass . +) + (inher-cont . c-lineup-multi-inher) + (knr-argdecl-intro . 0) + (label . -1000) + (statement . 0) + (statement-block-intro . +) + (statement-case-intro . +) + (statement-cont . +) + (substatement . +) + )) + (indent-tabs-mode . t) + (show-trailing-whitespace . t) + )) + (c-mode . ((c-file-style . "linux-kernel") + (c-basic-offset . 8) + (c-label-minimum-indentation . 0) + (c-offsets-alist . ( + (arglist-close . c-lineup-arglist-tabs-only) + (arglist-cont-nonempty . + (c-lineup-gcc-asm-reg c-lineup-arglist-tabs-only)) + (arglist-intro . +) + (brace-list-intro . +) + (c . c-lineup-C-comments) + (case-label . 0) + (comment-intro . c-lineup-comment) + (cpp-define-intro . +) + (cpp-macro . -1000) + (cpp-macro-cont . +) + (defun-block-intro . +) + (else-clause . 0) + (func-decl-cont . +) + (inclass . +) + (inher-cont . c-lineup-multi-inher) + (knr-argdecl-intro . 0) + (label . -1000) + (statement . 0) + (statement-block-intro . +) + (statement-case-intro . +) + (statement-cont . +) + (substatement . +) + )) + (indent-tabs-mode . t) + (show-trailing-whitespace . t) + )) +) diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..2c63b1f --- /dev/null +++ b/AUTHORS @@ -0,0 +1,287 @@ +Current Maintainers +------------------ + +Bernd Schubert +Ashley Pittman +Antonio SJ Musumeci + + +Past Maintainers +---------------- + +Nikolaus Rath (until 02/2024) +Miklos Szeredi (until 12/2015) + + +Contributors +------------ + +CUSE has been written by Tejun Heo . Furthermore, the +following people have contributed patches (autogenerated list): + + +1c7718e7 +a1346054 <36859588+a1346054@users.noreply.github.com> +admorgan +Ahmed Masud +AKowshik +Alan Somers +Albert Chen <58009229+hselin-kalista-io@users.noreply.github.com> +Albert Chen +Aleksandr Mikhailov +Alexander +alex +Alex Richman +Amir Goldstein +amosonn +Anatol Pomozov +André Schröder +Andrew Gaul +Andrew Gaul +Angelo G. Del Regno +Anthony Rebello +Antonio SJ Musumeci +Arunav Sanyal +asafkahlon <35964924+asafkahlon@users.noreply.github.com> +Ashley Pittman +AsumFace +Banglang +Baptiste Daroussin +Benjamin Barenblat +Bernd Schubert +Bernd Schubert +Bill Zissimooulos +Bill Zissimopoulos +bobrofon +Brian Naylor +Carl Edquist +Carlos Maiolino +Chad Austin +Changli Gao +Christian Menges +Christopher Harrison +Ciaran +Consus +Craig Chi +Csaba Henk +Csaba Henk +cvs2git <> +Dalvik Khertel +Daniel Fullmer +Daniel Thau +David Galeano +David McNab +David Sheets +dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> +Dharmendra singh +Dharmendra Singh +divinity76 +DrDaveD <2129743+DrDaveD@users.noreply.github.com> +Dr. David Alan Gilbert +Emily Herbert +Emmanuel Dreyfus +Enke Chen +Eric Engestrom +Eric Wong +Etienne Dublé +Fabian Vogt +Fabrice Bauzac +Fabrice Fontaine +Fedor Korotkov +Feng Shuo +ferivoz <72023087+ferivoz@users.noreply.github.com> +Feverfew +Fina Wilke +Florian Weimer +Forty-Bot +Frank Dinoff +Giulio Benetti +Giuseppe Scrivano +Goswin von Brederlow +guraga +HazelFZ +Heiko Becker +Hendrik Brueckner +HereThereBeDragons +Hookey +human +ikbenlike +Ikey Doherty +itsdeepak +Jan Blumschein +Jann Horn +Jay Hankins +Jean-Pierre André +Jean-Yves VET +Jérémie Galarneau +Joachim Schiele +Joachim Schiele +Joerg Thalheim +John Baber-Lucero +John Muir +Joseph Dodge +Josh Soref +Junichi Uekawa +Junichi Uekawa +Junichi Uekawa +Kangjing "Chaser" Huang +Ken Schalk +Kevin Vigor +Kirill Smelkov +Kyle Lippincott +Laszlo Boszormenyi (GCS) +Laszlo Papp +Laurent Bigonville +Lilo Huang +Liu Bo +Li-Wen Hsu +lixiaokeng <63774002+lixiaokeng@users.noreply.github.com> +lixiaokeng +Luis Henriques +Madan Valluri +Manuel Jacob +Marcin Sulikowski +Mark Glines +Martin Blanchard +Martin Pärtel +Mateusz Urbańczyk +Matthias Goergens +Matthias Görgens +Mattias Nissler +maxice8 <30738253+maxice8@users.noreply.github.com> +Maximilian Heinzler +Max Krasnyansky +Michael Forney +Michael Grigoriev +Mihail Konev +Miklos Szeredi +Miklos Szeredi +Miklos Szeredi +Miklos Szeredi +Misono Tomohiro +mkmm@gmx-topmail.de +mrdvdrm +Natanael Copa +Niels de Vos +Nikola Petrov <73067824+Petrov22Nikola@users.noreply.github.com> +Nikolaus Rath +Nozomi Miyamori <99280467+nm004@users.noreply.github.com> +Oded Arbel +Olivier Blin +pablomh +Pedro Kaj Kjellerup Nacht +Pedro Nacht +Peri +Peter Lemenkov +philmd +Pierre Labastie +Przemyslaw Pawelczyk +Przemysław Pawełczyk +psykose +Ratna_Bolla@dell.com +Rethan <359062468@qq.con> +Reuben Hawkins +rfjakob +richardweinberger +Richard W.M. Jones +Riku Voipio +Robo Shimmer +Roland Bauerschmidt +Roman Bogorodskiy +Rosen Penev +Rostislav +Rostislav Skudnov +Rudi Heitbaum +Sam Huffman <40582525+samh-sifive@users.noreply.github.com> +Sam James +Sam Stuewe +Sangwoo Moon +Sarath Lakshman +Sargun Dhillon +scosu +Scott Worley +Sebastian Pipping +Sergey Fedoseev +Seunghoon Yeon +Sławek Rudnicki +Stefan Hajnoczi +Stefan Hajnoczi +Stephen Kitt +Tej Chajed +tenzap <46226844+tenzap@users.noreply.github.com> +therealneworld@gmail.com +Tobias Nießen +Tofik Sonono +Tomasz Kulasek <34129113+tkulasek@users.noreply.github.com> +Tom Callaway +Tom Callaway +Tomohiro Kusumi +userwithuid +Valentin Plugaru +Vivek Goyal +Waldir Pimenta +wdlkmpx +William Woodruff +Winfried Koehler +winndows +Xiubo Li +Yaroslav Halchenko +y +Yuri Per +Zhansong Gao +Zhiqiang Liu +zsugabubus + +# New authors since fuse-3.16.2 +farlongsignal <141166749+farlongsignal@users.noreply.github.com> +yangyun50 <149988609+yangyun50@users.noreply.github.com> +bigbrotherwei <1965867461@qq.com> +Caian Benedicto <2220062+Caian@users.noreply.github.com> +desertwitch <24509509+desertwitch@users.noreply.github.com> +SteveYang <40466358+SteveY4ng@users.noreply.github.com> +FredyVia <942513309@qq.com> +legezywzh <94814730+legezywzh@users.noreply.github.com> +CismonX +amitgeron +Bernd Schubert +Daniel Rosenberg +Horst Birthelmer +Joanne Koong +Josef Bacik +Matthew +gandalfs_cat +MJ Harvey +Nils +Norman Wilson +leipeng +Vladimir Serbinenko +George Hilliard +Tyler Hall +yangyun +Abhishek + +# New authors since fuse-3.17.1 +Luis Henriques +Zegang +swj <1186093704@qq.com> +Gleb Popov <6yearold@gmail.com> +WekaJosh <80121792+WekaJosh@users.noreply.github.com> +Alexander Monakov +Ben Dooks +Ben Linsay +Dave Vasilevsky +Darrick J. Wong +Georgi Valkov +Alik Aslanyan +jnr0006 +Jingbo Xu +Long Li +Maksim Harbachou +Meng Lu Wang +Vassili Tchersky +Vassili Tchersky +izxl007 +Zeno Sebastian Endemann + +# New authors since fuse-3.18.1 +Abhinav Agarwal diff --git a/ChangeLog.rst b/ChangeLog.rst new file mode 100644 index 0000000..9a364b5 --- /dev/null +++ b/ChangeLog.rst @@ -0,0 +1,950 @@ +libfuse 3.18.2 (2026-03-18) +=========================== +* Fix two io-uring issues that might be security critical + * fuse-io-uring: Fix UAF and NULL deref in startup error path + * fuse-io-uring: Fix NULL deref and memory leak in fuse_uring_init_queue + +libfuse 3.18.1 (2025-12-20) +=========================== +* Fix a critical ABI issue compared to libfuse-3.17.3+ +* Note: This breaks ABI compatibility to libfuse-3.18.0 + (given that 3.18.0 is out for 2 days only, probably the lesser evil) + +libfuse 3.18.0 (2025-12-18) +=========================== + +New Features +------------ + +* fuse-over-io-uring communication +* statx support +* Request timeouts: Prevent hung operations +* FUSE_NOTIFY_INC_EPOCH: New notification mechanism for epoch counters + +Important Fixes +---------------- + +* Fixed double unmount on FUSE_DESTROY +* Fixed junk readdirplus results when filesystem doesn't fill stat info +* Fixed memory deallocation in fuse_session_loop_remember +* Fixed COPY_FILE_RANGE interface + +Platform Support +---------------- + +* Improved FreeBSD support (mount error reporting, test runner, build fixes) +* Fixed 32-bit architecture builds +* Fixed build with musl libc and older kernels (< 5.9) + +Other Improvements +------------------ + +* Added PanFS to fusermount whitelist +* Thread naming support for easier debugging + + +libfuse 3.17.4 (2025-08-19) +=========================== +- Try to detect mount-utils by checking for /run/mount/utab + and don't try to update mtab if it does not exist +- Fix a build warning when HAVE_BACKTRACE is undefined +- fuse_loop_mt.c: fix close-on-exec flag on clone fd +- Remove struct size assertions from fuse_common.h + +libfuse 3.17.3 (2025-07-16) +=========================== +* more conn->want / conn->want_ext conversion fixes +* Fix feature detection for close_range +* Avoid double unmount on FUSE_DESTROY + +libfuse 3.17.2 (2025-04-23) +=========================== +* Fixed uninitized bufsize value (compilation warning and real + issue when HAVE_SPLICE was not defined) +* Fixed initialization races related to buffer realocation when + large buf sizes are used (/proc/sys/fs/fuse/max_pages_limit) +* Fix build with kernel < 5.9 +* Fix static_assert build failure with C++ version < 11 +* Compilation fix (remove second fuse_main_real_versioned declaration) +* Another conn.want flag conversion fix for high-level applications +* Check if pthread_setname_np() exists before use it +* fix example/memfs_ll rename deadlock error +* signal handlers: Store fuse_session unconditionally and restore + previous behavior that with multiple sessions the last session + was used for the signal exist handler + +libfuse 3.17.1 (2025-03-24) +=========================== +* fuse: Fix want conn.want flag conversion +* Prevent re-usage of stdio FDs for fusermount +* PanFS added to fusermount whitelist + +libfuse 3.17.1-rc1 (2025-02-18) +=============================== +* several BSD fixes +* x86 (32bit) build fixes +* nested declarations moved out of the inlined functions to avoid + build warnings +* signify public key added for future 3.18 + +libfuse 3.17.1-rc0 (2025-02.10) +=============================== + +* Fix libfuse build with FUSE_USE_VERSION 30 +* Fix build of memfs_ll without manual meson reconfigure +* Fix junk readdirplus results when filesystem not filling stat info +* Fix conn.want_ext truncation to 32bit +* Fix some build warnings with -Og +* Fix fuse_main_real symbols +* Several changes related to functions/symbols that added in + the libfuse version in 3.17 +* Add thread names to libfuse threads +* With auto-umounts the FUSE_COMMFD2 (parent process fd is + exported to be able to silence leak checkers + + +libfuse 3.17 (2025-01-01, not officially releaesed) +================================================== + +* 3.11 and 3.14.2 introduced ABI incompatibilities, the ABI is restored + to 3.10, .so version was increased since there were releases with + the incompatible ABI + +* The libfuse version a program was compiled against is now encoded into + that program, using inlined functions in fuse_lowlevel.h and fuse.h +* Allows to handle fatal signals and to print a backtrace. + New API function: fuse_set_fail_signal_handlers() + +* Allows fuse_log() messages to be send to syslog instead of stderr + New API functions: fuse_log_enable_syslog() and fuse_log_close_syslog() + +* Handle buffer misalignment for FUSE_WRITE + +* Added support for filesystem passthrough read/write of files when + FUSE_PASSTHROUGH capability is enabled + New API functions: fuse_passthrough_open() and fuse_passthrough_close(), + also see example/passthrough_hp.cc + +* Added fmask and dmask options to high-level API + - dmask: umask applied to directories + - fmask: umask applied to non-directories + +* Added FUSE_FILL_DIR_DEFAULTS enum to support C++ programs using + fuse_fill_dir_t function + +* Added support for FUSE_CAP_HANDLE_KILLPRIV_V2 + +Fixes: +* Fixed compilation failure on FreeBSD (mount_bsd.c now points to correct + header) + +libfuse 3.16.2 (2023-10-10) +=========================== + +* Various small fixes and improvements. + +libfuse 3.16.1 (2023-08-08) +=========================== + +* Readdir kernel cache can be enabled from high-level API. + +libfuse 3.15.1 (2023-07-05) +=========================== + +Future libfuse releases will be signed with `signify`_ rather than PGP (rationale_). This +release is the last to be signed with PGP and contains the signify public key for current +(3.15.X) and upcoming (3.16.X) minor release cycle. + +.. _signify: https://www.openbsd.org/papers/bsdcan-signify.html +.. _rationale: https://latacora.micro.blog/2019/07/16/the-pgp-problem.html + + +libfuse 3.15.0 (2023-06-09) +=========================== + +* Improved support for some less common systems (32 bit, alternative libcs) + +* Unsupported mount options are no longer silently accepted. + +* auto_unmount is now compatible with allow_other. + + +libfuse 3.14.1 (2023-03-26) +=========================== + +* The extended attribute name passed to the setxattr() handler is no longer + truncated at the beginning (bug introduced in 3.13.0). + +* As a result of the above, the additional setattr() flags introduced in 3.14 are no + longer available for now. They will hopefully be reintroduced in the next release. + +* Further improvements of configuration header handling. + + +libfuse 3.14.0 (2023-02-17) +=========================== + +* Properly fix the header installation issue. The fix in 3.13.1 resulted + in conflicts with other packages. + +* Introduce additional setattr() flags (FORCE, KILL_SUID, KILL_SGID, FILE, KILL_PRIV, + OPEN, TIMES_SET) + + +libfuse 3.13.1 (2023-02-03) +=========================== + +* Fixed an issue that resulted in errors when attempting to compile against + installed libfuse headers (because libc symbol versioning support was not + detected correctly in this case). + +libfuse 3.13.0 (2023-01-13) +=========================== + +* There is a new low-level API function `fuse_session_custom_io` that allows to implement + a daemon with a custom io. This can be used to create a daemon that can process incoming + FUSE requests to other destinations than `/dev/fuse`. + +* A segfault when loading custom FUSE modules has been fixed. + +* There is a new `fuse_notify_expire_entry` function. + +* A deadlock when resolving paths in the high-level API has been fixed. + +* libfuse can now be build explicitly for C libraries without symbol versioning support. + +libfuse 3.12.0 (2022-09-08) +=========================== + +* There is a new build parameter to specify where the SysV init script should be + installed. + +* The *max_idle_threads* parameter has been deprecated in favor of the new max_threads* + parameter (which avoids the excessive overhead of creating and destructing threads). + Using max_threads == 1 and calling fuse_session_loop_mt() will run single threaded + similar to fuse_session_loop(). + +The following changes apply when using the most recent API (-DFUSE_USE_VERSION=312, +see `example/passthrough_hp.cc` for an example for how to usse the new API): + +* `struct fuse_loop_config` is now private and has to be constructed using + *fuse_loop_cfg_create()* and destroyed with *fuse_loop_cfg_destroy()*. Parameters can be + changed using `fuse_loop_cfg_set_*()` functions. + +* *fuse_session_loop_mt()* now accepts `struct fuse_loop_config *` as NULL pointer. + +* *fuse_parse_cmdline()* now accepts a *max_threads* option. + + +libfuse 3.11.0 (2022-05-02) +=========================== + +* Add support for flag FOPEN_NOFLUSH for avoiding flush on close. +* Fixed returning an error condition to ioctl(2) + + +libfuse 3.10.5 (2021-09-06) +=========================== + +* Various improvements to make unit tests more robust. + + +libfuse 3.10.4 (2021-06-09) +=========================== + +* Building of unit tests is now optional. +* Fixed a test failure when running tests under XFS. +* Fixed memory leaks in examples. +* Minor documentation fixes. + +libfuse 3.10.3 (2021-04-12) +=========================== + +* Fix returning d_ino and d_type from readdir(3) in non-plus mode + +libfuse 3.10.2 (2021-02-05) +=========================== + +* Allow "nonempty" as a mount option, for backwards compatibility with fusermount 2. The + option has no effect since mounting over non-empty directories is allowed by default. +* Fix returning inode numbers from readdir() in offset==0 mode. +* FUSE filesystems can now be mounted underneath EXFAT mountpoints. +* Various minor bugfixes. + +libfuse 3.10.1 (2020-12-07) +=========================== + +* Various minor bugfixes. + +libfuse 3.10.0 (2020-10-09) +=========================== + +* Add FUSE_CAP_CACHE_SYMLINKS: allow caching symlinks in kernel page cache. +* Various minor bugfixes and improvements. + +libfuse 3.9.4 (2020-08-09) +========================== + +This was an "accidental" release, it is equivalent to 3.9.3. + +libfuse 3.9.3 (2020-08-09) +========================== + +* Fixed compilation under OS X and µClibc. +* Minor bugfixes and doc updates. + +libfuse 3.9.2 (2020-06-12) +========================== + +* Remove obsolete workarounds in examples. +* Do not require C++ compiler for building. +* Minor bugfixes. + +libfuse 3.9.1 (2020-03-19) +=========================== + +* Fixed memory leak in fuse_session_new(). +* Fixed an issue with the linker version script. +* Make ioctl prototype conditional on FUSE_USE_VERSION. Define FUSE_USE_VERSION < 35 to + get old ioctl prototype with int commands; define FUSE_USE_VERSION >= 35 to get new + ioctl prototype with unsigned int commands. +* Various small bugfixes. + +libfuse 3.9.0 (2019-12-14) +========================== + +* Added support for FUSE_EXPLICIT_INVAL_DATA to enable + only invalidate cached pages on explicit request. + +libfuse 3.8.0 (2019-11-03) +========================== + +* Added support for FUSE_LSEEK operation which can be used to report holes + in sparse files. + +libfuse 3.7.0 (2019-09-27) +========================== + +* Added UFSD to whitelist (so users can now mount FUSE filesystems + on mountpoints within UFSD filesystems). +* Added custom log message handler function support so that libfuse + applications can direct messages to syslog(3) or other logging systems. + stderr remains the default. See `fuse_log.h` for the new API. + +libfuse 3.6.2 (2019-07-09) +========================== + +* The init script is now installed to /etc/ rather than /usr/local/etc + by default. + +libfuse 3.6.1 (2019-06-13) +========================== + +* Fixed version number (release 3.6.0 was shipped with a declared + version of 3.0.0). + +libfuse 3.6.0 (2019-06-13) +========================== + +* Added a new example (passthrough_hp). The functionality is similar + to passthrough_ll, but the implementation focuses on performance and + correctness rather than simplicity. +* Added support for fuse kernel feature `max_pages` which allows to increase + the maximum number of pages that can be used per request. This feature was + introduced in kernel 4.20. `max_pages` is set based on the value in + `max_write`. By default `max_write` will be 1MiB now for kernels that support + `max_pages`. If you want smaller buffers or writes you have to set + `max_write` manually. + +libfuse 3.5.0 (2019-04-16) +========================== + +* Changed ioctl commands to "unsigned int" in order to support commands + which do not fit into a signed int. Commands issued by applications + are still truncated to 32 bits. +* Added SMB2 to whitelist (so users can now mount FUSE filesystems + on mountpoints within SMB 2.0 filesystems). +* Added a new `cache_readdir` flag to `fuse_file_info` to enable + caching of readdir results. Supported by kernels 4.20 and newer. +* Add support and documentation for FUSE_CAP_NO_OPENDIR_SUPPORT. + +libfuse 3.4.2 (2019-03-09) +========================== + +* Fixed a memory leak in `examples/passthrough_ll.c`. +* Added OpenAFS to whitelist (so users can now mount FUSE filesystems + on mountpoints within OpenAFS filesystems). +* Added HFS+ to whitelist (so users can now mount FUSE filesystems + on mountpoints within HFS+ filesystems). +* Documentation improvements. + +libfuse 3.4.1 (2018-12-22) +========================== + +* The `examples/passthrough_ll.c` example filesystem has been + significantly extended. +* Support for `copy_file_range` has been added. +* Build system updates for non-Linux systems. + +libfuse 3.4.0 +============= + +* Add `copy_file_range()` to support efficient copying of data from one file to + an other. + +libfuse 3.3.0 (2018-11-06) +========================== + +* The `auto_unmount` mode now works correctly in combination with + autofs. + +* The FUSE_CAP_READDIRPLUS_AUTO capability is no longer enabled by + default unless the file system defines both a readdir() and a + readdirplus() handler. + +* The description of the FUSE_CAP_READDIRPLUS_AUTO flag has been + improved. + +* Allow open `/dev/fuse` file descriptors to be passed via mountpoints of the + special format `/dev/fd/%u`. This allows mounting to be handled by the parent + so the FUSE filesystem process can run fully unprivileged. + +* Add a `drop_privileges` option to mount.fuse3 which causes it to open + `/dev/fuse` and mount the file system itself, then run the FUSE file + filesystem fully unprivileged and unable to re-acquire privilege via setuid, + fscaps, etc. + +* Documented under which conditions the `fuse_lowlevel_notify_*` + functions may block. + +libfuse 3.2.6 (2018-08-31) +========================== + +* The fuse_main() function now returns more fine-grained error codes. +* FUSE filesystems may now be mounted on mountpoint within + bcachefs, aufs and FAT filesystems. +* libfuse may now be used as a Meson subproject. +* Fix a few low-impact memory leaks. +* The `fuse.conf` file is no longer looked for in `/etc`, but in the + *sysconfdir* directory (which can be set with `meson configure`). By + default, the location is thus `/usr/local/etc/fuse.conf`. + +libfuse 3.2.5 (2018-07-24) +========================== + +* SECURITY UPDATE: In previous versions of libfuse it was possible to + for unprivileged users to specify the `allow_other` option even when + this was forbidden in `/etc/fuse.conf`. The vulnerability is + present only on systems where SELinux is active (including in + permissive mode). +* The fusermount binary has been hardened in several ways to reduce + potential attack surface. Most importantly, mountpoints and mount + options must now match a hard-coded whitelist. It is expected that + this whitelist covers all regular use-cases. +* Added a test of `seekdir` to test_syscalls. +* Fixed `readdir` bug when non-zero offsets are given to filler and the + filesystem client, after reading a whole directory, re-reads it from a + non-zero offset e. g. by calling `seekdir` followed by `readdir`. + +libfuse 3.2.4 (2018-07-11) +========================== + +* Fixed `rename` deadlock on FreeBSD. + +libfuse 3.2.3 (2018-05-11) +========================== + +* Fixed a number of compiler warnings. + +libfuse 3.2.2 (2018-03-31) +========================== + +* Added example fuse.conf file. +* Added "support" for -o nofail mount option (the option is accepted + and ignored). +* Various small bugfixes. + +libfuse 3.2.1 (2017-11-14) +========================== + +* Various small bugfixes. + +libfuse 3.2.0 (2017-09-12) +========================== + +* Support for building with autotools has been dropped. + +* Added new `fuse_invalidate_path()` routine for cache invalidation + from the high-level FUSE API, along with an example and tests. + +* There's a new `printcap` example that can be used to determine the + capabilities of the running kernel. + +* `fuse_loop_mt()` now returns the minus the actual errno if there was + an error (instead of just -1). + +* `fuse_loop()` no longer returns a positive value if the filesystem + loop was terminated without errors or signals. + +* Improved documentation of `fuse_lowlevel_notify_*` functions. + +* `fuse_lowlevel_notify_inval_inode()` and + `fuse_lowlevel_notify_inval_entry()` now return -ENOSYS instead of + an undefined error if the function is not supported by the kernel. + +* Documented the special meaning of the *zero* offset for the + fuse_fill_dir_t function. + +* The `passthrough_fh` example now works under FreeBSD. + +* libfuse can now be build without libiconv. + +* Fixed support for `FUSE_CAP_POSIX_ACL`: setting this capability + flag had no effect in the previous versions of libfuse 3.x; + now ACLs should actually work. + +* Fixed a number of compilation problems under FreeBSD. + +* Fixed installation directory for udev rules. + +* Fixed compilation with LTO. + +libfuse 3.1.1 (2017-08-06) +========================== + +* Documentation: clarified how filesystems are supposed to process + open() and create() flags (see include/fuse_lowlevel.h). + +* Fixed a compilation problem of the passthrough_ll example on + 32 bit systems (wrong check and wrong error message). + +* pkg-config is now used to determine the proper directory for + udev rules. + +* Fixed a symbol versioning problem that resulted in very strange + failures (segfaults, unexpected behavior) in different situations. + +* Fixed a test failure when /tmp is on btrfs. + +* The maximum number of idle worker threads used by `fuse_loop_mt()` + is now configurable. + +* `fuse_loop_mt()` and `fuse_session_loop_mt()` now take a + `struct fuse_loop_config` parameter that supersedes the *clone_fd* + parameter. + +* Incorporated several patches from the FreeBSD port. libfuse should + now compile under FreeBSD without the need for patches. + +* The passthrough_ll example now supports writeback caching. + +libfuse 3.1.0 (2017-07-08) +========================== + +* Added new `fuse_lib_help()` function. File-systems that previously + passed a ``--help`` option to `fuse_new()` must now process the + ``--help`` option internally and call `fuse_lib_help()` to print the + help for generic FUSE options. +* Fixed description of the `fuse_conn_info->time_gran`. The default + value of zero actually corresponds to full nanosecond resolution, + not one second resolution. +* The init script is now installed into the right location + (``$DESTDIR/etc/init.d`` rather than ``$prefix/$sysconfdir/init.d``) +* The `example/passthrough_ll` filesystem now supports creating + and writing to files. +* `fuse_main()` / `fuse_remove_signal_handlers()`: do not reset + `SIGPIPE` handler to `SIG_DFL` if it was not set by us. +* Documented the `RENAME_EXCHANGE` and `RENAME_NOREPLACE` flags that + may be passed to the `rename` handler of both the high- and + low-level API. Filesystem authors are strongly encouraged to check + that these flags are handled correctly. + +libfuse 3.0.2 (2017-05-24) +========================== + +* Option parsing for the high-level API now works correctly + (previously, default values would override specified values). +* Tests should now build (and run) under FreeBSD. +* Improved documentation of `struct fuse_context` +* Internal: calculate request buffer size from page size and kernel + page limit instead of using hardcoded 128 kB limit. + + +libfuse 3.0.1 (2017-04-10) +========================== + +* Re-introduced *examples/null.c*. +* Added experimental support for building with Meson. +* Document that `-o auto_unmount` implies `-o nodev,nosuid`. +* Document that the *use_ino* option of the high-level interface does + not affect the inode that libfuse and the kernel use internally. +* Fixed test cases for passthrough* examples (they weren't actually + testing the examples). +* Fixed several bugs in the passthrough* examples. + +libfuse 3.0.0 (2016-12-08) +========================== + +* NOTE TO PACKAGERS: + + libfuse 3 is designed to be co-installable with libfuse 2. However, + some files will be installed by both libfuse 2 and libfuse 3 + (e.g. /etc/fuse.conf, the udev and init scripts, and the + mount.fuse(8) manpage). These files should be taken from + libfuse 3. The format/content is guaranteed to remain backwards + compatible with libfuse 2. + + We recommend to ship libfuse2 and libfuse3 in three separate + packages: a libfuse-common package that contains files shared by + libfuse 2+3 (taken from the libfuse3 tarball), and libfuse2 and + libfuse3 packages that contain the shared library and helper + programs for the respective version. + +* Fixed test errors when running tests as root. + +* Made check for util-linux version more robust. + +* Added documentation for all fuse capability flags (`FUSE_CAP_*`) and + `struct fuse_conn_info` fields. + +* fuse_loop(), fuse_loop_mt(), fuse_session_loop() and + fuse_session_loop_mt() now return more detailed error codes instead + of just -1. See the documentation of fuse_session_loop() for details. + +* The FUSE main loop is now aborted if the file-system requests + capabilities that are not supported by the kernel. In this case, the + session loop is exited with a return code of ``-EPROTO``. + +* Most file-system capabilities that were opt-in in libfuse2 are now + enabled by default. Filesystem developers are encouraged to review + the documentation of the FUSE_CAP_* features to ensure that their + filesystem is compatible with the new semantics. As before, a + particular capability can still be disabled by unsetting the + corresponding bit of `fuse_conn_info.wants` in the init() handler. + +* Added FUSE_CAP_PARALLEL_DIROPS and FUSE_CAP_POSIX_ACL, + FUSE_HANDLE_KILLPRIV feature flags. + +* FUSE filesystems are now responsible for unsetting the setuid/setgid + flags when a file is written, truncated, or its owner + changed. Previously, this was handled by the kernel but subject to + race conditions. + +* The fusermount and mount.fuse binaries have been renamed to + fusermount3 and mount.fuse3 to allow co-installation of libfuse 2.x + and 3.x + +* Added a `max_read` field to `struct fuse_conn_info`. For the time + being, the maximum size of read requests has to be specified both + there *and* passed to fuse_session_new() using the ``-o + max_read=`` mount option. At some point in the future, specifying + the mount option will no longer be necessary. + +* Documentation: clarified that the fuse_argv structure that is passed + to `fuse_new()` and `fuse_lowlevel_new()` must always contain at + least one element. + +* The high-level init() handler now receives an additional struct + fuse_config pointer that can be used to adjust high-level API + specific configuration options. + +* The `nopath_flag` field of struct fuse_operations has been + removed. Instead, a new `nullpath_ok` flag can now be set + in struct fuse_config. + +* File systems that use the low-level API and support lookup requests + for '.' and '..' should continue make sure to set the + FUSE_CAP_EXPORT_SUPPORT bit in fuse_conn_info->want. + + (This has actually always been the case, but was not very obvious + from the documentation). + +* The help text generated by fuse_lowlevel_help(), fuse_new() (and + indirectly fuse_main()) no longer includes options that are unlikely + to be of interest to end-users. The full list of accepted options is + now included in the respective function's documentation (located in + the fuse.h/fuse_lowlevel.h and doc/html). + +* The ``-o nopath`` option has been dropped - it never actually did + anything (since it is unconditionally overwritten with the value of + the `nopath` flag in `struct fuse_operations`). + +* The ``-o large_read`` mount option has been dropped. Hopefully no + one uses a Linux 2.4 kernel anymore. + +* The `-o nonempty` mount point has been removed, mounting over + non-empty directories is now always allowed. This brings the + behavior of FUSE file systems in-line with the behavior of the + regular `mount` command. + + File systems that do not want to allow mounting to non-empty + directories should perform this check themselves before handing + control to libfuse. + +* The chmod, chown, truncate, utimens and getattr handlers of the + high-level API now all receive an additional struct fuse_file_info + pointer (which, however, may be NULL even if the file is currently + open). + + The fgetattr and ftruncate handlers have become obsolete and have + been removed. + +* The `fuse_session_new` function no longer accepts the ``-o + clone_fd`` option. Instead, this has become a parameter of the + `fuse_session_loop_mt` and `fuse_loop_mt` functions. + +* For low-level file systems that implement the `write_buf` handler, + the `splice_read` option is now enabled by default. As usual, this + can be changed in the file system's `init` handler. + +* The treatment of low-level options has been made more consistent: + + Options that can be set in the init() handler (via the + fuse_conn_info parameter) can now be set only here, + i.e. fuse_session_new() no longer accepts arguments that change the + fuse_conn_info object before or after the call do init(). As a side + effect, this removes the ambiguity where some options can be + overwritten by init(), while others overwrite the choices made by + init(). + + For file systems that wish to offer command line options for these + settings, the new fuse_parse_conn_info_opts() and + fuse_apply_conn_info_opts() functions are available. + + Consequently, the fuse_lowlevel_help() method has been dropped. + +* The `async_read` field in `struct fuse_conn_info` has been + removed. To determine if the kernel supports asynchronous reads, + file systems should check the `FUSE_CAP_ASYNC_READ` bit of the + `capable` field. To enable/disable asynchronous reads, file systems + should set the flag in the `wanted` field. + +* The `fuse_parse_cmdline` function no longer prints out help when the + ``--verbose`` or ``--help`` flags are given. This needs to be done + by the file system (e.g. using the `fuse_cmdline_help()` and + `fuse_lowlevel_help()` functions). + +* Added ``example/cuse_client.c`` to test ``example/cuse.c``. + +* Removed ``example/null.c``. This has not been working for a while + for unknown reasons -- maybe because it tries to treat the + mountpoint as a file rather than a directory? + +* There are several new examples that demonstrate the use of + the ``fuse_lowlevel_notify_*`` functions: + + - ``example/notify_store_retrieve.c`` + - ``example/notify_inval_inode.c`` + - ``example/notify_inval_entry.c`` + +* The ``-o big_writes`` mount option has been removed. It is now + always active. File systems that want to limit the size of write + requests should use the ``-o max_write=`` option instead. + +* The `fuse_lowlevel_new` function has been renamed to + `fuse_session_new` and no longer interprets the --version or --help + options. To print help or version information, use the new + `fuse_lowlevel_help` and `fuse_lowlevel_version` functions. + +* The ``allow_other`` and ``allow_root`` mount options (accepted by + `fuse_session_new()`) may now be specified together. In this case, + ``allow_root`` takes precedence. + +* There are new `fuse_session_unmount` and `fuse_session_mount` + functions that should be used in the low-level API. The `fuse_mount` + and `fuse_unmount` functions should be used with the high-level API + only. + +* Neither `fuse_mount` nor `fuse_session_mount` take struct fuse_opts + parameters anymore. Mount options are parsed by `fuse_new` (for the + high-level API) and `fuse_session_new` (for the low-level API) + instead. To print help or version information, use the new + `fuse_mount_help` and `fuse_mount_version` functions. + +* The ``fuse_lowlevel_notify_*`` functions now all take a `struct + fuse_session` parameter instead of a `struct fuse_chan`. + +* The channel interface (``fuse_chan_*`` functions) has been made + private. As a result, the typical initialization sequence of a + low-level file system has changed from :: + + ch = fuse_mount(mountpoint, &args); + se = fuse_lowlevel_new(&args, &lo_oper, sizeof(lo_oper), &lo); + fuse_set_signal_handlers(se); + fuse_session_add_chan(se, ch); + fuse_daemonize(fg); + if (mt) + fuse_session_loop_mt(se); + else + fuse_session_loop(se); + fuse_remove_signal_handlers(se); + fuse_session_remove_chan(ch); + fuse_session_destroy(se); + fuse_unmount(mountpoint, ch); + + to :: + + se = fuse_session_new(&args, &ll_ops, sizeof(ll_ops), NULL); + fuse_set_signal_handlers(se); + fuse_session_mount(se, mountpoint); + fuse_daemonize(fg); + if (mt) + fuse_session_loop_mt(se); + else + fuse_session_loop(se); + fuse_remove_signal_handlers(se); + fuse_session_unmount(se); + fuse_lowlevel_destroy(se); + + The typical high-level setup has changed from :: + + ch = fuse_mount(*mountpoint, &args); + fuse = fuse_new(ch, &args, op, op_size, user_data); + se = fuse_get_session(fuse); + fuse_set_signal_handlers(se); + fuse_daemonize(fg); + if (mt) + fuse_loop_mt(fuse); + else + fuse_loop(fuse); + fuse_remove_signal_handlers(se); + fuse_unmount(mountpoint, ch); + fuse_destroy(fuse); + + to :: + + fuse = fuse_new(&args, op, op_size, user_data); + se = fuse_get_session(fuse); + fuse_set_signal_handlers(se); + fuse_mount(fuse, mountpoint); + fuse_daemonize(fg); + if (mt) + fuse_loop_mt(fuse); + else + fuse_loop(fuse); + fuse_remove_signal_handlers(se); + fuse_unmount(fuse); + fuse_destroy(fuse); + + File systems that use `fuse_main` are not affected by this change. + + For integration with custom event loops, the new `fuse_session_fd` + function provides the file descriptor that's used for communication + with the kernel. + +* Added *clone_fd* option. This creates a separate device file + descriptor for each processing thread, which might improve + performance. + +* Added *writeback_cache* option. With kernel 3.14 and newer this + enables write-back caching which can significantly improve + performance. + +* Added *async_dio* option. With kernel 3.13 and newer, this allows + direct I/O to be done asynchronously. + +* The (high- and low-level) `rename` handlers now takes a *flags* + parameter (with values corresponding to the *renameat2* system call + introduced in Linux 3.15). + +* The "ulockmgr_server" has been dropped. + +* There is a new (low-level) `readdirplus` handler, with a + corresponding example in ``examples/fuse_lo-plus.c`` and a new + `fuse_add_direntry_plus` API function. + +* The (high-level) `readdir` handler now takes a *flags* argument. + +* The (high-level) `filler` function passed to `readdir` now takes an + additional *flags* argument. + +* The (high-level) `getdir` handler has been dropped. + +* The *flag_nullpath_ok* and *flag_utime_omit_ok* flags have been + dropped. + +* The (high-level) *utime* handler has been dropped. + +* The `fuse_invalidate` function has been removed. + +* The `fuse_is_lib_option` function has been removed. + +* The *fh_old* member of `struct fuse_file_info` has been dropped. + +* The type of the *writepage* member of `struct fuse_file_info` was + changed from *int* to *unsigned int*. + +* The `struct fuse_file_info` gained a new *poll_events* member. + +* There is a new `fuse_pkgversion` function. + +* The *fuse_off_t* and *fuse_ino_t* changed from *unsigned long* to + *uint64_t*, i.e. they are now 64 bits also on 32-bit systems. + +* The type of the *generation* member of `struct fuse_entry_param*` + changed from *unsigned* to *uint64_t*. + +* The (low-level) `setattr` handler gained a *FUSE_SET_ATTR_CTIME* bit + *for its *to_set* parameter. + +* The `struct fuse_session_ops` data structure has been dropped. + +* The documentation has been clarified and improved in many places. + + +FUSE 2.9.7 (2016-06-20) +======================= + +* Added SELinux support. +* Fixed race-condition when session is terminated right after starting + a FUSE file system. + +FUSE 2.9.6 (2016-04-23) +======================= + +* Tarball now includes documentation. +* Shared-object version has now been bumped correctly. + +FUSE 2.9.5 (2016-01-14) +======================= + +* New maintainer: Nikolaus Rath . Many thanks to + Miklos Szeredi for bringing FUSE to where it is + now! + +* fix warning in mount.c:receive_fd(). Reported by Albert Berger + +* fix possible memory leak. Reported by Jose R. Guzman + +FUSE 2.9.4 (2015-05-22) +======================= + +* fix exec environment for mount and umount. Found by Tavis Ormandy + (CVE-2015-3202). + +* fix fuse_remove_signal_handlers() to properly restore the default + signal handler. Reported by: Chris Johnson + +* highlevel API: fix directory file handle passed to ioctl() method. + Reported by Eric Biggers + +* libfuse: document deadlock avoidance for fuse_notify_inval_entry() + and fuse_notify_delete() + +* fusermount, libfuse: send value as unsigned in "user_id=" and + "group_id=" options. Uids/gids larger than 2147483647 would result + in EINVAL when mounting the filesystem. This also needs a fix in + the kernel. + +* Initialize stat buffer passed to ->getattr() and ->fgetattr() to + zero in all cases. Reported by Daniel Iwan + +* libfuse: Add missing includes. This allows compiling fuse with + musl. Patch by Daniel Thau + + +Older Versions (before 2013-01-01) +================================== + +Please see Git history, e.g. at +https://github.com/libfuse/libfuse/blob/fuse_2_9_3/ChangeLog. diff --git a/GPL2.txt b/GPL2.txt new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/GPL2.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/LGPL2.txt b/LGPL2.txt new file mode 100644 index 0000000..4362b49 --- /dev/null +++ b/LGPL2.txt @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..184a5b2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +The following files may be used under the terms of the GNU Lesser +General Public License, version 2.1 ("LGPL"): + +- All files in the include/ directory. +- All files in the lib/ directory. +- meson.build + +The full terms of the LGPL can be found in the LGPL2.txt file. + + +All other files may be used only under the terms of the GNU General +Public License, version 2 ("GPL"). The full text of this license can +be found in the GPL2.txt file. diff --git a/README.md b/README.md new file mode 100644 index 0000000..eaf1308 --- /dev/null +++ b/README.md @@ -0,0 +1,162 @@ +libfuse +======= + +About +----- + +FUSE (Filesystem in Userspace) is an interface for userspace programs +to export a filesystem to the Linux kernel. The FUSE project consists +of two components: the *fuse* kernel module (maintained in the regular +kernel repositories) and the *libfuse* userspace library (maintained +in this repository). libfuse provides the reference implementation +for communicating with the FUSE kernel module. + +A FUSE file system is typically implemented as a standalone +application that links with libfuse. libfuse provides functions to +mount the file system, unmount it, read requests from the kernel, and +send responses back. libfuse offers two APIs: a "high-level", +synchronous API, and a "low-level" asynchronous API. In both cases, +incoming requests from the kernel are passed to the main program using +callbacks. When using the high-level API, the callbacks may work with +file names and paths instead of inodes, and processing of a request +finishes when the callback function returns. When using the low-level +API, the callbacks must work with inodes and responses must be sent +explicitly using a separate set of API functions. + + +Development Status +------------------ + +libfuse is shipped by all major Linux distributions and has been in +production use across a wide range of systems for many years. However, +at present libfuse does not have any active, regular contributors. The +current maintainer continues to apply pull requests and makes regular +releases, but unfortunately has no capacity to do any development +beyond addressing high-impact issues. When reporting bugs, please +understand that unless you are including a pull request or are +reporting a critical issue, you will probably not get a response. If +you are using libfuse, please consider contributing to the project. + + +Supported Platforms +------------------- + +* Linux (fully) +* BSD (mostly/best-effort) +* For OS-X, please use [OSXFUSE](https://osxfuse.github.io/) + + +Installation +------------ + +You can download libfuse from https://github.com/libfuse/libfuse/releases. To build and +install, you must use [Meson](http://mesonbuild.com/) and +[Ninja](https://ninja-build.org). After downloading the tarball and `.sig` file, verify +it using [signify](https://www.openbsd.org/papers/bsdcan-signify.html): + + signify -V -m fuse-X.Y.Z.tar.gz -p fuse-X.Y.pub + +The `fuse-X.Y.pub` file contains the signing key and needs to be obtained from a +trustworthy source. Each libfuse release contains the signing key for the release after it +in the `signify` directory, so you only need to manually acquire this file once when you +install libfuse for the first time. + +After you have validated the tarball, extract it, create a (temporary) build directory and +run Meson: + + $ tar xzf fuse-X.Y.Z.tar.gz; cd fuse-X.Y.Z + $ mkdir build; cd build + $ meson setup .. + +Normally, the default build options will work fine. If you +nevertheless want to adjust them, you can do so with the +*meson configure* command: + + $ meson configure # list options + $ meson configure -D disable-mtab=true # set an optionq + + $ # ensure all meson options are applied to the final build system + $ meson setup --reconfigure ../ + +To build, test, and install libfuse, you then use Ninja: + + $ ninja + $ sudo python3 -m pytest test/ + $ sudo ninja install + +Running the tests requires the [py.test](http://www.pytest.org/) +Python module. Instead of running the tests as root, the majority of +tests can also be run as a regular user if *util/fusermount3* is made +setuid root first: + + $ sudo chown root:root util/fusermount3 + $ sudo chmod 4755 util/fusermount3 + $ python3 -m pytest test/ + +Security implications +--------------------- + +The *fusermount3* program is installed setuid root. This is done to +allow normal users to mount their own filesystem implementations. + +To limit the harm that malicious users can do this way, *fusermount3* +enforces the following limitations: + + - The user can only mount on a mountpoint for which they have write + permission + + - The mountpoint must not be a sticky directory which isn't owned by + the user (like /tmp usually is) + + - No other user (including root) can access the contents of the + mounted filesystem (though this can be relaxed by allowing the use + of the *allow_other* and *allow_root* mount options in + */etc/fuse.conf*) + + +If you intend to use the *allow_other* mount options, be aware that +FUSE has an unresolved [security +bug](https://github.com/libfuse/libfuse/issues/15): if the +*default_permissions* mount option is not used, the results of the +first permission check performed by the file system for a directory +entry will be re-used for subsequent accesses as long as the inode of +the accessed entry is present in the kernel cache - even if the +permissions have since changed, and even if the subsequent access is +made by a different user. This is of little concern if the filesystem +is accessible only to the mounting user (which has full access to the +filesystem anyway), but becomes a security issue when other users are +allowed to access the filesystem (since they can exploit this to +perform operations on the filesystem that they do not actually have +permissions for). + +This bug needs to be fixed in the Linux kernel and has been known +since 2006 but unfortunately no fix has been applied yet. If you +depend on correct permission handling for FUSE file systems, the only +workaround is to use `default_permissions` (which does not currently +support ACLs), or to completely disable caching of directory entry +attributes. + +Building your own filesystem +------------------------------ + +FUSE comes with several example file systems in the `example` +directory. For example, the *passthrough* examples mirror the contents +of the root directory under the mountpoint. Start from there and adapt +the code! + +The documentation of the API functions and necessary callbacks is +mostly contained in the files `include/fuse.h` (for the high-level +API) and `include/fuse_lowlevel.h` (for the low-level API). An +autogenerated html version of the API is available in the `doc/html` +directory and at http://libfuse.github.io/doxygen. + + +Getting Help +------------ + +If you need help, please ask on the +mailing list (subscribe at +https://lists.sourceforge.net/lists/listinfo/fuse-devel). + +Please report any bugs on the GitHub issue tracker at +https://github.com/libfuse/libfuse/issues. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..19e1366 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,20 @@ +# Security Policy + +If you have discovered a security vulnerability in this project, please report it +privately. **Do not disclose it as a public issue.** This gives me time to work with you +to fix the issue before public exposure, reducing the chance that the exploit will be +used before a patch is released. + +Please submit information on the vulnerability as a +[private report](https://github.com/libfuse/libfuse/security/advisories/new). + +Please provide the following information in your report: + +- A description of the vulnerability and its impact +- How to reproduce the issue + +This project is maintained by a single volunteer on a reasonable-effort basis. As such, +I ask that you give me 90 days to work on a fix before public exposure. + +Note we are aware of a long-standing security issue when using `allow_others` (see +[#15](https://github.com/libfuse/libfuse/issues/15)). \ No newline at end of file diff --git a/checkpatch.pl b/checkpatch.pl new file mode 100755 index 0000000..9eed368 --- /dev/null +++ b/checkpatch.pl @@ -0,0 +1,7820 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# +# (c) 2001, Dave Jones. (the file handling bit) +# (c) 2005, Joel Schopp (the ugly bit) +# (c) 2007,2008, Andy Whitcroft (new conditions, test suite) +# (c) 2008-2010 Andy Whitcroft +# (c) 2010-2018 Joe Perches + +use strict; +use warnings; +use POSIX; +use File::Basename; +use Cwd 'abs_path'; +use Term::ANSIColor qw(:constants); +use Encode qw(decode encode); + +my $P = $0; +my $D = dirname(abs_path($P)); + +my $V = '0.32'; + +use Getopt::Long qw(:config no_auto_abbrev); + +my $quiet = 0; +my $verbose = 0; +my %verbose_messages = (); +my %verbose_emitted = (); +my $tree = 1; +my $chk_signoff = 1; +my $chk_fixes_tag = 1; +my $chk_patch = 1; +my $tst_only; +my $emacs = 0; +my $terse = 0; +my $showfile = 0; +my $file = 0; +my $git = 0; +my %git_commits = (); +my $check = 0; +my $check_orig = 0; +my $summary = 1; +my $mailback = 0; +my $summary_file = 0; +my $show_types = 0; +my $list_types = 0; +my $fix = 0; +my $fix_inplace = 0; +my $root; +my $gitroot = $ENV{'GIT_DIR'}; +$gitroot = ".git" if !defined($gitroot); +my %debug; +my %camelcase = (); +my %use_type = (); +my @use = (); +my %ignore_type = (); +my @ignore = (); +my $help = 0; +my $configuration_file = ".checkpatch.conf"; +my $max_line_length = 100; +my $ignore_perl_version = 0; +my $minimum_perl_version = 5.10.0; +my $min_conf_desc_length = 4; +my $spelling_file = "$D/spelling.txt"; +my $codespell = 0; +my $codespellfile = "/usr/share/codespell/dictionary.txt"; +my $user_codespellfile = ""; +my $conststructsfile = "$D/const_structs.checkpatch"; +my $docsfile = "$D/../Documentation/dev-tools/checkpatch.rst"; +my $typedefsfile; +my $color = "auto"; +my $allow_c99_comments = 1; # Can be overridden by --ignore C99_COMMENT_TOLERANCE +# git output parsing needs US English output, so first set backtick child process LANGUAGE +my $git_command ='export LANGUAGE=en_US.UTF-8; git'; +my $tabsize = 8; +my ${CONFIG_} = "CONFIG_"; + +my %maybe_linker_symbol; # for externs in c exceptions, when seen in *vmlinux.lds.h + +sub help { + my ($exitcode) = @_; + + print << "EOM"; +Usage: $P [OPTION]... [FILE]... +Version: $V + +Options: + -q, --quiet quiet + -v, --verbose verbose mode + --no-tree run without a kernel tree + --no-signoff do not check for 'Signed-off-by' line + --no-fixes-tag do not check for 'Fixes:' tag + --patch treat FILE as patchfile (default) + --emacs emacs compile window format + --terse one line per report + --showfile emit diffed file position, not input file position + -g, --git treat FILE as a single commit or git revision range + single git commit with: + + ^ + ~n + multiple git commits with: + .. + ... + - + git merges are ignored + -f, --file treat FILE as regular source file + --subjective, --strict enable more subjective tests + --list-types list the possible message types + --types TYPE(,TYPE2...) show only these comma separated message types + --ignore TYPE(,TYPE2...) ignore various comma separated message types + --show-types show the specific message type in the output + --max-line-length=n set the maximum line length, (default $max_line_length) + if exceeded, warn on patches + requires --strict for use with --file + --min-conf-desc-length=n set the min description length, if shorter, warn + --tab-size=n set the number of spaces for tab (default $tabsize) + --root=PATH PATH to the kernel tree root + --no-summary suppress the per-file summary + --mailback only produce a report in case of warnings/errors + --summary-file include the filename in summary + --debug KEY=[0|1] turn on/off debugging of KEY, where KEY is one of + 'values', 'possible', 'type', and 'attr' (default + is all off) + --test-only=WORD report only warnings/errors containing WORD + literally + --fix EXPERIMENTAL - may create horrible results + If correctable single-line errors exist, create + ".EXPERIMENTAL-checkpatch-fixes" + with potential errors corrected to the preferred + checkpatch style + --fix-inplace EXPERIMENTAL - may create horrible results + Is the same as --fix, but overwrites the input + file. It's your fault if there's no backup or git + --ignore-perl-version override checking of perl version. expect + runtime errors. + --codespell Use the codespell dictionary for spelling/typos + (default:$codespellfile) + --codespellfile Use this codespell dictionary + --typedefsfile Read additional types from this file + --color[=WHEN] Use colors 'always', 'never', or only when output + is a terminal ('auto'). Default is 'auto'. + --kconfig-prefix=WORD use WORD as a prefix for Kconfig symbols (default + ${CONFIG_}) + -h, --help, --version display this help and exit + +When FILE is - read standard input. +EOM + + exit($exitcode); +} + +sub uniq { + my %seen; + return grep { !$seen{$_}++ } @_; +} + +sub list_types { + my ($exitcode) = @_; + + my $count = 0; + + local $/ = undef; + + open(my $script, '<', abs_path($P)) or + die "$P: Can't read '$P' $!\n"; + + my $text = <$script>; + close($script); + + my %types = (); + # Also catch when type or level is passed through a variable + while ($text =~ /(?:(\bCHK|\bWARN|\bERROR|&\{\$msg_level})\s*\(|\$msg_type\s*=)\s*"([^"]+)"/g) { + if (defined($1)) { + if (exists($types{$2})) { + $types{$2} .= ",$1" if ($types{$2} ne $1); + } else { + $types{$2} = $1; + } + } else { + $types{$2} = "UNDETERMINED"; + } + } + + print("#\tMessage type\n\n"); + if ($color) { + print(" ( Color coding: "); + print(RED . "ERROR" . RESET); + print(" | "); + print(YELLOW . "WARNING" . RESET); + print(" | "); + print(GREEN . "CHECK" . RESET); + print(" | "); + print("Multiple levels / Undetermined"); + print(" )\n\n"); + } + + foreach my $type (sort keys %types) { + my $orig_type = $type; + if ($color) { + my $level = $types{$type}; + if ($level eq "ERROR") { + $type = RED . $type . RESET; + } elsif ($level eq "WARN") { + $type = YELLOW . $type . RESET; + } elsif ($level eq "CHK") { + $type = GREEN . $type . RESET; + } + } + print(++$count . "\t" . $type . "\n"); + if ($verbose && exists($verbose_messages{$orig_type})) { + my $message = $verbose_messages{$orig_type}; + $message =~ s/\n/\n\t/g; + print("\t" . $message . "\n\n"); + } + } + + exit($exitcode); +} + +my $conf = which_conf($configuration_file); +if (-f $conf) { + my @conf_args; + open(my $conffile, '<', "$conf") + or warn "$P: Can't find a readable $configuration_file file $!\n"; + + while (<$conffile>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + $line =~ s/\s+/ /g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + + my @words = split(" ", $line); + foreach my $word (@words) { + last if ($word =~ m/^#/); + push (@conf_args, $word); + } + } + close($conffile); + unshift(@ARGV, @conf_args) if @conf_args; +} + +sub load_docs { + open(my $docs, '<', "$docsfile") + or warn "$P: Can't read the documentation file $docsfile $!\n"; + + my $type = ''; + my $desc = ''; + my $in_desc = 0; + + while (<$docs>) { + chomp; + my $line = $_; + $line =~ s/\s+$//; + + if ($line =~ /^\s*\*\*(.+)\*\*$/) { + if ($desc ne '') { + $verbose_messages{$type} = trim($desc); + } + $type = $1; + $desc = ''; + $in_desc = 1; + } elsif ($in_desc) { + if ($line =~ /^(?:\s{4,}|$)/) { + $line =~ s/^\s{4}//; + $desc .= $line; + $desc .= "\n"; + } else { + $verbose_messages{$type} = trim($desc); + $type = ''; + $desc = ''; + $in_desc = 0; + } + } + } + + if ($desc ne '') { + $verbose_messages{$type} = trim($desc); + } + close($docs); +} + +# Perl's Getopt::Long allows options to take optional arguments after a space. +# Prevent --color by itself from consuming other arguments +foreach (@ARGV) { + if ($_ eq "--color" || $_ eq "-color") { + $_ = "--color=$color"; + } +} + +GetOptions( + 'q|quiet+' => \$quiet, + 'v|verbose!' => \$verbose, + 'tree!' => \$tree, + 'signoff!' => \$chk_signoff, + 'fixes-tag!' => \$chk_fixes_tag, + 'patch!' => \$chk_patch, + 'emacs!' => \$emacs, + 'terse!' => \$terse, + 'showfile!' => \$showfile, + 'f|file!' => \$file, + 'g|git!' => \$git, + 'subjective!' => \$check, + 'strict!' => \$check, + 'ignore=s' => \@ignore, + 'types=s' => \@use, + 'show-types!' => \$show_types, + 'list-types!' => \$list_types, + 'max-line-length=i' => \$max_line_length, + 'min-conf-desc-length=i' => \$min_conf_desc_length, + 'tab-size=i' => \$tabsize, + 'root=s' => \$root, + 'summary!' => \$summary, + 'mailback!' => \$mailback, + 'summary-file!' => \$summary_file, + 'fix!' => \$fix, + 'fix-inplace!' => \$fix_inplace, + 'ignore-perl-version!' => \$ignore_perl_version, + 'debug=s' => \%debug, + 'test-only=s' => \$tst_only, + 'codespell!' => \$codespell, + 'codespellfile=s' => \$user_codespellfile, + 'typedefsfile=s' => \$typedefsfile, + 'color=s' => \$color, + 'no-color' => \$color, #keep old behaviors of -nocolor + 'nocolor' => \$color, #keep old behaviors of -nocolor + 'kconfig-prefix=s' => \${CONFIG_}, + 'h|help' => \$help, + 'version' => \$help +) or $help = 2; + +if ($user_codespellfile) { + # Use the user provided codespell file unconditionally + $codespellfile = $user_codespellfile; +} elsif (!(-f $codespellfile)) { + # If /usr/share/codespell/dictionary.txt is not present, try to find it + # under codespell's install directory: /data/dictionary.txt + if (($codespell || $help) && which("python3") ne "") { + my $python_codespell_dict = << "EOF"; + +import os.path as op +import codespell_lib +codespell_dir = op.dirname(codespell_lib.__file__) +codespell_file = op.join(codespell_dir, 'data', 'dictionary.txt') +print(codespell_file, end='') +EOF + + my $codespell_dict = `python3 -c "$python_codespell_dict" 2> /dev/null`; + $codespellfile = $codespell_dict if (-f $codespell_dict); + } +} + +# $help is 1 if either -h, --help or --version is passed as option - exitcode: 0 +# $help is 2 if invalid option is passed - exitcode: 1 +help($help - 1) if ($help); + +die "$P: --git cannot be used with --file or --fix\n" if ($git && ($file || $fix)); +die "$P: --verbose cannot be used with --terse\n" if ($verbose && $terse); + +if ($color =~ /^[01]$/) { + $color = !$color; +} elsif ($color =~ /^always$/i) { + $color = 1; +} elsif ($color =~ /^never$/i) { + $color = 0; +} elsif ($color =~ /^auto$/i) { + $color = (-t STDOUT); +} else { + die "$P: Invalid color mode: $color\n"; +} + +load_docs() if ($verbose); +list_types(0) if ($list_types); + +$fix = 1 if ($fix_inplace); +$check_orig = $check; + +my $exit = 0; + +my $perl_version_ok = 1; +if ($^V && $^V lt $minimum_perl_version) { + $perl_version_ok = 0; + printf "$P: requires at least perl version %vd\n", $minimum_perl_version; + exit(1) if (!$ignore_perl_version); +} + +#if no filenames are given, push '-' to read patch from stdin +if ($#ARGV < 0) { + push(@ARGV, '-'); +} + +# skip TAB size 1 to avoid additional checks on $tabsize - 1 +die "$P: Invalid TAB size: $tabsize\n" if ($tabsize < 2); + +sub hash_save_array_words { + my ($hashRef, $arrayRef) = @_; + + my @array = split(/,/, join(',', @$arrayRef)); + foreach my $word (@array) { + $word =~ s/\s*\n?$//g; + $word =~ s/^\s*//g; + $word =~ s/\s+/ /g; + $word =~ tr/[a-z]/[A-Z]/; + + next if ($word =~ m/^\s*#/); + next if ($word =~ m/^\s*$/); + + $hashRef->{$word}++; + } +} + +sub hash_show_words { + my ($hashRef, $prefix) = @_; + + if (keys %$hashRef) { + print "\nNOTE: $prefix message types:"; + foreach my $word (sort keys %$hashRef) { + print " $word"; + } + print "\n"; + } +} + +hash_save_array_words(\%ignore_type, \@ignore); +hash_save_array_words(\%use_type, \@use); + +my $dbg_values = 0; +my $dbg_possible = 0; +my $dbg_type = 0; +my $dbg_attr = 0; +for my $key (keys %debug) { + ## no critic + eval "\${dbg_$key} = '$debug{$key}';"; + die "$@" if ($@); +} + +my $rpt_cleaners = 0; + +if ($terse) { + $emacs = 1; + $quiet++; +} + +if ($tree) { + if (defined $root) { + if (!top_of_kernel_tree($root)) { + die "$P: $root: --root does not point at a valid tree\n"; + } + } else { + if (top_of_kernel_tree('.')) { + $root = '.'; + } elsif ($0 =~ m@(.*)/scripts/[^/]*$@ && + top_of_kernel_tree($1)) { + $root = $1; + } + } + + if (!defined $root) { + print "Must be run from the top-level dir. of a kernel tree\n"; + exit(2); + } +} + +my $emitted_corrupt = 0; + +our $Ident = qr{ + [A-Za-z_][A-Za-z\d_]* + (?:\s*\#\#\s*[A-Za-z_][A-Za-z\d_]*)* + }x; +our $Storage = qr{extern|static|asmlinkage}; +our $Sparse = qr{ + __user| + __kernel| + __force| + __iomem| + __must_check| + __kprobes| + __ref| + __refconst| + __refdata| + __rcu| + __private + }x; +our $InitAttributePrefix = qr{__(?:mem|cpu|dev|net_|)}; +our $InitAttributeData = qr{$InitAttributePrefix(?:initdata\b)}; +our $InitAttributeConst = qr{$InitAttributePrefix(?:initconst\b)}; +our $InitAttributeInit = qr{$InitAttributePrefix(?:init\b)}; +our $InitAttribute = qr{$InitAttributeData|$InitAttributeConst|$InitAttributeInit}; + +# Notes to $Attribute: +# We need \b after 'init' otherwise 'initconst' will cause a false positive in a check +our $Attribute = qr{ + const| + volatile| + __percpu| + __nocast| + __safe| + __bitwise| + __packed__| + __packed2__| + __naked| + __maybe_unused| + __always_unused| + __noreturn| + __used| + __cold| + __pure| + __noclone| + __deprecated| + __read_mostly| + __ro_after_init| + __kprobes| + $InitAttribute| + __aligned\s*\(.*\)| + ____cacheline_aligned| + ____cacheline_aligned_in_smp| + ____cacheline_internodealigned_in_smp| + __weak| + __alloc_size\s*\(\s*\d+\s*(?:,\s*\d+\s*)?\) + }x; +our $Modifier; +our $Inline = qr{inline|__always_inline|noinline|__inline|__inline__}; +our $Member = qr{->$Ident|\.$Ident|\[[^]]*\]}; +our $Lval = qr{$Ident(?:$Member)*}; + +our $Int_type = qr{(?i)llu|ull|ll|lu|ul|l|u}; +our $Binary = qr{(?i)0b[01]+$Int_type?}; +our $Hex = qr{(?i)0x[0-9a-f]+$Int_type?}; +our $Int = qr{[0-9]+$Int_type?}; +our $Octal = qr{0[0-7]+$Int_type?}; +our $String = qr{(?:\b[Lu])?"[X\t]*"}; +our $Float_hex = qr{(?i)0x[0-9a-f]+p-?[0-9]+[fl]?}; +our $Float_dec = qr{(?i)(?:[0-9]+\.[0-9]*|[0-9]*\.[0-9]+)(?:e-?[0-9]+)?[fl]?}; +our $Float_int = qr{(?i)[0-9]+e-?[0-9]+[fl]?}; +our $Float = qr{$Float_hex|$Float_dec|$Float_int}; +our $Constant = qr{$Float|$Binary|$Octal|$Hex|$Int}; +our $Assignment = qr{\*\=|/=|%=|\+=|-=|<<=|>>=|&=|\^=|\|=|=}; +our $Compare = qr{<=|>=|==|!=|<|(?}; +our $Arithmetic = qr{\+|-|\*|\/|%}; +our $Operators = qr{ + <=|>=|==|!=| + =>|->|<<|>>|<|>|!|~| + &&|\|\||,|\^|\+\+|--|&|\||$Arithmetic + }x; + +our $c90_Keywords = qr{do|for|while|if|else|return|goto|continue|switch|default|case|break}x; + +our $BasicType; +our $NonptrType; +our $NonptrTypeMisordered; +our $NonptrTypeWithAttr; +our $Type; +our $TypeMisordered; +our $Declare; +our $DeclareMisordered; + +our $NON_ASCII_UTF8 = qr{ + [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte + | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs + | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte + | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates + | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 + | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 +}x; + +our $UTF8 = qr{ + [\x09\x0A\x0D\x20-\x7E] # ASCII + | $NON_ASCII_UTF8 +}x; + +our $typeC99Typedefs = qr{(?:__)?(?:[us]_?)?int_?(?:8|16|32|64)_t}; +our $typeOtherOSTypedefs = qr{(?x: + u_(?:char|short|int|long) | # bsd + u(?:nchar|short|int|long) # sysv +)}; +our $typeKernelTypedefs = qr{(?x: + (?:__)?(?:u|s|be|le)(?:8|16|32|64)| + atomic_t +)}; +our $typeStdioTypedefs = qr{(?x: + FILE +)}; +our $typeTypedefs = qr{(?x: + $typeC99Typedefs\b| + $typeOtherOSTypedefs\b| + $typeKernelTypedefs\b| + $typeStdioTypedefs\b +)}; + +our $zero_initializer = qr{(?:(?:0[xX])?0+$Int_type?|NULL|false)\b}; + +our $logFunctions = qr{(?x: + printk(?:_ratelimited|_once|_deferred_once|_deferred|)| + (?:[a-z0-9]+_){1,2}(?:printk|emerg|alert|crit|err|warning|warn|notice|info|debug|dbg|vdbg|devel|cont|WARN)(?:_ratelimited|_once|)| + TP_printk| + WARN(?:_RATELIMIT|_ONCE|)| + panic| + MODULE_[A-Z_]+| + seq_vprintf|seq_printf|seq_puts +)}; + +our $allocFunctions = qr{(?x: + (?:(?:devm_)? + (?:kv|k|v)[czm]alloc(?:_array)?(?:_node)? | + kstrdup(?:_const)? | + kmemdup(?:_nul)?) | + (?:\w+)?alloc_skb(?:_ip_align)? | + # dev_alloc_skb/netdev_alloc_skb, et al + dma_alloc_coherent +)}; + +our $signature_tags = qr{(?xi: + Signed-off-by:| + Co-developed-by:| + Acked-by:| + Tested-by:| + Reviewed-by:| + Reported-by:| + Suggested-by:| + To:| + Cc: +)}; + +our @link_tags = qw(Link Closes); + +#Create a search and print patterns for all these strings to be used directly below +our $link_tags_search = ""; +our $link_tags_print = ""; +foreach my $entry (@link_tags) { + if ($link_tags_search ne "") { + $link_tags_search .= '|'; + $link_tags_print .= ' or '; + } + $entry .= ':'; + $link_tags_search .= $entry; + $link_tags_print .= "'$entry'"; +} +$link_tags_search = "(?:${link_tags_search})"; + +our $tracing_logging_tags = qr{(?xi: + [=-]*> | + <[=-]* | + \[ | + \] | + start | + called | + entered | + entry | + enter | + in | + inside | + here | + begin | + exit | + end | + done | + leave | + completed | + out | + return | + [\.\!:\s]* +)}; + +sub edit_distance_min { + my (@arr) = @_; + my $len = scalar @arr; + if ((scalar @arr) < 1) { + # if underflow, return + return; + } + my $min = $arr[0]; + for my $i (0 .. ($len-1)) { + if ($arr[$i] < $min) { + $min = $arr[$i]; + } + } + return $min; +} + +sub get_edit_distance { + my ($str1, $str2) = @_; + $str1 = lc($str1); + $str2 = lc($str2); + $str1 =~ s/-//g; + $str2 =~ s/-//g; + my $len1 = length($str1); + my $len2 = length($str2); + # two dimensional array storing minimum edit distance + my @distance; + for my $i (0 .. $len1) { + for my $j (0 .. $len2) { + if ($i == 0) { + $distance[$i][$j] = $j; + } elsif ($j == 0) { + $distance[$i][$j] = $i; + } elsif (substr($str1, $i-1, 1) eq substr($str2, $j-1, 1)) { + $distance[$i][$j] = $distance[$i - 1][$j - 1]; + } else { + my $dist1 = $distance[$i][$j - 1]; #insert distance + my $dist2 = $distance[$i - 1][$j]; # remove + my $dist3 = $distance[$i - 1][$j - 1]; #replace + $distance[$i][$j] = 1 + edit_distance_min($dist1, $dist2, $dist3); + } + } + } + return $distance[$len1][$len2]; +} + +sub find_standard_signature { + my ($sign_off) = @_; + my @standard_signature_tags = ( + 'Signed-off-by:', 'Co-developed-by:', 'Acked-by:', 'Tested-by:', + 'Reviewed-by:', 'Reported-by:', 'Suggested-by:' + ); + foreach my $signature (@standard_signature_tags) { + return $signature if (get_edit_distance($sign_off, $signature) <= 2); + } + + return ""; +} + +our $obsolete_archives = qr{(?xi: + \Qfreedesktop.org/archives/dri-devel\E | + \Qlists.infradead.org\E | + \Qlkml.org\E | + \Qmail-archive.com\E | + \Qmailman.alsa-project.org/pipermail\E | + \Qmarc.info\E | + \Qozlabs.org/pipermail\E | + \Qspinics.net\E +)}; + +our @typeListMisordered = ( + qr{char\s+(?:un)?signed}, + qr{int\s+(?:(?:un)?signed\s+)?short\s}, + qr{int\s+short(?:\s+(?:un)?signed)}, + qr{short\s+int(?:\s+(?:un)?signed)}, + qr{(?:un)?signed\s+int\s+short}, + qr{short\s+(?:un)?signed}, + qr{long\s+int\s+(?:un)?signed}, + qr{int\s+long\s+(?:un)?signed}, + qr{long\s+(?:un)?signed\s+int}, + qr{int\s+(?:un)?signed\s+long}, + qr{int\s+(?:un)?signed}, + qr{int\s+long\s+long\s+(?:un)?signed}, + qr{long\s+long\s+int\s+(?:un)?signed}, + qr{long\s+long\s+(?:un)?signed\s+int}, + qr{long\s+long\s+(?:un)?signed}, + qr{long\s+(?:un)?signed}, +); + +our @typeList = ( + qr{void}, + qr{(?:(?:un)?signed\s+)?char}, + qr{(?:(?:un)?signed\s+)?short\s+int}, + qr{(?:(?:un)?signed\s+)?short}, + qr{(?:(?:un)?signed\s+)?int}, + qr{(?:(?:un)?signed\s+)?long\s+int}, + qr{(?:(?:un)?signed\s+)?long\s+long\s+int}, + qr{(?:(?:un)?signed\s+)?long\s+long}, + qr{(?:(?:un)?signed\s+)?long}, + qr{(?:un)?signed}, + qr{float}, + qr{double}, + qr{bool}, + qr{struct\s+$Ident}, + qr{union\s+$Ident}, + qr{enum\s+$Ident}, + qr{${Ident}_t}, + qr{${Ident}_handler}, + qr{${Ident}_handler_fn}, + @typeListMisordered, +); + +our $C90_int_types = qr{(?x: + long\s+long\s+int\s+(?:un)?signed| + long\s+long\s+(?:un)?signed\s+int| + long\s+long\s+(?:un)?signed| + (?:(?:un)?signed\s+)?long\s+long\s+int| + (?:(?:un)?signed\s+)?long\s+long| + int\s+long\s+long\s+(?:un)?signed| + int\s+(?:(?:un)?signed\s+)?long\s+long| + + long\s+int\s+(?:un)?signed| + long\s+(?:un)?signed\s+int| + long\s+(?:un)?signed| + (?:(?:un)?signed\s+)?long\s+int| + (?:(?:un)?signed\s+)?long| + int\s+long\s+(?:un)?signed| + int\s+(?:(?:un)?signed\s+)?long| + + int\s+(?:un)?signed| + (?:(?:un)?signed\s+)?int +)}; + +our @typeListFile = (); +our @typeListWithAttr = ( + @typeList, + qr{struct\s+$InitAttribute\s+$Ident}, + qr{union\s+$InitAttribute\s+$Ident}, +); + +our @modifierList = ( + qr{fastcall}, +); +our @modifierListFile = (); + +our @mode_permission_funcs = ( + ["module_param", 3], + ["module_param_(?:array|named|string)", 4], + ["module_param_array_named", 5], + ["debugfs_create_(?:file|u8|u16|u32|u64|x8|x16|x32|x64|size_t|atomic_t|bool|blob|regset32|u32_array)", 2], + ["proc_create(?:_data|)", 2], + ["(?:CLASS|DEVICE|SENSOR|SENSOR_DEVICE|IIO_DEVICE)_ATTR", 2], + ["IIO_DEV_ATTR_[A-Z_]+", 1], + ["SENSOR_(?:DEVICE_|)ATTR_2", 2], + ["SENSOR_TEMPLATE(?:_2|)", 3], + ["__ATTR", 2], +); + +my $word_pattern = '\b[A-Z]?[a-z]{2,}\b'; + +#Create a search pattern for all these functions to speed up a loop below +our $mode_perms_search = ""; +foreach my $entry (@mode_permission_funcs) { + $mode_perms_search .= '|' if ($mode_perms_search ne ""); + $mode_perms_search .= $entry->[0]; +} +$mode_perms_search = "(?:${mode_perms_search})"; + +our %deprecated_apis = ( + "synchronize_rcu_bh" => "synchronize_rcu", + "synchronize_rcu_bh_expedited" => "synchronize_rcu_expedited", + "call_rcu_bh" => "call_rcu", + "rcu_barrier_bh" => "rcu_barrier", + "synchronize_sched" => "synchronize_rcu", + "synchronize_sched_expedited" => "synchronize_rcu_expedited", + "call_rcu_sched" => "call_rcu", + "rcu_barrier_sched" => "rcu_barrier", + "get_state_synchronize_sched" => "get_state_synchronize_rcu", + "cond_synchronize_sched" => "cond_synchronize_rcu", + "kmap" => "kmap_local_page", + "kunmap" => "kunmap_local", + "kmap_atomic" => "kmap_local_page", + "kunmap_atomic" => "kunmap_local", +); + +#Create a search pattern for all these strings to speed up a loop below +our $deprecated_apis_search = ""; +foreach my $entry (keys %deprecated_apis) { + $deprecated_apis_search .= '|' if ($deprecated_apis_search ne ""); + $deprecated_apis_search .= $entry; +} +$deprecated_apis_search = "(?:${deprecated_apis_search})"; + +our $mode_perms_world_writable = qr{ + S_IWUGO | + S_IWOTH | + S_IRWXUGO | + S_IALLUGO | + 0[0-7][0-7][2367] +}x; + +our %mode_permission_string_types = ( + "S_IRWXU" => 0700, + "S_IRUSR" => 0400, + "S_IWUSR" => 0200, + "S_IXUSR" => 0100, + "S_IRWXG" => 0070, + "S_IRGRP" => 0040, + "S_IWGRP" => 0020, + "S_IXGRP" => 0010, + "S_IRWXO" => 0007, + "S_IROTH" => 0004, + "S_IWOTH" => 0002, + "S_IXOTH" => 0001, + "S_IRWXUGO" => 0777, + "S_IRUGO" => 0444, + "S_IWUGO" => 0222, + "S_IXUGO" => 0111, +); + +#Create a search pattern for all these strings to speed up a loop below +our $mode_perms_string_search = ""; +foreach my $entry (keys %mode_permission_string_types) { + $mode_perms_string_search .= '|' if ($mode_perms_string_search ne ""); + $mode_perms_string_search .= $entry; +} +our $single_mode_perms_string_search = "(?:${mode_perms_string_search})"; +our $multi_mode_perms_string_search = qr{ + ${single_mode_perms_string_search} + (?:\s*\|\s*${single_mode_perms_string_search})* +}x; + +sub perms_to_octal { + my ($string) = @_; + + return trim($string) if ($string =~ /^\s*0[0-7]{3,3}\s*$/); + + my $val = ""; + my $oval = ""; + my $to = 0; + my $curpos = 0; + my $lastpos = 0; + while ($string =~ /\b(($single_mode_perms_string_search)\b(?:\s*\|\s*)?\s*)/g) { + $curpos = pos($string); + my $match = $2; + my $omatch = $1; + last if ($lastpos > 0 && ($curpos - length($omatch) != $lastpos)); + $lastpos = $curpos; + $to |= $mode_permission_string_types{$match}; + $val .= '\s*\|\s*' if ($val ne ""); + $val .= $match; + $oval .= $omatch; + } + $oval =~ s/^\s*\|\s*//; + $oval =~ s/\s*\|\s*$//; + return sprintf("%04o", $to); +} + +our $allowed_asm_includes = qr{(?x: + irq| + memory| + time| + reboot +)}; +# memory.h: ARM has a custom one + +# Load common spelling mistakes and build regular expression list. +my $misspellings; +my %spelling_fix; + +if (open(my $spelling, '<', $spelling_file)) { + while (<$spelling>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + + my ($suspect, $fix) = split(/\|\|/, $line); + + $spelling_fix{$suspect} = $fix; + } + close($spelling); +} else { + warn "No typos will be found - file '$spelling_file': $!\n"; +} + +if ($codespell) { + if (open(my $spelling, '<', $codespellfile)) { + while (<$spelling>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + next if ($line =~ m/, disabled/i); + + $line =~ s/,.*$//; + + my ($suspect, $fix) = split(/->/, $line); + + $spelling_fix{$suspect} = $fix; + } + close($spelling); + } else { + warn "No codespell typos will be found - file '$codespellfile': $!\n"; + } +} + +$misspellings = join("|", sort keys %spelling_fix) if keys %spelling_fix; + +sub read_words { + my ($wordsRef, $file) = @_; + + if (open(my $words, '<', $file)) { + while (<$words>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + if ($line =~ /\s/) { + print("$file: '$line' invalid - ignored\n"); + next; + } + + $$wordsRef .= '|' if (defined $$wordsRef); + $$wordsRef .= $line; + } + close($file); + return 1; + } + + return 0; +} + +my $const_structs; +if (show_type("CONST_STRUCT")) { + read_words(\$const_structs, $conststructsfile) + or warn "No structs that should be const will be found - file '$conststructsfile': $!\n"; +} + +if (defined($typedefsfile)) { + my $typeOtherTypedefs; + read_words(\$typeOtherTypedefs, $typedefsfile) + or warn "No additional types will be considered - file '$typedefsfile': $!\n"; + $typeTypedefs .= '|' . $typeOtherTypedefs if (defined $typeOtherTypedefs); +} + +sub build_types { + my $mods = "(?x: \n" . join("|\n ", (@modifierList, @modifierListFile)) . "\n)"; + my $all = "(?x: \n" . join("|\n ", (@typeList, @typeListFile)) . "\n)"; + my $Misordered = "(?x: \n" . join("|\n ", @typeListMisordered) . "\n)"; + my $allWithAttr = "(?x: \n" . join("|\n ", @typeListWithAttr) . "\n)"; + $Modifier = qr{(?:$Attribute|$Sparse|$mods)}; + $BasicType = qr{ + (?:$typeTypedefs\b)| + (?:${all}\b) + }x; + $NonptrType = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:typeof|__typeof__)\s*\([^\)]*\)| + (?:$typeTypedefs\b)| + (?:${all}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $NonptrTypeMisordered = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:${Misordered}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $NonptrTypeWithAttr = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:typeof|__typeof__)\s*\([^\)]*\)| + (?:$typeTypedefs\b)| + (?:${allWithAttr}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $Type = qr{ + $NonptrType + (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+){0,4} + (?:\s+$Inline|\s+$Modifier)* + }x; + $TypeMisordered = qr{ + $NonptrTypeMisordered + (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+){0,4} + (?:\s+$Inline|\s+$Modifier)* + }x; + $Declare = qr{(?:$Storage\s+(?:$Inline\s+)?)?$Type}; + $DeclareMisordered = qr{(?:$Storage\s+(?:$Inline\s+)?)?$TypeMisordered}; +} +build_types(); + +our $Typecast = qr{\s*(\(\s*$NonptrType\s*\)){0,1}\s*}; + +# Using $balanced_parens, $LvalOrFunc, or $FuncArg +# requires at least perl version v5.10.0 +# Any use must be runtime checked with $^V + +our $balanced_parens = qr/(\((?:[^\(\)]++|(?-1))*\))/; +our $LvalOrFunc = qr{((?:[\&\*]\s*)?$Lval)\s*($balanced_parens{0,1})\s*}; +our $FuncArg = qr{$Typecast{0,1}($LvalOrFunc|$Constant|$String)}; + +our $declaration_macros = qr{(?x: + (?:$Storage\s+)?(?:[A-Z_][A-Z0-9]*_){0,2}(?:DEFINE|DECLARE)(?:_[A-Z0-9]+){1,6}\s*\(| + (?:$Storage\s+)?[HLP]?LIST_HEAD\s*\(| + (?:SKCIPHER_REQUEST|SHASH_DESC|AHASH_REQUEST)_ON_STACK\s*\(| + (?:$Storage\s+)?(?:XA_STATE|XA_STATE_ORDER)\s*\( +)}; + +our %allow_repeated_words = ( + add => '', + added => '', + bad => '', + be => '', +); + +sub deparenthesize { + my ($string) = @_; + return "" if (!defined($string)); + + while ($string =~ /^\s*\(.*\)\s*$/) { + $string =~ s@^\s*\(\s*@@; + $string =~ s@\s*\)\s*$@@; + } + + $string =~ s@\s+@ @g; + + return $string; +} + +sub seed_camelcase_file { + my ($file) = @_; + + return if (!(-f $file)); + + local $/; + + open(my $include_file, '<', "$file") + or warn "$P: Can't read '$file' $!\n"; + my $text = <$include_file>; + close($include_file); + + my @lines = split('\n', $text); + + foreach my $line (@lines) { + next if ($line !~ /(?:[A-Z][a-z]|[a-z][A-Z])/); + if ($line =~ /^[ \t]*(?:#[ \t]*define|typedef\s+$Type)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)/) { + $camelcase{$1} = 1; + } elsif ($line =~ /^\s*$Declare\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[\(\[,;]/) { + $camelcase{$1} = 1; + } elsif ($line =~ /^\s*(?:union|struct|enum)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[;\{]/) { + $camelcase{$1} = 1; + } + } +} + +our %maintained_status = (); + +sub is_maintained_obsolete { + my ($filename) = @_; + + return 0 if (!$tree || !(-e "$root/scripts/get_maintainer.pl")); + + if (!exists($maintained_status{$filename})) { + $maintained_status{$filename} = `perl $root/scripts/get_maintainer.pl --status --nom --nol --nogit --nogit-fallback -f $filename 2>&1`; + } + + return $maintained_status{$filename} =~ /obsolete/i; +} + +sub is_SPDX_License_valid { + my ($license) = @_; + + return 1 if (!$tree || which("python3") eq "" || !(-x "$root/scripts/spdxcheck.py") || !(-e "$gitroot")); + + my $root_path = abs_path($root); + my $status = `cd "$root_path"; echo "$license" | scripts/spdxcheck.py -`; + return 0 if ($status ne ""); + return 1; +} + +my $camelcase_seeded = 0; +sub seed_camelcase_includes { + return if ($camelcase_seeded); + + my $files; + my $camelcase_cache = ""; + my @include_files = (); + + $camelcase_seeded = 1; + + if (-e "$gitroot") { + my $git_last_include_commit = `${git_command} log --no-merges --pretty=format:"%h%n" -1 -- include`; + chomp $git_last_include_commit; + $camelcase_cache = ".checkpatch-camelcase.git.$git_last_include_commit"; + } else { + my $last_mod_date = 0; + $files = `find $root/include -name "*.h"`; + @include_files = split('\n', $files); + foreach my $file (@include_files) { + my $date = POSIX::strftime("%Y%m%d%H%M", + localtime((stat $file)[9])); + $last_mod_date = $date if ($last_mod_date < $date); + } + $camelcase_cache = ".checkpatch-camelcase.date.$last_mod_date"; + } + + if ($camelcase_cache ne "" && -f $camelcase_cache) { + open(my $camelcase_file, '<', "$camelcase_cache") + or warn "$P: Can't read '$camelcase_cache' $!\n"; + while (<$camelcase_file>) { + chomp; + $camelcase{$_} = 1; + } + close($camelcase_file); + + return; + } + + if (-e "$gitroot") { + $files = `${git_command} ls-files "include/*.h"`; + @include_files = split('\n', $files); + } + + foreach my $file (@include_files) { + seed_camelcase_file($file); + } + + if ($camelcase_cache ne "") { + unlink glob ".checkpatch-camelcase.*"; + open(my $camelcase_file, '>', "$camelcase_cache") + or warn "$P: Can't write '$camelcase_cache' $!\n"; + foreach (sort { lc($a) cmp lc($b) } keys(%camelcase)) { + print $camelcase_file ("$_\n"); + } + close($camelcase_file); + } +} + +sub git_is_single_file { + my ($filename) = @_; + + return 0 if ((which("git") eq "") || !(-e "$gitroot")); + + my $output = `${git_command} ls-files -- $filename 2>/dev/null`; + my $count = $output =~ tr/\n//; + return $count eq 1 && $output =~ m{^${filename}$}; +} + +sub git_commit_info { + my ($commit, $id, $desc) = @_; + + return ($id, $desc) if ((which("git") eq "") || !(-e "$gitroot")); + + my $output = `${git_command} log --no-color --format='%H %s' -1 $commit 2>&1`; + $output =~ s/^\s*//gm; + my @lines = split("\n", $output); + + return ($id, $desc) if ($#lines < 0); + + if ($lines[0] =~ /^error: short SHA1 $commit is ambiguous/) { +# Maybe one day convert this block of bash into something that returns +# all matching commit ids, but it's very slow... +# +# echo "checking commits $1..." +# git rev-list --remotes | grep -i "^$1" | +# while read line ; do +# git log --format='%H %s' -1 $line | +# echo "commit $(cut -c 1-12,41-)" +# done + } elsif ($lines[0] =~ /^fatal: ambiguous argument '$commit': unknown revision or path not in the working tree\./ || + $lines[0] =~ /^fatal: bad object $commit/) { + $id = undef; + } else { + $id = substr($lines[0], 0, 12); + $desc = substr($lines[0], 41); + } + + return ($id, $desc); +} + +$chk_signoff = 0 if ($file); +$chk_fixes_tag = 0 if ($file); + +my @rawlines = (); +my @lines = (); +my @fixed = (); +my @fixed_inserted = (); +my @fixed_deleted = (); +my $fixlinenr = -1; + +# If input is git commits, extract all commits from the commit expressions. +# For example, HEAD-3 means we need check 'HEAD, HEAD~1, HEAD~2'. +die "$P: No git repository found\n" if ($git && !-e "$gitroot"); + +if ($git) { + my @commits = (); + foreach my $commit_expr (@ARGV) { + my $git_range; + if ($commit_expr =~ m/^(.*)-(\d+)$/) { + $git_range = "-$2 $1"; + } elsif ($commit_expr =~ m/\.\./) { + $git_range = "$commit_expr"; + } else { + $git_range = "-1 $commit_expr"; + } + my $lines = `${git_command} log --no-color --no-merges --pretty=format:'%H %s' $git_range`; + foreach my $line (split(/\n/, $lines)) { + $line =~ /^([0-9a-fA-F]{40,40}) (.*)$/; + next if (!defined($1) || !defined($2)); + my $sha1 = $1; + my $subject = $2; + unshift(@commits, $sha1); + $git_commits{$sha1} = $subject; + } + } + die "$P: no git commits after extraction!\n" if (@commits == 0); + @ARGV = @commits; +} + +my $vname; +$allow_c99_comments = !defined $ignore_type{"C99_COMMENT_TOLERANCE"}; +for my $filename (@ARGV) { + my $FILE; + my $is_git_file = git_is_single_file($filename); + my $oldfile = $file; + $file = 1 if ($is_git_file); + if ($git) { + open($FILE, '-|', "git format-patch -M --stdout -1 $filename") || + die "$P: $filename: git format-patch failed - $!\n"; + } elsif ($file) { + open($FILE, '-|', "diff -u /dev/null $filename") || + die "$P: $filename: diff failed - $!\n"; + } elsif ($filename eq '-') { + open($FILE, '<&STDIN'); + } else { + open($FILE, '<', "$filename") || + die "$P: $filename: open failed - $!\n"; + } + if ($filename eq '-') { + $vname = 'Your patch'; + } elsif ($git) { + $vname = "Commit " . substr($filename, 0, 12) . ' ("' . $git_commits{$filename} . '")'; + } else { + $vname = $filename; + } + while (<$FILE>) { + chomp; + push(@rawlines, $_); + $vname = qq("$1") if ($filename eq '-' && $_ =~ m/^Subject:\s+(.+)/i); + } + close($FILE); + + if ($#ARGV > 0 && $quiet == 0) { + print '-' x length($vname) . "\n"; + print "$vname\n"; + print '-' x length($vname) . "\n"; + } + + if (!process($filename)) { + $exit = 1; + } + @rawlines = (); + @lines = (); + @fixed = (); + @fixed_inserted = (); + @fixed_deleted = (); + $fixlinenr = -1; + @modifierListFile = (); + @typeListFile = (); + build_types(); + $file = $oldfile if ($is_git_file); +} + +if (!$quiet) { + hash_show_words(\%use_type, "Used"); + hash_show_words(\%ignore_type, "Ignored"); + + if (!$perl_version_ok) { + print << "EOM" + +NOTE: perl $^V is not modern enough to detect all possible issues. + An upgrade to at least perl $minimum_perl_version is suggested. +EOM + } + if ($exit) { + print << "EOM" + +NOTE: If any of the errors are false positives, please report + them to the maintainer, see CHECKPATCH in MAINTAINERS. +EOM + } +} + +exit($exit); + +sub top_of_kernel_tree { + my ($root) = @_; + + my @tree_check = ( + "COPYING", "CREDITS", "Kbuild", "MAINTAINERS", "Makefile", + "README", "Documentation", "arch", "include", "drivers", + "fs", "init", "ipc", "kernel", "lib", "scripts", + ); + + foreach my $check (@tree_check) { + if (! -e $root . '/' . $check) { + return 0; + } + } + return 1; +} + +sub parse_email { + my ($formatted_email) = @_; + + my $name = ""; + my $quoted = ""; + my $name_comment = ""; + my $address = ""; + my $comment = ""; + + if ($formatted_email =~ /^(.*)<(\S+\@\S+)>(.*)$/) { + $name = $1; + $address = $2; + $comment = $3 if defined $3; + } elsif ($formatted_email =~ /^\s*<(\S+\@\S+)>(.*)$/) { + $address = $1; + $comment = $2 if defined $2; + } elsif ($formatted_email =~ /(\S+\@\S+)(.*)$/) { + $address = $1; + $comment = $2 if defined $2; + $formatted_email =~ s/\Q$address\E.*$//; + $name = $formatted_email; + $name = trim($name); + $name =~ s/^\"|\"$//g; + # If there's a name left after stripping spaces and + # leading quotes, and the address doesn't have both + # leading and trailing angle brackets, the address + # is invalid. ie: + # "joe smith joe@smith.com" bad + # "joe smith ]+>$/) { + $name = ""; + $address = ""; + $comment = ""; + } + } + + # Extract comments from names excluding quoted parts + # "John D. (Doe)" - Do not extract + if ($name =~ s/\"(.+)\"//) { + $quoted = $1; + } + while ($name =~ s/\s*($balanced_parens)\s*/ /) { + $name_comment .= trim($1); + } + $name =~ s/^[ \"]+|[ \"]+$//g; + $name = trim("$quoted $name"); + + $address = trim($address); + $address =~ s/^\<|\>$//g; + $comment = trim($comment); + + if ($name =~ /[^\w \-]/i) { ##has "must quote" chars + $name =~ s/(?"; + } + $formatted_email .= "$comment"; + return $formatted_email; +} + +sub reformat_email { + my ($email) = @_; + + my ($email_name, $name_comment, $email_address, $comment) = parse_email($email); + return format_email($email_name, $name_comment, $email_address, $comment); +} + +sub same_email_addresses { + my ($email1, $email2) = @_; + + my ($email1_name, $name1_comment, $email1_address, $comment1) = parse_email($email1); + my ($email2_name, $name2_comment, $email2_address, $comment2) = parse_email($email2); + + return $email1_name eq $email2_name && + $email1_address eq $email2_address && + $name1_comment eq $name2_comment && + $comment1 eq $comment2; +} + +sub which { + my ($bin) = @_; + + foreach my $path (split(/:/, $ENV{PATH})) { + if (-e "$path/$bin") { + return "$path/$bin"; + } + } + + return ""; +} + +sub which_conf { + my ($conf) = @_; + + foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) { + if (-e "$path/$conf") { + return "$path/$conf"; + } + } + + return ""; +} + +sub expand_tabs { + my ($str) = @_; + + my $res = ''; + my $n = 0; + for my $c (split(//, $str)) { + if ($c eq "\t") { + $res .= ' '; + $n++; + for (; ($n % $tabsize) != 0; $n++) { + $res .= ' '; + } + next; + } + $res .= $c; + $n++; + } + + return $res; +} +sub copy_spacing { + (my $res = shift) =~ tr/\t/ /c; + return $res; +} + +sub line_stats { + my ($line) = @_; + + # Drop the diff line leader and expand tabs + $line =~ s/^.//; + $line = expand_tabs($line); + + # Pick the indent from the front of the line. + my ($white) = ($line =~ /^(\s*)/); + + return (length($line), length($white)); +} + +my $sanitise_quote = ''; + +sub sanitise_line_reset { + my ($in_comment) = @_; + + if ($in_comment) { + $sanitise_quote = '*/'; + } else { + $sanitise_quote = ''; + } +} +sub sanitise_line { + my ($line) = @_; + + my $res = ''; + my $l = ''; + + my $qlen = 0; + my $off = 0; + my $c; + + # Always copy over the diff marker. + $res = substr($line, 0, 1); + + for ($off = 1; $off < length($line); $off++) { + $c = substr($line, $off, 1); + + # Comments we are whacking completely including the begin + # and end, all to $;. + if ($sanitise_quote eq '' && substr($line, $off, 2) eq '/*') { + $sanitise_quote = '*/'; + + substr($res, $off, 2, "$;$;"); + $off++; + next; + } + if ($sanitise_quote eq '*/' && substr($line, $off, 2) eq '*/') { + $sanitise_quote = ''; + substr($res, $off, 2, "$;$;"); + $off++; + next; + } + if ($sanitise_quote eq '' && substr($line, $off, 2) eq '//') { + $sanitise_quote = '//'; + + substr($res, $off, 2, $sanitise_quote); + $off++; + next; + } + + # A \ in a string means ignore the next character. + if (($sanitise_quote eq "'" || $sanitise_quote eq '"') && + $c eq "\\") { + substr($res, $off, 2, 'XX'); + $off++; + next; + } + # Regular quotes. + if ($c eq "'" || $c eq '"') { + if ($sanitise_quote eq '') { + $sanitise_quote = $c; + + substr($res, $off, 1, $c); + next; + } elsif ($sanitise_quote eq $c) { + $sanitise_quote = ''; + } + } + + #print "c<$c> SQ<$sanitise_quote>\n"; + if ($off != 0 && $sanitise_quote eq '*/' && $c ne "\t") { + substr($res, $off, 1, $;); + } elsif ($off != 0 && $sanitise_quote eq '//' && $c ne "\t") { + substr($res, $off, 1, $;); + } elsif ($off != 0 && $sanitise_quote && $c ne "\t") { + substr($res, $off, 1, 'X'); + } else { + substr($res, $off, 1, $c); + } + } + + if ($sanitise_quote eq '//') { + $sanitise_quote = ''; + } + + # The pathname on a #include may be surrounded by '<' and '>'. + if ($res =~ /^.\s*\#\s*include\s+\<(.*)\>/) { + my $clean = 'X' x length($1); + $res =~ s@\<.*\>@<$clean>@; + + # The whole of a #error is a string. + } elsif ($res =~ /^.\s*\#\s*(?:error|warning)\s+(.*)\b/) { + my $clean = 'X' x length($1); + $res =~ s@(\#\s*(?:error|warning)\s+).*@$1$clean@; + } + + if ($allow_c99_comments && $res =~ m@(//.*$)@) { + my $match = $1; + $res =~ s/\Q$match\E/"$;" x length($match)/e; + } + + return $res; +} + +sub get_quoted_string { + my ($line, $rawline) = @_; + + return "" if (!defined($line) || !defined($rawline)); + return "" if ($line !~ m/($String)/g); + return substr($rawline, $-[0], $+[0] - $-[0]); +} + +sub ctx_statement_block { + my ($linenr, $remain, $off) = @_; + my $line = $linenr - 1; + my $blk = ''; + my $soff = $off; + my $coff = $off - 1; + my $coff_set = 0; + + my $loff = 0; + + my $type = ''; + my $level = 0; + my @stack = (); + my $p; + my $c; + my $len = 0; + + my $remainder; + while (1) { + @stack = (['', 0]) if ($#stack == -1); + + #warn "CSB: blk<$blk> remain<$remain>\n"; + # If we are about to drop off the end, pull in more + # context. + if ($off >= $len) { + for (; $remain > 0; $line++) { + last if (!defined $lines[$line]); + next if ($lines[$line] =~ /^-/); + $remain--; + $loff = $len; + $blk .= $lines[$line] . "\n"; + $len = length($blk); + $line++; + last; + } + # Bail if there is no further context. + #warn "CSB: blk<$blk> off<$off> len<$len>\n"; + if ($off >= $len) { + last; + } + if ($level == 0 && substr($blk, $off) =~ /^.\s*#\s*define/) { + $level++; + $type = '#'; + } + } + $p = $c; + $c = substr($blk, $off, 1); + $remainder = substr($blk, $off); + + #warn "CSB: c<$c> type<$type> level<$level> remainder<$remainder> coff_set<$coff_set>\n"; + + # Handle nested #if/#else. + if ($remainder =~ /^#\s*(?:ifndef|ifdef|if)\s/) { + push(@stack, [ $type, $level ]); + } elsif ($remainder =~ /^#\s*(?:else|elif)\b/) { + ($type, $level) = @{$stack[$#stack - 1]}; + } elsif ($remainder =~ /^#\s*endif\b/) { + ($type, $level) = @{pop(@stack)}; + } + + # Statement ends at the ';' or a close '}' at the + # outermost level. + if ($level == 0 && $c eq ';') { + last; + } + + # An else is really a conditional as long as its not else if + if ($level == 0 && $coff_set == 0 && + (!defined($p) || $p =~ /(?:\s|\}|\+)/) && + $remainder =~ /^(else)(?:\s|{)/ && + $remainder !~ /^else\s+if\b/) { + $coff = $off + length($1) - 1; + $coff_set = 1; + #warn "CSB: mark coff<$coff> soff<$soff> 1<$1>\n"; + #warn "[" . substr($blk, $soff, $coff - $soff + 1) . "]\n"; + } + + if (($type eq '' || $type eq '(') && $c eq '(') { + $level++; + $type = '('; + } + if ($type eq '(' && $c eq ')') { + $level--; + $type = ($level != 0)? '(' : ''; + + if ($level == 0 && $coff < $soff) { + $coff = $off; + $coff_set = 1; + #warn "CSB: mark coff<$coff>\n"; + } + } + if (($type eq '' || $type eq '{') && $c eq '{') { + $level++; + $type = '{'; + } + if ($type eq '{' && $c eq '}') { + $level--; + $type = ($level != 0)? '{' : ''; + + if ($level == 0) { + if (substr($blk, $off + 1, 1) eq ';') { + $off++; + } + last; + } + } + # Preprocessor commands end at the newline unless escaped. + if ($type eq '#' && $c eq "\n" && $p ne "\\") { + $level--; + $type = ''; + $off++; + last; + } + $off++; + } + # We are truly at the end, so shuffle to the next line. + if ($off == $len) { + $loff = $len + 1; + $line++; + $remain--; + } + + my $statement = substr($blk, $soff, $off - $soff + 1); + my $condition = substr($blk, $soff, $coff - $soff + 1); + + #warn "STATEMENT<$statement>\n"; + #warn "CONDITION<$condition>\n"; + + #print "coff<$coff> soff<$off> loff<$loff>\n"; + + return ($statement, $condition, + $line, $remain + 1, $off - $loff + 1, $level); +} + +sub statement_lines { + my ($stmt) = @_; + + # Strip the diff line prefixes and rip blank lines at start and end. + $stmt =~ s/(^|\n)./$1/g; + $stmt =~ s/^\s*//; + $stmt =~ s/\s*$//; + + my @stmt_lines = ($stmt =~ /\n/g); + + return $#stmt_lines + 2; +} + +sub statement_rawlines { + my ($stmt) = @_; + + my @stmt_lines = ($stmt =~ /\n/g); + + return $#stmt_lines + 2; +} + +sub statement_block_size { + my ($stmt) = @_; + + $stmt =~ s/(^|\n)./$1/g; + $stmt =~ s/^\s*{//; + $stmt =~ s/}\s*$//; + $stmt =~ s/^\s*//; + $stmt =~ s/\s*$//; + + my @stmt_lines = ($stmt =~ /\n/g); + my @stmt_statements = ($stmt =~ /;/g); + + my $stmt_lines = $#stmt_lines + 2; + my $stmt_statements = $#stmt_statements + 1; + + if ($stmt_lines > $stmt_statements) { + return $stmt_lines; + } else { + return $stmt_statements; + } +} + +sub ctx_statement_full { + my ($linenr, $remain, $off) = @_; + my ($statement, $condition, $level); + + my (@chunks); + + # Grab the first conditional/block pair. + ($statement, $condition, $linenr, $remain, $off, $level) = + ctx_statement_block($linenr, $remain, $off); + #print "F: c<$condition> s<$statement> remain<$remain>\n"; + push(@chunks, [ $condition, $statement ]); + if (!($remain > 0 && $condition =~ /^\s*(?:\n[+-])?\s*(?:if|else|do)\b/s)) { + return ($level, $linenr, @chunks); + } + + # Pull in the following conditional/block pairs and see if they + # could continue the statement. + for (;;) { + ($statement, $condition, $linenr, $remain, $off, $level) = + ctx_statement_block($linenr, $remain, $off); + #print "C: c<$condition> s<$statement> remain<$remain>\n"; + last if (!($remain > 0 && $condition =~ /^(?:\s*\n[+-])*\s*(?:else|do)\b/s)); + #print "C: push\n"; + push(@chunks, [ $condition, $statement ]); + } + + return ($level, $linenr, @chunks); +} + +sub ctx_block_get { + my ($linenr, $remain, $outer, $open, $close, $off) = @_; + my $line; + my $start = $linenr - 1; + my $blk = ''; + my @o; + my @c; + my @res = (); + + my $level = 0; + my @stack = ($level); + for ($line = $start; $remain > 0; $line++) { + next if ($rawlines[$line] =~ /^-/); + $remain--; + + $blk .= $rawlines[$line]; + + # Handle nested #if/#else. + if ($lines[$line] =~ /^.\s*#\s*(?:ifndef|ifdef|if)\s/) { + push(@stack, $level); + } elsif ($lines[$line] =~ /^.\s*#\s*(?:else|elif)\b/) { + $level = $stack[$#stack - 1]; + } elsif ($lines[$line] =~ /^.\s*#\s*endif\b/) { + $level = pop(@stack); + } + + foreach my $c (split(//, $lines[$line])) { + ##print "C<$c>L<$level><$open$close>O<$off>\n"; + if ($off > 0) { + $off--; + next; + } + + if ($c eq $close && $level > 0) { + $level--; + last if ($level == 0); + } elsif ($c eq $open) { + $level++; + } + } + + if (!$outer || $level <= 1) { + push(@res, $rawlines[$line]); + } + + last if ($level == 0); + } + + return ($level, @res); +} +sub ctx_block_outer { + my ($linenr, $remain) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 1, '{', '}', 0); + return @r; +} +sub ctx_block { + my ($linenr, $remain) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 0, '{', '}', 0); + return @r; +} +sub ctx_statement { + my ($linenr, $remain, $off) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 0, '(', ')', $off); + return @r; +} +sub ctx_block_level { + my ($linenr, $remain) = @_; + + return ctx_block_get($linenr, $remain, 0, '{', '}', 0); +} +sub ctx_statement_level { + my ($linenr, $remain, $off) = @_; + + return ctx_block_get($linenr, $remain, 0, '(', ')', $off); +} + +sub ctx_locate_comment { + my ($first_line, $end_line) = @_; + + # If c99 comment on the current line, or the line before or after + my ($current_comment) = ($rawlines[$end_line - 1] =~ m@^\+.*(//.*$)@); + return $current_comment if (defined $current_comment); + ($current_comment) = ($rawlines[$end_line - 2] =~ m@^[\+ ].*(//.*$)@); + return $current_comment if (defined $current_comment); + ($current_comment) = ($rawlines[$end_line] =~ m@^[\+ ].*(//.*$)@); + return $current_comment if (defined $current_comment); + + # Catch a comment on the end of the line itself. + ($current_comment) = ($rawlines[$end_line - 1] =~ m@.*(/\*.*\*/)\s*(?:\\\s*)?$@); + return $current_comment if (defined $current_comment); + + # Look through the context and try and figure out if there is a + # comment. + my $in_comment = 0; + $current_comment = ''; + for (my $linenr = $first_line; $linenr < $end_line; $linenr++) { + my $line = $rawlines[$linenr - 1]; + #warn " $line\n"; + if ($linenr == $first_line and $line =~ m@^.\s*\*@) { + $in_comment = 1; + } + if ($line =~ m@/\*@) { + $in_comment = 1; + } + if (!$in_comment && $current_comment ne '') { + $current_comment = ''; + } + $current_comment .= $line . "\n" if ($in_comment); + if ($line =~ m@\*/@) { + $in_comment = 0; + } + } + + chomp($current_comment); + return($current_comment); +} +sub ctx_has_comment { + my ($first_line, $end_line) = @_; + my $cmt = ctx_locate_comment($first_line, $end_line); + + ##print "LINE: $rawlines[$end_line - 1 ]\n"; + ##print "CMMT: $cmt\n"; + + return ($cmt ne ''); +} + +sub raw_line { + my ($linenr, $cnt) = @_; + + my $offset = $linenr - 1; + $cnt++; + + my $line; + while ($cnt) { + $line = $rawlines[$offset++]; + next if (defined($line) && $line =~ /^-/); + $cnt--; + } + + return $line; +} + +sub get_stat_real { + my ($linenr, $lc) = @_; + + my $stat_real = raw_line($linenr, 0); + for (my $count = $linenr + 1; $count <= $lc; $count++) { + $stat_real = $stat_real . "\n" . raw_line($count, 0); + } + + return $stat_real; +} + +sub get_stat_here { + my ($linenr, $cnt, $here) = @_; + + my $herectx = $here . "\n"; + for (my $n = 0; $n < $cnt; $n++) { + $herectx .= raw_line($linenr, $n) . "\n"; + } + + return $herectx; +} + +sub cat_vet { + my ($vet) = @_; + my ($res, $coded); + + $res = ''; + while ($vet =~ /([^[:cntrl:]]*)([[:cntrl:]]|$)/g) { + $res .= $1; + if ($2 ne '') { + $coded = sprintf("^%c", unpack('C', $2) + 64); + $res .= $coded; + } + } + $res =~ s/$/\$/; + + return $res; +} + +my $av_preprocessor = 0; +my $av_pending; +my @av_paren_type; +my $av_pend_colon; + +sub annotate_reset { + $av_preprocessor = 0; + $av_pending = '_'; + @av_paren_type = ('E'); + $av_pend_colon = 'O'; +} + +sub annotate_values { + my ($stream, $type) = @_; + + my $res; + my $var = '_' x length($stream); + my $cur = $stream; + + print "$stream\n" if ($dbg_values > 1); + + while (length($cur)) { + @av_paren_type = ('E') if ($#av_paren_type < 0); + print " <" . join('', @av_paren_type) . + "> <$type> <$av_pending>" if ($dbg_values > 1); + if ($cur =~ /^(\s+)/o) { + print "WS($1)\n" if ($dbg_values > 1); + if ($1 =~ /\n/ && $av_preprocessor) { + $type = pop(@av_paren_type); + $av_preprocessor = 0; + } + + } elsif ($cur =~ /^(\(\s*$Type\s*)\)/ && $av_pending eq '_') { + print "CAST($1)\n" if ($dbg_values > 1); + push(@av_paren_type, $type); + $type = 'c'; + + } elsif ($cur =~ /^($Type)\s*(?:$Ident|,|\)|\(|\s*$)/) { + print "DECLARE($1)\n" if ($dbg_values > 1); + $type = 'T'; + + } elsif ($cur =~ /^($Modifier)\s*/) { + print "MODIFIER($1)\n" if ($dbg_values > 1); + $type = 'T'; + + } elsif ($cur =~ /^(\#\s*define\s*$Ident)(\(?)/o) { + print "DEFINE($1,$2)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + push(@av_paren_type, $type); + if ($2 ne '') { + $av_pending = 'N'; + } + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:undef\s*$Ident|include\b))/o) { + print "UNDEF($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + push(@av_paren_type, $type); + + } elsif ($cur =~ /^(\#\s*(?:ifdef|ifndef|if))/o) { + print "PRE_START($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + + push(@av_paren_type, $type); + push(@av_paren_type, $type); + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:else|elif))/o) { + print "PRE_RESTART($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + + push(@av_paren_type, $av_paren_type[$#av_paren_type]); + + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:endif))/o) { + print "PRE_END($1)\n" if ($dbg_values > 1); + + $av_preprocessor = 1; + + # Assume all arms of the conditional end as this + # one does, and continue as if the #endif was not here. + pop(@av_paren_type); + push(@av_paren_type, $type); + $type = 'E'; + + } elsif ($cur =~ /^(\\\n)/o) { + print "PRECONT($1)\n" if ($dbg_values > 1); + + } elsif ($cur =~ /^(__attribute__)\s*\(?/o) { + print "ATTR($1)\n" if ($dbg_values > 1); + $av_pending = $type; + $type = 'N'; + + } elsif ($cur =~ /^(sizeof)\s*(\()?/o) { + print "SIZEOF($1)\n" if ($dbg_values > 1); + if (defined $2) { + $av_pending = 'V'; + } + $type = 'N'; + + } elsif ($cur =~ /^(if|while|for)\b/o) { + print "COND($1)\n" if ($dbg_values > 1); + $av_pending = 'E'; + $type = 'N'; + + } elsif ($cur =~/^(case)/o) { + print "CASE($1)\n" if ($dbg_values > 1); + $av_pend_colon = 'C'; + $type = 'N'; + + } elsif ($cur =~/^(return|else|goto|typeof|__typeof__)\b/o) { + print "KEYWORD($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(\()/o) { + print "PAREN('$1')\n" if ($dbg_values > 1); + push(@av_paren_type, $av_pending); + $av_pending = '_'; + $type = 'N'; + + } elsif ($cur =~ /^(\))/o) { + my $new_type = pop(@av_paren_type); + if ($new_type ne '_') { + $type = $new_type; + print "PAREN('$1') -> $type\n" + if ($dbg_values > 1); + } else { + print "PAREN('$1')\n" if ($dbg_values > 1); + } + + } elsif ($cur =~ /^($Ident)\s*\(/o) { + print "FUNC($1)\n" if ($dbg_values > 1); + $type = 'V'; + $av_pending = 'V'; + + } elsif ($cur =~ /^($Ident\s*):(?:\s*\d+\s*(,|=|;))?/) { + if (defined $2 && $type eq 'C' || $type eq 'T') { + $av_pend_colon = 'B'; + } elsif ($type eq 'E') { + $av_pend_colon = 'L'; + } + print "IDENT_COLON($1,$type>$av_pend_colon)\n" if ($dbg_values > 1); + $type = 'V'; + + } elsif ($cur =~ /^($Ident|$Constant)/o) { + print "IDENT($1)\n" if ($dbg_values > 1); + $type = 'V'; + + } elsif ($cur =~ /^($Assignment)/o) { + print "ASSIGN($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~/^(;|{|})/) { + print "END($1)\n" if ($dbg_values > 1); + $type = 'E'; + $av_pend_colon = 'O'; + + } elsif ($cur =~/^(,)/) { + print "COMMA($1)\n" if ($dbg_values > 1); + $type = 'C'; + + } elsif ($cur =~ /^(\?)/o) { + print "QUESTION($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(:)/o) { + print "COLON($1,$av_pend_colon)\n" if ($dbg_values > 1); + + substr($var, length($res), 1, $av_pend_colon); + if ($av_pend_colon eq 'C' || $av_pend_colon eq 'L') { + $type = 'E'; + } else { + $type = 'N'; + } + $av_pend_colon = 'O'; + + } elsif ($cur =~ /^(\[)/o) { + print "CLOSE($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(-(?![->])|\+(?!\+)|\*|\&\&|\&)/o) { + my $variant; + + print "OPV($1)\n" if ($dbg_values > 1); + if ($type eq 'V') { + $variant = 'B'; + } else { + $variant = 'U'; + } + + substr($var, length($res), 1, $variant); + $type = 'N'; + + } elsif ($cur =~ /^($Operators)/o) { + print "OP($1)\n" if ($dbg_values > 1); + if ($1 ne '++' && $1 ne '--') { + $type = 'N'; + } + + } elsif ($cur =~ /(^.)/o) { + print "C($1)\n" if ($dbg_values > 1); + } + if (defined $1) { + $cur = substr($cur, length($1)); + $res .= $type x length($1); + } + } + + return ($res, $var); +} + +sub possible { + my ($possible, $line) = @_; + my $notPermitted = qr{(?: + ^(?: + $Modifier| + $Storage| + $Type| + DEFINE_\S+ + )$| + ^(?: + goto| + return| + case| + else| + asm|__asm__| + do| + \#| + \#\#| + )(?:\s|$)| + ^(?:typedef|struct|enum)\b + )}x; + warn "CHECK<$possible> ($line)\n" if ($dbg_possible > 2); + if ($possible !~ $notPermitted) { + # Check for modifiers. + $possible =~ s/\s*$Storage\s*//g; + $possible =~ s/\s*$Sparse\s*//g; + if ($possible =~ /^\s*$/) { + + } elsif ($possible =~ /\s/) { + $possible =~ s/\s*$Type\s*//g; + for my $modifier (split(' ', $possible)) { + if ($modifier !~ $notPermitted) { + warn "MODIFIER: $modifier ($possible) ($line)\n" if ($dbg_possible); + push(@modifierListFile, $modifier); + } + } + + } else { + warn "POSSIBLE: $possible ($line)\n" if ($dbg_possible); + push(@typeListFile, $possible); + } + build_types(); + } else { + warn "NOTPOSS: $possible ($line)\n" if ($dbg_possible > 1); + } +} + +my $prefix = ''; + +sub show_type { + my ($type) = @_; + + $type =~ tr/[a-z]/[A-Z]/; + + return defined $use_type{$type} if (scalar keys %use_type > 0); + + return !defined $ignore_type{$type}; +} + +sub report { + my ($level, $type, $msg) = @_; + + if (!show_type($type) || + (defined $tst_only && $msg !~ /\Q$tst_only\E/)) { + return 0; + } + my $output = ''; + if ($color) { + if ($level eq 'ERROR') { + $output .= RED; + } elsif ($level eq 'WARNING') { + $output .= YELLOW; + } else { + $output .= GREEN; + } + } + $output .= $prefix . $level . ':'; + if ($show_types) { + $output .= BLUE if ($color); + $output .= "$type:"; + } + $output .= RESET if ($color); + $output .= ' ' . $msg . "\n"; + + if ($showfile) { + my @lines = split("\n", $output, -1); + splice(@lines, 1, 1); + $output = join("\n", @lines); + } + + if ($terse) { + $output = (split('\n', $output))[0] . "\n"; + } + + if ($verbose && exists($verbose_messages{$type}) && + !exists($verbose_emitted{$type})) { + $output .= $verbose_messages{$type} . "\n\n"; + $verbose_emitted{$type} = 1; + } + + push(our @report, $output); + + return 1; +} + +sub report_dump { + our @report; +} + +sub fixup_current_range { + my ($lineRef, $offset, $length) = @_; + + if ($$lineRef =~ /^\@\@ -\d+,\d+ \+(\d+),(\d+) \@\@/) { + my $o = $1; + my $l = $2; + my $no = $o + $offset; + my $nl = $l + $length; + $$lineRef =~ s/\+$o,$l \@\@/\+$no,$nl \@\@/; + } +} + +sub fix_inserted_deleted_lines { + my ($linesRef, $insertedRef, $deletedRef) = @_; + + my $range_last_linenr = 0; + my $delta_offset = 0; + + my $old_linenr = 0; + my $new_linenr = 0; + + my $next_insert = 0; + my $next_delete = 0; + + my @lines = (); + + my $inserted = @{$insertedRef}[$next_insert++]; + my $deleted = @{$deletedRef}[$next_delete++]; + + foreach my $old_line (@{$linesRef}) { + my $save_line = 1; + my $line = $old_line; #don't modify the array + if ($line =~ /^(?:\+\+\+|\-\-\-)\s+\S+/) { #new filename + $delta_offset = 0; + } elsif ($line =~ /^\@\@ -\d+,\d+ \+\d+,\d+ \@\@/) { #new hunk + $range_last_linenr = $new_linenr; + fixup_current_range(\$line, $delta_offset, 0); + } + + while (defined($deleted) && ${$deleted}{'LINENR'} == $old_linenr) { + $deleted = @{$deletedRef}[$next_delete++]; + $save_line = 0; + fixup_current_range(\$lines[$range_last_linenr], $delta_offset--, -1); + } + + while (defined($inserted) && ${$inserted}{'LINENR'} == $old_linenr) { + push(@lines, ${$inserted}{'LINE'}); + $inserted = @{$insertedRef}[$next_insert++]; + $new_linenr++; + fixup_current_range(\$lines[$range_last_linenr], $delta_offset++, 1); + } + + if ($save_line) { + push(@lines, $line); + $new_linenr++; + } + + $old_linenr++; + } + + return @lines; +} + +sub fix_insert_line { + my ($linenr, $line) = @_; + + my $inserted = { + LINENR => $linenr, + LINE => $line, + }; + push(@fixed_inserted, $inserted); +} + +sub fix_delete_line { + my ($linenr, $line) = @_; + + my $deleted = { + LINENR => $linenr, + LINE => $line, + }; + + push(@fixed_deleted, $deleted); +} + +sub ERROR { + my ($type, $msg) = @_; + + if (report("ERROR", $type, $msg)) { + our $clean = 0; + our $cnt_error++; + return 1; + } + return 0; +} +sub WARN { + my ($type, $msg) = @_; + + if (report("WARNING", $type, $msg)) { + our $clean = 0; + our $cnt_warn++; + return 1; + } + return 0; +} +sub CHK { + my ($type, $msg) = @_; + + if ($check && report("CHECK", $type, $msg)) { + our $clean = 0; + our $cnt_chk++; + return 1; + } + return 0; +} + +sub check_absolute_file { + my ($absolute, $herecurr) = @_; + my $file = $absolute; + + ##print "absolute<$absolute>\n"; + + # See if any suffix of this path is a path within the tree. + while ($file =~ s@^[^/]*/@@) { + if (-f "$root/$file") { + ##print "file<$file>\n"; + last; + } + } + if (! -f _) { + return 0; + } + + # It is, so see if the prefix is acceptable. + my $prefix = $absolute; + substr($prefix, -length($file)) = ''; + + ##print "prefix<$prefix>\n"; + if ($prefix ne ".../") { + WARN("USE_RELATIVE_PATH", + "use relative pathname instead of absolute in changelog text\n" . $herecurr); + } +} + +sub trim { + my ($string) = @_; + + $string =~ s/^\s+|\s+$//g; + + return $string; +} + +sub ltrim { + my ($string) = @_; + + $string =~ s/^\s+//; + + return $string; +} + +sub rtrim { + my ($string) = @_; + + $string =~ s/\s+$//; + + return $string; +} + +sub string_find_replace { + my ($string, $find, $replace) = @_; + + $string =~ s/$find/$replace/g; + + return $string; +} + +sub tabify { + my ($leading) = @_; + + my $source_indent = $tabsize; + my $max_spaces_before_tab = $source_indent - 1; + my $spaces_to_tab = " " x $source_indent; + + #convert leading spaces to tabs + 1 while $leading =~ s@^([\t]*)$spaces_to_tab@$1\t@g; + #Remove spaces before a tab + 1 while $leading =~ s@^([\t]*)( {1,$max_spaces_before_tab})\t@$1\t@g; + + return "$leading"; +} + +sub pos_last_openparen { + my ($line) = @_; + + my $pos = 0; + + my $opens = $line =~ tr/\(/\(/; + my $closes = $line =~ tr/\)/\)/; + + my $last_openparen = 0; + + if (($opens == 0) || ($closes >= $opens)) { + return -1; + } + + my $len = length($line); + + for ($pos = 0; $pos < $len; $pos++) { + my $string = substr($line, $pos); + if ($string =~ /^($FuncArg|$balanced_parens)/) { + $pos += length($1) - 1; + } elsif (substr($line, $pos, 1) eq '(') { + $last_openparen = $pos; + } elsif (index($string, '(') == -1) { + last; + } + } + + return length(expand_tabs(substr($line, 0, $last_openparen))) + 1; +} + +sub get_raw_comment { + my ($line, $rawline) = @_; + my $comment = ''; + + for my $i (0 .. (length($line) - 1)) { + if (substr($line, $i, 1) eq "$;") { + $comment .= substr($rawline, $i, 1); + } + } + + return $comment; +} + +sub exclude_global_initialisers { + my ($realfile) = @_; + + # Do not check for BPF programs (tools/testing/selftests/bpf/progs/*.c, samples/bpf/*_kern.c, *.bpf.c). + return $realfile =~ m@^tools/testing/selftests/bpf/progs/.*\.c$@ || + $realfile =~ m@^samples/bpf/.*_kern\.c$@ || + $realfile =~ m@/bpf/.*\.bpf\.c$@; +} + +sub process { + my $filename = shift; + + my $linenr=0; + my $prevline=""; + my $prevrawline=""; + my $stashline=""; + my $stashrawline=""; + + my $length; + my $indent; + my $previndent=0; + my $stashindent=0; + + our $clean = 1; + my $signoff = 0; + my $fixes_tag = 0; + my $is_revert = 0; + my $needs_fixes_tag = ""; + my $author = ''; + my $authorsignoff = 0; + my $author_sob = ''; + my $is_patch = 0; + my $is_binding_patch = -1; + my $in_header_lines = $file ? 0 : 1; + my $in_commit_log = 0; #Scanning lines before patch + my $has_patch_separator = 0; #Found a --- line + my $has_commit_log = 0; #Encountered lines before patch + my $commit_log_lines = 0; #Number of commit log lines + my $commit_log_possible_stack_dump = 0; + my $commit_log_long_line = 0; + my $commit_log_has_diff = 0; + my $reported_maintainer_file = 0; + my $non_utf8_charset = 0; + + my $last_git_commit_id_linenr = -1; + + my $last_blank_line = 0; + my $last_coalesced_string_linenr = -1; + + our @report = (); + our $cnt_lines = 0; + our $cnt_error = 0; + our $cnt_warn = 0; + our $cnt_chk = 0; + + # Trace the real file/line as we go. + my $realfile = ''; + my $realline = 0; + my $realcnt = 0; + my $here = ''; + my $context_function; #undef'd unless there's a known function + my $in_comment = 0; + my $comment_edge = 0; + my $first_line = 0; + my $p1_prefix = ''; + + my $prev_values = 'E'; + + # suppression flags + my %suppress_ifbraces; + my %suppress_whiletrailers; + my %suppress_export; + my $suppress_statement = 0; + + my %signatures = (); + + # Pre-scan the patch sanitizing the lines. + # Pre-scan the patch looking for any __setup documentation. + # + my @setup_docs = (); + my $setup_docs = 0; + + my $camelcase_file_seeded = 0; + + my $checklicenseline = 1; + + sanitise_line_reset(); + my $line; + foreach my $rawline (@rawlines) { + $linenr++; + $line = $rawline; + + push(@fixed, $rawline) if ($fix); + + if ($rawline=~/^\+\+\+\s+(\S+)/) { + $setup_docs = 0; + if ($1 =~ m@Documentation/admin-guide/kernel-parameters.txt$@) { + $setup_docs = 1; + } + #next; + } + if ($rawline =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@/) { + $realline=$1-1; + if (defined $2) { + $realcnt=$3+1; + } else { + $realcnt=1+1; + } + $in_comment = 0; + + # Guestimate if this is a continuing comment. Run + # the context looking for a comment "edge". If this + # edge is a close comment then we must be in a comment + # at context start. + my $edge; + my $cnt = $realcnt; + for (my $ln = $linenr + 1; $cnt > 0; $ln++) { + next if (defined $rawlines[$ln - 1] && + $rawlines[$ln - 1] =~ /^-/); + $cnt--; + #print "RAW<$rawlines[$ln - 1]>\n"; + last if (!defined $rawlines[$ln - 1]); + if ($rawlines[$ln - 1] =~ m@(/\*|\*/)@ && + $rawlines[$ln - 1] !~ m@"[^"]*(?:/\*|\*/)[^"]*"@) { + ($edge) = $1; + last; + } + } + if (defined $edge && $edge eq '*/') { + $in_comment = 1; + } + + # Guestimate if this is a continuing comment. If this + # is the start of a diff block and this line starts + # ' *' then it is very likely a comment. + if (!defined $edge && + $rawlines[$linenr] =~ m@^.\s*(?:\*\*+| \*)(?:\s|$)@) + { + $in_comment = 1; + } + + ##print "COMMENT:$in_comment edge<$edge> $rawline\n"; + sanitise_line_reset($in_comment); + + } elsif ($realcnt && $rawline =~ /^(?:\+| |$)/) { + # Standardise the strings and chars within the input to + # simplify matching -- only bother with positive lines. + $line = sanitise_line($rawline); + } + push(@lines, $line); + + if ($realcnt > 1) { + $realcnt-- if ($line =~ /^(?:\+| |$)/); + } else { + $realcnt = 0; + } + + #print "==>$rawline\n"; + #print "-->$line\n"; + + if ($setup_docs && $line =~ /^\+/) { + push(@setup_docs, $line); + } + } + + $prefix = ''; + + $realcnt = 0; + $linenr = 0; + $fixlinenr = -1; + foreach my $line (@lines) { + $linenr++; + $fixlinenr++; + my $sline = $line; #copy of $line + $sline =~ s/$;/ /g; #with comments as spaces + + my $rawline = $rawlines[$linenr - 1]; + my $raw_comment = get_raw_comment($line, $rawline); + +# check if it's a mode change, rename or start of a patch + if (!$in_commit_log && + ($line =~ /^ mode change [0-7]+ => [0-7]+ \S+\s*$/ || + ($line =~ /^rename (?:from|to) \S+\s*$/ || + $line =~ /^diff --git a\/[\w\/\.\_\-]+ b\/\S+\s*$/))) { + $is_patch = 1; + } + +#extract the line range in the file after the patch is applied + if (!$in_commit_log && + $line =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@(.*)/) { + my $context = $4; + $is_patch = 1; + $first_line = $linenr + 1; + $realline=$1-1; + if (defined $2) { + $realcnt=$3+1; + } else { + $realcnt=1+1; + } + annotate_reset(); + $prev_values = 'E'; + + %suppress_ifbraces = (); + %suppress_whiletrailers = (); + %suppress_export = (); + $suppress_statement = 0; + if ($context =~ /\b(\w+)\s*\(/) { + $context_function = $1; + } else { + undef $context_function; + } + next; + +# track the line number as we move through the hunk, note that +# new versions of GNU diff omit the leading space on completely +# blank context lines so we need to count that too. + } elsif ($line =~ /^( |\+|$)/) { + $realline++; + $realcnt-- if ($realcnt != 0); + + # Measure the line length and indent. + ($length, $indent) = line_stats($rawline); + + # Track the previous line. + ($prevline, $stashline) = ($stashline, $line); + ($previndent, $stashindent) = ($stashindent, $indent); + ($prevrawline, $stashrawline) = ($stashrawline, $rawline); + + #warn "line<$line>\n"; + + } elsif ($realcnt == 1) { + $realcnt--; + } + + my $hunk_line = ($realcnt != 0); + + $here = "#$linenr: " if (!$file); + $here = "#$realline: " if ($file); + + my $found_file = 0; + # extract the filename as it passes + if ($line =~ /^diff --git.*?(\S+)$/) { + $realfile = $1; + $realfile =~ s@^([^/]*)/@@ if (!$file); + $in_commit_log = 0; + $found_file = 1; + } elsif ($line =~ /^\+\+\+\s+(\S+)/) { + $realfile = $1; + $realfile =~ s@^([^/]*)/@@ if (!$file); + $in_commit_log = 0; + + $p1_prefix = $1; + if (!$file && $tree && $p1_prefix ne '' && + -e "$root/$p1_prefix") { + WARN("PATCH_PREFIX", + "patch prefix '$p1_prefix' exists, appears to be a -p0 patch\n"); + } + + if ($realfile =~ m@^include/asm/@) { + ERROR("MODIFIED_INCLUDE_ASM", + "do not modify files in include/asm, change architecture specific files in include/asm-\n" . "$here$rawline\n"); + } + $found_file = 1; + } + +#make up the handle for any error we report on this line + if ($showfile) { + $prefix = "$realfile:$realline: " + } elsif ($emacs) { + if ($file) { + $prefix = "$filename:$realline: "; + } else { + $prefix = "$filename:$linenr: "; + } + } + + if ($found_file) { + if (is_maintained_obsolete($realfile)) { + WARN("OBSOLETE", + "$realfile is marked as 'obsolete' in the MAINTAINERS hierarchy. No unnecessary modifications please.\n"); + } + if ($realfile =~ m@^(?:drivers/net/|net/|drivers/staging/)@) { + $check = 1; + } else { + $check = $check_orig; + } + $checklicenseline = 1; + + if ($realfile !~ /^MAINTAINERS/) { + my $last_binding_patch = $is_binding_patch; + + $is_binding_patch = () = $realfile =~ m@^(?:Documentation/devicetree/|include/dt-bindings/)@; + + if (($last_binding_patch != -1) && + ($last_binding_patch ^ $is_binding_patch)) { + WARN("DT_SPLIT_BINDING_PATCH", + "DT binding docs and includes should be a separate patch. See: Documentation/devicetree/bindings/submitting-patches.rst\n"); + } + } + + next; + } + + $here .= "FILE: $realfile:$realline:" if ($realcnt != 0); + + my $hereline = "$here\n$rawline\n"; + my $herecurr = "$here\n$rawline\n"; + my $hereprev = "$here\n$prevrawline\n$rawline\n"; + + $cnt_lines++ if ($realcnt != 0); + +# Verify the existence of a commit log if appropriate +# 2 is used because a $signature is counted in $commit_log_lines + if ($in_commit_log) { + if ($line !~ /^\s*$/) { + $commit_log_lines++; #could be a $signature + } + } elsif ($has_commit_log && $commit_log_lines < 2) { + WARN("COMMIT_MESSAGE", + "Missing commit description - Add an appropriate one\n"); + $commit_log_lines = 2; #warn only once + } + +# Check if the commit log has what seems like a diff which can confuse patch + if ($in_commit_log && !$commit_log_has_diff && + (($line =~ m@^\s+diff\b.*a/([\w/]+)@ && + $line =~ m@^\s+diff\b.*a/[\w/]+\s+b/$1\b@) || + $line =~ m@^\s*(?:\-\-\-\s+a/|\+\+\+\s+b/)@ || + $line =~ m/^\s*\@\@ \-\d+,\d+ \+\d+,\d+ \@\@/)) { + ERROR("DIFF_IN_COMMIT_MSG", + "Avoid using diff content in the commit message - patch(1) might not work\n" . $herecurr); + $commit_log_has_diff = 1; + } + +# Check for incorrect file permissions + if ($line =~ /^new (file )?mode.*[7531]\d{0,2}$/) { + my $permhere = $here . "FILE: $realfile\n"; + if ($realfile !~ m@scripts/@ && + $realfile !~ /\.(py|pl|awk|sh)$/) { + ERROR("EXECUTE_PERMISSIONS", + "do not set execute permissions for source files\n" . $permhere); + } + } + +# Check the patch for a From: + if (decode("MIME-Header", $line) =~ /^From:\s*(.*)/) { + $author = $1; + my $curline = $linenr; + while(defined($rawlines[$curline]) && ($rawlines[$curline++] =~ /^[ \t]\s*(.*)/)) { + $author .= $1; + } + $author = encode("utf8", $author) if ($line =~ /=\?utf-8\?/i); + $author =~ s/"//g; + $author = reformat_email($author); + } + +# Check the patch for a signoff: + if ($line =~ /^\s*signed-off-by:\s*(.*)/i) { + $signoff++; + $in_commit_log = 0; + if ($author ne '' && $authorsignoff != 1) { + if (same_email_addresses($1, $author)) { + $authorsignoff = 1; + } else { + my $ctx = $1; + my ($email_name, $email_comment, $email_address, $comment1) = parse_email($ctx); + my ($author_name, $author_comment, $author_address, $comment2) = parse_email($author); + + if (lc $email_address eq lc $author_address && $email_name eq $author_name) { + $author_sob = $ctx; + $authorsignoff = 2; + } elsif (lc $email_address eq lc $author_address) { + $author_sob = $ctx; + $authorsignoff = 3; + } elsif ($email_name eq $author_name) { + $author_sob = $ctx; + $authorsignoff = 4; + + my $address1 = $email_address; + my $address2 = $author_address; + + if ($address1 =~ /(\S+)\+\S+(\@.*)/) { + $address1 = "$1$2"; + } + if ($address2 =~ /(\S+)\+\S+(\@.*)/) { + $address2 = "$1$2"; + } + if ($address1 eq $address2) { + $authorsignoff = 5; + } + } + } + } + } + +# Check for patch separator + if ($line =~ /^---$/) { + $has_patch_separator = 1; + $in_commit_log = 0; + } + +# Check if MAINTAINERS is being updated. If so, there's probably no need to +# emit the "does MAINTAINERS need updating?" message on file add/move/delete + if ($line =~ /^\s*MAINTAINERS\s*\|/) { + $reported_maintainer_file = 1; + } + +# Check signature styles + if (!$in_header_lines && + $line =~ /^(\s*)([a-z0-9_-]+by:|$signature_tags)(\s*)(.*)/i) { + my $space_before = $1; + my $sign_off = $2; + my $space_after = $3; + my $email = $4; + my $ucfirst_sign_off = ucfirst(lc($sign_off)); + + if ($sign_off !~ /$signature_tags/) { + my $suggested_signature = find_standard_signature($sign_off); + if ($suggested_signature eq "") { + WARN("BAD_SIGN_OFF", + "Non-standard signature: $sign_off\n" . $herecurr); + } else { + if (WARN("BAD_SIGN_OFF", + "Non-standard signature: '$sign_off' - perhaps '$suggested_signature'?\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/$sign_off/$suggested_signature/; + } + } + } + if (defined $space_before && $space_before ne "") { + if (WARN("BAD_SIGN_OFF", + "Do not use whitespace before $ucfirst_sign_off\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + } + if ($sign_off =~ /-by:$/i && $sign_off ne $ucfirst_sign_off) { + if (WARN("BAD_SIGN_OFF", + "'$ucfirst_sign_off' is the preferred signature form\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + + } + if (!defined $space_after || $space_after ne " ") { + if (WARN("BAD_SIGN_OFF", + "Use a single space after $ucfirst_sign_off\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + } + + my ($email_name, $name_comment, $email_address, $comment) = parse_email($email); + my $suggested_email = format_email(($email_name, $name_comment, $email_address, $comment)); + if ($suggested_email eq "") { + ERROR("BAD_SIGN_OFF", + "Unrecognized email address: '$email'\n" . $herecurr); + } else { + my $dequoted = $suggested_email; + $dequoted =~ s/^"//; + $dequoted =~ s/" 1) { + WARN("BAD_SIGN_OFF", + "Use a single name comment in email: '$email'\n" . $herecurr); + } + + + # stable@vger.kernel.org or stable@kernel.org shouldn't + # have an email name. In addition comments should strictly + # begin with a # + if ($email =~ /^.*stable\@(?:vger\.)?kernel\.org/i) { + if (($comment ne "" && $comment !~ /^#.+/) || + ($email_name ne "")) { + my $cur_name = $email_name; + my $new_comment = $comment; + $cur_name =~ s/[a-zA-Z\s\-\"]+//g; + + # Remove brackets enclosing comment text + # and # from start of comments to get comment text + $new_comment =~ s/^\((.*)\)$/$1/; + $new_comment =~ s/^\[(.*)\]$/$1/; + $new_comment =~ s/^[\s\#]+|\s+$//g; + + $new_comment = trim("$new_comment $cur_name") if ($cur_name ne $new_comment); + $new_comment = " # $new_comment" if ($new_comment ne ""); + my $new_email = "$email_address$new_comment"; + + if (WARN("BAD_STABLE_ADDRESS_STYLE", + "Invalid email format for stable: '$email', prefer '$new_email'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$email\E/$new_email/; + } + } + } elsif ($comment ne "" && $comment !~ /^(?:#.+|\(.+\))$/) { + my $new_comment = $comment; + + # Extract comment text from within brackets or + # c89 style /*...*/ comments + $new_comment =~ s/^\[(.*)\]$/$1/; + $new_comment =~ s/^\/\*(.*)\*\/$/$1/; + + $new_comment = trim($new_comment); + $new_comment =~ s/^[^\w]$//; # Single lettered comment with non word character is usually a typo + $new_comment = "($new_comment)" if ($new_comment ne ""); + my $new_email = format_email($email_name, $name_comment, $email_address, $new_comment); + + if (WARN("BAD_SIGN_OFF", + "Unexpected content after email: '$email', should be: '$new_email'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$email\E/$new_email/; + } + } + } + +# Check for duplicate signatures + my $sig_nospace = $line; + $sig_nospace =~ s/\s//g; + $sig_nospace = lc($sig_nospace); + if (defined $signatures{$sig_nospace}) { + WARN("BAD_SIGN_OFF", + "Duplicate signature\n" . $herecurr); + } else { + $signatures{$sig_nospace} = 1; + } + +# Check Co-developed-by: immediately followed by Signed-off-by: with same name and email + if ($sign_off =~ /^co-developed-by:$/i) { + if ($email eq $author) { + WARN("BAD_SIGN_OFF", + "Co-developed-by: should not be used to attribute nominal patch author '$author'\n" . $herecurr); + } + if (!defined $lines[$linenr]) { + WARN("BAD_SIGN_OFF", + "Co-developed-by: must be immediately followed by Signed-off-by:\n" . $herecurr); + } elsif ($rawlines[$linenr] !~ /^signed-off-by:\s*(.*)/i) { + WARN("BAD_SIGN_OFF", + "Co-developed-by: must be immediately followed by Signed-off-by:\n" . $herecurr . $rawlines[$linenr] . "\n"); + } elsif ($1 ne $email) { + WARN("BAD_SIGN_OFF", + "Co-developed-by and Signed-off-by: name/email do not match\n" . $herecurr . $rawlines[$linenr] . "\n"); + } + } + +# check if Reported-by: is followed by a Closes: tag + if ($sign_off =~ /^reported(?:|-and-tested)-by:$/i) { + if (!defined $lines[$linenr]) { + WARN("BAD_REPORTED_BY_LINK", + "Reported-by: should be immediately followed by Closes: with a URL to the report\n" . $herecurr . "\n"); + } elsif ($rawlines[$linenr] !~ /^closes:\s*/i) { + WARN("BAD_REPORTED_BY_LINK", + "Reported-by: should be immediately followed by Closes: with a URL to the report\n" . $herecurr . $rawlines[$linenr] . "\n"); + } + } + } + +# These indicate a bug fix + if (!$in_header_lines && !$is_patch && + $line =~ /^This reverts commit/) { + $is_revert = 1; + } + + if (!$in_header_lines && !$is_patch && + $line =~ /((?:(?:BUG: K.|UB)SAN: |Call Trace:|stable\@|syzkaller))/) { + $needs_fixes_tag = $1; + } + +# Check Fixes: styles is correct + if (!$in_header_lines && + $line =~ /^\s*(fixes:?)\s*(?:commit\s*)?([0-9a-f]{5,40})(?:\s*($balanced_parens))?/i) { + my $tag = $1; + my $orig_commit = $2; + my $title; + my $title_has_quotes = 0; + $fixes_tag = 1; + if (defined $3) { + # Always strip leading/trailing parens then double quotes if existing + $title = substr($3, 1, -1); + if ($title =~ /^".*"$/) { + $title = substr($title, 1, -1); + $title_has_quotes = 1; + } + } else { + $title = "commit title" + } + + + my $tag_case = not ($tag eq "Fixes:"); + my $tag_space = not ($line =~ /^fixes:? [0-9a-f]{5,40} ($balanced_parens)/i); + + my $id_length = not ($orig_commit =~ /^[0-9a-f]{12}$/i); + my $id_case = not ($orig_commit !~ /[A-F]/); + + my $id = "0123456789ab"; + my ($cid, $ctitle) = git_commit_info($orig_commit, $id, + $title); + + if ($ctitle ne $title || $tag_case || $tag_space || + $id_length || $id_case || !$title_has_quotes) { + if (WARN("BAD_FIXES_TAG", + "Please use correct Fixes: style 'Fixes: <12 chars of sha1> (\"\")' - ie: 'Fixes: $cid (\"$ctitle\")'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = "Fixes: $cid (\"$ctitle\")"; + } + } + } + +# Check email subject for common tools that don't need to be mentioned + if ($in_header_lines && + $line =~ /^Subject:.*\b(?:checkpatch|sparse|smatch)\b[^:]/i) { + WARN("EMAIL_SUBJECT", + "A patch subject line should describe the change not the tool that found it\n" . $herecurr); + } + +# Check for Gerrit Change-Ids not in any patch context + if ($realfile eq '' && !$has_patch_separator && $line =~ /^\s*change-id:/i) { + if (ERROR("GERRIT_CHANGE_ID", + "Remove Gerrit Change-Id's before submitting upstream\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + +# Check if the commit log is in a possible stack dump + if ($in_commit_log && !$commit_log_possible_stack_dump && + ($line =~ /^\s*(?:WARNING:|BUG:)/ || + $line =~ /^\s*\[\s*\d+\.\d{6,6}\s*\]/ || + # timestamp + $line =~ /^\s*\[\<[0-9a-fA-F]{8,}\>\]/) || + $line =~ /^(?:\s+\w+:\s+[0-9a-fA-F]+){3,3}/ || + $line =~ /^\s*\#\d+\s*\[[0-9a-fA-F]+\]\s*\w+ at [0-9a-fA-F]+/) { + # stack dump address styles + $commit_log_possible_stack_dump = 1; + } + +# Check for line lengths > 75 in commit log, warn once + if ($in_commit_log && !$commit_log_long_line && + length($line) > 75 && + !($line =~ /^\s*[a-zA-Z0-9_\/\.]+\s+\|\s+\d+/ || + # file delta changes + $line =~ /^\s*(?:[\w\.\-\+]*\/)++[\w\.\-\+]+:/ || + # filename then : + $line =~ /^\s*(?:Fixes:|$link_tags_search|$signature_tags)/i || + # A Fixes:, link or signature tag line + $commit_log_possible_stack_dump)) { + WARN("COMMIT_LOG_LONG_LINE", + "Prefer a maximum 75 chars per line (possible unwrapped commit description?)\n" . $herecurr); + $commit_log_long_line = 1; + } + +# Reset possible stack dump if a blank line is found + if ($in_commit_log && $commit_log_possible_stack_dump && + $line =~ /^\s*$/) { + $commit_log_possible_stack_dump = 0; + } + +# Check for odd tags before a URI/URL + if ($in_commit_log && + $line =~ /^\s*(\w+:)\s*http/ && $1 !~ /^$link_tags_search$/) { + if ($1 =~ /^v(?:ersion)?\d+/i) { + WARN("COMMIT_LOG_VERSIONING", + "Patch version information should be after the --- line\n" . $herecurr); + } else { + WARN("COMMIT_LOG_USE_LINK", + "Unknown link reference '$1', use $link_tags_print instead\n" . $herecurr); + } + } + +# Check for misuse of the link tags + if ($in_commit_log && + $line =~ /^\s*(\w+:)\s*(\S+)/) { + my $tag = $1; + my $value = $2; + if ($tag =~ /^$link_tags_search$/ && $value !~ m{^https?://}) { + WARN("COMMIT_LOG_WRONG_LINK", + "'$tag' should be followed by a public http(s) link\n" . $herecurr); + } + } + +# Check for lines starting with a # + if ($in_commit_log && $line =~ /^#/) { + if (WARN("COMMIT_COMMENT_SYMBOL", + "Commit log lines starting with '#' are dropped by git as comments\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^/ /; + } + } + +# Check for git id commit length and improperly formed commit descriptions +# A correctly formed commit description is: +# commit <SHA-1 hash length 12+ chars> ("Complete commit subject") +# with the commit subject '("' prefix and '")' suffix +# This is a fairly compilicated block as it tests for what appears to be +# bare SHA-1 hash with minimum length of 5. It also avoids several types of +# possible SHA-1 matches. +# A commit match can span multiple lines so this block attempts to find a +# complete typical commit on a maximum of 3 lines + if ($perl_version_ok && + $in_commit_log && !$commit_log_possible_stack_dump && + $line !~ /^\s*(?:Link|Patchwork|http|https|BugLink|base-commit):/i && + $line !~ /^This reverts commit [0-9a-f]{7,40}/ && + (($line =~ /\bcommit\s+[0-9a-f]{5,}\b/i || + ($line =~ /\bcommit\s*$/i && defined($rawlines[$linenr]) && $rawlines[$linenr] =~ /^\s*[0-9a-f]{5,}\b/i)) || + ($line =~ /(?:\s|^)[0-9a-f]{12,40}(?:[\s"'\(\[]|$)/i && + $line !~ /[\<\[][0-9a-f]{12,40}[\>\]]/i && + $line !~ /\bfixes:\s*[0-9a-f]{12,40}/i))) { + my $init_char = "c"; + my $orig_commit = ""; + my $short = 1; + my $long = 0; + my $case = 1; + my $space = 1; + my $id = '0123456789ab'; + my $orig_desc = "commit description"; + my $description = ""; + my $herectx = $herecurr; + my $has_parens = 0; + my $has_quotes = 0; + + my $input = $line; + if ($line =~ /(?:\bcommit\s+[0-9a-f]{5,}|\bcommit\s*$)/i) { + for (my $n = 0; $n < 2; $n++) { + if ($input =~ /\bcommit\s+[0-9a-f]{5,}\s*($balanced_parens)/i) { + $orig_desc = $1; + $has_parens = 1; + # Always strip leading/trailing parens then double quotes if existing + $orig_desc = substr($orig_desc, 1, -1); + if ($orig_desc =~ /^".*"$/) { + $orig_desc = substr($orig_desc, 1, -1); + $has_quotes = 1; + } + last; + } + last if ($#lines < $linenr + $n); + $input .= " " . trim($rawlines[$linenr + $n]); + $herectx .= "$rawlines[$linenr + $n]\n"; + } + $herectx = $herecurr if (!$has_parens); + } + + if ($input =~ /\b(c)ommit\s+([0-9a-f]{5,})\b/i) { + $init_char = $1; + $orig_commit = lc($2); + $short = 0 if ($input =~ /\bcommit\s+[0-9a-f]{12,40}/i); + $long = 1 if ($input =~ /\bcommit\s+[0-9a-f]{41,}/i); + $space = 0 if ($input =~ /\bcommit [0-9a-f]/i); + $case = 0 if ($input =~ /\b[Cc]ommit\s+[0-9a-f]{5,40}[^A-F]/); + } elsif ($input =~ /\b([0-9a-f]{12,40})\b/i) { + $orig_commit = lc($1); + } + + ($id, $description) = git_commit_info($orig_commit, + $id, $orig_desc); + + if (defined($id) && + ($short || $long || $space || $case || ($orig_desc ne $description) || !$has_quotes) && + $last_git_commit_id_linenr != $linenr - 1) { + ERROR("GIT_COMMIT_ID", + "Please use git commit description style 'commit <12+ chars of sha1> (\"<title line>\")' - ie: '${init_char}ommit $id (\"$description\")'\n" . $herectx); + } + #don't report the next line if this line ends in commit and the sha1 hash is the next line + $last_git_commit_id_linenr = $linenr if ($line =~ /\bcommit\s*$/i); + } + +# Check for mailing list archives other than lore.kernel.org + if ($rawline =~ m{http.*\b$obsolete_archives}) { + WARN("PREFER_LORE_ARCHIVE", + "Use lore.kernel.org archive links when possible - see https://lore.kernel.org/lists.html\n" . $herecurr); + } + +# Check for added, moved or deleted files + if (!$reported_maintainer_file && !$in_commit_log && + ($line =~ /^(?:new|deleted) file mode\s*\d+\s*$/ || + $line =~ /^rename (?:from|to) [\w\/\.\-]+\s*$/ || + ($line =~ /\{\s*([\w\/\.\-]*)\s*\=\>\s*([\w\/\.\-]*)\s*\}/ && + (defined($1) || defined($2))))) { + $is_patch = 1; + $reported_maintainer_file = 1; + WARN("FILE_PATH_CHANGES", + "added, moved or deleted file(s), does MAINTAINERS need updating?\n" . $herecurr); + } + +# Check for adding new DT bindings not in schema format + if (!$in_commit_log && + ($line =~ /^new file mode\s*\d+\s*$/) && + ($realfile =~ m@^Documentation/devicetree/bindings/.*\.txt$@)) { + WARN("DT_SCHEMA_BINDING_PATCH", + "DT bindings should be in DT schema format. See: Documentation/devicetree/bindings/writing-schema.rst\n"); + } + +# Check for wrappage within a valid hunk of the file + if ($realcnt != 0 && $line !~ m{^(?:\+|-| |\\ No newline|$)}) { + ERROR("CORRUPTED_PATCH", + "patch seems to be corrupt (line wrapped?)\n" . + $herecurr) if (!$emitted_corrupt++); + } + +# UTF-8 regex found at http://www.w3.org/International/questions/qa-forms-utf-8.en.php + if (($realfile =~ /^$/ || $line =~ /^\+/) && + $rawline !~ m/^$UTF8*$/) { + my ($utf8_prefix) = ($rawline =~ /^($UTF8*)/); + + my $blank = copy_spacing($rawline); + my $ptr = substr($blank, 0, length($utf8_prefix)) . "^"; + my $hereptr = "$hereline$ptr\n"; + + CHK("INVALID_UTF8", + "Invalid UTF-8, patch and commit message should be encoded in UTF-8\n" . $hereptr); + } + +# Check if it's the start of a commit log +# (not a header line and we haven't seen the patch filename) + if ($in_header_lines && $realfile =~ /^$/ && + !($rawline =~ /^\s+(?:\S|$)/ || + $rawline =~ /^(?:commit\b|from\b|[\w-]+:)/i)) { + $in_header_lines = 0; + $in_commit_log = 1; + $has_commit_log = 1; + } + +# Check if there is UTF-8 in a commit log when a mail header has explicitly +# declined it, i.e defined some charset where it is missing. + if ($in_header_lines && + $rawline =~ /^Content-Type:.+charset="(.+)".*$/ && + $1 !~ /utf-8/i) { + $non_utf8_charset = 1; + } + + if ($in_commit_log && $non_utf8_charset && $realfile =~ /^$/ && + $rawline =~ /$NON_ASCII_UTF8/) { + WARN("UTF8_BEFORE_PATCH", + "8-bit UTF-8 used in possible commit log\n" . $herecurr); + } + +# Check for absolute kernel paths in commit message + if ($tree && $in_commit_log) { + while ($line =~ m{(?:^|\s)(/\S*)}g) { + my $file = $1; + + if ($file =~ m{^(.*?)(?::\d+)+:?$} && + check_absolute_file($1, $herecurr)) { + # + } else { + check_absolute_file($file, $herecurr); + } + } + } + +# Check for various typo / spelling mistakes + if (defined($misspellings) && + ($in_commit_log || $line =~ /^(?:\+|Subject:)/i)) { + while ($rawline =~ /(?:^|[^\w\-'`])($misspellings)(?:[^\w\-'`]|$)/gi) { + my $typo = $1; + my $blank = copy_spacing($rawline); + my $ptr = substr($blank, 0, $-[1]) . "^" x length($typo); + my $hereptr = "$hereline$ptr\n"; + my $typo_fix = $spelling_fix{lc($typo)}; + $typo_fix = ucfirst($typo_fix) if ($typo =~ /^[A-Z]/); + $typo_fix = uc($typo_fix) if ($typo =~ /^[A-Z]+$/); + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + if (&{$msg_level}("TYPO_SPELLING", + "'$typo' may be misspelled - perhaps '$typo_fix'?\n" . $hereptr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^|[^A-Za-z@])($typo)($|[^A-Za-z@])/$1$typo_fix$3/; + } + } + } + +# check for invalid commit id + if ($in_commit_log && $line =~ /(^fixes:|\bcommit)\s+([0-9a-f]{6,40})\b/i) { + my $id; + my $description; + ($id, $description) = git_commit_info($2, undef, undef); + if (!defined($id)) { + WARN("UNKNOWN_COMMIT_ID", + "Unknown commit id '$2', maybe rebased or not pulled?\n" . $herecurr); + } + } + +# check for repeated words separated by a single space +# avoid false positive from list command eg, '-rw-r--r-- 1 root root' + if (($rawline =~ /^\+/ || $in_commit_log) && + $rawline !~ /[bcCdDlMnpPs\?-][rwxsStT-]{9}/) { + pos($rawline) = 1 if (!$in_commit_log); + while ($rawline =~ /\b($word_pattern) (?=($word_pattern))/g) { + + my $first = $1; + my $second = $2; + my $start_pos = $-[1]; + my $end_pos = $+[2]; + if ($first =~ /(?:struct|union|enum)/) { + pos($rawline) += length($first) + length($second) + 1; + next; + } + + next if (lc($first) ne lc($second)); + next if ($first eq 'long'); + + # check for character before and after the word matches + my $start_char = ''; + my $end_char = ''; + $start_char = substr($rawline, $start_pos - 1, 1) if ($start_pos > ($in_commit_log ? 0 : 1)); + $end_char = substr($rawline, $end_pos, 1) if ($end_pos < length($rawline)); + + next if ($start_char =~ /^\S$/); + next if (index(" \t.,;?!", $end_char) == -1); + + # avoid repeating hex occurrences like 'ff ff fe 09 ...' + if ($first =~ /\b[0-9a-f]{2,}\b/i) { + next if (!exists($allow_repeated_words{lc($first)})); + } + + if (WARN("REPEATED_WORD", + "Possible repeated word: '$first'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$first $second\b/$first/; + } + } + + # if it's a repeated word on consecutive lines in a comment block + if ($prevline =~ /$;+\s*$/ && + $prevrawline =~ /($word_pattern)\s*$/) { + my $last_word = $1; + if ($rawline =~ /^\+\s*\*\s*$last_word /) { + if (WARN("REPEATED_WORD", + "Possible repeated word: '$last_word'\n" . $hereprev) && + $fix) { + $fixed[$fixlinenr] =~ s/(\+\s*\*\s*)$last_word /$1/; + } + } + } + } + +# ignore non-hunk lines and lines being removed + next if (!$hunk_line || $line =~ /^-/); + +#trailing whitespace + if ($line =~ /^\+.*\015/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (ERROR("DOS_LINE_ENDINGS", + "DOS line endings\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/[\s\015]+$//; + } + } elsif ($rawline =~ /^\+.*\S\s+$/ || $rawline =~ /^\+\s+$/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (ERROR("TRAILING_WHITESPACE", + "trailing whitespace\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+$//; + } + + $rpt_cleaners = 1; + } + +# Check for FSF mailing addresses. + if ($rawline =~ /\bwrite to the Free/i || + $rawline =~ /\b675\s+Mass\s+Ave/i || + $rawline =~ /\b59\s+Temple\s+Pl/i || + $rawline =~ /\b51\s+Franklin\s+St/i) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + my $msg_level = \&ERROR; + $msg_level = \&CHK if ($file); + &{$msg_level}("FSF_MAILING_ADDRESS", + "Do not include the paragraph about writing to the Free Software Foundation's mailing address from the sample GPL notice. The FSF has changed addresses in the past, and may do so again. Linux already includes a copy of the GPL.\n" . $herevet) + } + +# check for Kconfig help text having a real description +# Only applies when adding the entry originally, after that we do not have +# sufficient context to determine whether it is indeed long enough. + if ($realfile =~ /Kconfig/ && + # 'choice' is usually the last thing on the line (though + # Kconfig supports named choices), so use a word boundary + # (\b) rather than a whitespace character (\s) + $line =~ /^\+\s*(?:config|menuconfig|choice)\b/) { + my $ln = $linenr; + my $needs_help = 0; + my $has_help = 0; + my $help_length = 0; + while (defined $lines[$ln]) { + my $f = $lines[$ln++]; + + next if ($f =~ /^-/); + last if ($f !~ /^[\+ ]/); # !patch context + + if ($f =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) { + $needs_help = 1; + next; + } + if ($f =~ /^\+\s*help\s*$/) { + $has_help = 1; + next; + } + + $f =~ s/^.//; # strip patch context [+ ] + $f =~ s/#.*//; # strip # directives + $f =~ s/^\s+//; # strip leading blanks + next if ($f =~ /^$/); # skip blank lines + + # At the end of this Kconfig block: + # This only checks context lines in the patch + # and so hopefully shouldn't trigger false + # positives, even though some of these are + # common words in help texts + if ($f =~ /^(?:config|menuconfig|choice|endchoice| + if|endif|menu|endmenu|source)\b/x) { + last; + } + $help_length++ if ($has_help); + } + if ($needs_help && + $help_length < $min_conf_desc_length) { + my $stat_real = get_stat_real($linenr, $ln - 1); + WARN("CONFIG_DESCRIPTION", + "please write a help paragraph that fully describes the config symbol\n" . "$here\n$stat_real\n"); + } + } + +# check MAINTAINERS entries + if ($realfile =~ /^MAINTAINERS$/) { +# check MAINTAINERS entries for the right form + if ($rawline =~ /^\+[A-Z]:/ && + $rawline !~ /^\+[A-Z]:\t\S/) { + if (WARN("MAINTAINERS_STYLE", + "MAINTAINERS entries use one tab after TYPE:\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+[A-Z]):\s*/$1:\t/; + } + } +# check MAINTAINERS entries for the right ordering too + my $preferred_order = 'MRLSWQBCPTFXNK'; + if ($rawline =~ /^\+[A-Z]:/ && + $prevrawline =~ /^[\+ ][A-Z]:/) { + $rawline =~ /^\+([A-Z]):\s*(.*)/; + my $cur = $1; + my $curval = $2; + $prevrawline =~ /^[\+ ]([A-Z]):\s*(.*)/; + my $prev = $1; + my $prevval = $2; + my $curindex = index($preferred_order, $cur); + my $previndex = index($preferred_order, $prev); + if ($curindex < 0) { + WARN("MAINTAINERS_STYLE", + "Unknown MAINTAINERS entry type: '$cur'\n" . $herecurr); + } else { + if ($previndex >= 0 && $curindex < $previndex) { + WARN("MAINTAINERS_STYLE", + "Misordered MAINTAINERS entry - list '$cur:' before '$prev:'\n" . $hereprev); + } elsif ((($prev eq 'F' && $cur eq 'F') || + ($prev eq 'X' && $cur eq 'X')) && + ($prevval cmp $curval) > 0) { + WARN("MAINTAINERS_STYLE", + "Misordered MAINTAINERS entry - list file patterns in alphabetic order\n" . $hereprev); + } + } + } + } + + if (($realfile =~ /Makefile.*/ || $realfile =~ /Kbuild.*/) && + ($line =~ /\+(EXTRA_[A-Z]+FLAGS).*/)) { + my $flag = $1; + my $replacement = { + 'EXTRA_AFLAGS' => 'asflags-y', + 'EXTRA_CFLAGS' => 'ccflags-y', + 'EXTRA_CPPFLAGS' => 'cppflags-y', + 'EXTRA_LDFLAGS' => 'ldflags-y', + }; + + WARN("DEPRECATED_VARIABLE", + "Use of $flag is deprecated, please use \`$replacement->{$flag} instead.\n" . $herecurr) if ($replacement->{$flag}); + } + +# check for DT compatible documentation + if (defined $root && + (($realfile =~ /\.dtsi?$/ && $line =~ /^\+\s*compatible\s*=\s*\"/) || + ($realfile =~ /\.[ch]$/ && $line =~ /^\+.*\.compatible\s*=\s*\"/))) { + + my @compats = $rawline =~ /\"([a-zA-Z0-9\-\,\.\+_]+)\"/g; + + my $dt_path = $root . "/Documentation/devicetree/bindings/"; + my $vp_file = $dt_path . "vendor-prefixes.yaml"; + + foreach my $compat (@compats) { + my $compat2 = $compat; + $compat2 =~ s/\,[a-zA-Z0-9]*\-/\,<\.\*>\-/; + my $compat3 = $compat; + $compat3 =~ s/\,([a-z]*)[0-9]*\-/\,$1<\.\*>\-/; + `grep -Erq "$compat|$compat2|$compat3" $dt_path`; + if ( $? >> 8 ) { + WARN("UNDOCUMENTED_DT_STRING", + "DT compatible string \"$compat\" appears un-documented -- check $dt_path\n" . $herecurr); + } + + next if $compat !~ /^([a-zA-Z0-9\-]+)\,/; + my $vendor = $1; + `grep -Eq "\\"\\^\Q$vendor\E,\\.\\*\\":" $vp_file`; + if ( $? >> 8 ) { + WARN("UNDOCUMENTED_DT_STRING", + "DT compatible string vendor \"$vendor\" appears un-documented -- check $vp_file\n" . $herecurr); + } + } + } + +# check for using SPDX license tag at beginning of files + if ($realline == $checklicenseline) { + if ($rawline =~ /^[ \+]\s*\#\!\s*\//) { + $checklicenseline = 2; + } elsif ($rawline =~ /^\+/) { + my $comment = ""; + if ($realfile =~ /\.(h|s|S)$/) { + $comment = '/*'; + } elsif ($realfile =~ /\.(c|rs|dts|dtsi)$/) { + $comment = '//'; + } elsif (($checklicenseline == 2) || $realfile =~ /\.(sh|pl|py|awk|tc|yaml)$/) { + $comment = '#'; + } elsif ($realfile =~ /\.rst$/) { + $comment = '..'; + } + +# check SPDX comment style for .[chsS] files + if ($realfile =~ /\.[chsS]$/ && + $rawline =~ /SPDX-License-Identifier:/ && + $rawline !~ m@^\+\s*\Q$comment\E\s*@) { + WARN("SPDX_LICENSE_TAG", + "Improper SPDX comment style for '$realfile', please use '$comment' instead\n" . $herecurr); + } + + if ($comment !~ /^$/ && + $rawline !~ m@^\+\Q$comment\E SPDX-License-Identifier: @) { + WARN("SPDX_LICENSE_TAG", + "Missing or malformed SPDX-License-Identifier tag in line $checklicenseline\n" . $herecurr); + } elsif ($rawline =~ /(SPDX-License-Identifier: .*)/) { + my $spdx_license = $1; + if (!is_SPDX_License_valid($spdx_license)) { + WARN("SPDX_LICENSE_TAG", + "'$spdx_license' is not supported in LICENSES/...\n" . $herecurr); + } + if ($realfile =~ m@^Documentation/devicetree/bindings/@ && + $spdx_license !~ /GPL-2\.0(?:-only)? OR BSD-2-Clause/) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + if (&{$msg_level}("SPDX_LICENSE_TAG", + + "DT binding documents should be licensed (GPL-2.0-only OR BSD-2-Clause)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/SPDX-License-Identifier: .*/SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)/; + } + } + if ($realfile =~ m@^include/dt-bindings/@ && + $spdx_license !~ /GPL-2\.0(?:-only)? OR \S+/) { + WARN("SPDX_LICENSE_TAG", + "DT binding headers should be licensed (GPL-2.0-only OR .*)\n" . $herecurr); + } + } + } + } + +# check for embedded filenames + if ($rawline =~ /^\+.*\b\Q$realfile\E\b/) { + WARN("EMBEDDED_FILENAME", + "It's generally not useful to have the filename in the file\n" . $herecurr); + } + +# check we are in a valid source file if not then ignore this hunk + next if ($realfile !~ /\.(h|c|rs|s|S|sh|dtsi|dts)$/); + +# check for using SPDX-License-Identifier on the wrong line number + if ($realline != $checklicenseline && + $rawline =~ /\bSPDX-License-Identifier:/ && + substr($line, @-, @+ - @-) eq "$;" x (@+ - @-)) { + WARN("SPDX_LICENSE_TAG", + "Misplaced SPDX-License-Identifier tag - use line $checklicenseline instead\n" . $herecurr); + } + +# line length limit (with some exclusions) +# +# There are a few types of lines that may extend beyond $max_line_length: +# logging functions like pr_info that end in a string +# lines with a single string +# #defines that are a single string +# lines with an RFC3986 like URL +# +# There are 3 different line length message types: +# LONG_LINE_COMMENT a comment starts before but extends beyond $max_line_length +# LONG_LINE_STRING a string starts before but extends beyond $max_line_length +# LONG_LINE all other lines longer than $max_line_length +# +# if LONG_LINE is ignored, the other 2 types are also ignored +# + + if ($line =~ /^\+/ && $length > $max_line_length) { + my $msg_type = "LONG_LINE"; + + # Check the allowed long line types first + + # logging functions that end in a string that starts + # before $max_line_length + if ($line =~ /^\+\s*$logFunctions\s*\(\s*(?:(?:KERN_\S+\s*|[^"]*))?($String\s*(?:|,|\)\s*;)\s*)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = ""; + + # lines with only strings (w/ possible termination) + # #defines with only strings + } elsif ($line =~ /^\+\s*$String\s*(?:\s*|,|\)\s*;)\s*$/ || + $line =~ /^\+\s*#\s*define\s+\w+\s+$String$/) { + $msg_type = ""; + + # More special cases + } elsif ($line =~ /^\+.*\bEFI_GUID\s*\(/ || + $line =~ /^\+\s*(?:\w+)?\s*DEFINE_PER_CPU/) { + $msg_type = ""; + + # URL ($rawline is used in case the URL is in a comment) + } elsif ($rawline =~ /^\+.*\b[a-z][\w\.\+\-]*:\/\/\S+/i) { + $msg_type = ""; + + # Otherwise set the alternate message types + + # a comment starts before $max_line_length + } elsif ($line =~ /($;[\s$;]*)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = "LONG_LINE_COMMENT" + + # a quoted string starts before $max_line_length + } elsif ($sline =~ /\s*($String(?:\s*(?:\\|,\s*|\)\s*;\s*))?)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = "LONG_LINE_STRING" + } + + if ($msg_type ne "" && + show_type("LONG_LINE") && show_type($msg_type)) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + &{$msg_level}($msg_type, + "line length of $length exceeds $max_line_length columns\n" . $herecurr); + } + } + +# check for adding lines without a newline. + if ($line =~ /^\+/ && defined $lines[$linenr] && $lines[$linenr] =~ /^\\ No newline at end of file/) { + if (WARN("MISSING_EOF_NEWLINE", + "adding a line without newline at end of file\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr+1, "No newline at end of file"); + } + } + +# check for .L prefix local symbols in .S files + if ($realfile =~ /\.S$/ && + $line =~ /^\+\s*(?:[A-Z]+_)?SYM_[A-Z]+_(?:START|END)(?:_[A-Z_]+)?\s*\(\s*\.L/) { + WARN("AVOID_L_PREFIX", + "Avoid using '.L' prefixed local symbol names for denoting a range of code via 'SYM_*_START/END' annotations; see Documentation/core-api/asm-annotations.rst\n" . $herecurr); + } + +# check we are in a valid source file C or perl if not then ignore this hunk + next if ($realfile !~ /\.(h|c|pl|dtsi|dts)$/); + +# at the beginning of a line any tabs must come first and anything +# more than $tabsize must use tabs. + if ($rawline =~ /^\+\s* \t\s*\S/ || + $rawline =~ /^\+\s* \s*/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + $rpt_cleaners = 1; + if (ERROR("CODE_INDENT", + "code indent should use tabs where possible\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e; + } + } + +# check for space before tabs. + if ($rawline =~ /^\+/ && $rawline =~ / \t/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (WARN("SPACE_BEFORE_TAB", + "please, no space before tabs\n" . $herevet) && + $fix) { + while ($fixed[$fixlinenr] =~ + s/(^\+.*) {$tabsize,$tabsize}\t/$1\t\t/) {} + while ($fixed[$fixlinenr] =~ + s/(^\+.*) +\t/$1\t/) {} + } + } + +# check for assignments on the start of a line + if ($sline =~ /^\+\s+($Assignment)[^=]/) { + my $operator = $1; + if (CHK("ASSIGNMENT_CONTINUATIONS", + "Assignment operator '$1' should be on the previous line\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + # add assignment operator to the previous line, remove from current line + $fixed[$fixlinenr - 1] .= " $operator"; + $fixed[$fixlinenr] =~ s/\Q$operator\E\s*//; + } + } + +# check for && or || at the start of a line + if ($rawline =~ /^\+\s*(&&|\|\|)/) { + my $operator = $1; + if (CHK("LOGICAL_CONTINUATIONS", + "Logical continuations should be on the previous line\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + # insert logical operator at last non-comment, non-whitepsace char on previous line + $prevline =~ /[\s$;]*$/; + my $line_end = substr($prevrawline, $-[0]); + $fixed[$fixlinenr - 1] =~ s/\Q$line_end\E$/ $operator$line_end/; + $fixed[$fixlinenr] =~ s/\Q$operator\E\s*//; + } + } + +# check indentation starts on a tab stop + if ($perl_version_ok && + $sline =~ /^\+\t+( +)(?:$c90_Keywords\b|\{\s*$|\}\s*(?:else\b|while\b|\s*$)|$Declare\s*$Ident\s*[;=])/) { + my $indent = length($1); + if ($indent % $tabsize) { + if (WARN("TABSTOP", + "Statements should start on a tabstop\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s@(^\+\t+) +@$1 . "\t" x ($indent/$tabsize)@e; + } + } + } + +# check multi-line statement indentation matches previous line + if ($perl_version_ok && + $prevline =~ /^\+([ \t]*)((?:$c90_Keywords(?:\s+if)\s*)|(?:$Declare\s*)?(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*|(?:\*\s*)*$Lval\s*=\s*$Ident\s*)\(.*(\&\&|\|\||,)\s*$/) { + $prevline =~ /^\+(\t*)(.*)$/; + my $oldindent = $1; + my $rest = $2; + + my $pos = pos_last_openparen($rest); + if ($pos >= 0) { + $line =~ /^(\+| )([ \t]*)/; + my $newindent = $2; + + my $goodtabindent = $oldindent . + "\t" x ($pos / $tabsize) . + " " x ($pos % $tabsize); + my $goodspaceindent = $oldindent . " " x $pos; + + if ($newindent ne $goodtabindent && + $newindent ne $goodspaceindent) { + + if (CHK("PARENTHESIS_ALIGNMENT", + "Alignment should match open parenthesis\n" . $hereprev) && + $fix && $line =~ /^\+/) { + $fixed[$fixlinenr] =~ + s/^\+[ \t]*/\+$goodtabindent/; + } + } + } + } + +# check for space after cast like "(int) foo" or "(struct foo) bar" +# avoid checking a few false positives: +# "sizeof(<type>)" or "__alignof__(<type>)" +# function pointer declarations like "(*foo)(int) = bar;" +# structure definitions like "(struct foo) { 0 };" +# multiline macros that define functions +# known attributes or the __attribute__ keyword + if ($line =~ /^\+(.*)\(\s*$Type\s*\)([ \t]++)((?![={]|\\$|$Attribute|__attribute__))/ && + (!defined($1) || $1 !~ /\b(?:sizeof|__alignof__)\s*$/)) { + if (CHK("SPACING", + "No space is necessary after a cast\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/(\(\s*$Type\s*\))[ \t]+/$1/; + } + } + +# Block comments use * on subsequent lines + if ($prevline =~ /$;[ \t]*$/ && #ends in comment + $prevrawline =~ /^\+.*?\/\*/ && #starting /* + $prevrawline !~ /\*\/[ \t]*$/ && #no trailing */ + $rawline =~ /^\+/ && #line is new + $rawline !~ /^\+[ \t]*\*/) { #no leading * + WARN("BLOCK_COMMENT_STYLE", + "Block comments use * on subsequent lines\n" . $hereprev); + } + +# Block comments use */ on trailing lines + if ($rawline !~ m@^\+[ \t]*\*/[ \t]*$@ && #trailing */ + $rawline !~ m@^\+.*/\*.*\*/[ \t]*$@ && #inline /*...*/ + $rawline !~ m@^\+.*\*{2,}/[ \t]*$@ && #trailing **/ + $rawline =~ m@^\+[ \t]*.+\*\/[ \t]*$@) { #non blank */ + WARN("BLOCK_COMMENT_STYLE", + "Block comments use a trailing */ on a separate line\n" . $herecurr); + } + +# Block comment * alignment + if ($prevline =~ /$;[ \t]*$/ && #ends in comment + $line =~ /^\+[ \t]*$;/ && #leading comment + $rawline =~ /^\+[ \t]*\*/ && #leading * + (($prevrawline =~ /^\+.*?\/\*/ && #leading /* + $prevrawline !~ /\*\/[ \t]*$/) || #no trailing */ + $prevrawline =~ /^\+[ \t]*\*/)) { #leading * + my $oldindent; + $prevrawline =~ m@^\+([ \t]*/?)\*@; + if (defined($1)) { + $oldindent = expand_tabs($1); + } else { + $prevrawline =~ m@^\+(.*/?)\*@; + $oldindent = expand_tabs($1); + } + $rawline =~ m@^\+([ \t]*)\*@; + my $newindent = $1; + $newindent = expand_tabs($newindent); + if (length($oldindent) ne length($newindent)) { + WARN("BLOCK_COMMENT_STYLE", + "Block comments should align the * on each line\n" . $hereprev); + } + } + +# check for missing blank lines after struct/union declarations +# with exceptions for various attributes and macros + if ($prevline =~ /^[\+ ]};?\s*$/ && + $line =~ /^\+/ && + !($line =~ /^\+\s*$/ || + $line =~ /^\+\s*(?:EXPORT_SYMBOL|early_param|ALLOW_ERROR_INJECTION)/ || + $line =~ /^\+\s*MODULE_/i || + $line =~ /^\+\s*\#\s*(?:end|elif|else)/ || + $line =~ /^\+[a-z_]*init/ || + $line =~ /^\+\s*(?:static\s+)?[A-Z_]*ATTR/ || + $line =~ /^\+\s*DECLARE/ || + $line =~ /^\+\s*builtin_[\w_]*driver/ || + $line =~ /^\+\s*__setup/)) { + if (CHK("LINE_SPACING", + "Please use a blank line after function/struct/union/enum declarations\n" . $hereprev) && + $fix) { + fix_insert_line($fixlinenr, "\+"); + } + } + +# check for multiple consecutive blank lines + if ($prevline =~ /^[\+ ]\s*$/ && + $line =~ /^\+\s*$/ && + $last_blank_line != ($linenr - 1)) { + if (CHK("LINE_SPACING", + "Please don't use multiple blank lines\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + + $last_blank_line = $linenr; + } + +# check for missing blank lines after declarations +# (declarations must have the same indentation and not be at the start of line) + if (($prevline =~ /\+(\s+)\S/) && $sline =~ /^\+$1\S/) { + # use temporaries + my $sl = $sline; + my $pl = $prevline; + # remove $Attribute/$Sparse uses to simplify comparisons + $sl =~ s/\b(?:$Attribute|$Sparse)\b//g; + $pl =~ s/\b(?:$Attribute|$Sparse)\b//g; + if (($pl =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || + # function pointer declarations + $pl =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ || + # foo bar; where foo is some local typedef or #define + $pl =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || + # known declaration macros + $pl =~ /^\+\s+$declaration_macros/) && + # for "else if" which can look like "$Ident $Ident" + !($pl =~ /^\+\s+$c90_Keywords\b/ || + # other possible extensions of declaration lines + $pl =~ /(?:$Compare|$Assignment|$Operators)\s*$/ || + # not starting a section or a macro "\" extended line + $pl =~ /(?:\{\s*|\\)$/) && + # looks like a declaration + !($sl =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || + # function pointer declarations + $sl =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ || + # foo bar; where foo is some local typedef or #define + $sl =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || + # known declaration macros + $sl =~ /^\+\s+$declaration_macros/ || + # start of struct or union or enum + $sl =~ /^\+\s+(?:static\s+)?(?:const\s+)?(?:union|struct|enum|typedef)\b/ || + # start or end of block or continuation of declaration + $sl =~ /^\+\s+(?:$|[\{\}\.\#\"\?\:\(\[])/ || + # bitfield continuation + $sl =~ /^\+\s+$Ident\s*:\s*\d+\s*[,;]/ || + # other possible extensions of declaration lines + $sl =~ /^\+\s+\(?\s*(?:$Compare|$Assignment|$Operators)/)) { + if (WARN("LINE_SPACING", + "Missing a blank line after declarations\n" . $hereprev) && + $fix) { + fix_insert_line($fixlinenr, "\+"); + } + } + } + +# check for spaces at the beginning of a line. +# Exceptions: +# 1) within comments +# 2) indented preprocessor commands +# 3) hanging labels + if ($rawline =~ /^\+ / && $line !~ /^\+ *(?:$;|#|$Ident:)/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (WARN("LEADING_SPACE", + "please, no spaces at the start of a line\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e; + } + } + +# check we are in a valid C source file if not then ignore this hunk + next if ($realfile !~ /\.(h|c)$/); + +# check for unusual line ending [ or ( + if ($line =~ /^\+.*([\[\(])\s*$/) { + CHK("OPEN_ENDED_LINE", + "Lines should not end with a '$1'\n" . $herecurr); + } + +# check if this appears to be the start function declaration, save the name + if ($sline =~ /^\+\{\s*$/ && + $prevline =~ /^\+(?:(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*)?($Ident)\(/) { + $context_function = $1; + } + +# check if this appears to be the end of function declaration + if ($sline =~ /^\+\}\s*$/) { + undef $context_function; + } + +# check indentation of any line with a bare else +# (but not if it is a multiple line "if (foo) return bar; else return baz;") +# if the previous line is a break or return and is indented 1 tab more... + if ($sline =~ /^\+([\t]+)(?:}[ \t]*)?else(?:[ \t]*{)?\s*$/) { + my $tabs = length($1) + 1; + if ($prevline =~ /^\+\t{$tabs,$tabs}break\b/ || + ($prevline =~ /^\+\t{$tabs,$tabs}return\b/ && + defined $lines[$linenr] && + $lines[$linenr] !~ /^[ \+]\t{$tabs,$tabs}return/)) { + WARN("UNNECESSARY_ELSE", + "else is not generally useful after a break or return\n" . $hereprev); + } + } + +# check indentation of a line with a break; +# if the previous line is a goto, return or break +# and is indented the same # of tabs + if ($sline =~ /^\+([\t]+)break\s*;\s*$/) { + my $tabs = $1; + if ($prevline =~ /^\+$tabs(goto|return|break)\b/) { + if (WARN("UNNECESSARY_BREAK", + "break is not useful after a $1\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + } + +# check for RCS/CVS revision markers + if ($rawline =~ /^\+.*\$(Revision|Log|Id)(?:\$|)/) { + WARN("CVS_KEYWORD", + "CVS style keyword markers, these will _not_ be updated\n". $herecurr); + } + +# check for old HOTPLUG __dev<foo> section markings + if ($line =~ /\b(__dev(init|exit)(data|const|))\b/) { + WARN("HOTPLUG_SECTION", + "Using $1 is unnecessary\n" . $herecurr); + } + +# Check for potential 'bare' types + my ($stat, $cond, $line_nr_next, $remain_next, $off_next, + $realline_next); +#print "LINE<$line>\n"; + if ($linenr > $suppress_statement && + $realcnt && $sline =~ /.\s*\S/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0); + $stat =~ s/\n./\n /g; + $cond =~ s/\n./\n /g; + +#print "linenr<$linenr> <$stat>\n"; + # If this statement has no statement boundaries within + # it there is no point in retrying a statement scan + # until we hit end of it. + my $frag = $stat; $frag =~ s/;+\s*$//; + if ($frag !~ /(?:{|;)/) { +#print "skip<$line_nr_next>\n"; + $suppress_statement = $line_nr_next; + } + + # Find the real next line. + $realline_next = $line_nr_next; + if (defined $realline_next && + (!defined $lines[$realline_next - 1] || + substr($lines[$realline_next - 1], $off_next) =~ /^\s*$/)) { + $realline_next++; + } + + my $s = $stat; + $s =~ s/{.*$//s; + + # Ignore goto labels. + if ($s =~ /$Ident:\*$/s) { + + # Ignore functions being called + } elsif ($s =~ /^.\s*$Ident\s*\(/s) { + + } elsif ($s =~ /^.\s*else\b/s) { + + # declarations always start with types + } elsif ($prev_values eq 'E' && $s =~ /^.\s*(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?((?:\s*$Ident)+?)\b(?:\s+$Sparse)?\s*\**\s*(?:$Ident|\(\*[^\)]*\))(?:\s*$Modifier)?\s*(?:;|=|,|\()/s) { + my $type = $1; + $type =~ s/\s+/ /g; + possible($type, "A:" . $s); + + # definitions in global scope can only start with types + } elsif ($s =~ /^.(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?($Ident)\b\s*(?!:)/s) { + possible($1, "B:" . $s); + } + + # any (foo ... *) is a pointer cast, and foo is a type + while ($s =~ /\(($Ident)(?:\s+$Sparse)*[\s\*]+\s*\)/sg) { + possible($1, "C:" . $s); + } + + # Check for any sort of function declaration. + # int foo(something bar, other baz); + # void (*store_gdt)(x86_descr_ptr *); + if ($prev_values eq 'E' && $s =~ /^(.(?:typedef\s*)?(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*(?:\b$Ident|\(\*\s*$Ident\))\s*)\(/s) { + my ($name_len) = length($1); + + my $ctx = $s; + substr($ctx, 0, $name_len + 1, ''); + $ctx =~ s/\)[^\)]*$//; + + for my $arg (split(/\s*,\s*/, $ctx)) { + if ($arg =~ /^(?:const\s+)?($Ident)(?:\s+$Sparse)*\s*\**\s*(:?\b$Ident)?$/s || $arg =~ /^($Ident)$/s) { + + possible($1, "D:" . $s); + } + } + } + + } + +# +# Checks which may be anchored in the context. +# + +# Check for switch () and associated case and default +# statements should be at the same indent. + if ($line=~/\bswitch\s*\(.*\)/) { + my $err = ''; + my $sep = ''; + my @ctx = ctx_block_outer($linenr, $realcnt); + shift(@ctx); + for my $ctx (@ctx) { + my ($clen, $cindent) = line_stats($ctx); + if ($ctx =~ /^\+\s*(case\s+|default:)/ && + $indent != $cindent) { + $err .= "$sep$ctx\n"; + $sep = ''; + } else { + $sep = "[...]\n"; + } + } + if ($err ne '') { + ERROR("SWITCH_CASE_INDENT_LEVEL", + "switch and case should be at the same indent\n$hereline$err"); + } + } + +# if/while/etc brace do not go on next line, unless defining a do while loop, +# or if that brace on the next line is for something else + if ($line =~ /(.*)\b((?:if|while|for|switch|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|do\b|else\b)/ && $line !~ /^.\s*\#/) { + my $pre_ctx = "$1$2"; + + my ($level, @ctx) = ctx_statement_level($linenr, $realcnt, 0); + + if ($line =~ /^\+\t{6,}/) { + WARN("DEEP_INDENTATION", + "Too many leading tabs - consider code refactoring\n" . $herecurr); + } + + my $ctx_cnt = $realcnt - $#ctx - 1; + my $ctx = join("\n", @ctx); + + my $ctx_ln = $linenr; + my $ctx_skip = $realcnt; + + while ($ctx_skip > $ctx_cnt || ($ctx_skip == $ctx_cnt && + defined $lines[$ctx_ln - 1] && + $lines[$ctx_ln - 1] =~ /^-/)) { + ##print "SKIP<$ctx_skip> CNT<$ctx_cnt>\n"; + $ctx_skip-- if (!defined $lines[$ctx_ln - 1] || $lines[$ctx_ln - 1] !~ /^-/); + $ctx_ln++; + } + + #print "realcnt<$realcnt> ctx_cnt<$ctx_cnt>\n"; + #print "pre<$pre_ctx>\nline<$line>\nctx<$ctx>\nnext<$lines[$ctx_ln - 1]>\n"; + + if ($ctx !~ /{\s*/ && defined($lines[$ctx_ln - 1]) && $lines[$ctx_ln - 1] =~ /^\+\s*{/) { + ERROR("OPEN_BRACE", + "that open brace { should be on the previous line\n" . + "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); + } + if ($level == 0 && $pre_ctx !~ /}\s*while\s*\($/ && + $ctx =~ /\)\s*\;\s*$/ && + defined $lines[$ctx_ln - 1]) + { + my ($nlength, $nindent) = line_stats($lines[$ctx_ln - 1]); + if ($nindent > $indent) { + WARN("TRAILING_SEMICOLON", + "trailing semicolon indicates no statements, indent implies otherwise\n" . + "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); + } + } + } + +# Check relative indent for conditionals and blocks. + if ($line =~ /\b(?:(?:if|while|for|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|(?:do|else)\b)/ && $line !~ /^.\s*#/ && $line !~ /\}\s*while\s*/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0) + if (!defined $stat); + my ($s, $c) = ($stat, $cond); + + substr($s, 0, length($c), ''); + + # remove inline comments + $s =~ s/$;/ /g; + $c =~ s/$;/ /g; + + # Find out how long the conditional actually is. + my @newlines = ($c =~ /\n/gs); + my $cond_lines = 1 + $#newlines; + + # Make sure we remove the line prefixes as we have + # none on the first line, and are going to readd them + # where necessary. + $s =~ s/\n./\n/gs; + while ($s =~ /\n\s+\\\n/) { + $cond_lines += $s =~ s/\n\s+\\\n/\n/g; + } + + # We want to check the first line inside the block + # starting at the end of the conditional, so remove: + # 1) any blank line termination + # 2) any opening brace { on end of the line + # 3) any do (...) { + my $continuation = 0; + my $check = 0; + $s =~ s/^.*\bdo\b//; + $s =~ s/^\s*{//; + if ($s =~ s/^\s*\\//) { + $continuation = 1; + } + if ($s =~ s/^\s*?\n//) { + $check = 1; + $cond_lines++; + } + + # Also ignore a loop construct at the end of a + # preprocessor statement. + if (($prevline =~ /^.\s*#\s*define\s/ || + $prevline =~ /\\\s*$/) && $continuation == 0) { + $check = 0; + } + + my $cond_ptr = -1; + $continuation = 0; + while ($cond_ptr != $cond_lines) { + $cond_ptr = $cond_lines; + + # If we see an #else/#elif then the code + # is not linear. + if ($s =~ /^\s*\#\s*(?:else|elif)/) { + $check = 0; + } + + # Ignore: + # 1) blank lines, they should be at 0, + # 2) preprocessor lines, and + # 3) labels. + if ($continuation || + $s =~ /^\s*?\n/ || + $s =~ /^\s*#\s*?/ || + $s =~ /^\s*$Ident\s*:/) { + $continuation = ($s =~ /^.*?\\\n/) ? 1 : 0; + if ($s =~ s/^.*?\n//) { + $cond_lines++; + } + } + } + + my (undef, $sindent) = line_stats("+" . $s); + my $stat_real = raw_line($linenr, $cond_lines); + + # Check if either of these lines are modified, else + # this is not this patch's fault. + if (!defined($stat_real) || + $stat !~ /^\+/ && $stat_real !~ /^\+/) { + $check = 0; + } + if (defined($stat_real) && $cond_lines > 1) { + $stat_real = "[...]\n$stat_real"; + } + + #print "line<$line> prevline<$prevline> indent<$indent> sindent<$sindent> check<$check> continuation<$continuation> s<$s> cond_lines<$cond_lines> stat_real<$stat_real> stat<$stat>\n"; + + if ($check && $s ne '' && + (($sindent % $tabsize) != 0 || + ($sindent < $indent) || + ($sindent == $indent && + ($s !~ /^\s*(?:\}|\{|else\b)/)) || + ($sindent > $indent + $tabsize))) { + WARN("SUSPECT_CODE_INDENT", + "suspect code indent for conditional statements ($indent, $sindent)\n" . $herecurr . "$stat_real\n"); + } + } + + # Track the 'values' across context and added lines. + my $opline = $line; $opline =~ s/^./ /; + my ($curr_values, $curr_vars) = + annotate_values($opline . "\n", $prev_values); + $curr_values = $prev_values . $curr_values; + if ($dbg_values) { + my $outline = $opline; $outline =~ s/\t/ /g; + print "$linenr > .$outline\n"; + print "$linenr > $curr_values\n"; + print "$linenr > $curr_vars\n"; + } + $prev_values = substr($curr_values, -1); + +#ignore lines not being added + next if ($line =~ /^[^\+]/); + +# check for self assignments used to avoid compiler warnings +# e.g.: int foo = foo, *bar = NULL; +# struct foo bar = *(&(bar)); + if ($line =~ /^\+\s*(?:$Declare)?([A-Za-z_][A-Za-z\d_]*)\s*=/) { + my $var = $1; + if ($line =~ /^\+\s*(?:$Declare)?$var\s*=\s*(?:$var|\*\s*\(?\s*&\s*\(?\s*$var\s*\)?\s*\)?)\s*[;,]/) { + WARN("SELF_ASSIGNMENT", + "Do not use self-assignments to avoid compiler warnings\n" . $herecurr); + } + } + +# check for dereferences that span multiple lines + if ($prevline =~ /^\+.*$Lval\s*(?:\.|->)\s*$/ && + $line =~ /^\+\s*(?!\#\s*(?!define\s+|if))\s*$Lval/) { + $prevline =~ /($Lval\s*(?:\.|->))\s*$/; + my $ref = $1; + $line =~ /^.\s*($Lval)/; + $ref .= $1; + $ref =~ s/\s//g; + WARN("MULTILINE_DEREFERENCE", + "Avoid multiple line dereference - prefer '$ref'\n" . $hereprev); + } + +# check for declarations of signed or unsigned without int + while ($line =~ m{\b($Declare)\s*(?!char\b|short\b|int\b|long\b)\s*($Ident)?\s*[=,;\[\)\(]}g) { + my $type = $1; + my $var = $2; + $var = "" if (!defined $var); + if ($type =~ /^(?:(?:$Storage|$Inline|$Attribute)\s+)*((?:un)?signed)((?:\s*\*)*)\s*$/) { + my $sign = $1; + my $pointer = $2; + + $pointer = "" if (!defined $pointer); + + if (WARN("UNSPECIFIED_INT", + "Prefer '" . trim($sign) . " int" . rtrim($pointer) . "' to bare use of '$sign" . rtrim($pointer) . "'\n" . $herecurr) && + $fix) { + my $decl = trim($sign) . " int "; + my $comp_pointer = $pointer; + $comp_pointer =~ s/\s//g; + $decl .= $comp_pointer; + $decl = rtrim($decl) if ($var eq ""); + $fixed[$fixlinenr] =~ s@\b$sign\s*\Q$pointer\E\s*$var\b@$decl$var@; + } + } + } + +# TEST: allow direct testing of the type matcher. + if ($dbg_type) { + if ($line =~ /^.\s*$Declare\s*$/) { + ERROR("TEST_TYPE", + "TEST: is type\n" . $herecurr); + } elsif ($dbg_type > 1 && $line =~ /^.+($Declare)/) { + ERROR("TEST_NOT_TYPE", + "TEST: is not type ($1 is)\n". $herecurr); + } + next; + } +# TEST: allow direct testing of the attribute matcher. + if ($dbg_attr) { + if ($line =~ /^.\s*$Modifier\s*$/) { + ERROR("TEST_ATTR", + "TEST: is attr\n" . $herecurr); + } elsif ($dbg_attr > 1 && $line =~ /^.+($Modifier)/) { + ERROR("TEST_NOT_ATTR", + "TEST: is not attr ($1 is)\n". $herecurr); + } + next; + } + +# check for initialisation to aggregates open brace on the next line + if ($line =~ /^.\s*{/ && + $prevline =~ /(?:^|[^=])=\s*$/) { + if (ERROR("OPEN_BRACE", + "that open brace { should be on the previous line\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/\s*=\s*$/ = {/; + fix_insert_line($fixlinenr, $fixedline); + $fixedline = $line; + $fixedline =~ s/^(.\s*)\{\s*/$1/; + fix_insert_line($fixlinenr, $fixedline); + } + } + +# +# Checks which are anchored on the added line. +# + +# check for malformed paths in #include statements (uses RAW line) + if ($rawline =~ m{^.\s*\#\s*include\s+[<"](.*)[">]}) { + my $path = $1; + if ($path =~ m{//}) { + ERROR("MALFORMED_INCLUDE", + "malformed #include filename\n" . $herecurr); + } + if ($path =~ "^uapi/" && $realfile =~ m@\binclude/uapi/@) { + ERROR("UAPI_INCLUDE", + "No #include in ...include/uapi/... should use a uapi/ path prefix\n" . $herecurr); + } + } + +# no C99 // comments + if ($line =~ m{//}) { + if (ERROR("C99_COMMENTS", + "do not use C99 // comments\n" . $herecurr) && + $fix) { + my $line = $fixed[$fixlinenr]; + if ($line =~ /\/\/(.*)$/) { + my $comment = trim($1); + $fixed[$fixlinenr] =~ s@\/\/(.*)$@/\* $comment \*/@; + } + } + } + # Remove C99 comments. + $line =~ s@//.*@@; + $opline =~ s@//.*@@; + +# EXPORT_SYMBOL should immediately follow the thing it is exporting, consider +# the whole statement. +#print "APW <$lines[$realline_next - 1]>\n"; + if (defined $realline_next && + exists $lines[$realline_next - 1] && + !defined $suppress_export{$realline_next} && + ($lines[$realline_next - 1] =~ /EXPORT_SYMBOL.*\((.*)\)/)) { + # Handle definitions which produce identifiers with + # a prefix: + # XXX(foo); + # EXPORT_SYMBOL(something_foo); + my $name = $1; + $name =~ s/^\s*($Ident).*/$1/; + if ($stat =~ /^(?:.\s*}\s*\n)?.([A-Z_]+)\s*\(\s*($Ident)/ && + $name =~ /^${Ident}_$2/) { +#print "FOO C name<$name>\n"; + $suppress_export{$realline_next} = 1; + + } elsif ($stat !~ /(?: + \n.}\s*$| + ^.DEFINE_$Ident\(\Q$name\E\)| + ^.DECLARE_$Ident\(\Q$name\E\)| + ^.LIST_HEAD\(\Q$name\E\)| + ^.(?:$Storage\s+)?$Type\s*\(\s*\*\s*\Q$name\E\s*\)\s*\(| + \b\Q$name\E(?:\s+$Attribute)*\s*(?:;|=|\[|\() + )/x) { +#print "FOO A<$lines[$realline_next - 1]> stat<$stat> name<$name>\n"; + $suppress_export{$realline_next} = 2; + } else { + $suppress_export{$realline_next} = 1; + } + } + if (!defined $suppress_export{$linenr} && + $prevline =~ /^.\s*$/ && + ($line =~ /EXPORT_SYMBOL.*\((.*)\)/)) { +#print "FOO B <$lines[$linenr - 1]>\n"; + $suppress_export{$linenr} = 2; + } + if (defined $suppress_export{$linenr} && + $suppress_export{$linenr} == 2) { + WARN("EXPORT_SYMBOL", + "EXPORT_SYMBOL(foo); should immediately follow its function/variable\n" . $herecurr); + } + +# check for global initialisers. + if ($line =~ /^\+$Type\s*$Ident(?:\s+$Modifier)*\s*=\s*($zero_initializer)\s*;/ && + !exclude_global_initialisers($realfile)) { + if (ERROR("GLOBAL_INITIALISERS", + "do not initialise globals to $1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^.$Type\s*$Ident(?:\s+$Modifier)*)\s*=\s*$zero_initializer\s*;/$1;/; + } + } +# check for static initialisers. + if ($line =~ /^\+.*\bstatic\s.*=\s*($zero_initializer)\s*;/) { + if (ERROR("INITIALISED_STATIC", + "do not initialise statics to $1\n" . + $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\bstatic\s.*?)\s*=\s*$zero_initializer\s*;/$1;/; + } + } + +# check for misordered declarations of char/short/int/long with signed/unsigned + while ($sline =~ m{(\b$TypeMisordered\b)}g) { + my $tmp = trim($1); + WARN("MISORDERED_TYPE", + "type '$tmp' should be specified in [[un]signed] [short|int|long|long long] order\n" . $herecurr); + } + +# check for unnecessary <signed> int declarations of short/long/long long + while ($sline =~ m{\b($TypeMisordered(\s*\*)*|$C90_int_types)\b}g) { + my $type = trim($1); + next if ($type !~ /\bint\b/); + next if ($type !~ /\b(?:short|long\s+long|long)\b/); + my $new_type = $type; + $new_type =~ s/\b\s*int\s*\b/ /; + $new_type =~ s/\b\s*(?:un)?signed\b\s*/ /; + $new_type =~ s/^const\s+//; + $new_type = "unsigned $new_type" if ($type =~ /\bunsigned\b/); + $new_type = "const $new_type" if ($type =~ /^const\b/); + $new_type =~ s/\s+/ /g; + $new_type = trim($new_type); + if (WARN("UNNECESSARY_INT", + "Prefer '$new_type' over '$type' as the int is unnecessary\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b\Q$type\E\b/$new_type/; + } + } + +# check for static const char * arrays. + if ($line =~ /\bstatic\s+const\s+char\s*\*\s*(\w+)\s*\[\s*\]\s*=\s*/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "static const char * array should probably be static const char * const\n" . + $herecurr); + } + +# check for initialized const char arrays that should be static const + if ($line =~ /^\+\s*const\s+(char|unsigned\s+char|_*u8|(?:[us]_)?int8_t)\s+\w+\s*\[\s*(?:\w+\s*)?\]\s*=\s*"/) { + if (WARN("STATIC_CONST_CHAR_ARRAY", + "const array should probably be static const\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^.\s*)const\b/${1}static const/; + } + } + +# check for static char foo[] = "bar" declarations. + if ($line =~ /\bstatic\s+char\s+(\w+)\s*\[\s*\]\s*=\s*"/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "static char array declaration should probably be static const char\n" . + $herecurr); + } + +# check for const <foo> const where <foo> is not a pointer or array type + if ($sline =~ /\bconst\s+($BasicType)\s+const\b/) { + my $found = $1; + if ($sline =~ /\bconst\s+\Q$found\E\s+const\b\s*\*/) { + WARN("CONST_CONST", + "'const $found const *' should probably be 'const $found * const'\n" . $herecurr); + } elsif ($sline !~ /\bconst\s+\Q$found\E\s+const\s+\w+\s*\[/) { + WARN("CONST_CONST", + "'const $found const' should probably be 'const $found'\n" . $herecurr); + } + } + +# check for const static or static <non ptr type> const declarations +# prefer 'static const <foo>' over 'const static <foo>' and 'static <foo> const' + if ($sline =~ /^\+\s*const\s+static\s+($Type)\b/ || + $sline =~ /^\+\s*static\s+($BasicType)\s+const\b/) { + if (WARN("STATIC_CONST", + "Move const after static - use 'static const $1'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bconst\s+static\b/static const/; + $fixed[$fixlinenr] =~ s/\bstatic\s+($BasicType)\s+const\b/static const $1/; + } + } + +# check for non-global char *foo[] = {"bar", ...} declarations. + if ($line =~ /^.\s+(?:static\s+|const\s+)?char\s+\*\s*\w+\s*\[\s*\]\s*=\s*\{/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "char * array declaration might be better as static const\n" . + $herecurr); + } + +# check for sizeof(foo)/sizeof(foo[0]) that could be ARRAY_SIZE(foo) + if ($line =~ m@\bsizeof\s*\(\s*($Lval)\s*\)@) { + my $array = $1; + if ($line =~ m@\b(sizeof\s*\(\s*\Q$array\E\s*\)\s*/\s*sizeof\s*\(\s*\Q$array\E\s*\[\s*0\s*\]\s*\))@) { + my $array_div = $1; + if (WARN("ARRAY_SIZE", + "Prefer ARRAY_SIZE($array)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$array_div\E/ARRAY_SIZE($array)/; + } + } + } + +# check for function declarations without arguments like "int foo()" + if ($line =~ /(\b$Type\s*$Ident)\s*\(\s*\)/) { + if (ERROR("FUNCTION_WITHOUT_ARGS", + "Bad function definition - $1() should probably be $1(void)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\b($Type)\s+($Ident))\s*\(\s*\)/$2 $3(void)/; + } + } + +# check for new typedefs, only function parameters and sparse annotations +# make sense. + if ($line =~ /\btypedef\s/ && + $line !~ /\btypedef\s+$Type\s*\(\s*\*?$Ident\s*\)\s*\(/ && + $line !~ /\btypedef\s+$Type\s+$Ident\s*\(/ && + $line !~ /\b$typeTypedefs\b/ && + $line !~ /\b__bitwise\b/) { + WARN("NEW_TYPEDEFS", + "do not add new typedefs\n" . $herecurr); + } + +# * goes on variable not on type + # (char*[ const]) + while ($line =~ m{(\($NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)\))}g) { + #print "AA<$1>\n"; + my ($ident, $from, $to) = ($1, $2, $2); + + # Should start with a space. + $to =~ s/^(\S)/ $1/; + # Should not end with a space. + $to =~ s/\s+$//; + # '*'s should not have spaces between. + while ($to =~ s/\*\s+\*/\*\*/) { + } + +## print "1: from<$from> to<$to> ident<$ident>\n"; + if ($from ne $to) { + if (ERROR("POINTER_LOCATION", + "\"(foo$from)\" should be \"(foo$to)\"\n" . $herecurr) && + $fix) { + my $sub_from = $ident; + my $sub_to = $ident; + $sub_to =~ s/\Q$from\E/$to/; + $fixed[$fixlinenr] =~ + s@\Q$sub_from\E@$sub_to@; + } + } + } + while ($line =~ m{(\b$NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)($Ident))}g) { + #print "BB<$1>\n"; + my ($match, $from, $to, $ident) = ($1, $2, $2, $3); + + # Should start with a space. + $to =~ s/^(\S)/ $1/; + # Should not end with a space. + $to =~ s/\s+$//; + # '*'s should not have spaces between. + while ($to =~ s/\*\s+\*/\*\*/) { + } + # Modifiers should have spaces. + $to =~ s/(\b$Modifier$)/$1 /; + +## print "2: from<$from> to<$to> ident<$ident>\n"; + if ($from ne $to && $ident !~ /^$Modifier$/) { + if (ERROR("POINTER_LOCATION", + "\"foo${from}bar\" should be \"foo${to}bar\"\n" . $herecurr) && + $fix) { + + my $sub_from = $match; + my $sub_to = $match; + $sub_to =~ s/\Q$from\E/$to/; + $fixed[$fixlinenr] =~ + s@\Q$sub_from\E@$sub_to@; + } + } + } + +# do not use BUG() or variants + if ($line =~ /\b(?!AA_|BUILD_|DCCP_|IDA_|KVM_|RWLOCK_|snd_|SPIN_)(?:[a-zA-Z_]*_)?BUG(?:_ON)?(?:_[A-Z_]+)?\s*\(/) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + &{$msg_level}("AVOID_BUG", + "Do not crash the kernel unless it is absolutely unavoidable--use WARN_ON_ONCE() plus recovery code (if feasible) instead of BUG() or variants\n" . $herecurr); + } + +# avoid LINUX_VERSION_CODE + if ($line =~ /\bLINUX_VERSION_CODE\b/) { + WARN("LINUX_VERSION_CODE", + "LINUX_VERSION_CODE should be avoided, code should be for the version to which it is merged\n" . $herecurr); + } + +# check for uses of printk_ratelimit + if ($line =~ /\bprintk_ratelimit\s*\(/) { + WARN("PRINTK_RATELIMITED", + "Prefer printk_ratelimited or pr_<level>_ratelimited to printk_ratelimit\n" . $herecurr); + } + +# printk should use KERN_* levels + if ($line =~ /\bprintk\s*\(\s*(?!KERN_[A-Z]+\b)/) { + WARN("PRINTK_WITHOUT_KERN_LEVEL", + "printk() should include KERN_<LEVEL> facility level\n" . $herecurr); + } + +# prefer variants of (subsystem|netdev|dev|pr)_<level> to printk(KERN_<LEVEL> + if ($line =~ /\b(printk(_once|_ratelimited)?)\s*\(\s*KERN_([A-Z]+)/) { + my $printk = $1; + my $modifier = $2; + my $orig = $3; + $modifier = "" if (!defined($modifier)); + my $level = lc($orig); + $level = "warn" if ($level eq "warning"); + my $level2 = $level; + $level2 = "dbg" if ($level eq "debug"); + $level .= $modifier; + $level2 .= $modifier; + WARN("PREFER_PR_LEVEL", + "Prefer [subsystem eg: netdev]_$level2([subsystem]dev, ... then dev_$level2(dev, ... then pr_$level(... to $printk(KERN_$orig ...\n" . $herecurr); + } + +# prefer dev_<level> to dev_printk(KERN_<LEVEL> + if ($line =~ /\bdev_printk\s*\(\s*KERN_([A-Z]+)/) { + my $orig = $1; + my $level = lc($orig); + $level = "warn" if ($level eq "warning"); + $level = "dbg" if ($level eq "debug"); + WARN("PREFER_DEV_LEVEL", + "Prefer dev_$level(... to dev_printk(KERN_$orig, ...\n" . $herecurr); + } + +# trace_printk should not be used in production code. + if ($line =~ /\b(trace_printk|trace_puts|ftrace_vprintk)\s*\(/) { + WARN("TRACE_PRINTK", + "Do not use $1() in production code (this can be ignored if built only with a debug config option)\n" . $herecurr); + } + +# ENOSYS means "bad syscall nr" and nothing else. This will have a small +# number of false positives, but assembly files are not checked, so at +# least the arch entry code will not trigger this warning. + if ($line =~ /\bENOSYS\b/) { + WARN("ENOSYS", + "ENOSYS means 'invalid syscall nr' and nothing else\n" . $herecurr); + } + +# ENOTSUPP is not a standard error code and should be avoided in new patches. +# Folks usually mean EOPNOTSUPP (also called ENOTSUP), when they type ENOTSUPP. +# Similarly to ENOSYS warning a small number of false positives is expected. + if (!$file && $line =~ /\bENOTSUPP\b/) { + if (WARN("ENOTSUPP", + "ENOTSUPP is not a SUSV4 error code, prefer EOPNOTSUPP\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bENOTSUPP\b/EOPNOTSUPP/; + } + } + +# function brace can't be on same line, except for #defines of do while, +# or if closed on same line + if ($perl_version_ok && + $sline =~ /$Type\s*$Ident\s*$balanced_parens\s*\{/ && + $sline !~ /\#\s*define\b.*do\s*\{/ && + $sline !~ /}/) { + if (ERROR("OPEN_BRACE", + "open brace '{' following function definitions go on the next line\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + my $fixed_line = $rawline; + $fixed_line =~ /(^..*$Type\s*$Ident\(.*\)\s*)\{(.*)$/; + my $line1 = $1; + my $line2 = $2; + fix_insert_line($fixlinenr, ltrim($line1)); + fix_insert_line($fixlinenr, "\+{"); + if ($line2 !~ /^\s*$/) { + fix_insert_line($fixlinenr, "\+\t" . trim($line2)); + } + } + } + +# open braces for enum, union and struct go on the same line. + if ($line =~ /^.\s*{/ && + $prevline =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident)?\s*$/) { + if (ERROR("OPEN_BRACE", + "open brace '{' following $1 go on the same line\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = rtrim($prevrawline) . " {"; + fix_insert_line($fixlinenr, $fixedline); + $fixedline = $rawline; + $fixedline =~ s/^(.\s*)\{\s*/$1\t/; + if ($fixedline !~ /^\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + } + } + +# missing space after union, struct or enum definition + if ($line =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident){1,2}[=\{]/) { + if (WARN("SPACING", + "missing space after $1 definition\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*(?:typedef\s+)?(?:enum|union|struct)(?:\s+$Ident){1,2})([=\{])/$1 $2/; + } + } + +# Function pointer declarations +# check spacing between type, funcptr, and args +# canonical declaration is "type (*funcptr)(args...)" + if ($line =~ /^.\s*($Declare)\((\s*)\*(\s*)($Ident)(\s*)\)(\s*)\(/) { + my $declare = $1; + my $pre_pointer_space = $2; + my $post_pointer_space = $3; + my $funcname = $4; + my $post_funcname_space = $5; + my $pre_args_space = $6; + +# the $Declare variable will capture all spaces after the type +# so check it for a missing trailing missing space but pointer return types +# don't need a space so don't warn for those. + my $post_declare_space = ""; + if ($declare =~ /(\s+)$/) { + $post_declare_space = $1; + $declare = rtrim($declare); + } + if ($declare !~ /\*$/ && $post_declare_space =~ /^$/) { + WARN("SPACING", + "missing space after return type\n" . $herecurr); + $post_declare_space = " "; + } + +# unnecessary space "type (*funcptr)(args...)" +# This test is not currently implemented because these declarations are +# equivalent to +# int foo(int bar, ...) +# and this is form shouldn't/doesn't generate a checkpatch warning. +# +# elsif ($declare =~ /\s{2,}$/) { +# WARN("SPACING", +# "Multiple spaces after return type\n" . $herecurr); +# } + +# unnecessary space "type ( *funcptr)(args...)" + if (defined $pre_pointer_space && + $pre_pointer_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space after function pointer open parenthesis\n" . $herecurr); + } + +# unnecessary space "type (* funcptr)(args...)" + if (defined $post_pointer_space && + $post_pointer_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space before function pointer name\n" . $herecurr); + } + +# unnecessary space "type (*funcptr )(args...)" + if (defined $post_funcname_space && + $post_funcname_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space after function pointer name\n" . $herecurr); + } + +# unnecessary space "type (*funcptr) (args...)" + if (defined $pre_args_space && + $pre_args_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space before function pointer arguments\n" . $herecurr); + } + + if (show_type("SPACING") && $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*)$Declare\s*\(\s*\*\s*$Ident\s*\)\s*\(/$1 . $declare . $post_declare_space . '(*' . $funcname . ')('/ex; + } + } + +# check for spacing round square brackets; allowed: +# 1. with a type on the left -- int [] a; +# 2. at the beginning of a line for slice initialisers -- [0...10] = 5, +# 3. inside a curly brace -- = { [0...10] = 5 } + while ($line =~ /(.*?\s)\[/g) { + my ($where, $prefix) = ($-[1], $1); + if ($prefix !~ /$Type\s+$/ && + ($where != 0 || $prefix !~ /^.\s+$/) && + $prefix !~ /[{,:]\s+$/) { + if (ERROR("BRACKET_SPACE", + "space prohibited before open square bracket '['\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(\+.*?)\s+\[/$1\[/; + } + } + } + +# check for spaces between functions and their parentheses. + while ($line =~ /($Ident)\s+\(/g) { + my $name = $1; + my $ctx_before = substr($line, 0, $-[1]); + my $ctx = "$ctx_before$name"; + + # Ignore those directives where spaces _are_ permitted. + if ($name =~ /^(?: + if|for|while|switch|return|case| + volatile|__volatile__| + __attribute__|format|__extension__| + asm|__asm__|scoped_guard)$/x) + { + # cpp #define statements have non-optional spaces, ie + # if there is a space between the name and the open + # parenthesis it is simply not a parameter group. + } elsif ($ctx_before =~ /^.\s*\#\s*define\s*$/) { + + # cpp #elif statement condition may start with a ( + } elsif ($ctx =~ /^.\s*\#\s*elif\s*$/) { + + # If this whole things ends with a type its most + # likely a typedef for a function. + } elsif ($ctx =~ /$Type$/) { + + } else { + if (WARN("SPACING", + "space prohibited between function name and open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\b$name\s+\(/$name\(/; + } + } + } + +# Check operator spacing. + if (!($line=~/\#\s*include/)) { + my $fixed_line = ""; + my $line_fixed = 0; + + my $ops = qr{ + <<=|>>=|<=|>=|==|!=| + \+=|-=|\*=|\/=|%=|\^=|\|=|&=| + =>|->|<<|>>|<|>|=|!|~| + &&|\|\||,|\^|\+\+|--|&|\||\+|-|\*|\/|%| + \?:|\?|: + }x; + my @elements = split(/($ops|;)/, $opline); + +## print("element count: <" . $#elements . ">\n"); +## foreach my $el (@elements) { +## print("el: <$el>\n"); +## } + + my @fix_elements = (); + my $off = 0; + + foreach my $el (@elements) { + push(@fix_elements, substr($rawline, $off, length($el))); + $off += length($el); + } + + $off = 0; + + my $blank = copy_spacing($opline); + my $last_after = -1; + + for (my $n = 0; $n < $#elements; $n += 2) { + + my $good = $fix_elements[$n] . $fix_elements[$n + 1]; + +## print("n: <$n> good: <$good>\n"); + + $off += length($elements[$n]); + + # Pick up the preceding and succeeding characters. + my $ca = substr($opline, 0, $off); + my $cc = ''; + if (length($opline) >= ($off + length($elements[$n + 1]))) { + $cc = substr($opline, $off + length($elements[$n + 1])); + } + my $cb = "$ca$;$cc"; + + my $a = ''; + $a = 'V' if ($elements[$n] ne ''); + $a = 'W' if ($elements[$n] =~ /\s$/); + $a = 'C' if ($elements[$n] =~ /$;$/); + $a = 'B' if ($elements[$n] =~ /(\[|\()$/); + $a = 'O' if ($elements[$n] eq ''); + $a = 'E' if ($ca =~ /^\s*$/); + + my $op = $elements[$n + 1]; + + my $c = ''; + if (defined $elements[$n + 2]) { + $c = 'V' if ($elements[$n + 2] ne ''); + $c = 'W' if ($elements[$n + 2] =~ /^\s/); + $c = 'C' if ($elements[$n + 2] =~ /^$;/); + $c = 'B' if ($elements[$n + 2] =~ /^(\)|\]|;)/); + $c = 'O' if ($elements[$n + 2] eq ''); + $c = 'E' if ($elements[$n + 2] =~ /^\s*\\$/); + } else { + $c = 'E'; + } + + my $ctx = "${a}x${c}"; + + my $at = "(ctx:$ctx)"; + + my $ptr = substr($blank, 0, $off) . "^"; + my $hereptr = "$hereline$ptr\n"; + + # Pull out the value of this operator. + my $op_type = substr($curr_values, $off + 1, 1); + + # Get the full operator variant. + my $opv = $op . substr($curr_vars, $off, 1); + + # Ignore operators passed as parameters. + if ($op_type ne 'V' && + $ca =~ /\s$/ && $cc =~ /^\s*[,\)]/) { + +# # Ignore comments +# } elsif ($op =~ /^$;+$/) { + + # ; should have either the end of line or a space or \ after it + } elsif ($op eq ';') { + if ($ctx !~ /.x[WEBC]/ && + $cc !~ /^\\/ && $cc !~ /^;/) { + if (ERROR("SPACING", + "space required after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " "; + $line_fixed = 1; + } + } + + # // is a comment + } elsif ($op eq '//') { + + # : when part of a bitfield + } elsif ($opv eq ':B') { + # skip the bitfield test for now + + # No spaces for: + # -> + } elsif ($op eq '->') { + if ($ctx =~ /Wx.|.xW/) { + if (ERROR("SPACING", + "spaces prohibited around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # , must not have a space before and must have a space on the right. + } elsif ($op eq ',') { + my $rtrim_before = 0; + my $space_after = 0; + if ($ctx =~ /Wx./) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $line_fixed = 1; + $rtrim_before = 1; + } + } + if ($ctx !~ /.x[WEC]/ && $cc !~ /^}/) { + if (ERROR("SPACING", + "space required after that '$op' $at\n" . $hereptr)) { + $line_fixed = 1; + $last_after = $n; + $space_after = 1; + } + } + if ($rtrim_before || $space_after) { + if ($rtrim_before) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + } else { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]); + } + if ($space_after) { + $good .= " "; + } + } + + # '*' as part of a type definition -- reported already. + } elsif ($opv eq '*_') { + #warn "'*' is part of type\n"; + + # unary operators should have a space before and + # none after. May be left adjacent to another + # unary operator, or a cast + } elsif ($op eq '!' || $op eq '~' || + $opv eq '*U' || $opv eq '-U' || + $opv eq '&U' || $opv eq '&&U') { + if ($ctx !~ /[WEBC]x./ && $ca !~ /(?:\)|!|~|\*|-|\&|\||\+\+|\-\-|\{)$/) { + if (ERROR("SPACING", + "space required before that '$op' $at\n" . $hereptr)) { + if ($n != $last_after + 2) { + $good = $fix_elements[$n] . " " . ltrim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + } + if ($op eq '*' && $cc =~/\s*$Modifier\b/) { + # A unary '*' may be const + + } elsif ($ctx =~ /.xW/) { + if (ERROR("SPACING", + "space prohibited after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . rtrim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # unary ++ and unary -- are allowed no space on one side. + } elsif ($op eq '++' or $op eq '--') { + if ($ctx !~ /[WEOBC]x[^W]/ && $ctx !~ /[^W]x[WOBEC]/) { + if (ERROR("SPACING", + "space required one side of that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " "; + $line_fixed = 1; + } + } + if ($ctx =~ /Wx[BE]/ || + ($ctx =~ /Wx./ && $cc =~ /^;/)) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + if ($ctx =~ /ExW/) { + if (ERROR("SPACING", + "space prohibited after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # << and >> may either have or not have spaces both sides + } elsif ($op eq '<<' or $op eq '>>' or + $op eq '&' or $op eq '^' or $op eq '|' or + $op eq '+' or $op eq '-' or + $op eq '*' or $op eq '/' or + $op eq '%') + { + if ($check) { + if (defined $fix_elements[$n + 2] && $ctx !~ /[EW]x[EW]/) { + if (CHK("SPACING", + "spaces preferred around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + $fix_elements[$n + 2] =~ s/^\s+//; + $line_fixed = 1; + } + } elsif (!defined $fix_elements[$n + 2] && $ctx !~ /Wx[OE]/) { + if (CHK("SPACING", + "space preferred before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + } elsif ($ctx =~ /Wx[^WCE]|[^WCE]xW/) { + if (ERROR("SPACING", + "need consistent spacing around '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # A colon needs no spaces before when it is + # terminating a case value or a label. + } elsif ($opv eq ':C' || $opv eq ':L') { + if ($ctx =~ /Wx./ and $realfile !~ m@.*\.lds\.h$@) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + + # All the others need spaces both sides. + } elsif ($ctx !~ /[EWC]x[CWE]/) { + my $ok = 0; + + # Ignore email addresses <foo@bar> + if (($op eq '<' && + $cc =~ /^\S+\@\S+>/) || + ($op eq '>' && + $ca =~ /<\S+\@\S+$/)) + { + $ok = 1; + } + + # for asm volatile statements + # ignore a colon with another + # colon immediately before or after + if (($op eq ':') && + ($ca =~ /:$/ || $cc =~ /^:/)) { + $ok = 1; + } + + # messages are ERROR, but ?: are CHK + if ($ok == 0) { + my $msg_level = \&ERROR; + $msg_level = \&CHK if (($op eq '?:' || $op eq '?' || $op eq ':') && $ctx =~ /VxV/); + + if (&{$msg_level}("SPACING", + "spaces required around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + } + $off += length($elements[$n + 1]); + +## print("n: <$n> GOOD: <$good>\n"); + + $fixed_line = $fixed_line . $good; + } + + if (($#elements % 2) == 0) { + $fixed_line = $fixed_line . $fix_elements[$#elements]; + } + + if ($fix && $line_fixed && $fixed_line ne $fixed[$fixlinenr]) { + $fixed[$fixlinenr] = $fixed_line; + } + + + } + +# check for whitespace before a non-naked semicolon + if ($line =~ /^\+.*\S\s+;\s*$/) { + if (WARN("SPACING", + "space prohibited before semicolon\n" . $herecurr) && + $fix) { + 1 while $fixed[$fixlinenr] =~ + s/^(\+.*\S)\s+;/$1;/; + } + } + +# check for multiple assignments + if ($line =~ /^.\s*$Lval\s*=\s*$Lval\s*=(?!=)/) { + CHK("MULTIPLE_ASSIGNMENTS", + "multiple assignments should be avoided\n" . $herecurr); + } + +## # check for multiple declarations, allowing for a function declaration +## # continuation. +## if ($line =~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Ident.*/ && +## $line !~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Type\s*$Ident.*/) { +## +## # Remove any bracketed sections to ensure we do not +## # falsely report the parameters of functions. +## my $ln = $line; +## while ($ln =~ s/\([^\(\)]*\)//g) { +## } +## if ($ln =~ /,/) { +## WARN("MULTIPLE_DECLARATION", +## "declaring multiple variables together should be avoided\n" . $herecurr); +## } +## } + +#need space before brace following if, while, etc + if (($line =~ /\(.*\)\{/ && $line !~ /\($Type\)\{/) || + $line =~ /\b(?:else|do)\{/) { + if (ERROR("SPACING", + "space required before the open brace '{'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+.*(?:do|else|\)))\{/$1 {/; + } + } + +## # check for blank lines before declarations +## if ($line =~ /^.\t+$Type\s+$Ident(?:\s*=.*)?;/ && +## $prevrawline =~ /^.\s*$/) { +## WARN("SPACING", +## "No blank lines before declarations\n" . $hereprev); +## } +## + +# closing brace should have a space following it when it has anything +# on the line + if ($line =~ /}(?!(?:,|;|\)|\}))\S/) { + if (ERROR("SPACING", + "space required after that close brace '}'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/}((?!(?:,|;|\)))\S)/} $1/; + } + } + +# check spacing on square brackets + if ($line =~ /\[\s/ && $line !~ /\[\s*$/) { + if (ERROR("SPACING", + "space prohibited after that open square bracket '['\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\[\s+/\[/; + } + } + if ($line =~ /\s\]/) { + if (ERROR("SPACING", + "space prohibited before that close square bracket ']'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\s+\]/\]/; + } + } + +# check spacing on parentheses + if ($line =~ /\(\s/ && $line !~ /\(\s*(?:\\)?$/ && + $line !~ /for\s*\(\s+;/) { + if (ERROR("SPACING", + "space prohibited after that open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\(\s+/\(/; + } + } + if ($line =~ /(\s+)\)/ && $line !~ /^.\s*\)/ && + $line !~ /for\s*\(.*;\s+\)/ && + $line !~ /:\s+\)/) { + if (ERROR("SPACING", + "space prohibited before that close parenthesis ')'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\s+\)/\)/; + } + } + +# check unnecessary parentheses around addressof/dereference single $Lvals +# ie: &(foo->bar) should be &foo->bar and *(foo->bar) should be *foo->bar + + while ($line =~ /(?:[^&]&\s*|\*)\(\s*($Ident\s*(?:$Member\s*)+)\s*\)/g) { + my $var = $1; + if (CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around $var\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\(\s*\Q$var\E\s*\)/$var/; + } + } + +# check for unnecessary parentheses around function pointer uses +# ie: (foo->bar)(); should be foo->bar(); +# but not "if (foo->bar) (" to avoid some false positives + if ($line =~ /(\bif\s*|)(\(\s*$Ident\s*(?:$Member\s*)+\))[ \t]*\(/ && $1 !~ /^if/) { + my $var = $2; + if (CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around function pointer $var\n" . $herecurr) && + $fix) { + my $var2 = deparenthesize($var); + $var2 =~ s/\s//g; + $fixed[$fixlinenr] =~ s/\Q$var\E/$var2/; + } + } + +# check for unnecessary parentheses around comparisons in if uses +# when !drivers/staging or command-line uses --strict + if (($realfile !~ m@^(?:drivers/staging/)@ || $check_orig) && + $perl_version_ok && defined($stat) && + $stat =~ /(^.\s*if\s*($balanced_parens))/) { + my $if_stat = $1; + my $test = substr($2, 1, -1); + my $herectx; + while ($test =~ /(?:^|[^\w\&\!\~])+\s*\(\s*([\&\!\~]?\s*$Lval\s*(?:$Compare\s*$FuncArg)?)\s*\)/g) { + my $match = $1; + # avoid parentheses around potential macro args + next if ($match =~ /^\s*\w+\s*$/); + if (!defined($herectx)) { + $herectx = $here . "\n"; + my $cnt = statement_rawlines($if_stat); + for (my $n = 0; $n < $cnt; $n++) { + my $rl = raw_line($linenr, $n); + $herectx .= $rl . "\n"; + last if $rl =~ /^[ \+].*\{/; + } + } + CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around '$match'\n" . $herectx); + } + } + +# check that goto labels aren't indented (allow a single space indentation) +# and ignore bitfield definitions like foo:1 +# Strictly, labels can have whitespace after the identifier and before the : +# but this is not allowed here as many ?: uses would appear to be labels + if ($sline =~ /^.\s+[A-Za-z_][A-Za-z\d_]*:(?!\s*\d+)/ && + $sline !~ /^. [A-Za-z\d_][A-Za-z\d_]*:/ && + $sline !~ /^.\s+default:/) { + if (WARN("INDENTED_LABEL", + "labels should not be indented\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.)\s+/$1/; + } + } + +# check if a statement with a comma should be two statements like: +# foo = bar(), /* comma should be semicolon */ +# bar = baz(); + if (defined($stat) && + $stat =~ /^\+\s*(?:$Lval\s*$Assignment\s*)?$FuncArg\s*,\s*(?:$Lval\s*$Assignment\s*)?$FuncArg\s*;\s*$/) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + WARN("SUSPECT_COMMA_SEMICOLON", + "Possible comma where semicolon could be used\n" . $herectx); + } + +# return is not a function + if (defined($stat) && $stat =~ /^.\s*return(\s*)\(/s) { + my $spacing = $1; + if ($perl_version_ok && + $stat =~ /^.\s*return\s*($balanced_parens)\s*;\s*$/) { + my $value = $1; + $value = deparenthesize($value); + if ($value =~ m/^\s*$FuncArg\s*(?:\?|$)/) { + ERROR("RETURN_PARENTHESES", + "return is not a function, parentheses are not required\n" . $herecurr); + } + } elsif ($spacing !~ /\s+/) { + ERROR("SPACING", + "space required before the open parenthesis '('\n" . $herecurr); + } + } + +# unnecessary return in a void function +# at end-of-function, with the previous line a single leading tab, then return; +# and the line before that not a goto label target like "out:" + if ($sline =~ /^[ \+]}\s*$/ && + $prevline =~ /^\+\treturn\s*;\s*$/ && + $linenr >= 3 && + $lines[$linenr - 3] =~ /^[ +]/ && + $lines[$linenr - 3] !~ /^[ +]\s*$Ident\s*:/) { + WARN("RETURN_VOID", + "void function return statements are not generally useful\n" . $hereprev); + } + +# if statements using unnecessary parentheses - ie: if ((foo == bar)) + if ($perl_version_ok && + $line =~ /\bif\s*((?:\(\s*){2,})/) { + my $openparens = $1; + my $count = $openparens =~ tr@\(@\(@; + my $msg = ""; + if ($line =~ /\bif\s*(?:\(\s*){$count,$count}$LvalOrFunc\s*($Compare)\s*$LvalOrFunc(?:\s*\)){$count,$count}/) { + my $comp = $4; #Not $1 because of $LvalOrFunc + $msg = " - maybe == should be = ?" if ($comp eq "=="); + WARN("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses$msg\n" . $herecurr); + } + } + +# comparisons with a constant or upper case identifier on the left +# avoid cases like "foo + BAR < baz" +# only fix matches surrounded by parentheses to avoid incorrect +# conversions like "FOO < baz() + 5" being "misfixed" to "baz() > FOO + 5" + if ($perl_version_ok && + $line =~ /^\+(.*)\b($Constant|[A-Z_][A-Z0-9_]*)\s*($Compare)\s*($LvalOrFunc)/) { + my $lead = $1; + my $const = $2; + my $comp = $3; + my $to = $4; + my $newcomp = $comp; + if ($lead !~ /(?:$Operators|\.)\s*$/ && + $to !~ /^(?:Constant|[A-Z_][A-Z0-9_]*)$/ && + WARN("CONSTANT_COMPARISON", + "Comparisons should place the constant on the right side of the test\n" . $herecurr) && + $fix) { + if ($comp eq "<") { + $newcomp = ">"; + } elsif ($comp eq "<=") { + $newcomp = ">="; + } elsif ($comp eq ">") { + $newcomp = "<"; + } elsif ($comp eq ">=") { + $newcomp = "<="; + } + $fixed[$fixlinenr] =~ s/\(\s*\Q$const\E\s*$Compare\s*\Q$to\E\s*\)/($to $newcomp $const)/; + } + } + +# Return of what appears to be an errno should normally be negative + if ($sline =~ /\breturn(?:\s*\(+\s*|\s+)(E[A-Z]+)(?:\s*\)+\s*|\s*)[;:,]/) { + my $name = $1; + if ($name ne 'EOF' && $name ne 'ERROR' && $name !~ /^EPOLL/) { + WARN("USE_NEGATIVE_ERRNO", + "return of an errno should typically be negative (ie: return -$1)\n" . $herecurr); + } + } + +# Need a space before open parenthesis after if, while etc + if ($line =~ /\b(if|while|for|switch)\(/) { + if (ERROR("SPACING", + "space required before the open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\b(if|while|for|switch)\(/$1 \(/; + } + } + +# Check for illegal assignment in if conditional -- and check for trailing +# statements after the conditional. + if ($line =~ /do\s*(?!{)/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0) + if (!defined $stat); + my ($stat_next) = ctx_statement_block($line_nr_next, + $remain_next, $off_next); + $stat_next =~ s/\n./\n /g; + ##print "stat<$stat> stat_next<$stat_next>\n"; + + if ($stat_next =~ /^\s*while\b/) { + # If the statement carries leading newlines, + # then count those as offsets. + my ($whitespace) = + ($stat_next =~ /^((?:\s*\n[+-])*\s*)/s); + my $offset = + statement_rawlines($whitespace) - 1; + + $suppress_whiletrailers{$line_nr_next + + $offset} = 1; + } + } + if (!defined $suppress_whiletrailers{$linenr} && + defined($stat) && defined($cond) && + $line =~ /\b(?:if|while|for)\s*\(/ && $line !~ /^.\s*#/) { + my ($s, $c) = ($stat, $cond); + my $fixed_assign_in_if = 0; + + if ($c =~ /\bif\s*\(.*[^<>!=]=[^=].*/s) { + if (ERROR("ASSIGN_IN_IF", + "do not use assignment in if condition\n" . $herecurr) && + $fix && $perl_version_ok) { + if ($rawline =~ /^\+(\s+)if\s*\(\s*(\!)?\s*\(\s*(($Lval)\s*=\s*$LvalOrFunc)\s*\)\s*(?:($Compare)\s*($FuncArg))?\s*\)\s*(\{)?\s*$/) { + my $space = $1; + my $not = $2; + my $statement = $3; + my $assigned = $4; + my $test = $8; + my $against = $9; + my $brace = $15; + fix_delete_line($fixlinenr, $rawline); + fix_insert_line($fixlinenr, "$space$statement;"); + my $newline = "${space}if ("; + $newline .= '!' if defined($not); + $newline .= '(' if (defined $not && defined($test) && defined($against)); + $newline .= "$assigned"; + $newline .= " $test $against" if (defined($test) && defined($against)); + $newline .= ')' if (defined $not && defined($test) && defined($against)); + $newline .= ')'; + $newline .= " {" if (defined($brace)); + fix_insert_line($fixlinenr + 1, $newline); + $fixed_assign_in_if = 1; + } + } + } + + # Find out what is on the end of the line after the + # conditional. + substr($s, 0, length($c), ''); + $s =~ s/\n.*//g; + $s =~ s/$;//g; # Remove any comments + if (length($c) && $s !~ /^\s*{?\s*\\*\s*$/ && + $c !~ /}\s*while\s*/) + { + # Find out how long the conditional actually is. + my @newlines = ($c =~ /\n/gs); + my $cond_lines = 1 + $#newlines; + my $stat_real = ''; + + $stat_real = raw_line($linenr, $cond_lines) + . "\n" if ($cond_lines); + if (defined($stat_real) && $cond_lines > 1) { + $stat_real = "[...]\n$stat_real"; + } + + if (ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr . $stat_real) && + !$fixed_assign_in_if && + $cond_lines == 0 && + $fix && $perl_version_ok && + $fixed[$fixlinenr] =~ /^\+(\s*)((?:if|while|for)\s*$balanced_parens)\s*(.*)$/) { + my $indent = $1; + my $test = $2; + my $rest = rtrim($4); + if ($rest =~ /;$/) { + $fixed[$fixlinenr] = "\+$indent$test"; + fix_insert_line($fixlinenr + 1, "$indent\t$rest"); + } + } + } + } + +# Check for bitwise tests written as boolean + if ($line =~ / + (?: + (?:\[|\(|\&\&|\|\|) + \s*0[xX][0-9]+\s* + (?:\&\&|\|\|) + | + (?:\&\&|\|\|) + \s*0[xX][0-9]+\s* + (?:\&\&|\|\||\)|\]) + )/x) + { + WARN("HEXADECIMAL_BOOLEAN_TEST", + "boolean test with hexadecimal, perhaps just 1 \& or \|?\n" . $herecurr); + } + +# if and else should not have general statements after it + if ($line =~ /^.\s*(?:}\s*)?else\b(.*)/) { + my $s = $1; + $s =~ s/$;//g; # Remove any comments + if ($s !~ /^\s*(?:\sif|(?:{|)\s*\\?\s*$)/) { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr); + } + } +# if should not continue a brace + if ($line =~ /}\s*if\b/) { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line (or did you mean 'else if'?)\n" . + $herecurr); + } +# case and default should not have general statements after them + if ($line =~ /^.\s*(?:case\s*.*|default\s*):/g && + $line !~ /\G(?: + (?:\s*$;*)(?:\s*{)?(?:\s*$;*)(?:\s*\\)?\s*$| + \s*return\s+ + )/xg) + { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr); + } + + # Check for }<nl>else {, these must be at the same + # indent level to be relevant to each other. + if ($prevline=~/}\s*$/ and $line=~/^.\s*else\s*/ && + $previndent == $indent) { + if (ERROR("ELSE_AFTER_BRACE", + "else should follow close brace '}'\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/}\s*$//; + if ($fixedline !~ /^\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + $fixedline = $rawline; + $fixedline =~ s/^(.\s*)else/$1} else/; + fix_insert_line($fixlinenr, $fixedline); + } + } + + if ($prevline=~/}\s*$/ and $line=~/^.\s*while\s*/ && + $previndent == $indent) { + my ($s, $c) = ctx_statement_block($linenr, $realcnt, 0); + + # Find out what is on the end of the line after the + # conditional. + substr($s, 0, length($c), ''); + $s =~ s/\n.*//g; + + if ($s =~ /^\s*;/) { + if (ERROR("WHILE_AFTER_BRACE", + "while should follow close brace '}'\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + my $trailing = $rawline; + $trailing =~ s/^\+//; + $trailing = trim($trailing); + $fixedline =~ s/}\s*$/} $trailing/; + fix_insert_line($fixlinenr, $fixedline); + } + } + } + +#Specific variable tests + while ($line =~ m{($Constant|$Lval)}g) { + my $var = $1; + +#CamelCase + if ($var !~ /^$Constant$/ && + $var =~ /[A-Z][a-z]|[a-z][A-Z]/ && +#Ignore some autogenerated defines and enum values + $var !~ /^(?:[A-Z]+_){1,5}[A-Z]{1,3}[a-z]/ && +#Ignore Page<foo> variants + $var !~ /^(?:Clear|Set|TestClear|TestSet|)Page[A-Z]/ && +#Ignore ETHTOOL_LINK_MODE_<foo> variants + $var !~ /^ETHTOOL_LINK_MODE_/ && +#Ignore SI style variants like nS, mV and dB +#(ie: max_uV, regulator_min_uA_show, RANGE_mA_VALUE) + $var !~ /^(?:[a-z0-9_]*|[A-Z0-9_]*)?_?[a-z][A-Z](?:_[a-z0-9_]+|_[A-Z0-9_]+)?$/ && +#Ignore some three character SI units explicitly, like MiB and KHz + $var !~ /^(?:[a-z_]*?)_?(?:[KMGT]iB|[KMGT]?Hz)(?:_[a-z_]+)?$/) { + while ($var =~ m{\b($Ident)}g) { + my $word = $1; + next if ($word !~ /[A-Z][a-z]|[a-z][A-Z]/); + if ($check) { + seed_camelcase_includes(); + if (!$file && !$camelcase_file_seeded) { + seed_camelcase_file($realfile); + $camelcase_file_seeded = 1; + } + } + if (!defined $camelcase{$word}) { + $camelcase{$word} = 1; + CHK("CAMELCASE", + "Avoid CamelCase: <$word>\n" . $herecurr); + } + } + } + } + +#no spaces allowed after \ in define + if ($line =~ /\#\s*define.*\\\s+$/) { + if (WARN("WHITESPACE_AFTER_LINE_CONTINUATION", + "Whitespace after \\ makes next lines useless\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+$//; + } + } + +# warn if <asm/foo.h> is #included and <linux/foo.h> is available and includes +# itself <asm/foo.h> (uses RAW line) + if ($tree && $rawline =~ m{^.\s*\#\s*include\s*\<asm\/(.*)\.h\>}) { + my $file = "$1.h"; + my $checkfile = "include/linux/$file"; + if (-f "$root/$checkfile" && + $realfile ne $checkfile && + $1 !~ /$allowed_asm_includes/) + { + my $asminclude = `grep -Ec "#include\\s+<asm/$file>" $root/$checkfile`; + if ($asminclude > 0) { + if ($realfile =~ m{^arch/}) { + CHK("ARCH_INCLUDE_LINUX", + "Consider using #include <linux/$file> instead of <asm/$file>\n" . $herecurr); + } else { + WARN("INCLUDE_LINUX", + "Use #include <linux/$file> instead of <asm/$file>\n" . $herecurr); + } + } + } + } + +# multi-statement macros should be enclosed in a do while loop, grab the +# first statement and ensure its the whole macro if its not enclosed +# in a known good container + if ($realfile !~ m@/vmlinux.lds.h$@ && + $line =~ /^.\s*\#\s*define\s*$Ident(\()?/) { + my $ln = $linenr; + my $cnt = $realcnt; + my ($off, $dstat, $dcond, $rest); + my $ctx = ''; + my $has_flow_statement = 0; + my $has_arg_concat = 0; + ($dstat, $dcond, $ln, $cnt, $off) = + ctx_statement_block($linenr, $realcnt, 0); + $ctx = $dstat; + #print "dstat<$dstat> dcond<$dcond> cnt<$cnt> off<$off>\n"; + #print "LINE<$lines[$ln-1]> len<" . length($lines[$ln-1]) . "\n"; + + $has_flow_statement = 1 if ($ctx =~ /\b(goto|return)\b/); + $has_arg_concat = 1 if ($ctx =~ /\#\#/ && $ctx !~ /\#\#\s*(?:__VA_ARGS__|args)\b/); + + $dstat =~ s/^.\s*\#\s*define\s+$Ident(\([^\)]*\))?\s*//; + my $define_args = $1; + my $define_stmt = $dstat; + my @def_args = (); + + if (defined $define_args && $define_args ne "") { + $define_args = substr($define_args, 1, length($define_args) - 2); + $define_args =~ s/\s*//g; + $define_args =~ s/\\\+?//g; + @def_args = split(",", $define_args); + } + + $dstat =~ s/$;//g; + $dstat =~ s/\\\n.//g; + $dstat =~ s/^\s*//s; + $dstat =~ s/\s*$//s; + + # Flatten any parentheses and braces + while ($dstat =~ s/\([^\(\)]*\)/1u/ || + $dstat =~ s/\{[^\{\}]*\}/1u/ || + $dstat =~ s/.\[[^\[\]]*\]/1u/) + { + } + + # Flatten any obvious string concatenation. + while ($dstat =~ s/($String)\s*$Ident/$1/ || + $dstat =~ s/$Ident\s*($String)/$1/) + { + } + + # Make asm volatile uses seem like a generic function + $dstat =~ s/\b_*asm_*\s+_*volatile_*\b/asm_volatile/g; + + my $exceptions = qr{ + $Declare| + module_param_named| + MODULE_PARM_DESC| + DECLARE_PER_CPU| + DEFINE_PER_CPU| + __typeof__\(| + union| + struct| + \.$Ident\s*=\s*| + ^\"|\"$| + ^\[ + }x; + #print "REST<$rest> dstat<$dstat> ctx<$ctx>\n"; + + $ctx =~ s/\n*$//; + my $stmt_cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $stmt_cnt, $here); + + if ($dstat ne '' && + $dstat !~ /^(?:$Ident|-?$Constant),$/ && # 10, // foo(), + $dstat !~ /^(?:$Ident|-?$Constant);$/ && # foo(); + $dstat !~ /^[!~-]?(?:$Lval|$Constant)$/ && # 10 // foo() // !foo // ~foo // -foo // foo->bar // foo.bar->baz + $dstat !~ /^'X'$/ && $dstat !~ /^'XX'$/ && # character constants + $dstat !~ /$exceptions/ && + $dstat !~ /^\.$Ident\s*=/ && # .foo = + $dstat !~ /^(?:\#\s*$Ident|\#\s*$Constant)\s*$/ && # stringification #foo + $dstat !~ /^case\b/ && # case ... + $dstat !~ /^do\s*$Constant\s*while\s*$Constant;?$/ && # do {...} while (...); // do {...} while (...) + $dstat !~ /^while\s*$Constant\s*$Constant\s*$/ && # while (...) {...} + $dstat !~ /^for\s*$Constant$/ && # for (...) + $dstat !~ /^for\s*$Constant\s+(?:$Ident|-?$Constant)$/ && # for (...) bar() + $dstat !~ /^do\s*{/ && # do {... + $dstat !~ /^\(\{/ && # ({... + $ctx !~ /^.\s*#\s*define\s+TRACE_(?:SYSTEM|INCLUDE_FILE|INCLUDE_PATH)\b/) + { + if ($dstat =~ /^\s*if\b/) { + ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", + "Macros starting with if should be enclosed by a do - while loop to avoid possible if/else logic defects\n" . "$herectx"); + } elsif ($dstat =~ /;/) { + ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", + "Macros with multiple statements should be enclosed in a do - while loop\n" . "$herectx"); + } else { + ERROR("COMPLEX_MACRO", + "Macros with complex values should be enclosed in parentheses\n" . "$herectx"); + } + + } + + # Make $define_stmt single line, comment-free, etc + my @stmt_array = split('\n', $define_stmt); + my $first = 1; + $define_stmt = ""; + foreach my $l (@stmt_array) { + $l =~ s/\\$//; + if ($first) { + $define_stmt = $l; + $first = 0; + } elsif ($l =~ /^[\+ ]/) { + $define_stmt .= substr($l, 1); + } + } + $define_stmt =~ s/$;//g; + $define_stmt =~ s/\s+/ /g; + $define_stmt = trim($define_stmt); + +# check if any macro arguments are reused (ignore '...' and 'type') + foreach my $arg (@def_args) { + next if ($arg =~ /\.\.\./); + next if ($arg =~ /^type$/i); + my $tmp_stmt = $define_stmt; + $tmp_stmt =~ s/\b(__must_be_array|offsetof|sizeof|sizeof_field|__stringify|typeof|__typeof__|__builtin\w+|typecheck\s*\(\s*$Type\s*,|\#+)\s*\(*\s*$arg\s*\)*\b//g; + $tmp_stmt =~ s/\#+\s*$arg\b//g; + $tmp_stmt =~ s/\b$arg\s*\#\#//g; + my $use_cnt = () = $tmp_stmt =~ /\b$arg\b/g; + if ($use_cnt > 1) { + CHK("MACRO_ARG_REUSE", + "Macro argument reuse '$arg' - possible side-effects?\n" . "$herectx"); + } +# check if any macro arguments may have other precedence issues + if ($tmp_stmt =~ m/($Operators)?\s*\b$arg\b\s*($Operators)?/m && + ((defined($1) && $1 ne ',') || + (defined($2) && $2 ne ','))) { + CHK("MACRO_ARG_PRECEDENCE", + "Macro argument '$arg' may be better as '($arg)' to avoid precedence issues\n" . "$herectx"); + } + +# check if this is an unused argument + if ($define_stmt !~ /\b$arg\b/) { + WARN("MACRO_ARG_UNUSED", + "Argument '$arg' is not used in function-like macro\n" . "$herectx"); + } + } + +# check for macros with flow control, but without ## concatenation +# ## concatenation is commonly a macro that defines a function so ignore those + if ($has_flow_statement && !$has_arg_concat) { + my $cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("MACRO_WITH_FLOW_CONTROL", + "Macros with flow control statements should be avoided\n" . "$herectx"); + } + +# check for line continuations outside of #defines, preprocessor #, and asm + + } elsif ($realfile =~ m@/vmlinux.lds.h$@) { + $line =~ s/(\w+)/$maybe_linker_symbol{$1}++/ge; + #print "REAL: $realfile\nln: $line\nkeys:", sort keys %maybe_linker_symbol; + } else { + if ($prevline !~ /^..*\\$/ && + $line !~ /^\+\s*\#.*\\$/ && # preprocessor + $line !~ /^\+.*\b(__asm__|asm)\b.*\\$/ && # asm + $line =~ /^\+.*\\$/) { + WARN("LINE_CONTINUATIONS", + "Avoid unnecessary line continuations\n" . $herecurr); + } + } + +# do {} while (0) macro tests: +# single-statement macros do not need to be enclosed in do while (0) loop, +# macro should not end with a semicolon + if ($perl_version_ok && + $realfile !~ m@/vmlinux.lds.h$@ && + $line =~ /^.\s*\#\s*define\s+$Ident(\()?/) { + my $ln = $linenr; + my $cnt = $realcnt; + my ($off, $dstat, $dcond, $rest); + my $ctx = ''; + ($dstat, $dcond, $ln, $cnt, $off) = + ctx_statement_block($linenr, $realcnt, 0); + $ctx = $dstat; + + $dstat =~ s/\\\n.//g; + $dstat =~ s/$;/ /g; + + if ($dstat =~ /^\+\s*#\s*define\s+$Ident\s*${balanced_parens}\s*do\s*{(.*)\s*}\s*while\s*\(\s*0\s*\)\s*([;\s]*)\s*$/) { + my $stmts = $2; + my $semis = $3; + + $ctx =~ s/\n*$//; + my $cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $cnt, $here); + + if (($stmts =~ tr/;/;/) == 1 && + $stmts !~ /^\s*(if|while|for|switch)\b/) { + WARN("SINGLE_STATEMENT_DO_WHILE_MACRO", + "Single statement macros should not use a do {} while (0) loop\n" . "$herectx"); + } + if (defined $semis && $semis ne "") { + WARN("DO_WHILE_MACRO_WITH_TRAILING_SEMICOLON", + "do {} while (0) macros should not be semicolon terminated\n" . "$herectx"); + } + } elsif ($dstat =~ /^\+\s*#\s*define\s+$Ident.*;\s*$/) { + $ctx =~ s/\n*$//; + my $cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("TRAILING_SEMICOLON", + "macros should not use a trailing semicolon\n" . "$herectx"); + } + } + +# check for redundant bracing round if etc + if ($line =~ /(^.*)\bif\b/ && $1 !~ /else\s*$/) { + my ($level, $endln, @chunks) = + ctx_statement_full($linenr, $realcnt, 1); + #print "chunks<$#chunks> linenr<$linenr> endln<$endln> level<$level>\n"; + #print "APW: <<$chunks[1][0]>><<$chunks[1][1]>>\n"; + if ($#chunks > 0 && $level == 0) { + my @allowed = (); + my $allow = 0; + my $seen = 0; + my $herectx = $here . "\n"; + my $ln = $linenr - 1; + for my $chunk (@chunks) { + my ($cond, $block) = @{$chunk}; + + # If the condition carries leading newlines, then count those as offsets. + my ($whitespace) = ($cond =~ /^((?:\s*\n[+-])*\s*)/s); + my $offset = statement_rawlines($whitespace) - 1; + + $allowed[$allow] = 0; + #print "COND<$cond> whitespace<$whitespace> offset<$offset>\n"; + + # We have looked at and allowed this specific line. + $suppress_ifbraces{$ln + $offset} = 1; + + $herectx .= "$rawlines[$ln + $offset]\n[...]\n"; + $ln += statement_rawlines($block) - 1; + + substr($block, 0, length($cond), ''); + + $seen++ if ($block =~ /^\s*{/); + + #print "cond<$cond> block<$block> allowed<$allowed[$allow]>\n"; + if (statement_lines($cond) > 1) { + #print "APW: ALLOWED: cond<$cond>\n"; + $allowed[$allow] = 1; + } + if ($block =~/\b(?:if|for|while)\b/) { + #print "APW: ALLOWED: block<$block>\n"; + $allowed[$allow] = 1; + } + if (statement_block_size($block) > 1) { + #print "APW: ALLOWED: lines block<$block>\n"; + $allowed[$allow] = 1; + } + $allow++; + } + if ($seen) { + my $sum_allowed = 0; + foreach (@allowed) { + $sum_allowed += $_; + } + if ($sum_allowed == 0) { + WARN("BRACES", + "braces {} are not necessary for any arm of this statement\n" . $herectx); + } elsif ($sum_allowed != $allow && + $seen != $allow) { + CHK("BRACES", + "braces {} should be used on all arms of this statement\n" . $herectx); + } + } + } + } + if (!defined $suppress_ifbraces{$linenr - 1} && + $line =~ /\b(if|while|for|else)\b/) { + my $allowed = 0; + + # Check the pre-context. + if (substr($line, 0, $-[0]) =~ /(\}\s*)$/) { + #print "APW: ALLOWED: pre<$1>\n"; + $allowed = 1; + } + + my ($level, $endln, @chunks) = + ctx_statement_full($linenr, $realcnt, $-[0]); + + # Check the condition. + my ($cond, $block) = @{$chunks[0]}; + #print "CHECKING<$linenr> cond<$cond> block<$block>\n"; + if (defined $cond) { + substr($block, 0, length($cond), ''); + } + if (statement_lines($cond) > 1) { + #print "APW: ALLOWED: cond<$cond>\n"; + $allowed = 1; + } + if ($block =~/\b(?:if|for|while)\b/) { + #print "APW: ALLOWED: block<$block>\n"; + $allowed = 1; + } + if (statement_block_size($block) > 1) { + #print "APW: ALLOWED: lines block<$block>\n"; + $allowed = 1; + } + # Check the post-context. + if (defined $chunks[1]) { + my ($cond, $block) = @{$chunks[1]}; + if (defined $cond) { + substr($block, 0, length($cond), ''); + } + if ($block =~ /^\s*\{/) { + #print "APW: ALLOWED: chunk-1 block<$block>\n"; + $allowed = 1; + } + } + if ($level == 0 && $block =~ /^\s*\{/ && !$allowed) { + my $cnt = statement_rawlines($block); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("BRACES", + "braces {} are not necessary for single statement blocks\n" . $herectx); + } + } + +# check for single line unbalanced braces + if ($sline =~ /^.\s*\}\s*else\s*$/ || + $sline =~ /^.\s*else\s*\{\s*$/) { + CHK("BRACES", "Unbalanced braces around else statement\n" . $herecurr); + } + +# check for unnecessary blank lines around braces + if (($line =~ /^.\s*}\s*$/ && $prevrawline =~ /^.\s*$/)) { + if (CHK("BRACES", + "Blank lines aren't necessary before a close brace '}'\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + } + } + if (($rawline =~ /^.\s*$/ && $prevline =~ /^..*{\s*$/)) { + if (CHK("BRACES", + "Blank lines aren't necessary after an open brace '{'\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + +# no volatiles please + my $asm_volatile = qr{\b(__asm__|asm)\s+(__volatile__|volatile)\b}; + if ($line =~ /\bvolatile\b/ && $line !~ /$asm_volatile/) { + WARN("VOLATILE", + "Use of volatile is usually wrong: see Documentation/process/volatile-considered-harmful.rst\n" . $herecurr); + } + +# Check for user-visible strings broken across lines, which breaks the ability +# to grep for the string. Make exceptions when the previous string ends in a +# newline (multiple lines in one string constant) or '\t', '\r', ';', or '{' +# (common in inline assembly) or is a octal \123 or hexadecimal \xaf value + if ($line =~ /^\+\s*$String/ && + $prevline =~ /"\s*$/ && + $prevrawline !~ /(?:\\(?:[ntr]|[0-7]{1,3}|x[0-9a-fA-F]{1,2})|;\s*|\{\s*)"\s*$/) { + if (WARN("SPLIT_STRING", + "quoted string split across lines\n" . $hereprev) && + $fix && + $prevrawline =~ /^\+.*"\s*$/ && + $last_coalesced_string_linenr != $linenr - 1) { + my $extracted_string = get_quoted_string($line, $rawline); + my $comma_close = ""; + if ($rawline =~ /\Q$extracted_string\E(\s*\)\s*;\s*$|\s*,\s*)/) { + $comma_close = $1; + } + + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/"\s*$//; + $fixedline .= substr($extracted_string, 1) . trim($comma_close); + fix_insert_line($fixlinenr - 1, $fixedline); + $fixedline = $rawline; + $fixedline =~ s/\Q$extracted_string\E\Q$comma_close\E//; + if ($fixedline !~ /\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + $last_coalesced_string_linenr = $linenr; + } + } + +# check for missing a space in a string concatenation + if ($prevrawline =~ /[^\\]\w"$/ && $rawline =~ /^\+[\t ]+"\w/) { + WARN('MISSING_SPACE', + "break quoted strings at a space character\n" . $hereprev); + } + +# check for an embedded function name in a string when the function is known +# This does not work very well for -f --file checking as it depends on patch +# context providing the function name or a single line form for in-file +# function declarations + if ($line =~ /^\+.*$String/ && + defined($context_function) && + get_quoted_string($line, $rawline) =~ /\b$context_function\b/ && + length(get_quoted_string($line, $rawline)) != (length($context_function) + 2)) { + WARN("EMBEDDED_FUNCTION_NAME", + "Prefer using '\"%s...\", __func__' to using '$context_function', this function's name, in a string\n" . $herecurr); + } + +# check for unnecessary function tracing like uses +# This does not use $logFunctions because there are many instances like +# 'dprintk(FOO, "%s()\n", __func__);' which do not match $logFunctions + if ($rawline =~ /^\+.*\([^"]*"$tracing_logging_tags{0,3}%s(?:\s*\(\s*\)\s*)?$tracing_logging_tags{0,3}(?:\\n)?"\s*,\s*__func__\s*\)\s*;/) { + if (WARN("TRACING_LOGGING", + "Unnecessary ftrace-like logging - prefer using ftrace\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + +# check for spaces before a quoted newline + if ($rawline =~ /^.*\".*\s\\n/) { + if (WARN("QUOTED_WHITESPACE_BEFORE_NEWLINE", + "unnecessary whitespace before a quoted newline\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+.*\".*)\s+\\n/$1\\n/; + } + + } + +# concatenated string without spaces between elements + if ($line =~ /$String[A-Z_]/ || + ($line =~ /([A-Za-z0-9_]+)$String/ && $1 !~ /^[Lu]$/)) { + if (CHK("CONCATENATED_STRING", + "Concatenated strings should use spaces between elements\n" . $herecurr) && + $fix) { + while ($line =~ /($String)/g) { + my $extracted_string = substr($rawline, $-[0], $+[0] - $-[0]); + $fixed[$fixlinenr] =~ s/\Q$extracted_string\E([A-Za-z0-9_])/$extracted_string $1/; + $fixed[$fixlinenr] =~ s/([A-Za-z0-9_])\Q$extracted_string\E/$1 $extracted_string/; + } + } + } + +# uncoalesced string fragments + if ($line =~ /$String\s*[Lu]?"/) { + if (WARN("STRING_FRAGMENTS", + "Consecutive strings are generally better as a single string\n" . $herecurr) && + $fix) { + while ($line =~ /($String)(?=\s*")/g) { + my $extracted_string = substr($rawline, $-[0], $+[0] - $-[0]); + $fixed[$fixlinenr] =~ s/\Q$extracted_string\E\s*"/substr($extracted_string, 0, -1)/e; + } + } + } + +# check for non-standard and hex prefixed decimal printf formats + my $show_L = 1; #don't show the same defect twice + my $show_Z = 1; + while ($line =~ /(?:^|")([X\t]*)(?:"|$)/g) { + my $string = substr($rawline, $-[1], $+[1] - $-[1]); + $string =~ s/%%/__/g; + # check for %L + if ($show_L && $string =~ /%[\*\d\.\$]*L([diouxX])/) { + WARN("PRINTF_L", + "\%L$1 is non-standard C, use %ll$1\n" . $herecurr); + $show_L = 0; + } + # check for %Z + if ($show_Z && $string =~ /%[\*\d\.\$]*Z([diouxX])/) { + WARN("PRINTF_Z", + "%Z$1 is non-standard C, use %z$1\n" . $herecurr); + $show_Z = 0; + } + # check for 0x<decimal> + if ($string =~ /0x%[\*\d\.\$\Llzth]*[diou]/) { + ERROR("PRINTF_0XDECIMAL", + "Prefixing 0x with decimal output is defective\n" . $herecurr); + } + } + +# check for line continuations in quoted strings with odd counts of " + if ($rawline =~ /\\$/ && $sline =~ tr/"/"/ % 2) { + WARN("LINE_CONTINUATIONS", + "Avoid line continuations in quoted strings\n" . $herecurr); + } + +# warn about #if 0 + if ($line =~ /^.\s*\#\s*if\s+0\b/) { + WARN("IF_0", + "Consider removing the code enclosed by this #if 0 and its #endif\n" . $herecurr); + } + +# warn about #if 1 + if ($line =~ /^.\s*\#\s*if\s+1\b/) { + WARN("IF_1", + "Consider removing the #if 1 and its #endif\n" . $herecurr); + } + +# check for needless "if (<foo>) fn(<foo>)" uses + if ($prevline =~ /\bif\s*\(\s*($Lval)\s*\)/) { + my $tested = quotemeta($1); + my $expr = '\s*\(\s*' . $tested . '\s*\)\s*;'; + if ($line =~ /\b(kfree|usb_free_urb|debugfs_remove(?:_recursive)?|(?:kmem_cache|mempool|dma_pool)_destroy)$expr/) { + my $func = $1; + if (WARN('NEEDLESS_IF', + "$func(NULL) is safe and this check is probably not required\n" . $hereprev) && + $fix) { + my $do_fix = 1; + my $leading_tabs = ""; + my $new_leading_tabs = ""; + if ($lines[$linenr - 2] =~ /^\+(\t*)if\s*\(\s*$tested\s*\)\s*$/) { + $leading_tabs = $1; + } else { + $do_fix = 0; + } + if ($lines[$linenr - 1] =~ /^\+(\t+)$func\s*\(\s*$tested\s*\)\s*;\s*$/) { + $new_leading_tabs = $1; + if (length($leading_tabs) + 1 ne length($new_leading_tabs)) { + $do_fix = 0; + } + } else { + $do_fix = 0; + } + if ($do_fix) { + fix_delete_line($fixlinenr - 1, $prevrawline); + $fixed[$fixlinenr] =~ s/^\+$new_leading_tabs/\+$leading_tabs/; + } + } + } + } + +# check for unnecessary "Out of Memory" messages + if ($line =~ /^\+.*\b$logFunctions\s*\(/ && + $prevline =~ /^[ \+]\s*if\s*\(\s*(\!\s*|NULL\s*==\s*)?($Lval)(\s*==\s*NULL\s*)?\s*\)/ && + (defined $1 || defined $3) && + $linenr > 3) { + my $testval = $2; + my $testline = $lines[$linenr - 3]; + + my ($s, $c) = ctx_statement_block($linenr - 3, $realcnt, 0); +# print("line: <$line>\nprevline: <$prevline>\ns: <$s>\nc: <$c>\n\n\n"); + + if ($s =~ /(?:^|\n)[ \+]\s*(?:$Type\s*)?\Q$testval\E\s*=\s*(?:\([^\)]*\)\s*)?\s*$allocFunctions\s*\(/ && + $s !~ /\b__GFP_NOWARN\b/ ) { + WARN("OOM_MESSAGE", + "Possible unnecessary 'out of memory' message\n" . $hereprev); + } + } + +# check for logging functions with KERN_<LEVEL> + if ($line !~ /printk(?:_ratelimited|_once)?\s*\(/ && + $line =~ /\b$logFunctions\s*\(.*\b(KERN_[A-Z]+)\b/) { + my $level = $1; + if (WARN("UNNECESSARY_KERN_LEVEL", + "Possible unnecessary $level\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s*$level\s*//; + } + } + +# check for logging continuations + if ($line =~ /\bprintk\s*\(\s*KERN_CONT\b|\bpr_cont\s*\(/) { + WARN("LOGGING_CONTINUATION", + "Avoid logging continuation uses where feasible\n" . $herecurr); + } + +# check for unnecessary use of %h[xudi] and %hh[xudi] in logging functions + if (defined $stat && + $line =~ /\b$logFunctions\s*\(/ && + index($stat, '"') >= 0) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + pos($stat_real) = index($stat_real, '"'); + while ($stat_real =~ /[^\"%]*(%[\#\d\.\*\-]*(h+)[idux])/g) { + my $pspec = $1; + my $h = $2; + my $lineoff = substr($stat_real, 0, $-[1]) =~ tr@\n@@; + if (WARN("UNNECESSARY_MODIFIER", + "Integer promotion: Using '$h' in '$pspec' is unnecessary\n" . "$here\n$stat_real\n") && + $fix && $fixed[$fixlinenr + $lineoff] =~ /^\+/) { + my $nspec = $pspec; + $nspec =~ s/h//g; + $fixed[$fixlinenr + $lineoff] =~ s/\Q$pspec\E/$nspec/; + } + } + } + +# check for mask then right shift without a parentheses + if ($perl_version_ok && + $line =~ /$LvalOrFunc\s*\&\s*($LvalOrFunc)\s*>>/ && + $4 !~ /^\&/) { # $LvalOrFunc may be &foo, ignore if so + WARN("MASK_THEN_SHIFT", + "Possible precedence defect with mask then right shift - may need parentheses\n" . $herecurr); + } + +# check for pointer comparisons to NULL + if ($perl_version_ok) { + while ($line =~ /\b$LvalOrFunc\s*(==|\!=)\s*NULL\b/g) { + my $val = $1; + my $equal = "!"; + $equal = "" if ($4 eq "!="); + if (CHK("COMPARISON_TO_NULL", + "Comparison to NULL could be written \"${equal}${val}\"\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b\Q$val\E\s*(?:==|\!=)\s*NULL\b/$equal$val/; + } + } + } + +# check for bad placement of section $InitAttribute (e.g.: __initdata) + if ($line =~ /(\b$InitAttribute\b)/) { + my $attr = $1; + if ($line =~ /^\+\s*static\s+(?:const\s+)?(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*[=;]/) { + my $ptr = $1; + my $var = $2; + if ((($ptr =~ /\b(union|struct)\s+$attr\b/ && + ERROR("MISPLACED_INIT", + "$attr should be placed after $var\n" . $herecurr)) || + ($ptr !~ /\b(union|struct)\s+$attr\b/ && + WARN("MISPLACED_INIT", + "$attr should be placed after $var\n" . $herecurr))) && + $fix) { + $fixed[$fixlinenr] =~ s/(\bstatic\s+(?:const\s+)?)(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*([=;])\s*/"$1" . trim(string_find_replace($2, "\\s*$attr\\s*", " ")) . " " . trim(string_find_replace($3, "\\s*$attr\\s*", "")) . " $attr" . ("$4" eq ";" ? ";" : " = ")/e; + } + } + } + +# check for $InitAttributeData (ie: __initdata) with const + if ($line =~ /\bconst\b/ && $line =~ /($InitAttributeData)/) { + my $attr = $1; + $attr =~ /($InitAttributePrefix)(.*)/; + my $attr_prefix = $1; + my $attr_type = $2; + if (ERROR("INIT_ATTRIBUTE", + "Use of const init definition must use ${attr_prefix}initconst\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/$InitAttributeData/${attr_prefix}initconst/; + } + } + +# check for $InitAttributeConst (ie: __initconst) without const + if ($line !~ /\bconst\b/ && $line =~ /($InitAttributeConst)/) { + my $attr = $1; + if (ERROR("INIT_ATTRIBUTE", + "Use of $attr requires a separate use of const\n" . $herecurr) && + $fix) { + my $lead = $fixed[$fixlinenr] =~ + /(^\+\s*(?:static\s+))/; + $lead = rtrim($1); + $lead = "$lead " if ($lead !~ /^\+$/); + $lead = "${lead}const "; + $fixed[$fixlinenr] =~ s/(^\+\s*(?:static\s+))/$lead/; + } + } + +# check for __read_mostly with const non-pointer (should just be const) + if ($line =~ /\b__read_mostly\b/ && + $line =~ /($Type)\s*$Ident/ && $1 !~ /\*\s*$/ && $1 =~ /\bconst\b/) { + if (ERROR("CONST_READ_MOSTLY", + "Invalid use of __read_mostly with const type\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+__read_mostly\b//; + } + } + +# don't use __constant_<foo> functions outside of include/uapi/ + if ($realfile !~ m@^include/uapi/@ && + $line =~ /(__constant_(?:htons|ntohs|[bl]e(?:16|32|64)_to_cpu|cpu_to_[bl]e(?:16|32|64)))\s*\(/) { + my $constant_func = $1; + my $func = $constant_func; + $func =~ s/^__constant_//; + if (WARN("CONSTANT_CONVERSION", + "$constant_func should be $func\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$constant_func\b/$func/g; + } + } + +# prefer usleep_range over udelay + if ($line =~ /\budelay\s*\(\s*(\d+)\s*\)/) { + my $delay = $1; + # ignore udelay's < 10, however + if (! ($delay < 10) ) { + CHK("USLEEP_RANGE", + "usleep_range is preferred over udelay; see function description of usleep_range() and udelay().\n" . $herecurr); + } + if ($delay > 2000) { + WARN("LONG_UDELAY", + "long udelay - prefer mdelay; see function description of mdelay().\n" . $herecurr); + } + } + +# warn about unexpectedly long msleep's + if ($line =~ /\bmsleep\s*\((\d+)\);/) { + if ($1 < 20) { + WARN("MSLEEP", + "msleep < 20ms can sleep for up to 20ms; see function description of msleep().\n" . $herecurr); + } + } + +# check for comparisons of jiffies + if ($line =~ /\bjiffies\s*$Compare|$Compare\s*jiffies\b/) { + WARN("JIFFIES_COMPARISON", + "Comparing jiffies is almost always wrong; prefer time_after, time_before and friends\n" . $herecurr); + } + +# check for comparisons of get_jiffies_64() + if ($line =~ /\bget_jiffies_64\s*\(\s*\)\s*$Compare|$Compare\s*get_jiffies_64\s*\(\s*\)/) { + WARN("JIFFIES_COMPARISON", + "Comparing get_jiffies_64() is almost always wrong; prefer time_after64, time_before64 and friends\n" . $herecurr); + } + +# warn about #ifdefs in C files +# if ($line =~ /^.\s*\#\s*if(|n)def/ && ($realfile =~ /\.c$/)) { +# print "#ifdef in C files should be avoided\n"; +# print "$herecurr"; +# $clean = 0; +# } + +# warn about spacing in #ifdefs + if ($line =~ /^.\s*\#\s*(ifdef|ifndef|elif)\s\s+/) { + if (ERROR("SPACING", + "exactly one space required after that #$1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*\#\s*(ifdef|ifndef|elif))\s{2,}/$1 /; + } + + } + +# check for spinlock_t definitions without a comment. + if ($line =~ /^.\s*(struct\s+mutex|spinlock_t)\s+\S+;/ || + $line =~ /^.\s*(DEFINE_MUTEX)\s*\(/) { + my $which = $1; + if (!ctx_has_comment($first_line, $linenr)) { + CHK("UNCOMMENTED_DEFINITION", + "$1 definition without comment\n" . $herecurr); + } + } +# check for memory barriers without a comment. + + my $barriers = qr{ + mb| + rmb| + wmb + }x; + my $barrier_stems = qr{ + mb__before_atomic| + mb__after_atomic| + store_release| + load_acquire| + store_mb| + (?:$barriers) + }x; + my $all_barriers = qr{ + (?:$barriers)| + smp_(?:$barrier_stems)| + virt_(?:$barrier_stems) + }x; + + if ($line =~ /\b(?:$all_barriers)\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("MEMORY_BARRIER", + "memory barrier without comment\n" . $herecurr); + } + } + + my $underscore_smp_barriers = qr{__smp_(?:$barrier_stems)}x; + + if ($realfile !~ m@^include/asm-generic/@ && + $realfile !~ m@/barrier\.h$@ && + $line =~ m/\b(?:$underscore_smp_barriers)\s*\(/ && + $line !~ m/^.\s*\#\s*define\s+(?:$underscore_smp_barriers)\s*\(/) { + WARN("MEMORY_BARRIER", + "__smp memory barriers shouldn't be used outside barrier.h and asm-generic\n" . $herecurr); + } + +# check for waitqueue_active without a comment. + if ($line =~ /\bwaitqueue_active\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("WAITQUEUE_ACTIVE", + "waitqueue_active without comment\n" . $herecurr); + } + } + +# check for data_race without a comment. + if ($line =~ /\bdata_race\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("DATA_RACE", + "data_race without comment\n" . $herecurr); + } + } + +# check of hardware specific defines + if ($line =~ m@^.\s*\#\s*if.*\b(__i386__|__powerpc64__|__sun__|__s390x__)\b@ && $realfile !~ m@include/asm-@) { + CHK("ARCH_DEFINES", + "architecture specific defines should be avoided\n" . $herecurr); + } + +# check that the storage class is not after a type + if ($line =~ /\b($Type)\s+($Storage)\b/) { + WARN("STORAGE_CLASS", + "storage class '$2' should be located before type '$1'\n" . $herecurr); + } +# Check that the storage class is at the beginning of a declaration + if ($line =~ /\b$Storage\b/ && + $line !~ /^.\s*$Storage/ && + $line =~ /^.\s*(.+?)\$Storage\s/ && + $1 !~ /[\,\)]\s*$/) { + WARN("STORAGE_CLASS", + "storage class should be at the beginning of the declaration\n" . $herecurr); + } + +# check the location of the inline attribute, that it is between +# storage class and type. + if ($line =~ /\b$Type\s+$Inline\b/ || + $line =~ /\b$Inline\s+$Storage\b/) { + ERROR("INLINE_LOCATION", + "inline keyword should sit between storage class and type\n" . $herecurr); + } + +# Check for __inline__ and __inline, prefer inline + if ($realfile !~ m@\binclude/uapi/@ && + $line =~ /\b(__inline__|__inline)\b/) { + if (WARN("INLINE", + "plain inline is preferred over $1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b(__inline__|__inline)\b/inline/; + + } + } + +# Check for compiler attributes + if ($realfile !~ m@\binclude/uapi/@ && + $rawline =~ /\b__attribute__\s*\(\s*($balanced_parens)\s*\)/) { + my $attr = $1; + $attr =~ s/\s*\(\s*(.*)\)\s*/$1/; + + my %attr_list = ( + "alias" => "__alias", + "aligned" => "__aligned", + "always_inline" => "__always_inline", + "assume_aligned" => "__assume_aligned", + "cold" => "__cold", + "const" => "__attribute_const__", + "copy" => "__copy", + "designated_init" => "__designated_init", + "externally_visible" => "__visible", + "format" => "printf|scanf", + "gnu_inline" => "__gnu_inline", + "malloc" => "__malloc", + "mode" => "__mode", + "no_caller_saved_registers" => "__no_caller_saved_registers", + "noclone" => "__noclone", + "noinline" => "noinline", + "nonstring" => "__nonstring", + "noreturn" => "__noreturn", + "packed" => "__packed", + "pure" => "__pure", + "section" => "__section", + "used" => "__used", + "weak" => "__weak" + ); + + while ($attr =~ /\s*(\w+)\s*(${balanced_parens})?/g) { + my $orig_attr = $1; + my $params = ''; + $params = $2 if defined($2); + my $curr_attr = $orig_attr; + $curr_attr =~ s/^[\s_]+|[\s_]+$//g; + if (exists($attr_list{$curr_attr})) { + my $new = $attr_list{$curr_attr}; + if ($curr_attr eq "format" && $params) { + $params =~ /^\s*\(\s*(\w+)\s*,\s*(.*)/; + $new = "__$1\($2"; + } else { + $new = "$new$params"; + } + if (WARN("PREFER_DEFINED_ATTRIBUTE_MACRO", + "Prefer $new over __attribute__(($orig_attr$params))\n" . $herecurr) && + $fix) { + my $remove = "\Q$orig_attr\E" . '\s*' . "\Q$params\E" . '(?:\s*,\s*)?'; + $fixed[$fixlinenr] =~ s/$remove//; + $fixed[$fixlinenr] =~ s/\b__attribute__/$new __attribute__/; + $fixed[$fixlinenr] =~ s/\}\Q$new\E/} $new/; + $fixed[$fixlinenr] =~ s/ __attribute__\s*\(\s*\(\s*\)\s*\)//; + } + } + } + + # Check for __attribute__ unused, prefer __always_unused or __maybe_unused + if ($attr =~ /^_*unused/) { + WARN("PREFER_DEFINED_ATTRIBUTE_MACRO", + "__always_unused or __maybe_unused is preferred over __attribute__((__unused__))\n" . $herecurr); + } + } + +# Check for __attribute__ weak, or __weak declarations (may have link issues) + if ($perl_version_ok && + $line =~ /(?:$Declare|$DeclareMisordered)\s*$Ident\s*$balanced_parens\s*(?:$Attribute)?\s*;/ && + ($line =~ /\b__attribute__\s*\(\s*\(.*\bweak\b/ || + $line =~ /\b__weak\b/)) { + ERROR("WEAK_DECLARATION", + "Using weak declarations can have unintended link defects\n" . $herecurr); + } + +# check for c99 types like uint8_t used outside of uapi/ and tools/ + if ($realfile !~ m@\binclude/uapi/@ && + $realfile !~ m@\btools/@ && + $line =~ /\b($Declare)\s*$Ident\s*[=;,\[]/) { + my $type = $1; + if ($type =~ /\b($typeC99Typedefs)\b/) { + $type = $1; + my $kernel_type = 'u'; + $kernel_type = 's' if ($type =~ /^_*[si]/); + $type =~ /(\d+)/; + $kernel_type .= $1; + if (CHK("PREFER_KERNEL_TYPES", + "Prefer kernel type '$kernel_type' over '$type'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$type\b/$kernel_type/; + } + } + } + +# check for cast of C90 native int or longer types constants + if ($line =~ /(\(\s*$C90_int_types\s*\)\s*)($Constant)\b/) { + my $cast = $1; + my $const = $2; + my $suffix = ""; + my $newconst = $const; + $newconst =~ s/${Int_type}$//; + $suffix .= 'U' if ($cast =~ /\bunsigned\b/); + if ($cast =~ /\blong\s+long\b/) { + $suffix .= 'LL'; + } elsif ($cast =~ /\blong\b/) { + $suffix .= 'L'; + } + if (WARN("TYPECAST_INT_CONSTANT", + "Unnecessary typecast of c90 int constant - '$cast$const' could be '$const$suffix'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$cast\E$const\b/$newconst$suffix/; + } + } + +# check for sizeof(&) + if ($line =~ /\bsizeof\s*\(\s*\&/) { + WARN("SIZEOF_ADDRESS", + "sizeof(& should be avoided\n" . $herecurr); + } + +# check for sizeof without parenthesis + if ($line =~ /\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/) { + if (WARN("SIZEOF_PARENTHESIS", + "sizeof $1 should be sizeof($1)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/"sizeof(" . trim($1) . ")"/ex; + } + } + +# check for struct spinlock declarations + if ($line =~ /^.\s*\bstruct\s+spinlock\s+\w+\s*;/) { + WARN("USE_SPINLOCK_T", + "struct spinlock should be spinlock_t\n" . $herecurr); + } + +# check for seq_printf uses that could be seq_puts + if ($sline =~ /\bseq_printf\s*\(.*"\s*\)\s*;\s*$/) { + my $fmt = get_quoted_string($line, $rawline); + $fmt =~ s/%%//g; + if ($fmt !~ /%/) { + if (WARN("PREFER_SEQ_PUTS", + "Prefer seq_puts to seq_printf\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bseq_printf\b/seq_puts/; + } + } + } + +# check for vsprintf extension %p<foo> misuses + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?![^\{]*\{\s*).*\b(\w+)\s*\(.*$String\s*,/s && + $1 !~ /^_*volatile_*$/) { + my $stat_real; + + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + for (my $count = $linenr; $count <= $lc; $count++) { + my $specifier; + my $extension; + my $qualifier; + my $bad_specifier = ""; + my $fmt = get_quoted_string($lines[$count - 1], raw_line($count, 0)); + $fmt =~ s/%%//g; + + while ($fmt =~ /(\%[\*\d\.]*p(\w)(\w*))/g) { + $specifier = $1; + $extension = $2; + $qualifier = $3; + if ($extension !~ /[4SsBKRraEehMmIiUDdgVCbGNOxtf]/ || + ($extension eq "f" && + defined $qualifier && $qualifier !~ /^w/) || + ($extension eq "4" && + defined $qualifier && $qualifier !~ /^cc/)) { + $bad_specifier = $specifier; + last; + } + if ($extension eq "x" && !defined($stat_real)) { + if (!defined($stat_real)) { + $stat_real = get_stat_real($linenr, $lc); + } + WARN("VSPRINTF_SPECIFIER_PX", + "Using vsprintf specifier '\%px' potentially exposes the kernel memory layout, if you don't really need the address please consider using '\%p'.\n" . "$here\n$stat_real\n"); + } + } + if ($bad_specifier ne "") { + my $stat_real = get_stat_real($linenr, $lc); + my $msg_level = \&WARN; + my $ext_type = "Invalid"; + my $use = ""; + if ($bad_specifier =~ /p[Ff]/) { + $use = " - use %pS instead"; + $use =~ s/pS/ps/ if ($bad_specifier =~ /pf/); + } elsif ($bad_specifier =~ /pA/) { + $use = " - '%pA' is only intended to be used from Rust code"; + $msg_level = \&ERROR; + } + + &{$msg_level}("VSPRINTF_POINTER_EXTENSION", + "$ext_type vsprintf pointer extension '$bad_specifier'$use\n" . "$here\n$stat_real\n"); + } + } + } + +# Check for misused memsets + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*$FuncArg\s*\)/) { + + my $ms_addr = $2; + my $ms_val = $7; + my $ms_size = $12; + + if ($ms_size =~ /^(0x|)0$/i) { + ERROR("MEMSET", + "memset to 0's uses 0 as the 2nd argument, not the 3rd\n" . "$here\n$stat\n"); + } elsif ($ms_size =~ /^(0x|)1$/i) { + WARN("MEMSET", + "single byte memset is suspicious. Swapped 2nd/3rd argument?\n" . "$here\n$stat\n"); + } + } + +# Check for memcpy(foo, bar, ETH_ALEN) that could be ether_addr_copy(foo, bar) +# if ($perl_version_ok && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# if (WARN("PREFER_ETHER_ADDR_COPY", +# "Prefer ether_addr_copy() over memcpy() if the Ethernet addresses are __aligned(2)\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/ether_addr_copy($2, $7)/; +# } +# } + +# Check for memcmp(foo, bar, ETH_ALEN) that could be ether_addr_equal*(foo, bar) +# if ($perl_version_ok && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemcmp\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# WARN("PREFER_ETHER_ADDR_EQUAL", +# "Prefer ether_addr_equal() or ether_addr_equal_unaligned() over memcmp()\n" . "$here\n$stat\n") +# } + +# check for memset(foo, 0x0, ETH_ALEN) that could be eth_zero_addr +# check for memset(foo, 0xFF, ETH_ALEN) that could be eth_broadcast_addr +# if ($perl_version_ok && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# +# my $ms_val = $7; +# +# if ($ms_val =~ /^(?:0x|)0+$/i) { +# if (WARN("PREFER_ETH_ZERO_ADDR", +# "Prefer eth_zero_addr over memset()\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_zero_addr($2)/; +# } +# } elsif ($ms_val =~ /^(?:0xff|255)$/i) { +# if (WARN("PREFER_ETH_BROADCAST_ADDR", +# "Prefer eth_broadcast_addr() over memset()\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_broadcast_addr($2)/; +# } +# } +# } + +# strcpy uses that should likely be strscpy + if ($line =~ /\bstrcpy\s*\(/) { + WARN("STRCPY", + "Prefer strscpy over strcpy - see: https://github.com/KSPP/linux/issues/88\n" . $herecurr); + } + +# strlcpy uses that should likely be strscpy + if ($line =~ /\bstrlcpy\s*\(/) { + WARN("STRLCPY", + "Prefer strscpy over strlcpy - see: https://github.com/KSPP/linux/issues/89\n" . $herecurr); + } + +# strncpy uses that should likely be strscpy or strscpy_pad + if ($line =~ /\bstrncpy\s*\(/) { + WARN("STRNCPY", + "Prefer strscpy, strscpy_pad, or __nonstring over strncpy - see: https://github.com/KSPP/linux/issues/90\n" . $herecurr); + } + +# ethtool_sprintf uses that should likely be ethtool_puts + if ($line =~ /\bethtool_sprintf\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) { + if (WARN("PREFER_ETHTOOL_PUTS", + "Prefer ethtool_puts over ethtool_sprintf with only two arguments\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bethtool_sprintf\s*\(\s*($FuncArg)\s*,\s*($FuncArg)/ethtool_puts($1, $7)/; + } + } + + # use $rawline because $line loses %s via sanitization and thus we can't match against it. + if ($rawline =~ /\bethtool_sprintf\s*\(\s*$FuncArg\s*,\s*\"\%s\"\s*,\s*$FuncArg\s*\)/) { + if (WARN("PREFER_ETHTOOL_PUTS", + "Prefer ethtool_puts over ethtool_sprintf with standalone \"%s\" specifier\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bethtool_sprintf\s*\(\s*($FuncArg)\s*,\s*"\%s"\s*,\s*($FuncArg)/ethtool_puts($1, $7)/; + } + } + + +# typecasts on min/max could be min_t/max_t + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?:.*?)\b(min|max)\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) { + if (defined $2 || defined $7) { + my $call = $1; + my $cast1 = deparenthesize($2); + my $arg1 = $3; + my $cast2 = deparenthesize($7); + my $arg2 = $8; + my $cast; + + if ($cast1 ne "" && $cast2 ne "" && $cast1 ne $cast2) { + $cast = "$cast1 or $cast2"; + } elsif ($cast1 ne "") { + $cast = $cast1; + } else { + $cast = $cast2; + } + WARN("MINMAX", + "$call() should probably be ${call}_t($cast, $arg1, $arg2)\n" . "$here\n$stat\n"); + } + } + +# check usleep_range arguments + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?:.*?)\busleep_range\s*\(\s*($FuncArg)\s*,\s*($FuncArg)\s*\)/) { + my $min = $1; + my $max = $7; + if ($min eq $max) { + WARN("USLEEP_RANGE", + "usleep_range should not use min == max args; see function description of usleep_range().\n" . "$here\n$stat\n"); + } elsif ($min =~ /^\d+$/ && $max =~ /^\d+$/ && + $min > $max) { + WARN("USLEEP_RANGE", + "usleep_range args reversed, use min then max; see function description of usleep_range().\n" . "$here\n$stat\n"); + } + } + +# check for naked sscanf + if ($perl_version_ok && + defined $stat && + $line =~ /\bsscanf\b/ && + ($stat !~ /$Ident\s*=\s*sscanf\s*$balanced_parens/ && + $stat !~ /\bsscanf\s*$balanced_parens\s*(?:$Compare)/ && + $stat !~ /(?:$Compare)\s*\bsscanf\s*$balanced_parens/)) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + WARN("NAKED_SSCANF", + "unchecked sscanf return value\n" . "$here\n$stat_real\n"); + } + +# check for simple sscanf that should be kstrto<foo> + if ($perl_version_ok && + defined $stat && + $line =~ /\bsscanf\b/) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + if ($stat_real =~ /\bsscanf\b\s*\(\s*$FuncArg\s*,\s*("[^"]+")/) { + my $format = $6; + my $count = $format =~ tr@%@%@; + if ($count == 1 && + $format =~ /^"\%(?i:ll[udxi]|[udxi]ll|ll|[hl]h?[udxi]|[udxi][hl]h?|[hl]h?|[udxi])"$/) { + WARN("SSCANF_TO_KSTRTO", + "Prefer kstrto<type> to single variable sscanf\n" . "$here\n$stat_real\n"); + } + } + } + +# check for new externs in .h files. + if ($realfile =~ /\.h$/ && + $line =~ /^\+\s*(extern\s+)$Type\s*$Ident\s*\(/s) { + if (CHK("AVOID_EXTERNS", + "extern prototypes should be avoided in .h files\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(.*)\bextern\b\s*(.*)/$1$2/; + } + } + +# check for new externs in .c files. + if ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^.\s*(?:extern\s+)?$Type\s+($Ident)(\s*)\(/s) + { + my $function_name = $1; + my $paren_space = $2; + + my $s = $stat; + if (defined $cond) { + substr($s, 0, length($cond), ''); + } + if ($s =~ /^\s*;/) + { + WARN("AVOID_EXTERNS", + "externs should be avoided in .c files\n" . $herecurr); + } + + if ($paren_space =~ /\n/) { + WARN("FUNCTION_ARGUMENTS", + "arguments for function declarations should follow identifier\n" . $herecurr); + } + + } elsif ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^\+extern struct\s+(\w+)\s+(\w+)\[\];/) + { + my ($st_type, $st_name) = ($1, $2); + + for my $s (keys %maybe_linker_symbol) { + #print "Linker symbol? $st_name : $s\n"; + goto LIKELY_LINKER_SYMBOL + if $st_name =~ /$s/; + } + WARN("AVOID_EXTERNS", + "found a file-scoped extern type:$st_type name:$st_name in .c file\n" + . "is this a linker symbol ?\n" . $herecurr); + LIKELY_LINKER_SYMBOL: + + } elsif ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^.\s*extern\s+/) + { + WARN("AVOID_EXTERNS", + "externs should be avoided in .c files\n" . $herecurr); + } + +# check for function declarations that have arguments without identifier names + if (defined $stat && + $stat =~ /^.\s*(?:extern\s+)?$Type\s*(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*\(\s*([^{]+)\s*\)\s*;/s && + $1 ne "void") { + my $args = trim($1); + while ($args =~ m/\s*($Type\s*(?:$Ident|\(\s*\*\s*$Ident?\s*\)\s*$balanced_parens)?)/g) { + my $arg = trim($1); + if ($arg =~ /^$Type$/ && $arg !~ /enum\s+$Ident$/) { + WARN("FUNCTION_ARGUMENTS", + "function definition argument '$arg' should also have an identifier name\n" . $herecurr); + } + } + } + +# check for function definitions + if ($perl_version_ok && + defined $stat && + $stat =~ /^.\s*(?:$Storage\s+)?$Type\s*($Ident)\s*$balanced_parens\s*{/s) { + $context_function = $1; + +# check for multiline function definition with misplaced open brace + my $ok = 0; + my $cnt = statement_rawlines($stat); + my $herectx = $here . "\n"; + for (my $n = 0; $n < $cnt; $n++) { + my $rl = raw_line($linenr, $n); + $herectx .= $rl . "\n"; + $ok = 1 if ($rl =~ /^[ \+]\{/); + $ok = 1 if ($rl =~ /\{/ && $n == 0); + last if $rl =~ /^[ \+].*\{/; + } + if (!$ok) { + ERROR("OPEN_BRACE", + "open brace '{' following function definitions go on the next line\n" . $herectx); + } + } + +# checks for new __setup's + if ($rawline =~ /\b__setup\("([^"]*)"/) { + my $name = $1; + + if (!grep(/$name/, @setup_docs)) { + CHK("UNDOCUMENTED_SETUP", + "__setup appears un-documented -- check Documentation/admin-guide/kernel-parameters.txt\n" . $herecurr); + } + } + +# check for pointless casting of alloc functions + if ($line =~ /\*\s*\)\s*$allocFunctions\b/) { + WARN("UNNECESSARY_CASTS", + "unnecessary cast may hide bugs, see http://c-faq.com/malloc/mallocnocast.html\n" . $herecurr); + } + +# alloc style +# p = alloc(sizeof(struct foo), ...) should be p = alloc(sizeof(*p), ...) + if ($perl_version_ok && + $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k|v)[mz]alloc(?:_node)?)\s*\(\s*(sizeof\s*\(\s*struct\s+$Lval\s*\))/) { + CHK("ALLOC_SIZEOF_STRUCT", + "Prefer $3(sizeof(*$1)...) over $3($4...)\n" . $herecurr); + } + +# check for (kv|k)[mz]alloc with multiplies that could be kmalloc_array/kvmalloc_array/kvcalloc/kcalloc + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+\s*($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k)[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)\s*,/) { + my $oldfunc = $3; + my $a1 = $4; + my $a2 = $10; + my $newfunc = "kmalloc_array"; + $newfunc = "kvmalloc_array" if ($oldfunc eq "kvmalloc"); + $newfunc = "kvcalloc" if ($oldfunc eq "kvzalloc"); + $newfunc = "kcalloc" if ($oldfunc eq "kzalloc"); + my $r1 = $a1; + my $r2 = $a2; + if ($a1 =~ /^sizeof\s*\S/) { + $r1 = $a2; + $r2 = $a1; + } + if ($r1 !~ /^sizeof\b/ && $r2 =~ /^sizeof\s*\S/ && + !($r1 =~ /^$Constant$/ || $r1 =~ /^[A-Z_][A-Z0-9_]*$/)) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + + if (WARN("ALLOC_WITH_MULTIPLY", + "Prefer $newfunc over $oldfunc with multiply\n" . $herectx) && + $cnt == 1 && + $fix) { + $fixed[$fixlinenr] =~ s/\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k)[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)/$1 . ' = ' . "$newfunc(" . trim($r1) . ', ' . trim($r2)/e; + } + } + } + +# check for krealloc arg reuse + if ($perl_version_ok && + $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*krealloc\s*\(\s*($Lval)\s*,/ && + $1 eq $3) { + WARN("KREALLOC_ARG_REUSE", + "Reusing the krealloc arg is almost always a bug\n" . $herecurr); + } + +# check for alloc argument mismatch + if ($line =~ /\b((?:devm_)?((?:k|kv)?(calloc|malloc_array)(?:_node)?))\s*\(\s*sizeof\b/) { + WARN("ALLOC_ARRAY_ARGS", + "$1 uses number as first arg, sizeof is generally wrong\n" . $herecurr); + } + +# check for multiple semicolons + if ($line =~ /;\s*;\s*$/) { + if (WARN("ONE_SEMICOLON", + "Statements terminations use 1 semicolon\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\s*;\s*){2,}$/;/g; + } + } + +# check for #defines like: 1 << <digit> that could be BIT(digit), it is not exported to uapi + if ($realfile !~ m@^include/uapi/@ && + $line =~ /#\s*define\s+\w+\s+\(?\s*1\s*([ulUL]*)\s*\<\<\s*(?:\d+|$Ident)\s*\)?/) { + my $ull = ""; + $ull = "_ULL" if (defined($1) && $1 =~ /ll/i); + if (CHK("BIT_MACRO", + "Prefer using the BIT$ull macro\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\(?\s*1\s*[ulUL]*\s*<<\s*(\d+|$Ident)\s*\)?/BIT${ull}($1)/; + } + } + +# check for IS_ENABLED() without CONFIG_<FOO> ($rawline for comments too) + if ($rawline =~ /\bIS_ENABLED\s*\(\s*(\w+)\s*\)/ && $1 !~ /^${CONFIG_}/) { + WARN("IS_ENABLED_CONFIG", + "IS_ENABLED($1) is normally used as IS_ENABLED(${CONFIG_}$1)\n" . $herecurr); + } + +# check for #if defined CONFIG_<FOO> || defined CONFIG_<FOO>_MODULE + if ($line =~ /^\+\s*#\s*if\s+defined(?:\s*\(?\s*|\s+)(${CONFIG_}[A-Z_]+)\s*\)?\s*\|\|\s*defined(?:\s*\(?\s*|\s+)\1_MODULE\s*\)?\s*$/) { + my $config = $1; + if (WARN("PREFER_IS_ENABLED", + "Prefer IS_ENABLED(<FOO>) to ${CONFIG_}<FOO> || ${CONFIG_}<FOO>_MODULE\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = "\+#if IS_ENABLED($config)"; + } + } + +# check for /* fallthrough */ like comment, prefer fallthrough; + my @fallthroughs = ( + 'fallthrough', + '@fallthrough@', + 'lint -fallthrough[ \t]*', + 'intentional(?:ly)?[ \t]*fall(?:(?:s | |-)[Tt]|t)hr(?:ough|u|ew)', + '(?:else,?\s*)?FALL(?:S | |-)?THR(?:OUGH|U|EW)[ \t.!]*(?:-[^\n\r]*)?', + 'Fall(?:(?:s | |-)[Tt]|t)hr(?:ough|u|ew)[ \t.!]*(?:-[^\n\r]*)?', + 'fall(?:s | |-)?thr(?:ough|u|ew)[ \t.!]*(?:-[^\n\r]*)?', + ); + if ($raw_comment ne '') { + foreach my $ft (@fallthroughs) { + if ($raw_comment =~ /$ft/) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + &{$msg_level}("PREFER_FALLTHROUGH", + "Prefer 'fallthrough;' over fallthrough comment\n" . $herecurr); + last; + } + } + } + +# check for switch/default statements without a break; + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+[$;\s]*(?:case[$;\s]+\w+[$;\s]*:[$;\s]*|)*[$;\s]*\bdefault[$;\s]*:[$;\s]*;/g) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("DEFAULT_NO_BREAK", + "switch default: should use break\n" . $herectx); + } + +# check for gcc specific __FUNCTION__ + if ($line =~ /\b__FUNCTION__\b/) { + if (WARN("USE_FUNC", + "__func__ should be used instead of gcc specific __FUNCTION__\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b__FUNCTION__\b/__func__/g; + } + } + +# check for uses of __DATE__, __TIME__, __TIMESTAMP__ + while ($line =~ /\b(__(?:DATE|TIME|TIMESTAMP)__)\b/g) { + ERROR("DATE_TIME", + "Use of the '$1' macro makes the build non-deterministic\n" . $herecurr); + } + +# check for use of yield() + if ($line =~ /\byield\s*\(\s*\)/) { + WARN("YIELD", + "Using yield() is generally wrong. See yield() kernel-doc (sched/core.c)\n" . $herecurr); + } + +# check for comparisons against true and false + if ($line =~ /\+\s*(.*?)\b(true|false|$Lval)\s*(==|\!=)\s*(true|false|$Lval)\b(.*)$/i) { + my $lead = $1; + my $arg = $2; + my $test = $3; + my $otype = $4; + my $trail = $5; + my $op = "!"; + + ($arg, $otype) = ($otype, $arg) if ($arg =~ /^(?:true|false)$/i); + + my $type = lc($otype); + if ($type =~ /^(?:true|false)$/) { + if (("$test" eq "==" && "$type" eq "true") || + ("$test" eq "!=" && "$type" eq "false")) { + $op = ""; + } + + CHK("BOOL_COMPARISON", + "Using comparison to $otype is error prone\n" . $herecurr); + +## maybe suggesting a correct construct would better +## "Using comparison to $otype is error prone. Perhaps use '${lead}${op}${arg}${trail}'\n" . $herecurr); + + } + } + +# check for semaphores initialized locked + if ($line =~ /^.\s*sema_init.+,\W?0\W?\)/) { + WARN("CONSIDER_COMPLETION", + "consider using a completion\n" . $herecurr); + } + +# recommend kstrto* over simple_strto* and strict_strto* + if ($line =~ /\b((simple|strict)_(strto(l|ll|ul|ull)))\s*\(/) { + WARN("CONSIDER_KSTRTO", + "$1 is obsolete, use k$3 instead\n" . $herecurr); + } + +# check for __initcall(), use device_initcall() explicitly or more appropriate function please + if ($line =~ /^.\s*__initcall\s*\(/) { + WARN("USE_DEVICE_INITCALL", + "please use device_initcall() or more appropriate function instead of __initcall() (see include/linux/init.h)\n" . $herecurr); + } + +# check for spin_is_locked(), suggest lockdep instead + if ($line =~ /\bspin_is_locked\(/) { + WARN("USE_LOCKDEP", + "Where possible, use lockdep_assert_held instead of assertions based on spin_is_locked\n" . $herecurr); + } + +# check for deprecated apis + if ($line =~ /\b($deprecated_apis_search)\b\s*\(/) { + my $deprecated_api = $1; + my $new_api = $deprecated_apis{$deprecated_api}; + WARN("DEPRECATED_API", + "Deprecated use of '$deprecated_api', prefer '$new_api' instead\n" . $herecurr); + } + +# check for various structs that are normally const (ops, kgdb, device_tree) +# and avoid what seem like struct definitions 'struct foo {' + if (defined($const_structs) && + $line !~ /\bconst\b/ && + $line =~ /\bstruct\s+($const_structs)\b(?!\s*\{)/) { + WARN("CONST_STRUCT", + "struct $1 should normally be const\n" . $herecurr); + } + +# use of NR_CPUS is usually wrong +# ignore definitions of NR_CPUS and usage to define arrays as likely right +# ignore designated initializers using NR_CPUS + if ($line =~ /\bNR_CPUS\b/ && + $line !~ /^.\s*\s*#\s*if\b.*\bNR_CPUS\b/ && + $line !~ /^.\s*\s*#\s*define\b.*\bNR_CPUS\b/ && + $line !~ /^.\s*$Declare\s.*\[[^\]]*NR_CPUS[^\]]*\]/ && + $line !~ /\[[^\]]*\.\.\.[^\]]*NR_CPUS[^\]]*\]/ && + $line !~ /\[[^\]]*NR_CPUS[^\]]*\.\.\.[^\]]*\]/ && + $line !~ /^.\s*\.\w+\s*=\s*.*\bNR_CPUS\b/) + { + WARN("NR_CPUS", + "usage of NR_CPUS is often wrong - consider using cpu_possible(), num_possible_cpus(), for_each_possible_cpu(), etc\n" . $herecurr); + } + +# Use of __ARCH_HAS_<FOO> or ARCH_HAVE_<BAR> is wrong. + if ($line =~ /\+\s*#\s*define\s+((?:__)?ARCH_(?:HAS|HAVE)\w*)\b/) { + ERROR("DEFINE_ARCH_HAS", + "#define of '$1' is wrong - use Kconfig variables or standard guards instead\n" . $herecurr); + } + +# likely/unlikely comparisons similar to "(likely(foo) > 0)" + if ($perl_version_ok && + $line =~ /\b((?:un)?likely)\s*\(\s*$FuncArg\s*\)\s*$Compare/) { + WARN("LIKELY_MISUSE", + "Using $1 should generally have parentheses around the comparison\n" . $herecurr); + } + +# return sysfs_emit(foo, fmt, ...) fmt without newline + if ($line =~ /\breturn\s+sysfs_emit\s*\(\s*$FuncArg\s*,\s*($String)/ && + substr($rawline, $-[6], $+[6] - $-[6]) !~ /\\n"$/) { + my $offset = $+[6] - 1; + if (WARN("SYSFS_EMIT", + "return sysfs_emit(...) formats should include a terminating newline\n" . $herecurr) && + $fix) { + substr($fixed[$fixlinenr], $offset, 0) = '\\n'; + } + } + +# check for array definition/declarations that should use flexible arrays instead + if ($sline =~ /^[\+ ]\s*\}(?:\s*__packed)?\s*;\s*$/ && + $prevline =~ /^\+\s*(?:\}(?:\s*__packed\s*)?|$Type)\s*$Ident\s*\[\s*(0|1)\s*\]\s*;\s*$/) { + if (ERROR("FLEXIBLE_ARRAY", + "Use C99 flexible arrays - see https://docs.kernel.org/process/deprecated.html#zero-length-and-one-element-arrays\n" . $hereprev) && + $1 == '0' && $fix) { + $fixed[$fixlinenr - 1] =~ s/\[\s*0\s*\]/[]/; + } + } + +# nested likely/unlikely calls + if ($line =~ /\b(?:(?:un)?likely)\s*\(\s*!?\s*(IS_ERR(?:_OR_NULL|_VALUE)?|WARN)/) { + WARN("LIKELY_MISUSE", + "nested (un)?likely() calls, $1 already uses unlikely() internally\n" . $herecurr); + } + +# whine mightly about in_atomic + if ($line =~ /\bin_atomic\s*\(/) { + if ($realfile =~ m@^drivers/@) { + ERROR("IN_ATOMIC", + "do not use in_atomic in drivers\n" . $herecurr); + } elsif ($realfile !~ m@^kernel/@) { + WARN("IN_ATOMIC", + "use of in_atomic() is incorrect outside core kernel code\n" . $herecurr); + } + } + +# Complain about RCU Tasks Trace used outside of BPF (and of course, RCU). + our $rcu_trace_funcs = qr{(?x: + rcu_read_lock_trace | + rcu_read_lock_trace_held | + rcu_read_unlock_trace | + call_rcu_tasks_trace | + synchronize_rcu_tasks_trace | + rcu_barrier_tasks_trace | + rcu_request_urgent_qs_task + )}; + our $rcu_trace_paths = qr{(?x: + kernel/bpf/ | + include/linux/bpf | + net/bpf/ | + kernel/rcu/ | + include/linux/rcu + )}; + if ($line =~ /\b($rcu_trace_funcs)\s*\(/) { + if ($realfile !~ m{^$rcu_trace_paths}) { + WARN("RCU_TASKS_TRACE", + "use of RCU tasks trace is incorrect outside BPF or core RCU code\n" . $herecurr); + } + } + +# check for lockdep_set_novalidate_class + if ($line =~ /^.\s*lockdep_set_novalidate_class\s*\(/ || + $line =~ /__lockdep_no_validate__\s*\)/ ) { + if ($realfile !~ m@^kernel/lockdep@ && + $realfile !~ m@^include/linux/lockdep@ && + $realfile !~ m@^drivers/base/core@) { + ERROR("LOCKDEP", + "lockdep_no_validate class is reserved for device->mutex.\n" . $herecurr); + } + } + + if ($line =~ /debugfs_create_\w+.*\b$mode_perms_world_writable\b/ || + $line =~ /DEVICE_ATTR.*\b$mode_perms_world_writable\b/) { + WARN("EXPORTED_WORLD_WRITABLE", + "Exporting world writable files is usually an error. Consider more restrictive permissions.\n" . $herecurr); + } + +# check for DEVICE_ATTR uses that could be DEVICE_ATTR_<FOO> +# and whether or not function naming is typical and if +# DEVICE_ATTR permissions uses are unusual too + if ($perl_version_ok && + defined $stat && + $stat =~ /\bDEVICE_ATTR\s*\(\s*(\w+)\s*,\s*\(?\s*(\s*(?:${multi_mode_perms_string_search}|0[0-7]{3,3})\s*)\s*\)?\s*,\s*(\w+)\s*,\s*(\w+)\s*\)/) { + my $var = $1; + my $perms = $2; + my $show = $3; + my $store = $4; + my $octal_perms = perms_to_octal($perms); + if ($show =~ /^${var}_show$/ && + $store =~ /^${var}_store$/ && + $octal_perms eq "0644") { + if (WARN("DEVICE_ATTR_RW", + "Use DEVICE_ATTR_RW\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*$show\s*,\s*$store\s*\)/DEVICE_ATTR_RW(${var})/; + } + } elsif ($show =~ /^${var}_show$/ && + $store =~ /^NULL$/ && + $octal_perms eq "0444") { + if (WARN("DEVICE_ATTR_RO", + "Use DEVICE_ATTR_RO\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*$show\s*,\s*NULL\s*\)/DEVICE_ATTR_RO(${var})/; + } + } elsif ($show =~ /^NULL$/ && + $store =~ /^${var}_store$/ && + $octal_perms eq "0200") { + if (WARN("DEVICE_ATTR_WO", + "Use DEVICE_ATTR_WO\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*NULL\s*,\s*$store\s*\)/DEVICE_ATTR_WO(${var})/; + } + } elsif ($octal_perms eq "0644" || + $octal_perms eq "0444" || + $octal_perms eq "0200") { + my $newshow = "$show"; + $newshow = "${var}_show" if ($show ne "NULL" && $show ne "${var}_show"); + my $newstore = $store; + $newstore = "${var}_store" if ($store ne "NULL" && $store ne "${var}_store"); + my $rename = ""; + if ($show ne $newshow) { + $rename .= " '$show' to '$newshow'"; + } + if ($store ne $newstore) { + $rename .= " '$store' to '$newstore'"; + } + WARN("DEVICE_ATTR_FUNCTIONS", + "Consider renaming function(s)$rename\n" . $herecurr); + } else { + WARN("DEVICE_ATTR_PERMS", + "DEVICE_ATTR unusual permissions '$perms' used\n" . $herecurr); + } + } + +# Mode permission misuses where it seems decimal should be octal +# This uses a shortcut match to avoid unnecessary uses of a slow foreach loop +# o Ignore module_param*(...) uses with a decimal 0 permission as that has a +# specific definition of not visible in sysfs. +# o Ignore proc_create*(...) uses with a decimal 0 permission as that means +# use the default permissions + if ($perl_version_ok && + defined $stat && + $line =~ /$mode_perms_search/) { + foreach my $entry (@mode_permission_funcs) { + my $func = $entry->[0]; + my $arg_pos = $entry->[1]; + + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + + my $skip_args = ""; + if ($arg_pos > 1) { + $arg_pos--; + $skip_args = "(?:\\s*$FuncArg\\s*,\\s*){$arg_pos,$arg_pos}"; + } + my $test = "\\b$func\\s*\\(${skip_args}($FuncArg(?:\\|\\s*$FuncArg)*)\\s*[,\\)]"; + if ($stat =~ /$test/) { + my $val = $1; + $val = $6 if ($skip_args ne ""); + if (!($func =~ /^(?:module_param|proc_create)/ && $val eq "0") && + (($val =~ /^$Int$/ && $val !~ /^$Octal$/) || + ($val =~ /^$Octal$/ && length($val) ne 4))) { + ERROR("NON_OCTAL_PERMISSIONS", + "Use 4 digit octal (0777) not decimal permissions\n" . "$here\n" . $stat_real); + } + if ($val =~ /^$Octal$/ && (oct($val) & 02)) { + ERROR("EXPORTED_WORLD_WRITABLE", + "Exporting writable files is usually an error. Consider more restrictive permissions.\n" . "$here\n" . $stat_real); + } + } + } + } + +# check for uses of S_<PERMS> that could be octal for readability + while ($line =~ m{\b($multi_mode_perms_string_search)\b}g) { + my $oval = $1; + my $octal = perms_to_octal($oval); + if (WARN("SYMBOLIC_PERMS", + "Symbolic permissions '$oval' are not preferred. Consider using octal permissions '$octal'.\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$oval\E/$octal/; + } + } + +# validate content of MODULE_LICENSE against list from include/linux/module.h + if ($line =~ /\bMODULE_LICENSE\s*\(\s*($String)\s*\)/) { + my $extracted_string = get_quoted_string($line, $rawline); + my $valid_licenses = qr{ + GPL| + GPL\ v2| + GPL\ and\ additional\ rights| + Dual\ BSD/GPL| + Dual\ MIT/GPL| + Dual\ MPL/GPL| + Proprietary + }x; + if ($extracted_string !~ /^"(?:$valid_licenses)"$/x) { + WARN("MODULE_LICENSE", + "unknown module license " . $extracted_string . "\n" . $herecurr); + } + if (!$file && $extracted_string eq '"GPL v2"') { + if (WARN("MODULE_LICENSE", + "Prefer \"GPL\" over \"GPL v2\" - see commit bf7fbeeae6db (\"module: Cure the MODULE_LICENSE \"GPL\" vs. \"GPL v2\" bogosity\")\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bMODULE_LICENSE\s*\(\s*"GPL v2"\s*\)/MODULE_LICENSE("GPL")/; + } + } + } + +# check for sysctl duplicate constants + if ($line =~ /\.extra[12]\s*=\s*&(zero|one|int_max)\b/) { + WARN("DUPLICATED_SYSCTL_CONST", + "duplicated sysctl range checking value '$1', consider using the shared one in include/linux/sysctl.h\n" . $herecurr); + } + } + + # If we have no input at all, then there is nothing to report on + # so just keep quiet. + if ($#rawlines == -1) { + exit(0); + } + + # In mailback mode only produce a report in the negative, for + # things that appear to be patches. + if ($mailback && ($clean == 1 || !$is_patch)) { + exit(0); + } + + # This is not a patch, and we are in 'no-patch' mode so + # just keep quiet. + if (!$chk_patch && !$is_patch) { + exit(0); + } + + if (!$is_patch && $filename !~ /cover-letter\.patch$/) { + ERROR("NOT_UNIFIED_DIFF", + "Does not appear to be a unified-diff format patch\n"); + } + if ($is_patch && $has_commit_log && $chk_fixes_tag) { + if ($needs_fixes_tag ne "" && !$is_revert && !$fixes_tag) { + WARN("MISSING_FIXES_TAG", + "The commit message has '$needs_fixes_tag', perhaps it also needs a 'Fixes:' tag?\n"); + } + } + if ($is_patch && $has_commit_log && $chk_signoff) { + if ($signoff == 0) { + ERROR("MISSING_SIGN_OFF", + "Missing Signed-off-by: line(s)\n"); + } elsif ($authorsignoff != 1) { + # authorsignoff values: + # 0 -> missing sign off + # 1 -> sign off identical + # 2 -> names and addresses match, comments mismatch + # 3 -> addresses match, names different + # 4 -> names match, addresses different + # 5 -> names match, addresses excluding subaddress details (refer RFC 5233) match + + my $sob_msg = "'From: $author' != 'Signed-off-by: $author_sob'"; + + if ($authorsignoff == 0) { + ERROR("NO_AUTHOR_SIGN_OFF", + "Missing Signed-off-by: line by nominal patch author '$author'\n"); + } elsif ($authorsignoff == 2) { + CHK("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email comments mismatch: $sob_msg\n"); + } elsif ($authorsignoff == 3) { + WARN("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email name mismatch: $sob_msg\n"); + } elsif ($authorsignoff == 4) { + WARN("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email address mismatch: $sob_msg\n"); + } elsif ($authorsignoff == 5) { + WARN("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email subaddress mismatch: $sob_msg\n"); + } + } + } + + print report_dump(); + if ($summary && !($clean == 1 && $quiet == 1)) { + print "$filename " if ($summary_file); + print "total: $cnt_error errors, $cnt_warn warnings, " . + (($check)? "$cnt_chk checks, " : "") . + "$cnt_lines lines checked\n"; + } + + if ($quiet == 0) { + # If there were any defects found and not already fixing them + if (!$clean and !$fix) { + print << "EOM" + +NOTE: For some of the reported defects, checkpatch may be able to + mechanically convert to the typical style using --fix or --fix-inplace. +EOM + } + # If there were whitespace errors which cleanpatch can fix + # then suggest that. + if ($rpt_cleaners) { + $rpt_cleaners = 0; + print << "EOM" + +NOTE: Whitespace errors detected. + You may wish to use scripts/cleanpatch or scripts/cleanfile +EOM + } + } + + if ($clean == 0 && $fix && + ("@rawlines" ne "@fixed" || + $#fixed_inserted >= 0 || $#fixed_deleted >= 0)) { + my $newfile = $filename; + $newfile .= ".EXPERIMENTAL-checkpatch-fixes" if (!$fix_inplace); + my $linecount = 0; + my $f; + + @fixed = fix_inserted_deleted_lines(\@fixed, \@fixed_inserted, \@fixed_deleted); + + open($f, '>', $newfile) + or die "$P: Can't open $newfile for write\n"; + foreach my $fixed_line (@fixed) { + $linecount++; + if ($file) { + if ($linecount > 3) { + $fixed_line =~ s/^\+//; + print $f $fixed_line . "\n"; + } + } else { + print $f $fixed_line . "\n"; + } + } + close($f); + + if (!$quiet) { + print << "EOM"; + +Wrote EXPERIMENTAL --fix correction(s) to '$newfile' + +Do _NOT_ trust the results written to this file. +Do _NOT_ submit these changes without inspecting them for correctness. + +This EXPERIMENTAL file is simply a convenience to help rewrite patches. +No warranties, expressed or implied... +EOM + } + } + + if ($quiet == 0) { + print "\n"; + if ($clean == 1) { + print "$vname has no obvious style problems and is ready for submission.\n"; + } else { + print "$vname has style problems, please review.\n"; + } + } + return $clean; +} diff --git a/dev-docs/extend-authors.sh b/dev-docs/extend-authors.sh new file mode 100755 index 0000000..30e7dff --- /dev/null +++ b/dev-docs/extend-authors.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Check if a starting tag is provided +if [ $# -eq 0 ]; then + echo "Usage: $0 <starting_tag>" + echo "Example: $0 fuse-3.16.2" + exit 1 +fi + +START_TAG=$1 + +# Extract email addresses from git log +git_emails=$(git log ${START_TAG}..HEAD --format='<%aE>' | sort -u | sed 's/^<//;s/>$//') + +# Extract email addresses from AUTHORS file +authors_emails=$(grep -oP '(?<=<)[^>]+' AUTHORS | sort -u) + +# Find new email addresses (in git_emails but not in authors_emails) +# -1 suppresses lines unique to AUTHORS, -3 suppresses lines common to both +# Result: only lines unique to git_emails (i.e., new authors) +new_emails=$(comm -1 -3 <(echo "$authors_emails") <(echo "$git_emails")) + +# If there are new email addresses, add corresponding authors to the AUTHORS file +if [ -n "$new_emails" ]; then + echo -e "\nNew authors to be added:" + echo -e "\n# New authors since ${START_TAG}" >> AUTHORS + for email in $new_emails; do + author=$(git log -1 --format='%aN <%aE>' --author="$email") + echo "$author" + echo "$author" >> AUTHORS + done + echo "AUTHORS file has been updated." +else + echo "No new authors found since ${START_TAG}." +fi diff --git a/dev-docs/release-process.md b/dev-docs/release-process.md new file mode 100644 index 0000000..fe4c225 --- /dev/null +++ b/dev-docs/release-process.md @@ -0,0 +1,61 @@ +Release Process +=============== + +* `set TAG fuse-A.B.C` +* Update version in + * `ChangeLog.rst` + * `meson.build` + * `include/fuse_common.h` (`#define FUSE_{MINOR/MAJOR}_VERSION`) +* When creating new minor release: + * Create signing key for the next release: `P=fuse-<A.B+1> signify-openbsd -G -n -p signify/$P.pub -s + signify/$P.sec; git add signify/$P.pub` + * Expire old release signing keys (keep one around just in case) +* To update authors run : dev-docs/extend-authors.sh +* `git commit --all -m "Released $TAG"` +* `git tag $TAG` +* Build tarball, `./make_release_tarball.sh` +* Test build: + * `cd fuse-x.y.z` + * `md build && (cd build && meson .. && ninja)` + * `sudo sudo chown root:root build/util/fusermount3` + * `sudo chmod 4755 build/util/fusermount3` + * `(cd build; python3 -m pytest test/)` +* Upload API docs: + * `rm -r ../libfuse.github.io/doxygen && cp -a doc/html ../libfuse.github.io/doxygen` + * `git -C ../libfuse.github.io add doxygen/` + * `git -C ../libfuse.github.io commit --all -m "Re-generated doxygen documentation"` + * `git -C ../libfuse.github.io push` +* `git checkout master && git push && git push --tags` +* Create release on Github +* Write announcement to fuse-devel + + +Announcement email template + +``` +To: fuse-devel@lists.sourceforge.net +Subject: [ANNOUNCE] libfuse XXXX has been released + +Dear all, + +I am pleased to announce the release of libfuse XXX. + +The source code is available for download at https://github.com/libfuse/libfuse/releases. + +Please report any issues on this mailing list or the GitHub issue +tracker at https://github.com/libfuse/libfuse/issues. + +From ChangeLog.rst: + +[INSERT NEW ENTRIES] + +The following people have contributed code to this release: + +[INSERT CONTRIBUTORS] + +(a full list of credits containing all known contributors is included in +the `AUTHORS` file). + +Best, +-Nikolaus +``` diff --git a/doc/Doxyfile b/doc/Doxyfile new file mode 100644 index 0000000..c403ea2 --- /dev/null +++ b/doc/Doxyfile @@ -0,0 +1,2695 @@ +# Doxyfile 1.9.6 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = libfuse + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = YES + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if <section_label> ... \endif and \cond <section_label> +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = . + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.h \ + *.c \ + *.h \ + *.dox + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = example + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = *.c \ + *.h + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# <filter> <input-file> +# +# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = NO + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = doc/fast17-vangoor.pdf + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = YES + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use <access key> + S +# (what the <access key> is depends on the OS and browser, but it is typically +# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down +# key> to jump into the search results window, the results can be navigated +# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel +# the search. The filter options can be selected when the cursor is inside the +# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> +# to select a filter and <Enter> or <escape> to activate or cancel the filter +# option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using JavaScript. There +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain the +# search results. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: +# https://xapian.org/). +# +# See the section "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will return the search results when EXTERNAL_SEARCH is enabled. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. +# The default file is: searchdata.xml. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of +# to a relative location where the documentation can be found. The format is: +# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# The default value is: YES. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. +# +# Note that when not enabling USE_PDFLATEX the default is latex when enabling +# USE_PDFLATEX the default is pdflatex and when in the later case latex is +# chosen this is overwritten by pdflatex. For specific output languages the +# default can have been set differently, this depends on the implementation of +# the output language. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_CMD_NAME = + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate +# index for LaTeX. +# Note: This tag is used in the Makefile / make.bat. +# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file +# (.tex). +# The default file is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +MAKEINDEX_CMD_NAME = makeindex + +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = makeindex + +# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used by the +# printer. +# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x +# 14 inches) and executive (7.25 x 10.5 inches). +# The default value is: a4. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names +# that should be included in the LaTeX output. The package can be specified just +# by its name or with the correct syntax as to be used with the LaTeX +# \usepackage command. To get the times font for instance you can specify : +# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} +# To use the option intlimits with the amsmath package you can specify: +# EXTRA_PACKAGES=[intlimits]{amsmath} +# If left blank no extra packages will be included. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for +# the generated LaTeX document. The header should contain everything until the +# first chapter. If it is left blank doxygen will generate a standard header. It +# is highly recommended to start with a default header using +# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty +# and then modify the file new_header.tex. See also section "Doxygen usage" for +# information on how to generate the default header that doxygen normally uses. +# +# Note: Only use a user-defined header if you know what you are doing! +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. The following +# commands have a special meaning inside the header (and footer): For a +# description of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for +# the generated LaTeX document. The footer should contain everything after the +# last chapter. If it is left blank doxygen will generate a standard footer. See +# LATEX_HEADER for more information on how to generate a default footer and what +# special commands can be used inside the footer. See also section "Doxygen +# usage" for information on how to generate the default footer that doxygen +# normally uses. Note: Only use a user-defined footer if you know what you are +# doing! +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_FOOTER = + +# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# LaTeX style sheets that are included after the standard style sheets created +# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_STYLESHEET = + +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the LATEX_OUTPUT output +# directory. Note that the files will be copied as-is; there are no commands or +# markers available. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is +# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will +# contain links (just like the HTML output) instead of page references. This +# makes the output suitable for online browsing using a PDF viewer. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode +# command to the generated LaTeX files. This will instruct LaTeX to keep running +# if errors occur, instead of asking the user for help. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BATCHMODE = NO + +# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# index chapters (such as File Index, Compound Index, etc.) in the output. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HIDE_INDICES = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. See +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plain. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BIB_STYLE = plain + +# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_TIMESTAMP = NO + +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EMOJI_DIRECTORY = + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The +# RTF output is optimized for Word 97 and may not look too pretty with other RTF +# readers/editors. +# The default value is: NO. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: rtf. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will +# contain hyperlink fields. The RTF file will contain links (just like the HTML +# output) instead of page references. This makes the output suitable for online +# browsing using Word or some other Word compatible readers that support those +# fields. +# +# Note: WordPad (write) and others do not support links. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# configuration file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. +# +# See also section "Doxygen usage" for information on how to generate the +# default style sheet that doxygen normally uses. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an RTF document. Syntax is +# similar to doxygen's configuration file. A template extensions file can be +# generated using doxygen -e rtf extensionFile. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for +# classes and files. +# The default value is: NO. + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. A directory man3 will be created inside the directory specified by +# MAN_OUTPUT. +# The default directory is: man. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to the generated +# man pages. In case the manual section does not start with a number, the number +# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is +# optional. +# The default value is: .3. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_EXTENSION = .3 + +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + +# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# will generate one additional man file for each entity documented in the real +# man page(s). These additional files only source the real man page, but without +# them the man command would be unable to find the correct page. +# The default value is: NO. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that +# captures the structure of the code including all documentation. +# The default value is: NO. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: xml. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_OUTPUT = xml + +# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program +# listings (including syntax highlighting and cross-referencing information) to +# the XML output. Note that enabling this will significantly increase the size +# of the XML output. +# The default value is: YES. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_PROGRAMLISTING = YES + +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_NS_MEMB_FILE_SCOPE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- + +# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files +# that can be used to generate PDF. +# The default value is: NO. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. +# The default directory is: docbook. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_OUTPUT = docbook + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an +# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# the structure of the code including all documentation. Note that this feature +# is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module +# file that captures the structure of the code including all documentation. +# +# Note that this feature is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary +# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI +# output from the Perl module output. +# The default value is: NO. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely +# formatted so it can be parsed by a human reader. This is useful if you want to +# understand what is going on. On the other hand, if this tag is set to NO, the +# size of the Perl module output will be much smaller and Perl will parse it +# just the same. +# The default value is: YES. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file are +# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful +# so different doxyrules.make files included by the same Makefile don't +# overwrite each other's variables. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all +# C-preprocessor directives found in the sources and include files. +# The default value is: YES. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# in the source code. If set to NO, only conditional compilation will be +# performed. Macro expansion can be done in a controlled way by setting +# EXPAND_ONLY_PREDEF to YES. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then +# the macro expansion is limited to the macros specified with the PREDEFINED and +# EXPAND_AS_DEFINED tags. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES, the include files in the +# INCLUDE_PATH will be searched if a #include is found. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by the +# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of +# RECURSIVE has no effect here. +# This tag requires that the tag SEARCH_INCLUDES is set to YES. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will be +# used. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that are +# defined before the preprocessor is started (similar to the -D option of e.g. +# gcc). The argument of the tag is a list of macros of the form: name or +# name=definition (no spaces). If the definition and the "=" are omitted, "=1" +# is assumed. To prevent a macro definition from being undefined via #undef or +# recursively expanded use the := operator instead of the = operator. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +PREDEFINED = FUSE_USE_VERSION=35 + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this +# tag can be used to specify a list of macro names that should be expanded. The +# macro definition that is found in the sources will be used. Use the PREDEFINED +# tag if you want to use a different macro definition that overrules the +# definition found in the source code. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not +# removed. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tag files. For each tag +# file the location of the external documentation should be added. The format of +# a tag file without this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where loc1 and loc2 can be relative or absolute paths or URLs. See the +# section "Linking to external documentation" for more information about the use +# of tag files. +# Note: Each tag file must have a unique name (where the name does NOT include +# the path). If a tag file is not located in the directory in which doxygen is +# run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# tag file that is based on the input files it reads. See section "Linking to +# external documentation" for more information about the usage of tag files. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES, all external class will be listed in +# the class index. If set to NO, only the inherited external classes will be +# listed. +# The default value is: NO. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will be +# listed. +# The default value is: YES. + +EXTERNAL_GROUPS = YES + +# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in +# the related pages index. If set to NO, only the current project's pages will +# be listed. +# The default value is: YES. + +EXTERNAL_PAGES = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# If set to YES the inheritance and collaboration graphs will hide inheritance +# and usage relations if the target is undocumented or is not a class. +# The default value is: YES. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz (see: +# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# Bell Labs. The other options in this section have no effect if this option is +# set to NO +# The default value is: NO. + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed +# to run in parallel. When set to 0 doxygen will base this on the number of +# processors available in the system. You can set it explicitly to a value +# larger than 0 to get control over the balance between CPU load and processing +# speed. +# Minimum value: 0, maximum value: 32, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NUM_THREADS = 0 + +# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of +# subgraphs. When you want a differently looking font in the dot files that +# doxygen generates you can specify fontname, fontcolor and fontsize attributes. +# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node, +# Edge and Graph Attributes specification</a> You need to make sure dot is able +# to find the font, which can be done by putting it in a standard location or by +# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" + +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a +# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about +# arrows shapes.</a> +# The default value is: labelfontname=Helvetica,labelfontsize=10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" + +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' <a +# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a> +# The default value is: shape=box,height=0.2,width=0.4. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" + +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTPATH = + +# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a +# graph for each documented class showing the direct and indirect inheritance +# relations. In case HAVE_DOT is set as well dot will be used to draw the graph, +# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set +# to TEXT the direct and indirect inheritance relations will be shown as texts / +# links. +# Possible values are: NO, YES, TEXT and GRAPH. +# The default value is: YES. + +CLASS_GRAPH = TEXT + +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. See also the chapter Grouping +# in the manual. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag UML_LOOK is set to YES. + +UML_LIMIT_NUM_FIELDS = 10 + +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +TEMPLATE_RELATIONS = NO + +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDE_GRAPH = YES + +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DIRECTORY_GRAPH = YES + +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 1 + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# http://www.graphviz.org/)). +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# png:gdiplus:gdiplus. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +INTERACTIVE_SVG = NO + +# The DOT_PATH tag can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = + +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. + +PLANTUML_JAR_PATH = + +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = + +# When using plantuml, the specified paths are searched for files specified by +# the !include statement in a plantuml block. + +PLANTUML_INCLUDE_PATH = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that doxygen if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +MAX_DOT_GRAPH_DEPTH = 1000 + +# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate +# files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc temporary +# files. +# The default value is: YES. + +DOT_CLEANUP = YES diff --git a/doc/README.NFS b/doc/README.NFS new file mode 100644 index 0000000..edf5482 --- /dev/null +++ b/doc/README.NFS @@ -0,0 +1,44 @@ +NFS exporting is supported in Linux kernels 2.6.27 or later. + +You need to add an fsid=NNN option to /etc/exports to make exporting a +FUSE directory work. + +Filesystem support +------------------ + +NFS exporting works to some extent on all fuse filesystems, but not +perfectly. This is due to the stateless nature of the protocol, the +server has no way of knowing whether the client is keeping a reference +to a file or not, and hence that file may be removed from the server's +cache. In that case there has to be a way to look up that object +using the inode number, otherwise an ESTALE error will be returned. + +1) low-level interface + +Filesystems need to set FUSE_CAP_EXPORT_SUPPORT in conn->wants and +implement special lookups for the names "." and "..". The former may +be requested on any inode, including non-directories, while the latter +is only requested for directories. Otherwise these special lookups +should behave identically to ordinary lookups. + +Furthermore, setting FUSE_CAP_EXPORT_SUPPORT requires the file system +to handle node-ids (fuse_ino_t) that the file system may does not know +about - e.g. a fuse FORGET request might have been received or the node-id +was used in a previous instance of the file system daemon. The node-id might +not be valid at all when an invalid handle is passed to open_by_handle_at(). +This implies that the filesystem *must not* reuse node-ids even if +generation numbers are set correctly. This is because generation numbers +are not provided by the kernel to e.g. the getattr() handler, so the +handler would be unable to tell if the provided node-id refers to the +"known" current one, or a previous one that has been forgotten and re-used. + +2) high-level interface + +Because the high-level interface is path based, it is not possible to +delegate looking up by inode to the filesystem. + +To work around this, currently a "noforget" option is provided, which +makes the library remember nodes forever. This will make the NFS +server happy, but also results in an ever growing memory footprint for +the filesystem. For this reason if the filesystem is large (or the +memory is small), then this option is not recommended. diff --git a/doc/README.fuse-io-uring b/doc/README.fuse-io-uring new file mode 100644 index 0000000..5134890 --- /dev/null +++ b/doc/README.fuse-io-uring @@ -0,0 +1,38 @@ +fuse-over-io-uring uses io-uring for transport of kernel/userspace +messages. See also https://docs.kernel.org/filesystems/fuse-io-uring.html + +In order to enable it, the kernel module needs to have it enabled: +echo 1 > /sys/module/fuse/parameters/enable_uring + +Additionally, FUSE_CAP_OVER_IO_URING needs to be set and +se->uring.enable has to be true. The latter can be +achieved with the libfuse option '-o io_uring'. + +Default queue-depth is 8 and can be changed with the parameter +'-oio_uring_q_depth'. + +As of now there is always one queue per core. A reduced number +of cores in development. + +Benefits: +- Improved performance by using io_uring for kernel/userspace communication +- Reduced system call overhead compared to traditional FUSE +- Asynchronous I/O operations + +Usage: +To enable io_uring support when mounting a FUSE filesystem: +1. Enable kernel support: echo 1 > /sys/module/fuse/parameters/enable_uring +2. Mount with io_uring option: -o io_uring +3. Optionally adjust queue depth: -o io_uring_q_depth=<depth> + +Example: +./my_fuse_fs /source /mountpoint -o io_uring -o io_uring_q_depth=16 + +Requirements: +- Linux kernel with io_uring and FUSE io_uring support enabled +- libfuse compiled with io_uring support + +Build Dependencies: +- liburing (for io_uring support) +- libnuma (required alongside liburing) +- meson build system with option: -Denable-io-uring=true diff --git a/doc/README.fuse_reply_errors b/doc/README.fuse_reply_errors new file mode 100644 index 0000000..d60fd7a --- /dev/null +++ b/doc/README.fuse_reply_errors @@ -0,0 +1,5 @@ +Under normal operation, a call to any fuse_reply_* function should not result +in an error. + +Should the kernel abort the fuse connection, a fuse_reply_* call can return +-ENOENT. diff --git a/doc/README.notifications b/doc/README.notifications new file mode 100644 index 0000000..2ca6204 --- /dev/null +++ b/doc/README.notifications @@ -0,0 +1,37 @@ +During the life-cycle of a user-space filesystem the usual flow is: + + 1. User-space application does a filesystem-related syscall + 2. Kernel VFS calls into the FUSE kernel driver + 3. FUSE kernel redirects request to the user-space filesystem + 4. User-space server replies to request + 5. FUSE returns reply to VFS + 6. User-space application gets reply from the kernel + +However, there are occasions where the filesystem needs to send notifications to +the kernel that are not in reply to any particular request. If, for example, +when a READ request of 4096 bytes results in the filesystem having more data +available for the specific inode, it may be useful to provide this extra data to +the kernel cache so that future read operations will be faster. + +FUSE provides mechanisms for a user-space server to send the kernel certain +types of asynchronous notifications. Currently, these are the available +notifications: + +|-------------+----------------------------------| +| Operation | libfuse function | +|-------------+----------------------------------| +| POLL | fuse_lowlevel_notify_poll | +| INVAL_INODE | fuse_lowlevel_notify_inval_inode | +| ENTRY | fuse_lowlevel_notify_inval_entry | +| STORE | fuse_lowlevel_notify_store | +| RETRIEVE | fuse_lowlevel_notify_retrieve | +| DELETE | fuse_lowlevel_notify_delete | +| RESEND | - | +|-------------+----------------------------------| + +One important restriction is that these asynchronous operations SHALL NOT be +performed while executing other FUSE requests. Doing so will likely result in +deadlocking the user-space filesystem server. In the example above, if the +server is replying to a READ request and has extra data to add to the kernel +cache, it needs to reply to the READ request first, and, e.g., signal a +different thread to do the STORE. diff --git a/doc/fast17-vangoor.pdf b/doc/fast17-vangoor.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cef723762933f15b9b179794c7335574b96fa62f GIT binary patch literal 599157 zcmV()K;OS5P((&8F)lR<CF0}H(+V;mFd%PYY6?6&FHB`_XLM*FGc-3gIWJ6QZfA68 zF(5HEGC40yWo~D5Xfhx(GdMIgFHB`_XLM*YAT={GFGgu>bY*fcMr>hpWkh9TZ)9aY zK67+(Wnpa!c$}=aV{~P0w<a9hc6Myrb}F`Q+pL%s+qP{x728h5cDkPT?bD~n`TG0Q zeSWMl);)1eT=SlLFESzpWjY}fdm~eEdpj38CVED0fSjeRk*l+^p`9$9lBu~X;IEJk zhKx+a$<)xr(%w$g(8ZJ+pl)gc5H&RhFf#*~xVX4r$N(bt4xUby<`ymhN>wFwDjFKv z|CIb=0x<IY56xdsXG?QC0Quh+H&YvX2U}A+m%kAI*N)1jrT`ZUQ-GPJjVVAxUO`hz zP69wFA*TwEFtsyvGPD6GxEk458Uti4jZN*GO{oB8_D%qse;)wG_I4(g|CHuT{})Th z8DI!-b}%)z{Oe}wVQlK~kBS!HVCrOR>FoUX9boAUFn2PvbNSl?7khxEow1Fp$v+AF zm7CfBD<KCb`@a^pe|3Ms6zrW{oQ<6<9b5o^qbi7s|2t0?3qzNGVmn*@RRiqJ{#u&Y z8@v8f(!X?nVg71e3@z=P0WPK<F8{<bG6k4eIy=}Hdj5_57tF!Q@?UAVI$PSA|7QlY z04Gy(Lnjj(Q)lPDFn_WC>CS)F3HaZtH*|2Y@%)#!{lBdKR}Pjg&ZahI^e{}!e`6ZE z{EcmHX$Qmb&!9-znb`xF82>Fdadr3)otvrCzZyjO&&*K$O~TN`-p<AoU}9<p!ysqx z@;4-a^8b!y`u{$X{~IX&Zz1@<h2H-+a{ntw|Jx=0f4$HD6<XZY#zxN2_U{1vd%*zy zZWw2PEa0CVV`J#_|1meTwY2g4KREnXLv_=CC;9(_BjsZFw;4iq=6?xcq-XrM(9&7l z(!<n5!P3Rp0$^rn^S4v~lB?R8m^#^5+L``k^k2;c&@r(w{TH2zg{85z-9N2o`!}4a zoymVq{x3=YN}b`qF4KRh75<X$qT=cBH>3Yq!w-9t|8@AsSy<TK1K>@^#K8)nW9DT0 zd)FA509<S=zW;|n{{mwA&+`vM7bi;(fcD>N|2q9s^Z$JQ^Q8M<V8rZ<?M?m}6J-}e zJCnbY^}huFvvf;GS5qm`zXdZhaxpUhOKR-u<n)*3f31MO9r%y)zb4<*)Wg&m{wxN1 z#h`G2_7?kf(=vm2P+Obr(yD+hanx32qEj!4=6QMR=dGDcgk_dXlhbx?tnDk=)6gB* z+3g5d(?)stCG`;YHx13Ufp+Wxx!jhS5S={Hi1aXV_lJ)v$m`(!ktPifAaQ{=^X+ne zF(;y;ql|f90D}HkfdaE)HkL)8ooLgpi+eH<pC=<#wjBYr_7}XS?zkTsbi_>H>dK4K zch;sCF0N*@B`W?EkhYKpc)o2U3csB(8;uT&8*QxABAaYFw_BbpsZG&FRayu4oHOyv z?KZ$L@i<9&?PK&Ht#l}hCs9z*SjtPb>~sH|-CDx)sI>d8KCcw^w6IYt-pn{e^RlD4 z50+7oP{$;5b#|9Hc=Zk&yz1j}N+eL`1d}A-7-lV@i+i&0-VXB1#W})Us0rI*_qPWg zgAyL}{MaKDxM}xcAcYdYw`1}^yZ>N4#lkIcTm;IC#_Pq|L4q_da|r>mYkca3z73P) zI-5jTP;j+<v13EE(2dpUne4~bE`15#>ok+#pYVhmAXjdiOt~)ZxYg0E7O2`%dv%oX zPB8mn`JYsS!(QM~9~$I|$nIu3T}sYM7x!&nOB{01^A3>%1^U`yh{y0IG{sj~wp2T2 zF}eOxHLQ6iQiHavMp1*lCy`WlM<brqcNc0U8Dc1p=P}waFG0W}giP)5G&sM4{4eDW z&pfNrk$5{XxDyo&;f{+~jknBZi}Gz)k&HVBe`Xn%TC`fIwpE?B4WEVEb*4Rs#Ox%A zO4ByJgQdh+|9G?-8jB%tVt1@}X_MW2trSYaHBKQd7yxHc!B%9R*_ixw#_aK8?!xh` zxM@>>`z-$C0bs#;c!)w2VwDpA*xcYbMhWN_WVO@{9O3&Ftg2qf?5nSQku25>{WA5n zaBYe69Uqx)TZ{1=X};Gicqj?KDMMTF<>U1>Z<7{o8|*|AA1-!?V5#8$hL76nccqTZ z%EFZfy~nSRO|AMB?S*^$ZZonvr7mC|K4Dg@_s0nN<KrtqHL{hP^ZI*ld12DX?$j9} zVoJF9bEa{NM|SxJ6@5224=3&x(`0XGgwQC02Xhzr!ejV8gGO7FTF_)j0DSEg=e#{V z#&XOrXMLet>o6sB+_)@{{6HilWi9g%++lh54Uc6#puD_Y6nR6wX}l)H0z9t4DGtQ2 z5T8E6Dw~C@j#@EoraY;Q?|8b~!6T*+t~GvFfy(2y;tG;ShZ{C=RmAjb->p+61ztcv zqD@lRp89O(wF8XlfHQU){bBxUlDBLx=Gg?KY~=$u<dwl)`%~Hp-st<X(aGSnIsy#$ ziXL&8W{q4j((hLD*@oxvQwrZ>?zNa*u)9OGGeAmU*(P75ME;E+Y$DOE3+vVRk9tkC zmCUnxPE{;YuT4PKqx6Y8Epldfot;Lg;=A`;kiED?6n<Ejgm=F{BZ<Buv4W2SnegV& z6P*f}kaVCyUySmPvnDKr(=oO>^`&H<;_(W%dRSULv7_J9%x`fv@&XN!_jDgCnMa5# zHNRD^&c`0&A*c5H+cV={RM`NgI5M}!hcS*-C=XaQ{V>qat&8ULc@Ag>&4vS|yi~Ls z2PLj(d1vne7IGnkv`rb^h^Fj6Z={<ny9;dtT*%h1dow-*T5UZgu3y;ls$1PmujXAs z;do$N3r8_4z>}A*wTh{y9L90OtRx|!>R=yM8*6{(fn*{c0k%Ndm9vvR)VA)0JeN-+ zGI(){)hTsC0$ruGRdM4F`-gHC`5csP%19~RgM)D$B9wI%?nikv9=a0vwsU#FfU|Zf zKCYCbuL2qZECL)vO2S@&>wiNEE0LB(@;%I{V467xa6peJ407J-rFKdSMrVaH#sTFk zcT={mFe$N3X9bcnnoA0u5*yB${t@>Es^fHqJ?BDszTOwXVBgCepD9hI^rUrpRi)QD zg{qLI+;rj#y=Ms2mPCoc)z*0mHqtX^jij84G_{v4#vVUykV9f8G2ifqXfsH5mkA9a zb;NWFD#x2nEIC!<bcWs+5}o0eY`bxAp<OzZ3qX2YTumou_CTggiX@6%4+ZYcRh>t5 zJbeyNJ0WhbX|y*kdFBpT(o_Uh;SZ;RjcnnJVf>&c$ugMwShl(T(nKIC8yGRE<Y8Rz zcU6ICO{_3Y6uNXG;L@-_=8o^|wH3Til22`qOhV7ag2p_}TB2wlnF^ZpY;my*vD-la zG@w`@e8Lvyqiv)KzMo^|mn@&!8Z{0AVFQP}h||HX<I5^3{9c$Yt7lGQpE5MNBf6)L zeCG)lrY;4!ndQlbPkp}qmVg4TpHrZCYfQ?Ylo_V{cAv<(7kJ1l4A6wPfYVE#Gou{f z8cIRa^IerM9xQ!9_aYdLE4eB!9|+JF-(oIe3y*lZFnfb_@*k;j+qR{Z$oC}sF&%%H z7g3Di*h|PiF=3W}{R;|Mw~s>)J@LUoMDfIn6%VBC>AA<4Bv0YCq^;#jonYjL99C-O zXXbI~O_d?RZYyTyY>bQP_e|KTRaPZWT<G|?#YJ>ZK*gNyyaq0+;-`L*;A7xLLgGfN zsCHtREmuh2%M=wo>;jcYF?S~~H#lV<YGH36KJ}Mjv^`7`R!GUB2Me0R5>wG{C)`Da zf;+<r9~?%s>4?}2rCk-h`+($_I;md7OT2_2nA1~Ac)NU>BILZin%!cefe94j06`>8 zW#>xO`3;hJo}alU?(jazwo$+w7vwoE54eWkJCg`zbJ%Lv4ER7$>z#(fsU!ZGh_^v` z>SDiD9kt5m^jeBE^BZV#T&rPp=}9bohiTz_s})Hl;|UE!muhur>0v9P-?A)s9<Clb z*J!AB<#-^KcST2kLh?wuiO+N-EGK-ni&N9t7b5svK1C3YL2ixSltv8;jc$!ZlJDVr z<p7WCBE~M;u6+}={Q&d^BV)8?m!{(oq-Q+1NTe6pCsbH})3c#pY0HhLVjJhKyI16~ z8ALTa?)X=E-}y|O`%2CBl}wjY?-%9H6mdr^K|7aJb|0M`tq-TAG5basWIzvcBOc|S zkYkQ{`)l1}^SIuvs~TZKBgt$azPr;DsG)NxXTV;@Z}96(hl{mzz#|k~tRU$@$?s%X z27-J`(0RmRp}`B9jT`og$B|WpFDa`A>@A3E(9%2N;4mwDL0vW)!G!@M6doDdlUyFk zSIK~cv&2U#dz<(+q=%GtBs&U%+jAjxGt?uwE?FPMIgsQo5nonEo(2`?c4dQ`XH&tj zSy}YI&T`!)XO)h;wP3umAyidVbQ8ZQyESf7=p<k1M~ZV?i@|Ps&V6d<l?L!YF3B12 zg%ja1g(%ACbCFL=yi8AH%|W@lx9-Dp#&eh|duVN3f+_alPbIX4FNuDJm9{nIW)a`7 zIXkjqcY#r~uhmvs3fU`0!4D|%3Y!zNzd&>Dm-;FkR!k(e*_N0nqC-5sitR!qSx@Fn zF~4FH_7<?6&0saKeD?=~cJk;dkIZ`hZb;+3BKp>i$Q}_o(M7G-EHx&tl%g`vxkX-5 zIrP37_}TunJF!M9G=MkzX=G5ZznX_9c(<lb#<rwQ=svE&4_+BpTo_61ed&V@x%DW` zQ+_ez)=XsapfaBZj8Y@enh5~|uMP@fOgeSiv>+PH2|anSh<{WwDyHv?_<P;B!)kEr zwYF@2tq{f)u7f{sdM4F*;?i29*>Rll*>+KfFuZ4sLI*XA3wA(o9RjbL1WNlvfB!>J zf%4VZaR*LyT)3*(b%R$Zko?T7mL>k+eTvKAT5qo?%Q{rs^_;lk1Px*~^|JS9vYY~9 z*eFdYF4W5YW6k%XJO}y|E)q`=5@?5wjA-bimxIw3VsOwY37tV9`SW0dMO}wnElX`X zdLtZ9pj9JsXM8z>$Hb{B;%Cw4W44a3kSRh}_~X0Qvvar)d=gJL1Api;3ss(5El{G` zcFP@%zKXI|{obc@ZPOZCT`qfpCf}Ft8cdbXMeJq8UE#YitZ)ovDGa|ebKFrocbLzk zP*>eTlS4_kN$_wS14SzY@d*B<49659>FCFF`0}sXNk!2mA&|r{a|5MVibPOb<9h?c zA55yYQkf6A(F2S}{qt9*=Jm+XyS%k23?=c)90RDM$xR!Qeb()if(3bb%jDGFr031G zIVn|79?OsC_R?Wg7d||4<DC#3s@!a)^rJH7;Uv@|L<qTajggGo*LpaS-HRN^w@0n( z@RB7%LTa&IVP~q7Xg$x9cFCVPi}IN}wU$j6ujHD_`^5c~{;SFotr>=@QO{XCNb;A} z`KO6JR=`w*8pvh20{EH&%usp*Wg@<hH`Ex#c7X)e6SuQM3DnUq;z)tVe!8=AqOd)2 zU@$4&XoW76hz1g-<PG){6jQe0Q+)Xq2?jHdGMiXi`6UV(rPt6q9SBd#P$%FU+;1l+ z-oWvmS0QB+9rX)AGXqYr<fkxxAduaXUjgPAa6y{xN;TWz$)L#rGv$S_s~VUJt9d0T zW%TzGVMahp?=lzi`#ecjA=>&67lPNody=&MO}q|%WOES3A-kS7svJS!%!y~0P(wE? z8=OHHY<i7l?M=-SRmnr?x@Qwss?M6HwN)-r?oWE9vUTM!M(U?idk4%(g3IGreAtnF z1ozG<>wFW|A!d&+-1r8Qq9mWRa5!I!f1aQhCH<bm$<2!8EIO_W1Vi<WG$@*4BQb#) zz=lh__VB(D7zr>TZcC3+S%efe_-XwsDcT@JCL*FLXx~=N4dgj}L+uzPe|nPL<y1DE z8iq6g$9k|4E5O|O!BHgMajs5P>vdXgm|5Z4e696_V&mTnp5HFR7rAA!&Jno>!|`Z~ z!jqL-@()V8rfE-5281YnHI9IOP^0>f57qUxm0;oTnnR-xShDhuCKddQ6mD`$W|+TG z)H2!t{5G91_|0})<*--Z443YxLE)%T*<+Wh$|`}0)B+dr!=w2R-g(n8k9Si|;A-gj zj~9O~W8n@y)cL1W3E3V+{p~*$*j&8FmiC9U^e)c7Nf8-i0YslMJ|y|&TzJ)dGF2{m zI>a|E*l>h!KZ;7SyHtx7OOY9xikR)-W8_R?8QaVsokhCKpp97d7q4GbGzRG<*^W^% zTU2Mz1Az@29aiI#zAWsK>T%Qw#0jJIgZjy{<Y|~NpuZos;SH<CDFr;%g4Vw$B67A& z5(N>8fcB0GTnsl}@fK~kIX_({?0QtRFF^XmF1%)NBf(@=wh9|L`gR}uIw<Yrl^r0A z%ao{i6Z`UD=Q34UIv4b6T^}dZzXJTs$ot6{LDdiRu)r~8swTR;by%oAdRLR?)=SG! z57t%dp#OliZsc0PkzFZ<9P^?z*P3agR$HSZ|J=~eYw(NJ^GzPc=srK7TQq0z<_99* zKD>;tO$ER(g6eTbep#W&bYd0x_&Rr0eE8)9xGk(XyIOETkGD~NsV59+3{l9~AcjTA zvKo4`x1IOWIq)0=$!}Y;-9mlg5dd3KEl&kQ5d$kuuh=j(0QlE>bJ5nFlSL$g^@{W` z*69g1mqBOTo=GtR7tZs>c!)w^ji1lXn$eHB48rykqiMMukn;PEIt8spU%xC|bQoa^ z-myzS34zV)j#})q8qvicBoJf9^Vm7}4_*6Gos?*{3*j=DgtB)n76`VOx4sg`Yy@nT zfqyWuHh6Lj3Kn@e_YJVgiOVcSeH&$c1^!O!VZHrkZ{5bju+RpD?TtyrJ)=bf;j_}7 zo9?Ci`POd^t~japjle%T_355~=lSURemGALq)*gK(p=<6z6pK$tlFR574UgDh9d>2 z-nN*k38?#id3q1t;d=rO84wUk=Zu!(pj*F2j6(QEbr{9Tj~z*l@`?$o5Zc;oZ&%zA z1S`252-Wfz)~>_rP28Gw1E(j~ftcA!3o#lTQtduqDuvue$9B2ondHv$2jvgjspOle zg`V0!*!!3yOMq)zn-9~=wSi1~917gu@r80Ox&l);91^bt=La!~2I<mw+X)uk1qYe$ zITor^E_wv*6Vwp{uLD4){=}?$58cdLeKt6a&1GvX1C`tH-1;189XN+?l#)b<eRqE` zE!Kkloy{%2`Y{bci#V-wS$uJhg0Rb9`SynjB0c2JNH4Fsg$ACn1~dO=;d#iJVN008 zP8Zt5x&_2r4^*#9lHn4;_!TzhDG%DGEH%j4{o(_w>XgWE0j-f1xI`9LdrLim02c4X zZdHJft1}_GrKp)Md={fP6Vyx8KYA=`E%ITD(P%oY%%PvJ>j)rs8Kmw}fD6fl6kAC- zM_LwP|LbJC;l8-cvtwBKelLx(+dy%Qt1d~ySBXXz_#?x^VaKG-4yRaQnd1Dqm@Xr| z$QQ{Wa*%G(IhTJEpkL#)_MkZ|7{svdNj6`NQ$wt)NLDy=*CNHmuMEu9gSipO3kwj$ zDus8{gacc8NnLOz7}$mBzj=Jnctj+C4@05|R$n#EoC<`=L#idO^7$j&%amk^T(j(h zlGz2saKsUxZjMc}iQ5pl-y54t?yHEXN~YlWC>PXI^uF)vPj%ETCVIt#d}k@gV0Q)t zVUk9~=>G-m@pL@F+Ri_I4Xnx&2{x{eQru0~ttaH;!0Y-{f7*Jxepo-jplRtD<jq1H zwQH=g+qtAnV=UO*sy<~QRcQKJfC)<o1j^K~-CKyOz&jv^)<58`cO|Il=6k50Vvm!7 zV4-0TgPn=`K%oAMCpWee=G{bRcILFSMh;N$;`%3PM7e#m(Ctju-%L|!_(ksQ<d6eW z8Sz2<5f;Xdh%1n{aCDVgNcBxhD7~<o&*DdODwV{8As6)KIPkfom?)=*#2;G_yYg|! zRNS%J$~!q&VWpd!JfgUCV{SL<JI5s*(1tw(JdI<anbzu=Flt#4zfC)>KFfjZ3z>B& zcZ&<rvcyt+mZSW)OgmaHcIhW)9wTuqu*23?fZbi_;tKTC;o;1KQ2lyKt|c$YxumGI zvHJ-3UKrP~S@Q3Sws;530SOcdRXg=xlZqdgw)Y`_z*N}|rL89UX5QN>oS0K~fy4z` zdN7Dz-oN_WeXO_tpe?`S@Ot@Tt)5I@wB`VU5u*yr4T5)Fyx8J`j0-SkmuZKMYlw+d zjuSg<ao_?L{0PWZHvaH6aZ3+A<Y7J$z>sEjkB3lrw`1Q*?()acp~QlOVs1_i8pQsz zjG4hXyYZx-8&;X#j;n|Y^?ArxaAfYKL3Y=qswXuvQI?+>V*Ldld0r6atuagZ%AcLE zicV-V2tpNhyG#%>{`=RHS7xv%MGhr7@tcj?jp&?)uKjf4y4nhJye@QonTtop@#q>> z0NQZ<&nEg}G&{`xU(i8;pbTX5%0atcpxhatpx@Y(+(8Fz_V#2ysGpu)%m;}32H2~? zLkydC;Y8!ec}O2SR0Y&hukAr{OyMQriGcanpx;`%P*qu5xjI!><v<>m$;zN5P13`W zXNX8saq@Agj0pj>98LNXN;GTSYY?MVsF{9ALRqdf2?%RaCqQV>QS_Z_K4q-wODzi9 zh53cgc$gkDj5F+`m4%kCKG2<E*XbJvHnf%zKj>|=AN${4*5s;AhJd}c1{b>g3G`;o zNIOcHgH%O+KdsLPkcy)>9vqpeWf5QAkGGs@=SwQ=t9~F@&+??9#LW=nG-q~QBYyKy zHXb>uk+0MmtT7ztbQI1<@u@VO=1Du%*^P5(sCw5uLFSi*%Ood>2d_u8@|YxG$TBk! z4|PuL-^v1=?*Yto&LQ}`(Ze&&xz)#O8cjIce}Ft1CQY{1q)K}E5>n1^;a*P*x&ik0 z^oxE)MWy`K7PUzhkK_r`Y7<%?GEXX;$a<V{7Wf_Og!VDav+r|tWO@BNWzP7kG0?OZ z^%B-k3>?6dPq16>l8{IBJX<q@oHXIvG!lQHeNQc-lo25be9f974_e@&Yhc~uLO0EJ zN=Q9>Yo3ca|7ZybXLPO8=sT=$OE7m37)Jj|Sq*-@h$wKRG7FcHf5ui4;PCMnnQ-)n z<O;^h;;zr2;v@#|YRnh;7*po0KjvcCL0ODPd0*}$i+s#+O_TZy-z5Cqj5l47DmMW? z5COP()E>ivE*He_m=Wo?$S*Sjk1{cNZ9drbI5&wRke+Eh=J(DHTOiLG<Q(xm$ajkl z2G(e&_@imnDL=3VX_1T&*J<Wg@wpJUz-kf2bSt_U+`v|qYPQ^+mK|9vg|0zY#F<`q zXk+_LIiV4~8k5zljss!BGP-nRrqnOt1lyh=%2tSc%ur2MUG^=tt7o6n8GEp;PEgvL zt#bbrmOx1<iU=+%(TLyTLyM)Fe<bn1JN}FzQnrM`%JOiCR75r>`vR2^j!f1|0(TG{ z7~!26w5?e-SU|0(TIr(Xo&_y7WIqgW#31s1RA3q(i)0Hfh^5)4v=$S;n5oJT^Wu$^ znbpF64ihYO%2-X)uWHv-?ip-uahbUQqYmgBHFm#iK7JFdTVlTDfLQ63){D3biU&Lh zt|f-}IM^WzYrZDY^Z2un(<L7W<}qi&h5dPRNHuLy7J2cwqbsc|=Nc@d`D~I?jSRqo z7AQRJFe_y65SOUq&DTIl%G30L`vW+hRWc3Ok|`bXwlJ?Uq?PtA%+;IDK<QM`j|pK* z(P10lB+wLjfam85rn=rx@;Nc`5}+Mt2<_RL>&gKzA^2wm`s=llVnnW=<~v%Ct-)-( zIV(F)-46o~E~NvRIV-N6Ec|Jb_?#HgUpiN(Zi^7%1Y*B1P)1E&cwpspcTBOhM_75f zv}yEHA>U{v_=5xuq~3>0OgD8YEtJx{<s54WXi7#Wur;{2WXnwC$rXP&p)5_y{}RN> z;RxpbZ0fA2^O3)QsKLhBd41eKbYC=NpA_#X7hZnH+^hleH)KDa(S`J_IP|lQt2-#Q z`_4$=&&Jya8kRQbW|C)GA3#T{#9s;|Noqt71^FQma*3Y)N!7luZ+3vt#W>P^YX!vG z{b2Jx7y!rJzGte{iCz3Nt0_E^jnUz_=NR}pH~DOJgl9$eOhJsPXbdgLLo*)T;R)#N zp(%0TXt9x&bQ2rLf}uP3Zeinz16|%Ry<_izeedKHZ@YgZuchfRk;U_v>5V!&MxNoi zb0HP|KHS}j^esLfn*oy!yaO~tLPFZWIXw2NyL5~Odk)8|T5jKpEGU=eOI;eJzt3f< z3LV#u1wZ}je26hYEHPD@cS2_}qJ!7@4m(#w<ao=dqj>u+WroR}%J1#W>cj2xNM<kD z`nBw`JHIfst=H9Gx@8|0I}bGbI(G#=9z!DcZ0)Boop2D4Q%gCF$I5$b@bpl1X|7KQ zsJ_)0zZ6+Avo76rn&SEVTG9AOlU;jnHXx8)?S1twA+o{MhXvJKSU;G`K15RBKxC;$ z*QnFZooX;(4F4!X`w{yi(Upbi0BFDiO<8IpxFnndB0M?@UBZH=#a)1`jUaiiPB;TG zdrZ9!ifwt`VM)w(2_T|}-Jw|>c|ESuprC{5ieunFKHrGt26$^P&ZZaFF8kOF5ob&4 zI4g<B{zQKwrBKFENPc~~x9@~|^T@}JCfXIfY_1NW?G73Zq4V15M#@x_f%KD_F%t`0 zkyJ7+7+0BI4UaED6Q13F_Z~C_S0snr4ap#?t-ji*L<hH_uZez#$?wbk!}-(Wr&e4D zx;N~uRw1{*&zY;i)F-NW&FZ5e#GAf1q!cC^4x497Wi98MLT(ho)ig#~yP6x3;>i0A zre8b1`@tve#hixEdtOwhL!kvms{Gj<j@OE+vl>FjTKZhKVYX(6wlLYqoX3IP$7IyW zCrY#*=La>%wGL#;GE>$z_gn5j%x;m8;S8$ge>Dg2%Q4T(>uG-jeH#_2wkjS81wxfn z`a|fgTqS`|QXRs*&sq_J$4oEKDPY?+Q@k17NpzX6raN}3B2_1{0_II5Qh(@PFL*fq zO1~qz!vN<hpeW$cHDPgOzEqYk_E8`>Z==5EkZ<p;6lp-1UC#&VaSG;I=LWpLUrUho zLosCepKq~as8j=1X4}=|&9FdE+Y=2Pl21`Fb_d~OV7VMZqs~oG$cYB7>`Voh3Jhq2 zY*LLevaQQ0dC9ZsdB)*Jel&XV;}TxsA!lfe6XZmt%eKtK;FwIW;iO_2BeR%YihYb- zXFAg$$=-oAZ_YFSIRzo`l4?N|>P0v9iz%DN>k2#NeI^p()D2i_z*&qrD|~F;tZA5N zn{*F)hsY?&az-=8I7v;5^y=mFrJKqPUk$M>wa9&Az8gCTUOui^!7c;bk*}L1KL_d8 z(o`5+m0Ga&S-^Q&rtpG!tk4)?dp~OHgu$NV>Oqm#*4bYfx0-Nb-pqK$42u^-{%XtZ zDw`PxJ*%9w+SyQytPd3;l*)vjjzcjkypvbDoBHGVbG}M;HU*knWqtHV0BSLqdOyUO zYS0CVMETkhtGSSg8aGV{=O;(?D?2sHv`L-s9y17YCJ>26N?(QP)__v*Dgwf}wBBt; zk~%QG@<BJUsA|<`T2L@lFu_iQ=fJ4buPRb32=iop2gMtV)&`f_8$vJPScEZl+9G}M z@}V?$3GF#W1oUp@B5bC5@X*RXY5tD)6r-a$HQ4hBU3fs%8Ui+!75x!}J)H#QV^CQ# z;|IBFtV_dfbROf;pZ9Twc*_bRRf{74rD+K)JSCdD&yq&Kv)JBTgfEMHg<mTMBLzM; zQ4+OzhV*V7!N-N~_GkpQuB#ag)y2425N3xW(UjRwiV5H=^E5gGT4%+o*Q9dyKN-;> zb&zwYx?#*L3yuvm0R+-7iP%qd6A@$)C31ZrnCHcWUW43$N&YoWV5Lzjrez-imWy*w z@cL&o+=zS!QLxn~*5VccJ|t1d7<a5?+wnM*|M80l_V9d`y3fFiEWfGU%4-hex)|n# zjBDmTV9xCx70g0D6L=R39MW<yr|bfUdvoLDCL*JsjPtw>sz5p|-AyIb_(6V$C#Qas zbebULW2)&8qP5sLY_E?->w)AnTT3SkCm17#F`|!@=FsXB7#?qUY~3HHNMWzuED|w% zC`V}#5|heENL941f__>jt)t9YFPr!bxqSIUeT(=oU>d>}3ERf2S|9|~cd5l+byIIv z->D1vLg`gPgQFOJ`JG{M?rlnmE$-1d#<cv48R0EER$6RRjvt^ftB?+b3mR3Ah0Hn` zE@8(^4XX^{)~b~eh?%OTT9KSYh#N{=T0ECQr#C1}6dyOD3N)^DN55zb*K@O3>8+t< zs8p%w%tY>xdlTGdz2HfJMOEvu9a@C2v_UV$vw+p;)gHVYRy}+*^06f&m&bQP%d;O& z6mMwn$=8Uw8_0ym+cn1^qv3W(swSH62KDTYv(!OJNL#HaIjb_ZTOKr|KGRL1eEBg* zLqxZhq(p;F@f%7<$HQ-Xj8}Y29F^dWO-O^>yfh5Fa1OEhY`)}zhWd7;p^(~J`*-|8 z9gyoTC`ljxw(n)Q*^t3OeWcj(BL-x&llt@h;iEJUQRsxb0RK+SBw(?H*A8kiP7oHy zfC@`+JJ)$k#b8$}V_tc-Rv$F(3FPE=3;(%oo+9vh+Qf&;FW~G-#u#|R*RzZU%_Yx6 zEbC<aXXzk-k^`<}8=>xPkt>eSd8ZeKsD_CYhgO9_n1f!qU1?fdg;`VLWBH^mdKqGi zlK2`wdntu^;#|2rOmun(S~eJ*UeX}I-y)}DP;pL%7&*WqI7mQb9-dH^ILqibx^#;A zmh`UmbKW2On9T#(r3eF5>cv;F%B?~roUF#x?G;pYV;do$B<qaX`rEiv?|_ZVRkyT4 zamtzq*#jB?k`tXX(IXa%TJv5#W5yn4BYpePjQ@TwfI|Pea(Pg4M_YQ?VtReWppgoX z7^v1sjU?KFF;dKB%E*!SB#(GVWB0~<1=J$$xu^eCE|07B(L%rl5BX7Rv)jI{S+CB< zSDkSPYGBz$Llf``W*2&T%p2Shrp2_r4U+aIszLAR;PGmbbsrj1?e)QqU&HXFTb-TP zq*>5(jB{X;Gn4BnIqm6_cr-Xz#T-rSdz)6#&UcU4FgPGtaP_Qao{+}pM)#PyM+MU+ z{br<<ezp-Y#H5)0&hT5gQ9Xv+M#(j*!y&SQmd^RIe}4ct&{ibRItYgQz|mb{Qr9CB zUUrXcPoywpRSt>ysQ}=BPzohv!J2zcsveA!P);(Mtzy}!_Dg)V1?JX#X@}<L_BMwp z8O*xPZ%M^yh@Qc~q!mY=J#d+tG{Z+RVSsx9iA3BC7plosY!;Lt=vka`*zYlu^FTEy zZL9`Sl&UfFivlR-fxhmB78l`e8~3}*gE(~rYWX^JB5G-j9AS@71LWU8T&XnymG@-% z>eV9}i^K;I!KGT!Ro!}#Z2mah35d7G`;na$7ggV`wp<cGh3_2>Z7(zj3*bQ6(h9Ql zZEMHR7xtOBOt)?bvYWS)YqAOId+$FA?FV&CjPq5}@=)+q<BGY!@)IR67Mwl@sO%h< z%uH|NPx`uBb$HY2JHrhG^7WxjfsyFsXBS)2xGzyk3}^>LN)%(wJ~Rpd5!_9Mi^{Em zrR_^&2$g1(9|Swa>`pJuXs|(G4OC;I(4I=QSPK#n90N@7cfXHMyMAa1sbb~on@M4y zhRN~z-Wb3s;7&99cJ-QR14Wp-`N#?0UKjO_T#+}ZB^gBy#WsEAg|t1L`QxU-ZWWMI ztx^}Wh!+>VqWMzomo%qP(bto7gg<oIMV-R3Gyo9k<CaR14h*SASmr8)yC(A~-w$hw zlq#BW0>i$a`aeiIahe(IGrYqG`jsE${t~x_c-wu{FC1f3NavsK*+W`VB>MZAiOhWb zV`JnfRpcFjd##4%a1`ivN`!aeEc<)DlG@PTcB2B>DNjyRi}G>Gzp=QC|AG5qKSOGM zM(BNKS>;k7#Mm1X<r+n_tF-nj*v&6eH>Z)wqjYNIkwsmcphBK*o%)COWn}(uliFYW zgfS35ZW%a|^CE`%r7ocu`v`Uk9?vt_l39V5$^JC#HPB5n;)>yEe(ZN?{LZ~*C*x4v zOzt3h;wNIZ{h$_8RIgV=FHQD?U#!13_b^}7(?DxVlp94bP~+*ZY_%xBFlHoML9`aH zaK^vdI0+YFyl&_;)693}V`WOr{Yq3o_~o4eCi@{86QXG_vb(oh``c1m5}i}-3qZ9p zDFb$!rryi$w#?JQT3j94VlV3u;x(+}KI%R1JmmjccVjP+ud3z<GNQ|<=!A_wIbNhn zed`I4=peLtG_QU7+?K;?Y)hSjF%Bd?=m7;D7n*T4nBInBai7<Et0aL5!IrZ1f}u;s zw)b_73cN#w)e^0tMUaU<3dw8c1l9N>Pl!ED+85+jRS_}(%&eo;QC|!O6D>TKWib29 z{Zwjv{(85oZ9KoyLp}oI(%b)DC;HLsGRv}~P?lyYzi*9JgOZ@?m?89NS!1QAF3ItS z=T0;R3=jeIol_&h13vGOL?4m%{*8g`yVst;qJS#`30kJFa^lVEK@=?(8$WMA7&Wwl zVyx_m4^({q=vdz2S0ie~FNBgVgCD+2BS@8GzO5#{@8P3Rd$`2Lo%;TbXRxcWc>rOi zGtyVKQm&iV8F0IC+x`L)#iAWKr}iTINudre{Ry&lxdY6+{AQ}zBttYuox#o{Lm0Qx z)>>JiEhcr@!lTMT)sZgM3k+w6O}ix~7?tbJ19s%?{%X>PUnFv|?xhn|Em4Ce#G0I* zADeT3vY#cqun_MR)rwppBNrZZIResV`_7Rjn}Qk6In{y5U<HQnnnv8|tzdt{21+V* z*7OHC!5x;Vui@XCIIUDBne9mRk({tNXs0e*yQ3E%iz#{OEqu4KY@Z?HG+V;d46yA> zX^M9ri-i>H?lAe;i9jjEYtpx9@!0gpNJZRSiPoA`Q=Y8lkRIWYM5dnqFcIT`5g>p$ z!F6_I926w0$n(TS7Ca0TsKM0wsi!5hk_9ns6F3pBj0v!1w-BvWc%>ha?=fkm-MfU1 zWF@orC|yi)>XJ0aVf&^hY$oi*S4trxSDCzK6#?_pA|4bD#(4hHBLRCziA(H6{b};9 zT8=1HvWO|%Ne!|(EDto!L7$UcrRz&_`3hsxD#60AckuBmR8~tcIkYo`)^lJRzKbx} z8ngAoX9K+|Fi`(O<Ev$4tA!18mgYzt{w{fZnrqGXIa2H%cl`&?)W9z7%5wa?Gtnlv zib1%FpAb|l-p_N}Db>d%6e5mpT)QJi0Zq^B$lbUO-i^f3=iXPUj#QL@+>lT`xj?tH zBBOqUvDfEX-u2D>FWWHH?7lxavZ3<{^(rv2T+tlp`NqPlFT7BR5>d}RAxRFTkq9bD z<_Y@#L1KiCc#zUr;Q2nCoG0G!Cx5?cy|X9i>4j=#G<!Vt9f$>OZw>}k9kMecB1d|! zcgKvRm1D0v%)`sH0FCZTbnyp!Gf54%who6F5#>|tJepuWC{e*RomnP`01ApI8Qys^ z#j9uvl5HBhYqSY34n3A`;<4OX8z{yr4m?JT+w3m3!>$0G*Z@(-KW)jf-0@c)!#b%t zdM@Re0)lp8-+G7d2<@nWs1DW-h^YN(nw}^gUvx#yJv!g&qeZ5A7Y@M;Omma7PNnKs zU=ZlCE@(f(<A<!Clvg25<jXEC51Im7R>sBpW+*~a@qb^=9MAY$Sw#rhObrW1$h_%S z#(C%1c3bLa;3Uaop111+i7E!6!;&YqK$iHyoVN!0V-fqoeuB)z+)HnOXw4kg4($ic zcPJrTrumJX0M-AYouP9mqc$*V&VFj#_sRGYzb4ekfBo_C=ssjOp(x6IDNHet3ep=0 z7JzIcd}T24V!)y()W&ts_7b{bXcN~1<~~r;PS##?`mXtYqa9oLAJ;yy*HhW%YmiYk z!aGtSb|F~D2h3uQQAR7_G=nD4$*nREU<f@H;R<`yZ7R|O<I*j}CQubtHyv7jAUk}K zwdh(2;RSlG-OSJAkNLwJ>utecdSbYiO2$aeOR0r(1FxQB>f2JixPyvIH8&+Ei*uvw zgFkS|4D{+ZE$OIl?<{DfCdQy*IG&N8zxj@Fy$0<*@Jat<G~1u33K)5)JvzrTlVriM zg(0Dl^o{B;uchMSB^j3D6aEc}q7*zupcaS%rmVgDPm(%|D%8_$J<)mo67ge8X6nF2 zeaUITjI)P0|MiFVG&!qWDVtnru#y7foVxl{?s4V4=gQ=*dk2n1QXL@V%xw5GeoXDt z>6spuAcW!-k3AsxE`!Q?y(nE9)}8hfMC0LQ?yDoI$uTzqa-v!3QhHw31)3c=e0<n3 zoLI`NuCEmk-z4Ig)xG9gGlR5eX$Flv77Bj@nJ^?BAWo#)m-pJqDpXzrvN?LxwQXIi z+MEyhs0fIW(t{OD-}n7sWJupU#>7|)vE_;;ZhSl+G9bD+<|dRkwVP<$onG0IU3-7I zaZc0T`1nz!`VUs5jiIs8GXm77!V?nc5@O9e{KL(klH$S+Ye2cR-{_{>V%inwdj-g6 z{qBT2;tQy)lVC7&hJFNn2JXUq9-$2iS&@0K6uN}$kXt!(9;mPL2nAb2iF!uH#5zA@ zT-PA6Z_!FxTMv$#=!-T|Pf)1mJ87Qo(t!B9j~vVqa%YNs1c^$c6qB&Hf8gb-6?V9# z2?sfWyj5ma7v_(ekWXeG>~&+H2M3V3g1GqF%jS0+Kx3kDFT9T7eB`9lv^PjFFx0QZ zE(<)g73kvXGf(A=7S(nt+E%ua)mkX~Th5aff?rq>f^21$tJO5@yJmw`+!O-5`dYJH z{jBX>b2r53-+)36v~(^_Q4XmDXx~Q)g9=(wXl@Vsb5SwkIQru)V7+Skw=ogcDUue; z3mFIZr9`+O#{@j(yrA~(Z#dz<?wcfKk6k#qE}gCp>4wJP@_oPDuj4^0iX(UXHIm@Q zZ1{Oin9t@Lyes!Z$w`xC&wsUU^QYD4L)wyWu%W6Y*Q5nMeSxh#b!o}8vyppZVD-+= zJDwd=AtNH3*Z+j?A#OUK=*4{uM6uiyj77nKaxlVHa1+RSwil?UV?zJ=;4qBeyyDER zduR8H1Qxrjo;)Ii#Xg$nleU6Izp4LD5H}Y4iqTsBq1HPVEIs<tT&5yp`Xonic=alK z$wh4<W;r)zTCtP=_Y93tanmY|nS^~ife)$0XTf~+ERBXB<&UG(Pb*GHlZX~VPzalC z%uCYoDaR&!X(bXr+%9f-P6bg5GeR!hBC!U9UeLPgbL3hD+42{A=rfURAMZ0fuoYhE zdTbJ6vJ6=bRJ9*i7NDj-XMm4|nY~?FFv{3(R_SE|^qJ(FPjI&jqwWHStIN7_ZsDZ! z-5M3Sy!6`-tM3Jxsj%jwH|D~YgNSs3&C9W!a498j=+bTLU=kN@a~`G<2XF8t$`h7^ zF6sU7{Y7C71-~OiS(I;Yn7^ZS=DU((!p4v?8lKu@%X3->VKa7qB0u8&a?%&2S`0hx zxtuv^9iO?(ey!Z$>wz>ab&4L_3q{O<VPVuQ@dS_cDsF4ObNWTrMI)89(7(q_cTWZ~ z#xEY|MB^@2je&EYb0_G9HT;%{GjY%taf-XimV{Y`v4FM7CY<F}NgEu;L<KRC2X3d0 z;$Y?K<a&kFD0#Zx_U4<YCBI_pMgD|E(59L*TUKd88TIGg{w&YUjv~!dXnR~eLk8wV zhII?@YwLOV1^bY(X^4hoj)fpmKk2SGQW`l%bf#tR%dJ>l-2L!OoA74>4GiavpO7kd zsrW~kHZ(yDTy0fgVYg)OKDb0&T}5nDkR!?UK-9`%&ry7;DiEpQylx{X%^ZxxXlEbu z#+bo_?S{U*OD{**$Z!l@V$XV)kAyOfj5SLi6&tLuIFv}DbT!h?a)Sg3zoMMgiF|sf zBBJxqGI~R-B~=G`)3KA+Y~p|}Rr+bLeafNe`X09sW4lg9KvLqMDbAT{(Z;72g#0lt zI4Cq{f27Qj#_w+0kjZ766dS>C9&=d+u6FK<(G$<*W}pBbThbHKJ45LJ8U~wMZrHvl zeBRk0-r_Tpv1f`KN>~%8^z<H&rh#KWAdUCTgNK#aPEFJbWvuj7BiK~aFn0_JV%!=` zn56=i0NboN_G?p<pC&{~=b+wpj-0{XZgM&ys{_9MJh8%u3MMVks-B$iA~YWGNUQC5 zf<<ik;7XZ5KMa1J$&Npito^Z`MSK=CjaYnYW)Y0zb7t1G26#)GXmay;u4>8V3?Lmr z_2On4u5Ep7Gu4c{-<D$AV>gWG3Frnef9y8HwZh-7E-_(5_KhsLx<>&;TbE!|qO`mX zWYVw!hsAUN?QLYrT<1mh{d!1*w0EQ2-aS6a4c+MoloZcF>s|T@%{xOSc+U~P<pYn+ z7r8ZGZRTo3rXc4w#E1(rt&J4%OaER@*e+Cetb@SBUpguZ3?yy}c<Lhx+v?AQ9X}C0 zwHPnEb1+Y)0>;(Q>`erJjA<#Hq})Z^HMyN3C0E;<Dw8e*rt5GISXg8+AB09f^6yzS z-a~zNsV0AnQg-vhcN*-?Ott}brG6H!{bnICaL8PrXgE5w!SK6o_FI3JQ`<lXGp$Z` z<swaXcxY>RJBXy#RRPa_8IT*&KgzIt(*1Snt+sW2hLaR2WM6VpxqyM0Y4G88um^I4 zJ!SJ1A}a2btiAI=nvUJ9i}JNZuINS~3i#%8__swnJ3Ol<{BkmQqsMZ|Y`3<}Qu2(= zZADMWgXcvRhN>IFDfhBs6amPS{I}Ly(oI3wDPrr8K)i(#Mu)3?g3=-zVfS`J6M3>G zFOo2g!LjZ+uBE=a@{xwgb%IPc$w&%f(v5G;0t+rQ7=Vg7xt6@93U$3?_zBLa#xfP^ zH%+}S1H~;}M-VsHD;Z^$UN&&IJS!Ns`??g_tkkn&Fn=&(g=`w>R8<w(_wo64Mp{Bn zksZNzJ*~s{B^E^8Q}iSnh7pO9rTw9u0}u7_LG8x=t%m}ryk{+A+&bZG*NXOCzZL1; z19+yJY~_9s{>_4a)lcYeCW}R7Fwx|BYoziVA~;W~eFm@cB24B-;48e`3#&eG`(w(# z|I9@mNuyULLe<o^oQ<gDlNI?@Ix|1zu~!f$OeEsV2}G~1t3L)E{X6hcB-jwo!lFZg zPJR7o%-U57V7jY|j&Sh}hPyTNG9-N?l^}}0-kVY96gFMAVSi`QZw@3NpFb7LB0w({ zS+$ZV4`qzum=Zn%A1>W@vB~W6y~R*?f%5CnE8Zd64`9n6C(sVi3DTzR_8fp60BsNE zS&E<uW1E1?1hp3?V#R!*M<}YlA5+)1%$OsB-ST0)2X(dd;<S;oR$)ZQC4ryQHVgv{ zMF(neVaXSjXdWY?FJ$)g>(+uFOk&x>d}jTo@*swCke~@`9OXBf)ow5dM;(p`i!R<$ zFDoQPMn4!gTdl<x?+Ew}qo9zV23JZ{xYOu7w2QOFizJufK1@>($<&+L+(24Gl>UGV zP_Dga;95T0%J@Mro>l}hB`j@b<qRVsBWxe2U~7>XyJm~C48$z&HrcqxX%!AcJ^sn* z3irEe)&=5k={lWz0I`ES8b<Xw4<bcdeKbsCa<IZ}WKR1D55JS!mZg+SS4=*7dboQb zyj1@H9_rBok9xR0yoawB-4P2R5n;$k`UQOF;&kWTH-{{lRJpGD>%Jq1aP4B33=^@# zOzKuko^43ki$ug1bQ`ky;o)@_UZ`0=8Y?li-Qc9)aT6o+_NVJn!VJ+IJwGD|CxZ>! zb*f!f9h9};fPJd|SxB|C#N`oCd0;>*md2kvZy7%uMtj9}IXClnN-=7GsP-o<j7G?{ z`r`O_FlDSBlYCU!H6zu)T8@G(ff$QpZ`QOj%b}zeN>)wIAGAVgtR&{;=$Iybwzo&K z`ZIsZ{d)$Z<*-`3<e*R^)dT1uyM-4zcW^v2g)N1c&vjl&&>}u=q|u3caF^T8*PAeD zUFZ!=kdW_jDPM69R<REUp_(3Qw?F)v=Yj&o{@_uesd(f~p&qGG8Dwp|7wiyy_(U;R z=)AIIi%Q05Y%HhwLt8_X6S=1Ov0~m3ESh<PNsC~sX;xA})A>y#R$Hl+aRn3N#2_Q+ zrfF5p4bSa&?Sgnp2t^A)<rRg0aTfH>>g2)GJXGJ;+onff%O81q>8{t+bh)()dP9nz z!Cu4khZOyKO-fncRu6{}m?-F08Q0{{-JJ&%aGVs7SPLv=yzF~=H`C^+4G}L>{2~Zx zbj9LcyCmHB1HK};cj`zbV92^)#T{$_JZY@?(Si7U_L96~l6#bnn|GJ6gxaY2#VU2L z7P{pc{3X*@SQzEL5NtiLJoGVbf5}k?Sqxj<j&JU>CN^l%8{r|Fx4xl^U;!s@4!ukV zvZWrYhpkgvcU3NLSaSuAv`Xsp4OxXsR!q9=hJl;;IlZMOE@C~U+tL4XQ?I?Ig1fo+ z0U|1K2S<7~zhgKks4+4DR2+hY#8^4*Ar><62Tra7;4V4+dy8Y)hbtB))Z9F{95|^B zYs`03wD$$u>{N{Ciw*}l?Z;(~z2xL_;Rm|RPL&}>Oo2Sp(hO2P0{@IBvRl-vln|=V zwYFLTd0)ItPP$F6ruT<}Rvca?T-8>eBMNPL*mdU7<Q#LazIXX7C3(2Y{YzskH07xB z4s1L~ChKlyg=)@Q`jWGlC|T#ABihCpdDYxNY}!C|LpovT(iJd_>re>xOex`Q)B=KB zV8~f)vio)dGzg&laaE8cS<hn7B_HZczh-!E*7<H<4M*iLtPaodf|W2%fh>utAkIvE z)ZV=%b?BkpVV5VWLs@SE-OZ~nk1aDcF`er~!15E3#;ergy)SCVJ_u;J>YbQ5U<9i_ zP#&3nm;Ro)n6d>Zquh_4xz+I%8vSH9Dc(_8g%Zn;l9t#8o5YPWBdM?i12UNm@6Rs2 zKUpD>UBT0b2Mxnm%w{Ru<A7j=lTLMkI=!8W-r#%7CIkyh+U;ycgi^Key;N+~ZI4RQ zCZawcj=x~Zi;CG5iq@I%LHagVMmM*{(Vvd>^IwadM;P>_-T3b8jHWsT`$>kj4{SRj z^?6<e)KzH|AT;|iC@oO^=}>BZ=yHHju7Mr;KL(BlUSWJ49AkI@9aJIgXIo9E_GeJ& zjS80E5HMC(?h|rXq~&qTprT4mEFa8qKZ0!mA6dOWu$+`HRGnMLy#%02msK*IL0uln z@bVrFQ|PX!_AES!+~)=j&;q!>(jFVGGD{dh(X9>i>lzN5J%BdaRj@H|Moec_6PRmZ zS@@VnY@j=C=iQzR4Oc4~@@v2=(`3&<W_1lz`Y`<bX2>cq9crW4q%`!BNOf~oG5wC} z%07RHaFWHlqrxz;M_Rnf?hTA(4%2)VGQJe-Trr_@Oh>WhDBD7Y(lN$YMF#rGd29;` z8HFkd6f5W7N(+RM&xBdM#dZ-H%mTT<h|@Efo~XIoMReOui(-#uRVq(5);Td6PP<&p z&``wo2kZdZD^}Psve?e=f?4cO6oaf2+%ajUjAa~48tF()Kc_UvV8@sMD?rr009pF> z4KJn$l2ce-9wTQIZw$S6dU~m<NQTf4gn4iPH`>p}SK7Ru;3QIvH#O!zsiFw4gux;K zGllE6{cfcO0f1PS08v65-iR$l4DEToSq5leq&>$-3q(Fb*t|<jUl)xZanESOOC9m~ zgLZW-8M$UHH92Jgdd14_7-sY!U#TAX)v_SHCI@yO(TnV(b?Z<=qu{*Og99UrDJP6h z@_P1cy&R0JnV<PnR7H}RS!c&`akXNDXLrusg%nbVc8~hA4UuhX02F7bQ5*>sE8)D3 zDGP0%ZIo%o=KG+kFQ&FKz4fdmAYs9M+yx|u4^YefVKHXgVOyCzq8oZaT?ao$R%}BK zZds!k65$vmPk|$624FrTH={L!AsP5X0M}@9tgD^Zg61_-ZiTQvX5T4Lx@}a05gSd! zMu{|5<;q+C2LK5`_P;6l3v*+YA$z=rSa_JyLh5DJLw7w?=Ak=DD{!*f&>%^%w8Ie# zn(71#+VmG$*jN%ZlW<^m)-Oyz?=V+w93t-zr4^v|s(OHtLN1v*S9q10wH|dTR8)+6 zM}6vJ1%G}E?>L^68_Om8`6={}$&;x5ECEYdU77D|2j}SYi3H5F7U*#}8q)?gefO56 zX(0a6$APd@s#D7;#3fvbKX*yQMz3zs{JX%ckQu(G?RAWL_Qellqgbvkh*KTcu9d-< zi-#AeBRM`v(hmg=F&uD?9PcCscUK%`#Wv3!_FG?N%)G_)E)=H`Br-D?O?{c{15BQc z^1~!k{?o3h@FMxi^ft?43O*g^$)@vJcd<9zNa3Z9e3utdWPeUm<1VyvrjNH^*h-Xl zT-U(bMQI3&1`v43)gU3#+p>aMX3;?@E=)6lL}7@mmr!G|I<4d>6;Taq+-)na!5q7u zt1il}nCX~_6((m|mtv4E(ta~Nr<jFa7DQ<Zv|9hlgo38i-y$&Z7OZrr83S?>uWt`4 zD^q=h|HK-1W2_Bh4xv}~Y<8@q+`FOAq}ml0$?H}xWy9OTiP9AdfkDa4JxgxV2=X_N zD?pziAySP(zb6=g`$mavI>#YH-vdJ9tUZ*-<`4jET8ggoS{lNGbTB$*3W-2tgDlnA z_c`mmFfA1Ft6hWcI}(1<^r8=u6ofq8@slbhwwa>^&kXKJkUtGo&*uZE9{@=NC%8|{ zQ_0EH;dZqRxr0H-8fK5`8|{o{1@GTrGF7#xDr(=Tz+LVGqy+^=cqm50BljoOMzhr$ zIHCv{5U!n$!K@0llR-Ngx1*XMF!|Y2Gsv+9-aywU9-lWqa~uTAd<G^OR8Fg01i(@U zkY-g3ZOE_SJc#~5#mLyXj~IW>QLB$~3&}wHTMhWw$Q0)08H$hcn;!nk|1pyA@gon* zmj(-h<bA<_*V<MPVwe)Veb2eZg+g}!bg^~ToKaaI`p=*O7|P3^Tbd@^146P=4C3Io zR68v^aEiIlF~WvmRdae~a%21Th?!5_b+v{P=gk6VMKlv2JZMNN{LXMW!uAmgLeTdO ztBMiKI-&yq;@k)f<zgSV?JH!=rVOTbV_H@_c3}lF#-V=9!B9<jI|UOD?mnWXE9xvP zoa~@g+Efi&rd<=71EFCo+yvA_p>~Moqq_d3vKl<bh{7}FO|~eyRla7pf{R1nxYNK& z9+qr`3`uLzsIpk=;~9sWUA=$v{f)KxQehi#@9OQ<e75n`iY@KipfmA#`JBKV%R6#I zvs^m%K&$k%SG3KP`)nQmwEq=wS5B<CuT2mmO(R=h_Zt}EOC$KdD@Kte6=}qKs4v7O z2HAr}{+;H)RYAbpLOuuO1;kVuUU~Kub`~_}puvLs7x9E;dD{X)9Li6Aye3Tn-f}uc zyh(%~*jx1dQH(9f9i=l_-Yf#xzR(<=mX>d)1PhKY^ZUVY-A=sS3jA(p#US?;6hFY2 z>x#Nmo<DDDIemvAzV>S1Ea6efGPl|}z=PQFYvx`j1L6{gb{cbrgpN_N0_Epk!xGBg z7+cee&n{%nO2B%fmT#7Slp9sVvFJWp3(?1H6$Xv~j{*gnItPr-fMtWci&Q*eK-)6> z&mXW08G81E;eW{9YqIpu!4dx|vj)D_B3g}=nvJ<Oru<1tSU!wB=$P=I;(l)Xg?TP` z=pCHkOk6e%&?ugufiCQMtFEj+=RdqX2PZ)k8tj_Bvc2K<g`y6Qg^H^#AjHF>hTruD zA2f`VGx8HtT|*AJ-FOAtnJ#<psC%9%kf?8Wygc@Fs5qA9DLncBkSE05pzK!D^0yo~ zx)Qh!vMUjSqf^6}_d<tPeEYFN8H(MCnq1p`=wqXl0)SmOe(bFS2XE2_US{lqJ<~DU zS-6XhA}J$ASUQToHPxYd|KuuQU2^ypkAO4$6Q3#$)0rE)NNPd_hjze{ooO4;f|1@q z{YZP3PcU<C`TV=zYQzn_XH5oo*#`wyP`ID{a?51N#dgRl3O`wVa(R_lBl5UY8SuO1 zX0h`@JMd{7BV&H(8nn^SA3iD&AARm+Q@Tf+#65OvWSiPAmBy}vz8rg%$PR|F)0Bcl zeR=@YzYr+Gn%?qP1KoS(*IVgaaVp9`#fOqKtzr0vHk`lk+ABh#_^R|iQ%p)}e|m7n zQL;S43sgDeuNn>$DRw-Hq`qg*$|_irHdGBxZv3^dFGENBN+~>!HBZWQY3<B(NXT7^ zvkgtw#e4t5Heye@(wG%tsj<Go!QC$*CLnB=vr!Cmxn0`JtQH`==GKAge47%j$}~Sg z798XOcof!?aE@LLW8dv<tMq4KY@ns%1jm|-z^#THU={Mg)Z(<{`)(laLc-C4?hPz` z^2LfLLh#=O!8{uLSz$9Id)PwUwo|hr<EaPaSOrr>^(sN6Q|2Vb%I)}$s@j-E_tXT) z7=0CsEVMMlRxj!giyecy$P?v@5}!}F0qQ=%axEk1k^_&a`wSYXBu$Z8%H>G3qwM&| zB?OfSJ9^q-6qI4`C3*3f@M=FRU4$MquJ<V2^y}^}@ZTmp1?94@3@Cm?mdyYLaTh*F zWW2(45rK)7X{>7bd?nY@RP+Jf^vmBX)ZkG^t=@J_<mP4acew3aux<8{!chj=I>PmJ z^r?CO1o2&4U8g`{{>89>#SYyG0B!~f;l^nw{v{N_nve@FfHNow0MacnT1Vt<8=?X( zlED7VD_F9Rg-|nXp-Y~@XFsb1yLwVBdLrN*h3$_VRtSfDUuLIVc9FL#)Iz;x(>6H_ zJEe$ww9jkkrivHYF?K6(9VaYpG~c?M+F%i-)BK`M2?_R#?Pe*Ar*AM4*ZzY8UOe@V zO)LWLfL@OzJJSN7?lS3bauCiWXA|S@3Yv!$OQJJ?*3!em9hyLd5{Ir{&pyK(Fp;i7 zC$u24_j<!s0lBa(<+7RJ0?X5TkxA38V*ZpU)P-CXqrm!ynij`xhKsKRQXV+~xcRbF zdMNMEdz@Tl-mgCO-sxANr!$7Z5&c*Wb5?#8j<^wL)$=M_%|(%-|H_DZgxyemnf%46 zPDnnVDje~6G!D#~Tr#?J5_QwgQds_*I=NinEu@+O8JuD@xu6gU5heKO*Sz<R(;p)E zu))xtdS!ia>z6iX%QJ46MX5w_vXV(n388!7c~C;7+&#LMY>~f%uSPP)kzbsCM~+ED znpUIF^#*Q^I0F<}=^W}?x4}E;2O^V{dPD`%!o=@nZxowhL2=p;xcH^Zt%|cG$l4hg zX9J7&qL-73JeY3Lhaw&Vo-N2Okr^q{1M>J}htRFK&dIvLPcy%lhp`y@j6#PjLL^=z z{AXThoeUixm)StKs}hvVG^FU`B)&w%ars@%>y^1)K9qjolwjjMux7ZO=!6!!5U8M` z{8}5XB1rg1J2dbPoNI-;`W(14V@wH`@vg~qI86HT<Yt5!`xLYDS*c^3z?gwR7ZXQ+ zAKT+Elyp0HCezC0ylG-7F1lw`<5=+RO8dCE95IKXA$|m!o-&-Zno*cwUU>^eh+1Uh zJ%!+$h^_0bwGs~AV^M9WYABwQ@*CSMR}YyL4KsI^1eSeKaJTu=RKsmdyjc_)x&_;x zGG)R7Ew%YDHFWaxER<qx5%#t#dxi6n=zvp|#{ei&K{>XUz;s?Q&K4%v^G=Ls8Ssy= zwl0WZM`O;nHyRjCvD3ZeEE2M6B9-X?XEZi_KY_N%xOk9$wqK`1P_d+RPLloAOWllr zgh=bLguYA9A6;!&p1E$ly8}0SyTF+;PHusb@wNZ_<Z1Ml!QSQ2qy3~1mq_weV<poX z9;JVZpmL2Ki-w|k?3@DRFa;<F2tV9Cn5<Z#*izZ)-!-bKZcaJd7OM+iBuwt47<h40 z2kn}Y!9jrSj~v=9N!QIdG@<}YHcr}Ga^C&S+#MBOSi1(bA=Nqd0#SVEwVI{MgV9or zj`g%N^FZSZ$cOHxu!vOO!_hLcR+yY&lo%%{E8{=jO#bq9_AYV+oe--xyDup27w!K; zP>|G*J#SxIy2@Q-$xl-=K5q?elNzMuA3@j*h51X+QX@H3fKX;PU9c@)f)4ZfRDVsH zq~Dpp!|q=y1LAhtJNtE+ur6AGF!VE(MIfzBxyGk7{NLVv+NUX!!I-SHem?lS8mFE4 z%M*5xJlL1V{6;g-<Q}A5LB;O!S_dj^T>0{m$u`eSG2~rc;;z1g>Ev`BwwP2mVbvmd z6`d0_<%*1gH;n*?+{y4W=)_`##oN^`Wcs8x&X20;Rg*8xwcv%p&UDq-m~0HCILKEg z#!ps6XBK48y&cWMR?Xe@IY1FYUIjz6A<%jAbQ>wx)z&)fRvXx3EyAwV7bbqNWdF;$ zvPpWmP+WHIybcE6F*MmIs<{Qb{q}<dmxDw&uwXsDbuyA!$7z$QFwv_rw-xWE0i9}C zBjG)R%!C7)alNJqxIw0dakC||9h2vIMV0G0Km!k4t%Np`>7<L<=Zb|F!qUv3RLHA) zUz@BN=pLqIw3*P1_4V*jZJ5{lkEfoH1q|omAYx|KblKlBEdos+-~p}oVe=tVOGr1x z9ySa~xy**r|K!9HoGmmgr&Jo(OC}%yGFs^BUm2&`?*Gn<4DjbeL`*-hus?$Nkxkpq z=<T{8H}5-dnp|%g2rx_sWXEI;PPBRM?jUA}3Lhy=yg0~TnHSPBid&1wyQJ)=kgM+s zl`}N3w5-6bzf3W2h9SHSP-lBbL^RM$GW3u>_UPD8Z0ge<=^2VK0mDzxfU`ejRB8N2 zYceIHQ%tQ`=$toDfR8Qqqqb-qW4vgmLOn(tI;g>7#*?t~q(fzB4$?S$;E~W{P<IdX z;q4Al=axh_AXh`_v0mQ0c%eXG6QWd6e{`(Peb9uQOCA&Y#2DaN+##;)J8Fl)tDuP1 z)f&F;asx|mh0tUlF#J-&E*k{EU>fX=G<=TsN=r3}Px+~3ylHD~zubHMEa0{UkUK(; zl~uzm4H%SJY=8{(YJ6$7ys!=Oz@f)z{PH@Dj{oz8*?4SjIJ8eQ(|=zU+uzCV69!QX z_f`EK2>r4Vj^xS8i|~IEuAPGZb`|O94I=1X+CER%48;`hI_}0dUE)1m?Ni2F%k4Gi zxsAZrpwRyLaA1KfJ9gZ8TU1(NRqZE<z0;5^D<gz4pNb{~s5bKD**l5^Ew&&KR>o9z zUJ#))NSY75VNpWmF=~27ugj?$a@Rx(dkh}RVw6oQkyN6oPkcW?hLs%A-Is8!(^>{h zv8GBzD#;4m(?`MpCgC=TbgVSuRRXwfkD3KyX1r>(mULN9?{&Ka<Kv0lQ8wARfN&=} zTrz2E0HtjnysEV*L%Fj*&+Rv4(fnXk<jUSA=na+-4Gif{X7@R|(f0vmsM~u!fcUY& zx>z*!qIlGBc-C|nEmo*VNMNS~?$sTPut{u>_N_P#`vc?(QO8R9W+#7k;*MjuIk*B@ zWQTB7Mae+Z^jR3#)<*|n;V-T%`r>V}%Q}EpjRz&A3fd^b+%->pasumXcnH~IHbwO@ zPh^U((5#n{`C%v!+f2u!%ry8?rlyvG%A3|a+s&TP1+Im2tEGw(F=IRA;94Xzy=jj7 z@}^rs9`6*k-l83#@|Y3}mlQWD&Vj$|O~%^Kf<ML4YZ}oNC#M`R%E&qt(qF)BjUt8` z5-{sPz#Q7A2OdpbPAGrOqe_&audC6Ygu>F>&0FvSTpbiQ?#mCBRD3$M@IwWB59e;6 zh`=k{2$yw6XG0<$HUx8bm7Bp=FfPxzwek6KQrz7M0Sa;R2MKqtkIzD654}6;xnjbt zD)xSn>?@IiM4<eeHv}rf+vr-~&o9zLERBTY7ANsu`6mf0TNaLZ2Cwnw@nHw27E#J> zJFI*v1JCuTj=59F8T}|T3F!u8SEN>2y+SJj#|l&q)-t=qP0l@MyCQYisu8o0<$&m% z-7!vB>FLG}n8g!TYsRUfPA|o}9$Ya<?h>$syTuw=(xyT%tZfv7L1R?GdYb--O$n3Z zM0}ENAWQ7d@`d8Y$w;puil{|{U!~%a;E6n6qauwQA%arhRAtvQ!|ZqkVzav#bd}w> zlbsdz$qfcp+dT*z>CJerF6xvil@38Rm;_IE$?p@#e;JG10;nk71;d+v;y*7uCYdA( zkYpR@FUYQ)gInWSJJt8Z!^>}Rd0+!e@7U=HF&?t_4kkTdPE!dVdrd=r?|?D1`O8a2 z&?>NAA+M99T)R+S?!7^4=0OJv;eoU)H>!K7ptPuV#}SB9PAXUF-M$`H9-xd?Rp0r= zlPd~+$8Nd)aB4Ahb00n!t-3P^r-yv+7It6dHb#w^z6W%RjE60_IfxyiR$O~hBYHPd zb00MoKR|$URP3QuNr2%yH~twW4!BnWP~>we`+&biBsX&qdFXbCbJFR+g}bnW8WfNi zkTCLZeLNGb<l^@3st4r6XRMY4NNvpeNv!HKN3$i$yh7{^^W&p*gEf4)I}iTh&?6}- zp&{XW#qX}0o0S6epdVBCx3=oBZlt!c!Bbdz4ADH04NKl!0)Aim=V%k0$tp5<WUPf` zrT~VTV1_c;F4=^{Dp#e6ir|+=(|7C%?9~`LB9zSv${D^or+}Hl&yPw%Vqiae1F*uH z$aVS5cA58DaZ~js09;_xyTJME1%RF1Yi7e(z_I$U5@Jh(1oJFd*>cx`EmESVS$=;R z4qAN!E!Nd&N9*<d+++og9H08NDifAdG+r0$hdVdj@L>oUaQn*3d;zLM+mmJpE}CS5 z6ZAXkWF7>XYijq@YllUBV4Y$4y*lB6uHz906z1a~Ni$FaghR&a9I<U>=fL|_(<*~c zu%@bZ*=ma0UkrZB%?66PI}h%44=^cXZul3EOLE=^9qT!Fr4x#HSE%|=fq1Ye${WM( zJbry){|(lF&r;Oh>_y{~$;ZKz=(L6tE_?BnkW>T;Vli_RC4{}PPu5bt<fa}Y9urD8 zeiksdz~}CJHEloc$QS;^B0iX}Pb0%1+EuI9d@%b`r6Z=KIBRm0hj%NHzLbl4oA&e! zo^fVSycSa4_x~YjHtx&RErLQJZp7Yv(<#@xdb^MrfDX90c}FSM;cKmJGYfwgd+WTO z0jhCws9~KBUO1_jXujgPzd*olOIL4j82(iN8I}4RMO7!esZix=GWOIuu!NR&`$7o= z0oC1m2-51=ReQCoqgZDDS#hevb0$?0nh(UO&1TAbb|~a8hZKinpMp&Xf(PA_bP4bT zcMp#1Dos(1_5POIyJFn7xmno62SWnZG@R}G&y<U;x*aw^5KM;^9_*a3&EEO9?kV&{ zf7qr_%>l!dRR9@T{r_W=&4q1ee#ocMcmf)O{&RVvXF~k<g~*fu2re3s?u!f3@KJ>A z%(Xn85pKyazZu!){%Rldd%szg?=b1n${p5WKROZc#+4@=>(WX&nTv?+jm`}&y-TxH zcy+o@pp$VNTOS5=71)seS=FPhCr9cs{<Iun_Krx{OKkt*xFXpk=6ewFd3r{MvVvd! z?zh{xU4BGlh;!CgPZr5aj)=1Y&~6ed_-CpMMU9U1f<dRt!x1-odEZs$BbqGdspp3b zKJ3QB?DfwpoT(@LCauBF#npv`#Q>bE!W5&47Ve@Ea1h_Zx#919Z}0$Bg>1nA@8V5p z9(S}=qJ662Ln;!{b9RzaIIup5<<an}8UN;@O<6Xopw716{%~(N8fT6+ad-_j^k&5R zfQO^>jo#y$WzxG$52t`_teF9sgZFyNCtP3gdNCx7$5_Nd8xxHzJeOcj(*dnNsp7b! zG4?w0R!=(2)y@1(JH?6S$Gm?0JqXE{`kwb&JQ_C1nQ!$*CD2o2KBMKrfT&jA=b(fV zXD<dYrXm%FY^-;Wcs|y`AbT1@m3KquqigpN)T8AFK2zBw>A6;dX#6lK6A-2KTgP$@ zHu9n=N$qc`{M<Vy1*3P4`+7Fy5+$Bs3s9|<n!$ZmV2y42*11}BO@Gzf$j0`^+sA4g z@|wE3|1VV~6vMPeoY_~JMwvYuKFZZUWypO{)z33Ym2QJM=Gy~+pxMFqA6gX}M+*n8 zhk<i0q=;QHDHx|sgj~^&tL*kAKK|nD!J{iwMeTR{FPM*KM*Hg}yS~w5>O|$u1f$2% zR7~_34ZlPPYCd06qpWe(0>D@4Asr>hE%S6}b55gR8+=B@zXI@b>)d;h3%jeR@KncV z7}9)IAr{)$(qINPx;+NdZBh9Y(9W9X|7)3V^S<so1zzw(GotENCtj#GFdm=nJn;7W z``7s!SNL8>i_hX_Mv1*0R!67++L4}c2jw0ZHQ5f)))Ggt&vPVFI->)~^fBEkv~<rU zq{@(wR@wLMb&!z;E^+!pr;g}+C6Oyde8h1~rBw?$`SZDC)GWyUgTzl5tp!_4Z)VJ+ zl|4fQ6Hx6eA^9I8MpzO2aU9@y)Ds5m$9h~mmovuH5kfBlq0Nc*jgd1Qt#)x7-%{DO zEkpwGq2Ldkqd<2QFz-~QQtcpZY05&ko>KZ(FywePo-fD<<(VVc{i*gESy&+p{@2ze zd2;7coR1a60WZRjE!MOig6_xI1&V`_Xc=InR4qgtv#QYYLZ~7v4j<S@vGR*HR=dJ} zgaovE<if4a#xMrmy5yq8wbUJ60}lO&%q1u+1b@MOSEmR&mF%Z}o5)uxM`|Bg3xHcR zQ6^2&%~OM>lK)7-LKON5WGQW&u@>&C>EFr|xCQdN;>8rzG=N`%!I3_NxmKrV6`h5c zb6_0_Da|?rx^dzL4}2DQ=&7zmo|9XLySUCw0oQ&F6~n3reV7@nl|jTkJ)PowYq(|d zY|y3Oj)%!FX4t&FsXJ(G_!tzv%O@X1KT`ZIXz!~~ri6$uTKeZo0WB*ELkKZ0ylR}8 zVPr2;l@5^2p#oMZ#c=CU8Gy4~Rwlu(!$7&8?LcFf?f>rHGWV<O7$lbv!r0^CnxNNK zW3OBnH^}o|h1j}(-vXvhY6TzoWL=Tkv8MvbR<$VL*4ZqHyj6kjYqND{BMRWxSTTQQ zKeTs!Mjn`<mouT$$WFn@$Ntbfz-#XdiUy7JMHbMee$ki6B!7>gdD0!xK{T9X2kk=t zA2Tt#RHG5KoRctP3EU}Uqfsir9?@l(noD#LCBwo5=dm@Rh(hA)wDwH)W60wdb}GJ^ zxRC3^8Xkrm?eDad8p~utHorGJ5j{&cUD{SYDbqItO8o*d@U;AZQ7|M7wjWmZR=%<~ z-p!r0s>eeC@iA5>P_;nJOM0dOMMcYnIC51EphC)l^$hwY8j8GkiGc;i?+^@x316?$ z9#KelFIlxAf12MsU)-UK+L87ztS>GfDeux+co<m2T@IfZn}>Y`*rqpQ&J2J#$_TW_ zKPRSWL@!+pY*v4rTNP5FTtyC1i1!XNz25NB03Zkv$ALbtxvl+z_0NU)Ji@;A%K?|6 z=iQ{d`Yw`Tqa~j#6!LCn3l~c<<gSlGbv<<|kKF*x>pDJ&h$MG7ruTn}F4QccoBn6? zh}e_IGV&R$94_rd<+S6@USU7Wj(C<{oYTcPCPxvykHei?t+wBO0U4g2I5*reXh?QI zBDh>OK^v<*a(q=uh=Xu1UTZ0NI8!YP0LYwg5ql)wL+(DHUA2&G41H-cP5>IB=UL-Q zr@O1(f%?~cFfp~cVzN5Mo%KD}!ovbTgd7(Fd$#T7jkK>F(ZV?4$X{3b6{m^5J|yZc zzu#;VWspa4k_d%t1VzLxv%p=aUxO6XC<g*7Bb3CDqoA@zLkw+j?Ve!*kXzSTew>C$ zGDbVU!#F9Y7<3&=_TG&7IRj&yAT&B(%J?8!X4zT3A2~W3kizQkKk^S}>*Euf>s%tJ zAYPufIQ`;0H`rMkN@nXSY=wMU9{CRj`*}xUlT$Bbty=CERUKb6yZ2rg(nm8t_VBgP z%ncAnlG3J0<}LXXvBDhIket~AIz<tOpxb^tml-VPT?SfJjpuGnyqJBG%t<P@k?^Z! zGRN^n`7ma}XBDAaO$P0CZx{A+Q#3GQ>Io>&jVuxOX~T#xEdT~X8^4Ms{?wi61{)~9 zut4|n3~_>B3fM*f73P*zz+C-m_XTu|?}JWF{O0(Mn#87Zy<^7?QC$EsAh3Ec;0{v* zf;unXhN>{Q!S~@+i1Vz$^#WLw9pukxi$dr;H~1*j*TnEz>+~Ltw)Z_51$K<Yj?l_k z1e?+O<ZY&hPzh2f1Ktys><#$dRvsTcvh!daqWKL+8Bw#6M0PGqX8zaIaXtP)pfI}a zKi7r0R{o~3wX22oR+PAlPuDTdq%}o3i=M6>b&NGGhxIbCbo+Iot{z;a)%i0_#Bfb9 zWAiJ_|FHEA1k0(SkrFy5Z#09ekZh~T<UQf`F6;#Qv)(|MgJhqfx;0?NUE1)3S{+*H zV>hjqy<}~S`sDf4x41V3ZekYsO869M%J{-8tSjAn*|o_`#BuA&E2)tB*YbV?u%Et$ z`{U26^lMtpS>Loa7LQ9#Ey|wxSEhc+hC_<YehQ%s=4!n%$%ttwow`LK-{}g9chrpy zjzeiy!^pZnbF^|_b$7SDB<G86GfcQUKvuz-7u7`#U~+z^g`WOuNp5_17di}JmJDf~ zOM98+B}#+a0K>S#2o#%u0M9VJMeBX{$T9so(luW1+}(S-VgOX5RdzZ<?)n$teZ~fz zRtLc4pi)_e)k^8UY_oaIgb@^TN>K?<n?<^6rh0o?m$$!oB5Z0kI2%w!mpStUw=;GD z#99z&uxSN4s%$-&LIM}1GGk>2&BqrVyYNpKGEgujs7(;hUgmDWhwP!u<WiW8V;>J8 zCT?(l2#^pK7F}puMRsQm!Ws_<7Im+8FWpA5Blsgju3KfUMHrvC#Ux~qP_tHqiBd|~ zG}Ki@3nojyxsUOKRCLYRGqcbChuF)qut~5|pTL-_YSq#0t^Xm1b=%G8xUUO6!V6^i zKl5plj6k?ztwisdLMQX_I6s{#=IT|=u%b>-ukiBF%XX&xSy%u#fY55k<>#0A(fyfC z{)A#0`v;|+!5qmOm9^9NpDs|k+=eE1N*&uwqv$E+EFerv&zaqYxV7!-(ohl<xp}%Z zdA_hwMRK0`*M^_Ov)HjIAk`9|qKZRQDQ?%X<j)@Su9SLN{2$x3H#Ib$g=Wwng6Ez3 z50IEYY`@|}v8lj=(B9HQ9?Tu!?8C)3uzED7@uevp>At(_LgMy<0E=*l5}rmp#q71a z>Wy0+=yD*GMv!s~`D)f94aW#Wp}!RlH#H{DRSDYj>U=$VGz#_9Dcl(Svrdc><V!<M zFXm%Beo@dk_~m6<Q((AToj44gjY3yyoEDU(U}(-}OUz!L#E-QHqY--X!l7ZC4cH(= zv&863Z*}Ac3EUJ)P>MkuEuYcERZ3uqE4cs@tFd8;<c#st$wkPu%1dl+=Rh;oxSgQl zEb?oqNT|YKk_lVXpbuu@%+Ml1l5gU!%@k#c+rt;7r+IWmns|6ddg)(0szy7k1T*$J zSBnH<p(q%k1jTw!vck$viT1R6hb#Jc1*KrFs~3gy4uhnta#1;05Fp492FD8I(E1U5 z2u-Kwwq>k*)V`FPHv!#?ICBuFN-BI|JhV+LgcxayQ_NrK8`gRmGdG->32Re{xsBQ+ zOil~8cK%@}mx{V(QoGy6w8+GP>yO{L*fObbSO$i?x9NHxKAxDcN9xYQd&m%YQF0&o zs6U=Bo?<cLY?y3sO3im>rsJz1{^Dt9YgyW9Lr)t=%)ZO5tATI^lErR|%mpJhz)=7p z3tBIJYWggV#I}VJkQPE<0#|pWll9(u<xM`<Ys|0+Km+TwMPzWfR?6kHnXXuC&ngFi zTDjhLHX8N&M31<;*Vi2Jut+ezLxOh=L8+cz2?%<LWF#+<zph{OIqy>$OkFvE8t@^T zY2s#35ZNd%#)*oLT(OAmpC}K;x?*PMK*e!Sfi{QVRPA~W(pFgSov-YvSUSnx|B3g* zinLI*4$l`(ssvE+q{eLx*c*=<eb?0}?aHEz+Yr7{#Vlc;t?%>qou5hjSYEXl0#MpH z4bJ+H7UG2J<jL8hHnPCGGj1eMy?1)&5e2Q2l0L8_y+Q&cqoHp824piTpIroeuiXfO z0dhh%G#eDriYSZavO%b%J!wAm7GMTMjk<cJxS8B`SEBf1JS;)Xsr6%-n8~oU#@DiU z`GHSqpt$$HXG^lP5^$3@4wRec8vZcRP#gDsHYFShv<e_BjcZ>Sk=y&{W8h%j+P(Ir zcf!_)tN(b|V>H0*z-?`|X~jtMC=XUpg{n;Y30^p7#{Il)1p1S^d0qcX7BzJ4a`OM! z{lyLo5VLWM7qMw1&wY(D;dK)njKqicdJc@7f1O(MmkVQa&#hWsSk$JHq&vFSRJ>ty zotPPX`?`}vWW0E%{^l=?HDd&aK_hzGcQpwkhsmyUbOe;`dG!FubaQrXOlCEGe0br^ z#j)K38LDQ-xehK|93+S!h91>7*N~MuoC~ul6tEd0@y>O1#Ama3aLZ<5DC7bUn8-S7 zlun?~FG*M*XiPOnW-%>#^EuS`r&;X`{E=33e;{qFaH~UAPizr-u9)P+$Y*=vhfxHM zhB?GjPOCUOb(DNr_Qo2m5D+gaod>u0`asW&24|1B#7ZGEk<E76_${^@BV*MT3gbnx z_1n<OmT5ZC)@AaS@0KOy9aydEIRT);WNkks02CuAF*p#scpUN3+;qhr^6FP@CaNik zg4(}Rap8ODRb_jjS{VmM<%Y0KdLCfkiEY^2cec$P!o8v*TdR#!wLYY<920vX3yQ!m z`b}C}9UqmtF5o2Y7jl5Y1pseTy){W(o3#{Vo*PK*%RVQ=t_1Y&i-9i_V|(UK6IFB* zKf=T%-Rn3e5Fy>X-ji|_pG{*K!y4Mu+`L&Ru4q6y$I|Ql-h98`kSBBLpNU%GR4w_s z<(lev>*1<H{NA7PDP^9WzOSos6mJs$dKZ#HqZ?_o*e=Iosd;XebvmfI)DL3>F_yZb zR|IYoU$XzN{k9DO4dgqrAZYfl<ioq~i)|A`EQD0R)?4X1QxV+$_6H;q6^1+F0*dzR zvsM>N{pT`DJe`2aaTm?86w&Sbia1+0iRaI^K~R;p+9YZI?Nh(SR1%`^2LNy39{=#( zz7=K$f<XQrX1p$^IOn%7`Uu%$eL<AWOI`0Wj*w-wd^6i<hx{lOA_F1})M*=zjNB%M zoJsFA8gaB8*iqo8=OtFV|IDkzmLFie;uW6D@h-&UqftV;vYc#fShB`M+OQ@TD*oBx zypt?roSNKxzq8#HA1XMPMhAAJWE|AWP&NETpl$S5OOPM+NIeL0%eTAEO&LZ4qJ21f zK(h;KymH5#C+jwwuBn;wAV5({BeVSehB=b&yAOycoo4OR31!(QENt-6eH$AV47)zf z9jkxH`!KFCh=Sqs*g>hSjb(&)bd3Hsd>*(>LEuwJf3Cb3vZ0}u&q(MI4{lq)zJO&+ ze-z6CXtmVv#eTuPJEk^FaQ#G2HZUb8g!TSF3Ory(5HDrLNs88Ixn3nsy|BMzZAO{F zJ-jZoNRHIti+3!)9j<v%s2MmbaWAtw{4^7d5V24P{wc3PLUg>sgk9RIu4rh06~RG( z!$g>9U0WXG7GSqx84|HAi2PRbozJuDUfqvOA7r2i)Va?{+~l@U7J&B%)(HvA7%5PR zu&@mEo3@OnuI%D#92Q=3d)^Mh6<@dW!M4^$$5A4NZk%~jm2|?yo?ir{U>Bm94Twf) z!ERSUhVDf4{4<`S@DPae?J=a(Qy{$%+0R(V9*v{aM^SgZ1#Tl<QZ;CCRF-XvLWhr` zD3RKctA}<Pd<PDvQ9$51&5Pt6vqU-32V9Lhd64<~#a6&u1_!4t6fA^GstqppGWyej z(&S;tvD4^PfV$AaP=QZXB;4~j`sx~dVZ){v@vfAK8Y(YPIw7<06mA!l7l@ciFhh}e z%*t6agSw<jAp@1`s9tR-!Z(8P#!;x~wAX;Q4a-id-X_*P;}^jS!Hbh4?o5Uc=Jr@3 z86~uFRcQwxo`ICF_?b;(2z0pk0cld{dnf#=PIXZx!xh4gc>-1)DZ;1qhT8OaX9C6- zWiM`S#&?Rf&`pj>9tL0&V7PMnE5Rb|_G}mlH*V`o%@>8yX@wL8O7&9(p4I{Xt_34e z>Ha!JYAm5HebJ}#11dF0Ouh=Fs4}m6n`-?{yMUHw`g8doX>$0_NSC|2cA&6f1#uRD z5r<oOA&9qb{%&jXb8JkHbXGQmrKmJH!5#>dG@Q-*s9KlATA|sAMoXd`;8H7#a{9!j zX4)<SlVuU|#ladnno{6=i5)(a)9p*W8(y>VXyXE2>y6+((W^axIdFEe5ZU?i6!^L$ z(KXbcaXm}rivedCeLQOkUCexv{1o!aB?vL@o~BC2)ULGj&hfw&6f7ExGkW6mR@I@C z^8G(XJfy2T^1~*UFu}Z8$*RC8ry+&E5$?dCc6efYvJ5LZtyQA;kP1E}NLONFDFTYT z2>U1gDk1rD1q<?#!pZ1Yox|7XoG}jPD@u%E^<9sZQ|TRH1|qD;y!avcL+9QLzmD{N zfzS@U+}R*EE97Af<8ZXrJ9q#X-L%L6h!`Wf5&r)#>VP9%?6dL`UdHJ`c+3;shrW!H zS7nLSDxpdtPqgSqJ3^ZcKy#&I#HZPF|2956d<u1G)Mq6akuRvNm}I!C+=|H+dolC# zYu%v<C<$RB{n)|cy&X$l(wrJb86|4#x_BsT-$#|W!5x*~D?`Fa93zMW!~?_0kp{pe zH!jY-(cE_?es|*<D-EE7DR&~MS9dxm&^2Sw93TE;84Y76@n1(KH6%rg{00hy-{2Y} z3dVmFBn9npJ2gGehy{Va&_8GAJHc`uyS;mwfn}IX$+>pp0bg<e5e#-jjVuV;X9~kh zc&(0kajsja$14ICRO0^zy<mPtumwh#jz2iFJ?W%s$#D?S<1TNdW@x5=K<$Kgmg`G+ zxdRlhm5ATy4Wc)p6$G6Gj#WvFE%*L0GA$mB6MYPmAw!D9$m{3L|7laviqXPXGt>i7 z7M|iFU(F&~vHS$r?#w#o?$AWWBYvHTdKnqql$VaGIv%XcMh9k3(J0lANY6>rKEKMx zy)eYB6m4WQGWVLTRI0Wq5))vBn1~7t(N%VkENGllT1H@<U|l$-O@O-q^o&ua2N(7A z-O``^)e8_-ET(d0Z{u*eY+!xFmsxZ9EH#6IxlAeqi(@vZAq;4BS6m3<3<5v%rNJi< zPOBFN<0pU5&*%ou@;{DD4_2{}nOLIV2ATm<tyc^V`0(R}bWt=FZaA!EZqK1%n3HDB z(>OsFf@4q)|CQni?m=0~!G<+|8$2+GPfpMS2&gs&W6}%tg+5)HSr0g%4Vc&Bjv$XT z0@R?y>Nv0zgQ`N=4G>lEZ7k>XFqqyN*~w;={!lTM+TRS9^kIp;S^Q}`0y-4K;E1I` zUosgD*{(*Lh|`r74#Ln_5`e!Qn{8S)X_FgL*oB~Q>assUfKRPo-Rv^f*TtMnAAbmg z;E-l)uol{Y!>jsYNdi7WMSqwqQvsD9%#VTPFo2?|@hQB~j)1Zj!gD9z_*%u+M76MC zde;x=g1PI-d1uYNt{$$R9{~;_^Pptea)_NOxmrB7-KDu8`ymLynbnIt?`xI*S|poU z`cNBV_gXS+K#;`VXQ9SB4z)yyHW%4Bh#^4#aBg)j<Eh?Ub_k((^2Y`5G(r)QoW)8N zlosT%Pcw@}^g8VNq5$#VksRfJi8bE#YGj+-#iTjSn3X?g!Q?ngjhl}6DamxFcB%s? z8<0}Pkh>sEmcB?Okx9t*cU9}6@d}s9*ASpHZ^o-&id@r5pYQbF+fCmt1a5qLHHJGo zlNMo+8}e+24?1W_o$c=(G#%Nx+-qSaVS_UdAq$HS?MgX7cdyLNg3rP;#t)ZiIZ<NJ zIR-iN8BuKFxY6=<&rg|>YKJQi8*}Qv_8G~u_m!LInnTm@*4eBbr27v^-e&Lyr^89G z3f*>uHusL3!O|ig)E7SoxhVN=MH584>>qo&w1LoEX$XoAq1))TqJ0Q28;6Hs#)==S zvT7l76vr+WPKh)V+Z&@|WMgY^IldFuNM=g>W^_A2lMo;dfV)3FV8U@%?4WL1fDZ{m zHO~SS?OY!7-1O{x18g<;h8GSb7tJ{r9v3OxGRxmw`0fgZ{RuYc-DH`sIZ8kE_v$2R z6kf{K=_u;xI#nHRP{ffCJ8UgX6giJFlEUK-2^$POzRK<HqbA~{J-^6d0REXz63b=K zM3k(cr?6pMHl0#^htmvB7^0FQn_}Vib#%Nmo|n81@dH+WnERz1?oj&6NsZ84cP9lR z@kCaat;ROgy~FiS5n)%USsoB7bG2f#BKgB=R#3RdeIyxVj)YOOsWT*ukf+YqQN`<F zE$>C2*!aVSv1XwoO5ZsVg8w|YeG^nPhJ0lSKeuOz1?tt*cpYm_F0To40ddNynC?rX z^!s748NxCZOFv5M(64qXF&=2r^HVN`x4Sy&0J8x7zFv3iK@{APi_s#VTi8^T!lt~D zSg)DJADGLK#}XriWL0^{kBE+L^*LS#b3wHkBW!CdQQ@`ZxBFbCX^K)}H(rMMo(?aD zXp!I(Gd2T&#<b&Di|&0Jld|Fc`>i%Z$z#VHX?v@UD6udizO1ar81;}|6rRY*gn^4@ z$%<gLzU`3L4wh!gA~YS<1NAQ<_Mb-2)UZS`BWBr8J1xLodG?h2Mm664Yl|re#jES- zW)bJ!>)XTT!&l8abCwO%8<%03oV=%{CFAw;D^MMsh9kG<+(0CG;nn5Q|M1<UJyCNy zYJ`tf-RlLHAYekkICit$t;7llRXZpZRrN3>F#w^($$-s4tG&SJGDJ7h<}^}x8x2}P zA|tP@1mY*J@JK-n67Fk4)9!htWs~FaSyl=(F)g6`z#6^lp%qj~u1Asm2`#2`w>$`O z{pE)z@1(1F#b;M)lOMG~o#W6s04wnR5%GAbtXjt)iGyYniL0ttG8Ajy>fFNh1BysW z0_UKadtAO6LSlhjap?x1{e?)vPtLScxE{+8!^|bDVcnNoA(hYgW&p-S2$&m`Eh6$9 ztPnYXGd*+oxG34&T6qd329pi{BD5Wi@J`NdB%4G@eHKPf0BNV)1hf_>Z<uh9N5f{& z;{_YBJEHoFo2!0J7p+x9)A_n|Wi|FDC~&g#U(73koa`E|0qw3~S(t*b0$n9`B(v^X zFue}dc>;mni0~!c13u?MQpy!FvQ%&!(bHw(QyegACuaueG7E3;`&_d$E$IH0b6BHZ zxJ|cGgScBBM9i#t!;|xmb6gkSIIU;P#TZ7v+|rb92w#SE&^=QYkpJ?*ePj6v5=OFS zNxIe&Hg~72>h!p|p@~}QX~p<htxTg$*Lo>F_*prY1WUp8CVQVwpxjTYB6IJo-MQPY z8W<oOp5X-1gGvU)FyZVgz}H~faH}u))=<to<liFCK+biy)|~<xH^-Z{uetf^uVNfq z@gSK5i;`s9rY+@0G1r$(Xr%MJFR(cL0=bru?T<A`S7Ovq_0I5PBmZM2O~UrZb-{|C z{!J-^1Evw96gCDR46aPYsRKh1Txm!V76U1_NO9#Wv#HjnSz{Vi=TtFG8~`<i=)OL{ z4m$~yc4#j(X)y|7AGUk5OCCkn)80H>V|%L?k;6WkdRJpnJbfSqsLlSC3AUl#WaDtE zy(XsMIZ$b5*wO*{1D`zy>)fp^97UhFf=;^d>^;M*r%F>EI;?z(A%GBAI&crZ_Ta~; zim%7<m+Qa4oL+O09}yA)b@T|@k6!3vbsv2X(YP@NmdvXTHmX9Z+jf{O?IAKr(GLeY zMiYg4jm19%wc?~Hdq622x(BC;#)~}TVqzG4^qPf5r((|?cs|FmTSM?SRb|C<8(_?% ztYTYwsUXK-9dkDHz4~sdqh`8tFL^n)QrRpfCBZY|xjmzB#b{)r-+9?V_thn$=$HO~ zJg>2LycnrpoT&Xeoo=FT%r^1Ii_rcuvSN7szFhY$OYpCF?oC<H?Z~ybX1@hT#s60L z;sIC~ebjv3#6_26_|~w~;aJEo7I5PrfvNO^=#mB}yJ;rT2B?}X?KY8D6crsQ&YkT@ zIV*ee;e8>LHp|-9uHwy^LF84Es<BOe$yVh<vEswzz0LIe23kBu;$I(qvA1(`EbX_G zc@v#SoQA0AyyRNZa6(D{&br)1N0A%)4TvRfh}3<;p;{v;Cm--?d-2IA(M%7_g36P( zd$1^`%x5Pv_}&*Xm=40N@HbcGU8oaDV%o2u1I*<1o1A#P*#L~I0=a(@n$7st&j@~e zWL>Y689Y!cT-%oU2iL9X-)j(UEt8wfk#_!1s)5M_bjT0bk(%gFiV8W~-|)U+7?7ze zMcJc%O?HQJU4A?HLFsO&#PsSQh=o^0JD*utsnhL{e$8DUfoxbq<@M2x$pqJwDf$6? zuci3o;b9~owVX*mt>vKFN#xX+`G@j(idP%3Q2yjpq375RCRZ|`l*$5c4+*7C-{fwI zM;bWoKJC7Y19f%K5cPqu;g@EvPe(kC``03hj$5Mo0)JdTro%Pq#^W!%Rml^f=?mt8 z>pvv64Lzrp#$7+Q>3YP8QS}^1_GmgInJcaXl~r!?zOEs{2rHIW=VMK;9r?KePkgx` z>6c6?n_IZ_*%;9(CTZ3w=6e@I<OvfN4;Vw5u-HS77{Q9x@OdPSZyi~2<?E(@>_<@_ zFu_bRTxt&0BqJQ~$`6>ZBB^aLqmJb0oY9y$^N*=Ph6R-k>SA{yU~sKvgCU=2J#c}X z;1sgeVRD%}@*Sx;HBY<lGer>IJj$Q7PjXS+!&4G(Ac>X(rydKd@3wQN#7K<e00+r^ zW{)=PPiKrLu}b|0MEFd(J|%rbYe&p|z1+=a`Ls>>KT~Fe$V3>NF5w_Ar6NnGiTFK^ z{bEoo93?JRx;rZ_#N#-pT5ozGuG;a7C*AE`@ti%%(@PPZ?nKn1Ql0VXe;g>zCyfey zTzp=BvE{k@7g{-WfOINw0_a?W2N=}FCp|puncf8ZEW6;4+3tnj=PG-61ADI^NhsdR zBOy_r$Poo*S_}=1EgL<RanSR4;F@!P&7skuM=XawunA{tJo-ZDUUx4{#2P#(0ozF+ zxcV+ji+bl8KSSwYJGBkV96MzYh&pe-(eY9_9+({6qU)wB8l&FvXQ7Kc2eTp1pa^{^ z$b#oLXGY1-pLd$?ksE>Z|1=&&Q>as&pAKD!ZxCaa$csD)Tb38MgfDyT>TF9t1;V!W zIH?tY=d3fuP`w9YaqS31`%<;j0to=vNu%P8WxikLMQRA69S4`k(BF+M=ld=67el~M zWRt{XvP4#`ry`u=6k*RXGP7b+;-y!AyhV#RpUy%fMR#uwRXrQ+oafvHf0h|t1Yl=H zE+ge;VhsnIJV$5Us555anC$LGIn*5O+EDtIoj$~d#kWi981zy{L0E6*Yo;zWs@8l_ z6|HQWv%^;N1}^Be{i6r?`u;K9zd>XkyodS>mB(z|&Z+;hBbz(CN?`)1jUWQbMVA8z zCdWGHz|TO~v~>?4n50KA+~lX0e`Tr6&DLuY+2pqOUF@;4k_9_vj-&i0v2-KL<TgSH znhjL9N%#Bl(m`pib()$f6Q1W_yc@5T#g>U&oUdKDD#Oia`c_#H(N98LSCg|imGwND zgy6$G-Ni;lnxlUcr<_DgH^LJFMyIxsLvy_(2jpTGLUoH#vERRLO1Uw&7eU=Vp~ANy z6h*{@?>OitI__R#FbDW`{QMs{(-hR!xW4ni1xm(%PdNNAz)hcYy5ggGbxmh|CW#CZ zI3wxw$G$6Ap7|>v9$2=yN&V5i_54Ue(a3<!Yy5bK3wR;uS-9{@t#t{x(2d@W%bFqo z1A=o#ZE!wJIJr2$C2p}0R|y^V6RbqPkGrg~*BR>4lLf%zEI^l-SXI+Z;!$GFsx-F; z_ANdsu1{I1`k#Z4voWxlXKfh(;Xmo<G}MmtWQVNC|E*nuTwC+mK?uNmn&RUir@92G zD`JLZFx4pFZC}EE0j{4#XK7-y7KFS@h9#`J&Hc0xIno_Lx*o5JiPLAw4nS$<$?)J? zQd2Q#sHwc4R7zP$j~J<&wx<FZ6y<Jso;0eWtxsLW<dWZ-?%^zLwEJ*|p-EpvpP^$t zxV4D&P0OF9I8pE~CL>NY14xqJzfp5kHnOT52UY2eS6Nm(=R4<9VqMl%1V!RcOe|-> zD9DS6IJ2e;uYM_vg~rAxgT&8BZPm8wa0TKfDP{QM)io6gYO+Y|`>B!Mxnx!);w0jh zbHU&1^bTWbL<7s`ZIJFjQZiO-?#sSD+@Q;uBDACLnnz*z#-3#u-U3?CLTw01v}sV( zV>e-+hOf@0p*LaxKUQf8k3oUkQewm;@s6YVV^MO`|2EN@wP~&7(d4C(8oY7NAd$|P zJB{Wrb$|kiCB1lp(e|X<6}`YFU|TTsuQ9nk4R8vka$8vidFzSeOFxyQpWhV{SZbQ_ z12Ky?y_U&1-;w6CrWM^nvhSmv^$(@rmci}S%zBuM%uF2`2X{eCH4mz4$Pi|+u=q71 z=7{QT%tJa9>><qsDfB)J=KlcsHX#qKMyIasN+<HEmK(NlK*89{O^<)_7Wu-au+Nxp z*(8DY5?Q1qDGDH?b~FO`BLXGQWpVKrA{f<myD&J;TS(38T)`LO8l>Z-PpoIV4rR%Z z%2H)toc+Unv`=`Kf#YU1O#=U+sRx9se%c7}Ot$)Lb(1bHDU4nJiho6`?rVaV0m|cC z?iT-O@aV6911VN-^b2)QOi%sEl)yq%R(7}d8_*I;)a@iE>kb7DNKeC+I)%hDQQ>U` zz6~PmZ5ciDH~sDxD;@E}@DDB=vC70z;rr!1Qyd1Ohkq~^f*-Jd=N<iuILMQMu>5;> zhRc%Wxv14~k-B}iF3cR*o-A)WM$Eo6Wdu$pRO!#_)t2ocX@k6Gb~}I#Ux{!|i8iV3 zKq6!Xg2{4a@|^gs?dS@#g*TBc5k6llf{kcyiduTPfHE>yN<yG8f5C=}Up*FV6RkD7 zDFqQWk!rOjGhNav3V?tdz^lU%ZTHC_v!`=!4%HLiMM#Wd(DE&?Qk*$`xY#m@Y|%^z zv2@z-e(Q6xkaM(Beo|R^B0PvtUafBLQgB=QgJe3v0W*5#|HbrTR2ROieFZ=jUDH1e z(%o^TLEv)flJ4%1j!SnVA)$14iL`_?5+V%(A`Q|Z4N`)Xfc!6*&-3`c@BjP07r5NB zyJyeN`JI_LXJ=+-L+y(0c$44Xi3g_}>z{VNrCE8qL;Y1k96TjxIcRWY<zPTiu2MPy z9Yqm;nWZgn(;FB{f#9C#b8bKBw$-HQ?@r;f<3jT}bl8>B3dvd{f4Tha{W&A{ay~+V zugDE?BZz6TmO137*wOab0-y(YB=>nl$X=Vd-X*gz!4RP`B$IXY|1e*<_W3xu1!jj} zewrs*qO`TpL^mKuq;qB+>#SZ}iyScJufwe{RFd;WC!CMv0#y0JOw~U7d0<Qc;*wnn zX`zpfB(ls2zWir7AHvCK%q~qsS+G4i!t6Ab>{OY|cnnY{PTArBEtB>htyIZe%<{AL zm`Z@40L7dZI{ZrK*S$zq!52-$Q{5T1LX~V};>0$uu{&(T-+y@32`Z!DJB@)sStHt= z_$Y*f4BO@eqCK7Ez`aXUXX<S(-Vv*F#6T<+thb*0WW`xeDi}}Z?Ynf{-rc*rjccmn z+Ij1BT;i)=8Xo7OOAek>3^pBk`8{eo-36+&yqhH04|ru~7emR(In&fyZdbOwd7&{* zJF?o${i-({i#9cBVvER_x|JoQL&-ziJ&>$hqf{hU=~68#@vev-<RyfQCuy5Q!W;4! z7E)F8o>q--o2PN?n&MZ7B6t!Cz3(2|1<%)MC&lhh83J2rIy!0<@paG|2?)Gv@DqmP zF4d|<IXPGA6!4l!hSMA7)|)J973wk0K44TSvCZDe0ccn*754j|uPZKe&!c^LBGhYD z+~Y0okz~NJ!yTzmN4b<;Rq<%i=}<L>nD0)U-@QCa;=+ap30&dWamW}AwMT?Llq(0U z{<PJva*r}c(-!#O%Oij3`?$1K6G0lcXMsXe-WI$4yuEz%0f+?fNyv-)3*`0gPskn5 zO5BE(Kq`L2(WV9a8xJ%L<$LWtd=osN6thKjG&h~ZEIPEfq|Ea|+PZURq;<kyACHH% z<ni+7vWb1p!nQbb%3(30A%)J!@~)D8!pqdtO*pb2s2egw(P-YKzCb#mAPla1{y0=E z8O~LB_5DmWp@4c4a1+OmT>lf8(3$uo%K1IZlSlgZFA}O&c^!ctZHBU*+A4|_BaGz& zDrpii52_2K$^|Vyx)Sl8ojy<?jkhW-7NXPa2Xe<wkQANrk5wDy$oCk`sy6u~9yVYp z!;<QfuStApiYZcNWT%fOqYEwas9wRmqT}D=v2%J}o)k-Itl=YG)-}(r;WzRzEwEa) zzsnY8Z!HtnK#M90LMkcI)XvFg`J&2ZrcZP}#rbC~69)F2&{g|}OzAe6h6N3DA}NE) z!p~|DbuY+}(gMnLnw1uYFf^mvHv~U)kq?<xAf%_iX3yhy86-kGe?bwnQE43!`9+NM zeW`pi%EY_Ch}c(jeLnBl^(_TGf=u9bRnx#g$U_iUKI*mlH6pP5!NX8{j8l{7g3Rl6 z^)3Mzt)wN*%qqN9!dh39O4=VzE=0|{q!;MOU_#iPMs{H$cUFTW+{2|Vi%eV0pJ&~@ z(#CO(p53`N?PGGOK+E-#=;Bk6^-U#wfX2}m#h;@x?D;;Wh_>Yb_chMiOPy|e^jD%i zOygSQxdRl$+h`bqrhX09Fn!_SC;3F|mT{IHP!4G<5~@v4ZK$!xo%I^AwhM{V?8iQ! zw2)UCMN{&dXcNR;Wo>YyvUQrLs@c1_Q4|uuHQgBxB*8@-fhN`n%^$;ldCywqz%UH3 z&+q{BF}f&h-;20hYVc$b^Rp~UX^(YHvXtM$`{R9J_{4Fu_gTsPIq5QCapBP!>%C?f z`rR+>?rw~5a&&SVz(WmSImH~j_Nu+d;|`5kslV}+NA6HRP##K_4!wWKKy)tGN4zU2 zs0O{F#hK;nc$a_ADeRqfwY?6@sAlsFPMa%JqI}py*Wt5`J!!VDfVq2_Ol(>89s>ko zSuGAdW^U~i#x*7sPe3mieYbiapw>q`R4-Prl;l`h2~-iZ!JR2p$+idNjbfG5dcEy8 zM#K)*M~j}$@itocqC)Wk@TBI=lT+_9eLSM~=Og+fWuW)ie6NhT76XRnOKoPhC|smB zy~%j><%ph$qNYLTomsuiSKQEb<~PJBy&zg{GLWZ1%zSke;Cx7P6#2j+S3?kfk#oeM zw7@O{y>or%T1weTr5*#=s}$TUzbq!8!?ua1C(JH&rBnPAq?c{N+kn6!rvjC<`DE=W ztP>$8bSt$vl7hERTj|TIDIctuYiOzy9D1bBhl)FH{-v_Ubo#9?1@GQ%c-3_Y9ZFQK zFs&Hl*NLA0jL(OTtv1xKCrtAizXU~g;WD>vp|irp!Xk_X2Mwxm{+)43$+$&|gVhVD zdj{hb=XPb}Co9{lyHwPkcu}8D?+VQxOTuRX*CcS6Wcq2ariSV&jZNzZv4Gus)XI!K z%aUoroGw}q6j8S*85DD$1`rJK3DUf&nRvPv%)0oMO|895jbMcLLh5zPiH6LordJWZ zR{K)k#ga65`Qs<>2Mn<VOkr-bRc}kScfpYYy;+x2-d_U~WRh@+#;pC*sJSN;Ir?-u z{VeMh>G_==dFtQ<Y;_c~%E_g)O`SvqbHHxDo~%a^euuS<=R+8KSo=`+p2~p9K_?Zl zXTSoA(d)k8xz|RfhS>r`xTCGUoqBJc_lbE8S1k^7yN*Ab{FH%Zv#@y@%6~4u@b+D5 z4M`u#SB!+OLI)L4q1;!#l(`+EK9thfqN0zW3uMI#kiqt5F0RgCW4oJ@gNZdVJAf5% z^DQ6%RQGZO10@~oUB$sJrp{K5t`5#X5f@Xiy(@s5jTb0p>?jSkvatNNbyMf&2s0aq z=k_E~Q3p?5W)L?AWET%70L01)0`PLO>)#$!G`0f+6|L+{++5U*?d6$O!4_^n8CPRl zD^n4B3tKRN6{zM4w$lXgu-?{}w1VtogOH{Ql(aRrZ~?Hh2@3uo8T1E|fog6ht~d1G zbOHnvHFg2t^yDAcl(08-Ftf6^0BTv;i`ctZ{oIzca&~bQvov;wTnZ>}{JjhUaY1UD zLBjcU*|>Qi*Zp_DoSa;M|DVra+u-8jhP2AfcGKING8gEkf2@GtulMcfxAT6~`LXZY z`G0Nmh6+{yCoAOhou-@CewJ^q1-ZiQ_m90dEwHn*-<}1j{cYTSaRT6mQB;2o0taOC zJKrcfJD9ndf}QCd&CJ!o+RPvp4ptTrH$T7*0=2mn*bLz20)ZT0r4IIVEeCM2aI)|M zirH9MS(({bL7d<A1EicC+#CUlngBg|5h-PPW_Pf&i<N`D9s>hV48pJwW~7HS{(S>- zK-}0BOfSv{X@Q#+#0~=SuyeArF|%^gv9i)J02Lg}ey;$j1>yy<ad19h0IEA1J34~R zfRe_xE?}Utx`Z~1q?@g+iLt#sqz~$rRxSX@-?zRn0W`mn0bpkVaez2^*|<2F*;u%K z^btZVgeZW#l{<u8SwUQS3;-KPV+gx|jRD^~0N`R_V_{?Xg&gNE<mAEj7Os{MSph;A z-WBY8!|bkLaWI5O!GeO{1=Tb2-YN_f)E<=2Kc)9(n7@z0kG443{(WQM=qBV2VO;Tx z3p_RJJTF`FWJWC8$FX>$!JY{(x|$m@u6rbya4p^%ucKN&grsrl4h=NLy%Uf6MP&9p z{Z2OG1O1F`XZxyCgNI|xk~<F;i$~S3-4obm^1B}i4JO}t7MC{_Kn8htAwC^DJD;~$ zP3T9-;B~)hwtmYgDte_><&lgPNo2aDz_<NSZt`N%HV>g=yEyp%*|i&~_*>Wh*D&2k z?%&$tWdHXt*+o}DV8Rr?xC&=@IimNPP_0C~5=&H3nq=9)8QbAg{B@5Q6YkuWd8$`L zc$NyH7_v;FXcpyw5qYhB=1jJKRc)xbK+*tgxtkX+H&1vy>68cRC!WT*kuN0MI-;}e zs2)KnDi3?cM(2t-)UTXflPkh6F+RdhDJ8g;KYe;u5wJRse!n}WHh5ugS>31_RJI9k zmBdl_Pn>xF*|+~SPM{kl@OPj<{~joCv<OO|Fz)01eXVy@ENgGmy=qxEu|r8b@w`h( z>S8png_uWjEZ2E_*n`ruMs9Gb{f?8JegMpyQR{lC#J9;<lZCG`bxFH*(RaLqJu~OJ zHZRczD04^%gD*z<se@L$?Do`&N*-CbCOd>*KH45L9IW|VuA6xV6!hve&h}Kb**ufG z%Q~I<{xb~G1c$~yVY=0-f9u-+9;X{+{kO(A|3^5zOfMv7Mtudhj9}2cupPXcH*u{b z=SrW>i_X57U1)J2q6UXYdN%^=`7p*zoo0?dSM4jViM6@hOp7hMdTl2gUx=^gYBtK{ z<uv&Eie6vRM>2XD)!Y5P7^$MWiR)glIo2W?>cG{eS$z9pWOnVDXfbyW2CgFQop@jJ z?5w`WwS@#xG#uQ2!0ERI!4H-4zsKpfS>i`y{~4#|8^L>g;d>)^b7_mJG=Wi=&Z0Az z+<h{yyX6)rFshU@8A8$=lt<XljHK@(YxZ)u!W?DWnQ|hhW@%2odYwtKv!TN`%c40q zb1Pl$Zsd0E(~k^k`a_3$RrH3k^m{Nv5}p#4HRTv|)x&l=a`UB`RSHniYZw-74t#=r zlHe4{GXOizetZ`qcF{Z>9Dl&+zp2JSf70XsBYrBVe&zMzhuoF_N$wcpH*xvIk~;+~ zXZfPA&9gOB0E`1EKjaRBbn5g*KZT8)lcY<CS`gjJ-TP0kxjafxCtBGVCo@#9m3+UP z9x*k4#Hafjp|s#wiL1m2ud!pIW<+fAU<?Jnm_z9gIQ=*EIOtD`{6FIKJ3mpc*Q<va zQ9aI#<?0$(qh45E`^8Vg!|Z3-^pd!wdD5|F$`~{5h2rp>quVp@Q(Qu>s(XzdI&_s! zJhhe>aNuUh8z?{9?@4=hK*SqPHeE$VXyU}io>$LZYoJ#_6duy|mTBf7F#GkHsDwlG z7_MR=!{}>N<sOdU+8lzUIS$D`;Pl_r<81$DJzh{}{V(*>;=6D9DUzN?G7BJ+Ls4tD ziw<9#qmc}tSNzFOL?=5BgLyJ{x;Ez^dc1{%F#Terk2+}0t88E03Zlo=(|_slI^7oy zK*5Kde%X$y3`MpS=>iFNtQ3HlDjZ_}#EI><)&CEb^1s$kw+rC^lOA7tTk=cp{^fiZ zce^gIdn$Uz)GCQCRCStdc}6|OB_hwWKfregGbSN}@M&v~4z(*p7YRK~GjkNE7<kGk zcDx@s7_Ojm`8T;Eq|X{B=3DG8Y%?>3$Q>FD$NfLx^xxFuY=6?@|6!eJ7yY~3HKqPh zk2@BUnzx8Z3@ruSMZ<d$ClQLBifBhtiuW%Rd2*f}M3Fy(e#%58cu&gdzPCHs?Sxmm z7yJ-w5goO2{^x{eQq0|pfouN;w9J8_J~SRun@g}{!;$_6p#GbRob6AF{67E%j#j!^ zKy~f!t3eh}1?yinw2g;pFu4yHPxQELDa!mFJ;)UsdFPaRj`njwzIq26{wmwfhCOi@ zwya#ijkkVksOIyHAiZo+D=7ZDODj=3y>@-2%}gaoCFKPB*OdVgWI^uA7FGqB@zic- zyx@*L4vBO;=<T%NSjY-0L77AN4?z7lH96a#H2Hr3$}Sp&(!q-R`1JIi{kTNN)C^yV zx+9h>l`HdcLDaZ_{KYjWG$_zx#p#rc@u4IIDg*Tha%1q|?TUPKs$Khm-p4~{`^smu zTYCKbpX6Mgk18MQzL%(;S$%|{&V_OI;&%DTf3y7TwY)+*X3E(m3T|rer*B@lqtf`r zgvLR6lVL$#Eq|6$6*eKtN&@)7pCjcDK>asWIs5-zl~<SkQc?eMX&@0S|7&Sbj+wL# z?s{3wvVkx42^R6>zL<cby@~7BVDq?Po!}3CpV`9PeyXU^?n0)o&jqh8ypk8(B5<y| z=^S{p!*A~JJgOL2(LiNe@HoC4GXKPh{lBTo+5fa8|Bnpy*ISpF^^uNjIe`6=#!m6i zB~*6W3hNQt4MQ>AFchssdb^<IEKfmf=SeNwom}<0$cFc8YjaEVEgNkurlCE33X$&@ zm4VnZ0mh9a&GKwKS(7J<1qItFdF(ZP0~GHOmEYC4mg{GJyTya;&UhfUf8xZ*dVF(> zXAb7b{sU0I>GB(^7Q|L3$<6~{ztPI7kRmUD{YHsFijWD7{YC{riX0Gg&W*%EiXaXE zq&lQ1C<ufYYfRO^uDU>FaY^8fK}P?VWdhRB&)b??W1zCJ1sEs>xgx~U=c0Ri3#AHx zGz@790J=5L=>t{4E)H(areK#Fs{Sv;0ky4I0XHo1ugC-{fX%FoZ_JE06mUTZ;O1r1 z2Pzvo-xwk}zMpyn-_4YMOXgqab`Ex)e}C6M6UEo(z9Jd#@dzF`AH0AA>V?;xEPktD zP}qE5ZqKV?!VL;Kv<nxh5_;JB{3Bf*95hIS6A?QtF3x)=Zp6GbjBC*s&-cT{-q+IW zFYnDrz+$F2&5e)dR=q3}6o@5q!Uxt{W(43)xf{{HWRykcEzK_O3py54oL#sGIT=65 zzKn`!UD%{A3h5ysgZ)~y`St;y(BbK66}i1UvF*O%6Pt@Vs{+?k0mWmd*XN$si<R$u z^3+p%K+I#?tXk*hrShq>k(1|p%BbD0OD&IDRh%3J@vGl1ZkIe<Uly`YR23Q7^`3%3 zD?6q|JK|8~sSej5>~V*cf>9f7d)pMq%4uGspFLmxh9X0g_H}DtE>tqtNkgHKfJ>oF zk=yG`)mE;869pYtJOV+~<7awbEL4u438;Y?<>ObaJu=F|)Kk~DzDR>NxE_Lu0^PZf zpW2?xJDPr|HnwkBDnExU*Oq9XlK0P@bM{4i_M}*mJHqMMW-Mdkv(W2{zWGCK0&!&; z^5YMN51u>BnyO5YMSo_bV8ziZoGPKd6Q#(dYbRFVnOxuGL>%s%?m&xi1m}n;G$5AH zGYmvy9*(Yz3Jc09N-y4O%7wR<HhIm8^Sr$^SDGq`M40Hw+njhfrDMR-x~KI=(|HBA zZhui_#ctM5NgrUAs4YL-Q3{U)tK(lf_be)Tem)+a*d<)BHRVa%_g;PTocQ{>O)c<a z6&CY51(>V1<j;H7uZNrOM)nK>BM_%Nja7JL3}oNQO=EEMB+S+g!o))HP)9uvQk*Gv zCK_i_gd=vZ4ZlKrQRWg`ILmJSa!PXc*=7+`-j)vvUtZ<(wm3YMEC~;WDQ7Rx06tSq z1#gI|THS<*D1Xu&w=Yrrh}a6LUh)NQknlxMv7ep2p6(kH?AGTw&%SJ(tGgs=Pvv_7 z_yb#W?-L|&7!D#kDGfK$V;nb-tG?ez%hE!z;lRqAp4#D`^EC^k0H2OK!>IUTjX_yT zeogk}=E93L?>^oFAoX-S8rNXQ$8*Td2)}Qnn~Hh|sTDYcZ@gz)CGd<{(>>HHt3edU zG(ZLi-TQ$;>k`aU8h=IpUK4>`eK|U)B{7h5j;<5*-976DVe3@}MZ2L{1f+`6)b!CR zm(AHrUDEplfq_C|_p@fV-j@}bZ;n4pkB_K*aApuk+GZXP`?evLG%DO;A1t6ASW8y+ zX}wV7Gy1NxZ8-i*7xa2RBLx&o(?>5h&|N3A)h6XezeK19ixZ9QDP&UF!C2g9e<7v| zjUrxkFh3DVvjZJlxB}GEc4JX-&E4@&_~@j@=C0x3do<(>l{Nwwvr#wSVXdIQyM>I? zOqlG%!c>;RWM@f+<SN?9lk?$jJq6Tcpplt#=V&pHF*lRo#3i#ZqQG?+9y97=Wpl|8 z6(?i-t0x{AuOB8nXNq&lVT2>Sa55Rj(W4{=AYF}vQsgR9Qo=WD&Yrkq;b)~$ge+?h zEmu<r<^)Q3W3;~ZQT|FZW4xx0fo4Yl7~pU=W-#_yIqi4??}B0<2n{=B!wH+#2(+=h z$Q4~P*C4*cNPjR2-(X5Ss2F6T#Ae`&h&*28y!IHG!3(BM1{%wlXeZ?k;Ul;OipHqA z2>7lF89Q-euMz>)OXd3fSWm!YpLd=p5i$C+XxYxF7f(R7J727<KbEryMWGs%VyK98 zWm8jIJ|fq18L{_W{UT7UrzF7<m5;K#M6i9wD9*<*!1H0?tSeq`bMg9&D?KfX#&K~l z<Q<yYyUkyOyRvljiA8I7VAB1q88thD0D0;SV5fS)s6-B-ba*2}-(;B=Xm3UHQ4&3d zF2USyv|-BvC3BHR`VP}ly|U5YX=8wW)6<_CZr>dU1Pv78Xsd>pKhhRnLYa#*TL;d) zR^}z+79!It4$+Bj<D4K#^wNxkfrN}PD=!v2cOYB5{KBu>zENzIA%ueE43&M~Bxmbw z4vVByg}X^L$#IHu<V+GCY21Aj{H7!LI>vCySa%%4&)IdEfabc8Dhtv#noJfao9gyJ zO8k5G&!5E5(`zUS#3=JzQ?ZctqaCn##z^B7L<&;|4l6y!zk{s_Sb-+-E^4L^;ExM# z|N0`l7_)Q3j-Tuc`$FP-FGt(AIg2cj;5X0UL0T5z92ugEt!O->eC-5@T(KNnc*O;n z0}*KBdzI`Ohgf)-)r#(7D@>79Lod{*s@SW91)-9bFz=W8<Lt~Ld{zm2To5=hh<OmZ zbSZ4|K&>6_jh{HiU9Kg*kEwA)r@7H*mA){j$9)uMliWq{nTpkbkW>mx*T)f1rjFi> zZ{pw(U{c=n<shiAEJ^JRaIa*z`0C05&)!-?Yio>4<>I~&$ZxQ!G{mzf?lE|!`bna+ zC;K$y6*Y3(A$vm#{WcED;gyCIW7QXHv`-yVZHJ>yhrG^WF}Bw6nOpSmA72YSoi#D? zKwav(Gj$+MNg>iZS1HrbLoFi29F2R)VE<aTyw<W2X!-Im!H{k@6z;e~XQD5VG$*Hs zF(7Ljdq*}=Z|=}@PuAVrtMQ@lP-K_&8-8BK2h_1VuTp8Xvz~7R^OSV@BGHjQ-<(w6 z(4l5UP=Ui&E()qX8sw}lWJO42qcfKlruOR!5(@^j0;6k5^Q*)OLk$uoG$=9xbC^n@ zFJ}!G+09<i#ZEJBo=`Pjfk;BHDVmUbaj8Y;KhUV_ou4)`kogomdz!6PjljgSG5V;X z>!BtnX8MUsSX+ThSQJKZpuAOVzgD;Ea5%D#5fv_(PT^)g>NDKv<M>s9WgQb#=f(Fd zV)(P}TMMQiY0msp34})0v3W%fiYa<mJK56Rr(}0Fn|wCrd*qy9+{iAEX8D7Krq1%( z{92v`@VVofn8^7u?~?=}3LOWbhCkMFRoL|y@9iBfLgshxE2q}7Hw<5F&56r0e)sVs z|K8{MidsftU)B*RAP`5t4Z*KVx!jgANFnx6#ASP9y}?w7zQ=Mw8ESs-V}*l}0~OVy zqmw<U$$?k)4ocDt{`iOsWPaD=8`W*77{nLaj;5_2FYeC+gs0gm+c-V2a{zi^(nANg zV_#4o-1wQkH+i!;b1HHzC~=?fnm%XbH2bh%Jg;Xbn6mexF-So#<RPO_9`=+U<#IL3 zgIc)<99kpn;($ra7WhZKAP4G(fZ9g|NSU@qM;4-14($E7PxTX!>jqHXu2(Kk&Q()r z>w;2@g;#SG1WxOg;9-0B3qPpn?Mt=*yLg4GQ%y4-T~}S3n@2^8U8*~;D@A_jE-{Ef z?Aa#JWWg2Lf+uT6J^xS^z#@|MJQvA#Ct&h^JJ3wd+UmU#`?D**RpV(%1V>)nm39*5 z1<O*==CCnpbr#{TuA5<Yxx7C9rOL=r52^8N4<D;Q%`04UY6bN7?TWiP4?($$gO+n# z^U0>P^i`xM;e?nwN_2*~nxI*d1A}B=i$4z1M)cV7Ok83B1S<I=c)xQPK{Mgu9Ybf@ zD+K5H>xWI+F;BX~ZB+9%lG8aF4~LLU?i6(LIANVk)7GCK_j0vrznSv;(4fa;`dBrz zZ*Wk6khTcLz?8*=_5h*8{PCI-lPZv7*upe{829sdEanC#cZmRp_c;sw=(NU_7q=Hf zQ@Qe&ATZWvu$Q1rD(Xl0N16f={^iid5$_c{0=^JVipB90uf?^RdG-h%#)Y-NUC)Qv z3@lj?0k6I-&&r~|48sGYs4Z4q$G8>d&G{$13grG8`-YSW%IPeds<u>}oRF>3|K!CN zY1=gTQ^fZ4lt4U=#;~ItJ=Ci-PvWliE_`fm`K7DLSuL2h%?G(;$;{OBj7k|a_xcGj z(OHn9@i4<T=l5*f`(y;6Q^jo8C8%Mbfn<b>Rt<!Y;eA}2KItAH5(`h3Gt8T5DH3>W znMFR4uqGfm@Cvkul!|NAkBnk(CaGG=2y4ZSfuH6@(TYGI&<>?*|9Ez<g(*=F89wuJ z3HAvtrYy9~Q$LV!zu-&w{>*U`+=P@VL3Ue#{jUUP?pXT`;DWHbl#<48qF`TzC&NaW z99(oNiCsxcrpY9f;`Wo&xo*hAF2AVYO`_@?+#^&ofUhe<`0TBg*c%3Q>}eeghJi*> zMQl}{pb;wK>~)MhcEWh8X%wJ_qs{l!!;YOiG%VoBLujg~hpEP7{JLg~zSZ8*PF*83 zHgIeA2-e&{UxNs1a4<3HjIt@QoZj+>8haNC<4XBeve5Q@Og?yBCt!e3+^jj)zqT+~ zyegOaw$K<oq#MK-hSnNtX5~W!qMDc83=?YwQq3df95CbKhmbFazl=~A3s)nv&S-U% zphe$KMH2t&x-=!%?q7oNWYL+HC!JkQg06+a1p6bApWlZ_J@)4pW*kD^FTiM{@m<pm zj>+-j1j@lBwlrcnacfk#;W+{vIF<Wen4yasG~G`Ky5d&FnakF>kz`U#(6LChj2gE# zJ@sj&s!)<Fqlw%l?iEiHB+z~IoaiXZ8Rt9Hm=<jPpuW?Q(f$6oFEBMQfuZ0zVXbzq zKO3$)Oi_AI+2WgM*E{&w=8wBnCeq|^;x0@dV`8@OkcyB#YdU28JotHK5Rbro@7!kz zwai-qYtm)Iq}XA~*xc>eR)I}~2DW6H4h2G+(7qCnTJRWkuxYck$oo{|oH8|gnAzB@ zPxnX*%8PJ~n~3rExs9I2t{hM$XZn{<?kBBF-Ek3|q#DyR=~F%Vv^^3K<ubalxi-w# z7#T3Q3zyO*$y{ghI_eJk1(S2JAj3LbWq~%&BVIS-cFdL8!LZZ(PX*>UaD%Nmp}sWN z$vh6h>{txiB14&bQpkJoJaN26;sc$hosVeyX~JFJkx2t#YIW(D*98}9&oi59BoEtX z;+Z3%`rt&vJP^O4@x4dNjzEwP!>#G-J;@E|msn!d@Vu~o3>Svkpizx&ma9zZkd&&r znjWj5h>Nr?ytS$ICUuM_5@wHh;b@^ha>F>S!zgz{4!v<R`bh$XMOLRQ47ywd94RAt z3jf`JNceiM{m=0s{p=|U@3dwM#=9=JF)F7N5SVhPxA+fFi|uD=sGrZCke_u=jm9&M zEWZK>zn8y*<&y}7u-?neWH91e(Kt}~Uei72Ws)SpJm1Tx=ldhOjDc(PAsltsLlnv- zIp9*egPQ!u4m4_XE__GsrSim*pAT%>C5jx*3nC+WiBBU7tmp#RU9)XLFf7y7&&MM5 z1!h6<B-|$`I=1~v1!wR`2|W^T%5e_HeO}OMcS&=Fn^>TJdOvM{(WrfYt$)7%-k>tW z+NjHm2RQ-aFqC?tc;R)`^G?EOB;AdYd4%sPSyRh*&!cO4DCt#q7rx}rxFj+RDMjJ& z?Zyo0>28dVO&Uah;Bz=9UKIrko3gwil(-Ld;M#y?C#sL_;e!5B_zIwqxf@alL|WI` zlnJ)MsGC|{ND+MXya8xN5NIF}?@802@kA&76<tc^TC>1s-ywv2q5h1=I@Z7t@qH2h z8T0NZ-s1CcffTR-X78x58-3q^@8q;iD|IEL+{;pn?H?>RjJ<1nf?f^30yUSS2YnC} zCNx?aNAIbde5Kq7co9-2pU4l({p_e#8)oI93DzC>cgLil$0CZP2MruAjkx77cpk7Y z(O=_@#j@M?_r&t70+buB!^sIvyDbK+6&a0cq&28eU#`4IOi=D1(0g28_N+=jkTi+u z4z8kuHAw}#U#KHqMMKegX;0#_>8iS|WwK-i;Q8HhK|*AjcFm~dz=C%$T!M|z+sU1N z>Nv8&*zDm7h69~ZW7Eypi2c<(F+fst^T{NY#!7D?3n7*)l6?11==cfrDcpYU=WmAM zSyTgRUo5~<`A1<IW`F6t(z-_MRh&5ZgJJ47zrfGvv40hcC9W;4A|rk8TNIe8gPo!S zuRQaON$1vV^joA6$E{@v5_Rw+BJ1~nEH<|90a*Z!+lVCKj|eUR$E`W+H*3~!hW-CY zoT8Y#qNd8<h`Wta`)fcPfaANR?T^GkeB0lm#Q+@NtZ@IDxZfNF|B*OlZ6#?5)q7$J zs^3EGe#h#&iSH*l9K64x*Em^!lJnjC_ro}LYZ?4*9Q%)#SJP0Ddm#G<Y;FSh{xNtr zHbjWc@h3sQ58f|)IB$)J|7P%h^AG$-0>w2U&}c&NQPa5zrGgkCe?y4#)-L#Gf`0N6 z=Pk?q;+<P#;_vv}SVCPOrcgJCrIG80$&vFL2g+HQx#-^7PE`S%w@e5LwY#;E-iF~p zcHRyWfb(_`ezUi7{@vcHtz=>iHg)|eL6Q#6c98x!+uc9_0DTWugTzMO#8c9<0L`r| z+?*kFnY+1wSs)Q>4Bu+p5Uc2BX99NqagL0=xr4ZsDTMZKxZehA-W1t+AvRY|&|l5C z|FjVUB|KfF)LbFKX%H)~>P?X1P5&U~<nI<=RY>Fz*Eg|M<pFTr;vxw70seH+@^V3j z;NQf!6==yhknp0m3{?!<&BCycTsYYVxMMU4$Ia}Lhu`Npp?0^iAmO(fdDKc>@Jad* zu1Fs)pc|_%=8L$>IPA((Bhu^9edkF%U&o!jVWi+k7fq56chl5^pR}cMK{AG~pJ?Rq zclvRd^$T9|7IOw|t%`3hr9r9i3}$jdUfQ=oB;pdN$x~{xSv-UlVWG{65l&l^k(J7c zVQB-1BrCI+Eo!TQR<O%(GrcQ4U}w<?r;(Ug$-2@oAQ>ddth1k{`Y@Kwa5KZTY#;Y= z165yY?B_Ql*-n%J4tZYj&W4*8&$M;~O9=I-;3?7M2hwe{Kk368@ZAfLoow4GD71m9 zIAc01Zn!rEGbN{h#Z`zw6NA0}8do`Wc>%SJnlD1PA|L16Q8^tm%4G0kUaD6lW;Xc? z*~EDIhLIq9LGp20aVRXkG(`-o$OHzecy!VbedM=jGEUfHTV;WanVsvG!JK<TcY5#) z@iO3jANw_)e~DC&om=Ojesh%)7~#Y7r9xuwXbL$7E?}D~j=FG{Zr(@0*^}~1<kyZh z5MDfbfhg*BDzY1DK|OMbYjnXEXB)l}&K?>=&Wt+QCyO8oM)>#gOn9n=ssR*b(J#g5 zXTvZDOYe$)8rCsKW24MR;+QMU5kC+Ci^jgq$@bzhtuDh=eRI}UU(IN>tf<+5<A=<X zP<Mb`<1!dG+#$+Gzc<_(KtXChrhJc-Hi@C#8+VhhUm(hL>1oVZSoEjmdrr?UaUE9^ z(?gF2Vxy@Zz63r?tKKnO0#CqM7Cklz;89!X`H=jOgxotLM6%22i_YFu25+scW1bgR zYXNOhx1jVU`US{xbiHZ4+wkgZ%VJCBBddi$<E>{nMmrz!;dHm!5x$CThi&E|t@XGN z7krx1+1?F4u_TSMC`&xxqENEhv$0Hk6Bwvk5?GXq)+4Gavk-$v@1f}`Ta2$tUv8fy zdDfv5ruItl=_q`%SYF~ModY!|t;!_g0y+7~)_Aw6#-nI#pdbZY0-vj_7@r&VO3&&? z(7qm`{tFk|uO3IsTWb_(0z3j6gEjBn7MwS;HcInn-VO?@Bss?O<=iFCY<!azS`@%8 zY)gYn+LO73dN*M7qr+ld#@e#e(c=9Ql!kLyh1VB6-u?F6vn}^81q4p`>mGb=D*8M( z9e^qwvayo0ND+hn6lHDO<&%IA!%P4L&4@>@BG|iz=kq-{Bq$|1e9AXQI;hFXfxesy zJ`cG`iltZUW&L&Zww<f=PU+0<;0fnV+-)@6<z8GV;VBbza$TN|Q1ot~)!p2Z`MA|9 zeqP7k-lau)7=OQhdw-~PjIlEztp5;8Ry(9S8L9Qb@nVJjGxRGbtm%6x_ZR~w)LfR2 zo*;@jy)9`tPK=!$81SpQ=EaI+|1??>n09s<(XOt8RB?=cN((-_=y7YcFpNooGxy6F zT|RKd89Q){JbHCbkrpQ#_0J^=*KbRdpYEw&4zu6fXumnkAPSr7R)a${*ms=_(R(s> zAi%B4{#OpFAG7Q2B=Gmy<Ch=pHiShL66}3DN8HAssQy&}|IZ19IL|;oooD|8lK*Eu zwOf+EckH`n=j34h_u)4)WdgI#vy#>OUy(I;I|EAO;n)d{{O>D8+tCQd;Z)F6a3`%o zQ{KH}gS`ZkZMQr5tmPdPth2OAwVX!B9BWF8M_$OtjywK<-$VSgjTo&VKc1K)9rE>J zc?&IWc4mtQ;>Nfirzf0Gq#VC~I6Qu)y8X4hNXX6Nh$lMGO=Y6go4#LQlf?exzO4Ob z$+5YH^Gbw=LqpYeqwyG*1?3$<t^VpxouRL<z6eeA>V>i@VH-GU9N$;W7RUAB4SJ+- z(N~N|rj@K<hrr1xRDWD;+tgB&qT$=Gpy42?<z`stR<-<KB&&};^+MBdPQ$lxv)=BU zwC+W<e{>DY$Ck~~!a_;1He%08rm4hTg|YI8hbqpU6sp&FSpzspa2uZEg~w&1wz`B_ z79w7>(4=P-+ICj(w%kxbUqAFtX}go{<aMYSQ3Q~u_qe!t&wrj90VE5QQYl_<Fv-BA zY9SNNY1DfhB~2smH0n9=1BzdjHTpzh&YtKoXzF(sl<TCpI~uCrV>fl_R0<Trd#`5s z{51|iEz(Op1P4qQMufnUgDF{fvZWws*d!p0&~+r`ose~8Th|fI$-;i#tc`<72`y$v ztuP}F6X>G)b?iZ40um;y|Jf@Y=wg?iT-%Er81WI|mwO?MWL<J9%@Q^*m}3u$_;RD1 z`N{CI;6>B=avtDKzLVmE!tNAB&L)f-$0ifrcSr%BdH{AJvsb1?w926#(`0G%v`c6> zw3=Wo)iF1s$=o3!G#oyF=W$)Io2k}z9;&-%Jn4iFPcg&>hw&1>P4)EpNt*A5WEfzK zN1H>Ak*L}qKbW7RR$_n|VGgW&_r9xn!xpLe13cd_o*qSjXCOu$i9e~?b~Gjc+O-xo zEuA5!T@-dk@NPvwBH&O8cZUi#1n<5_2E*u5I@crJ3C_N!^I#vdMXqav$ZWXF7ou3< zghm;GdR3KCYW-%~FjK%rGkOFO75#b%@1cPR^Javw54`7#%S_Gqlh+Z6*f_!~NOcpo zm2DSY2<*UGpJZclMqC%RY5}AHb9my{me#9}2bHY(;TR9^)El{}eU50VUU{3+WbI@T zP9u~ufsGOmPjnoMTf5I*#-ur0kTT;iV~#clMMA?+O1T?X4dqaF1iipR{P?U+JIA1t z7^wQ9l-M|idAO%$f@7Yb;sn4N*MQ%SACM^}sfdzCS(Wo3WlsEH2vK-NWwwNlt;3vZ z6h1H$Zs7}NDVE9`UVqeQ)0I-zdIZ;!(1E+$;DA?UE9mwESR#@joMk4Ijx!)w_b}C@ zaZYCrlf5sxOO~C<4X;>?i7i1m2cAgXwNP~D!wDZYV;IvpU^1}iF-^N<pSh1wm?ZL! zaJceTrUyU_IuT1k<$&F~I%1TR#zX;uy~T1!oNL?t1?Dbl@bJe{3mD`P`D}Px3!T(r z*E(&Xi=i6U5w>;3=hFHocw+~h881_tt+TFRzwUIo_mUPq<v)sFE6CMBNY*MjP=-NI zU8`)+Jm8Sx7lIYbef5O7hmW-l8?a;g0cMWTQK0U=vq%L~r5z8~05sZ%)t!?@(>sGS ziD2|zP2>2tBd)?aqSVtGWFrr4k%+S3h4fUyj}=Bv%%S2w^I)5i^zV{gz+NOv^ey{D zH-$gvXr-mqJQ&1NQAt*U_e@UHs6uQ+(M2$gmPR8Ek4D7RdpTpomlm=)Ip8ceLzKwk zyy(Kg#3{{QOI7JKf!hWg&xHO|6TI1mwA;{X^a=r)@GeWdfIQSu9F`driP#m3fjGH2 z-^(CnrWe8*i=A^e2+6~Mb~iTo$?7uFzQ{n_ovjk?+>}8(1<{=^8|UEOf_u0MS<M|> z@$^-*Jg5734Y;3c$W~{?sy3r&>EDgQ3kg&DHghAYsuYdcO1(=sfN@D#d#KB)+eMSw zJYT4%j+6qAVT9?F_F>!!EQ@xqeth49sgxx1^LnU^O7$)Q8MV<4ER2CJ5myZdQ|Gud z&g?4~unky2ul);57RyAXtTG(g4#_IN<_>h#J4ErTr+Ydbf?Vm2j8plr#?KpH3cJ=y zweeNMWm=}t=c9K^X9zL%4+)WGFN=pTCb+L2m+1H`d!;0ANECK(Ot*j8unusyWQ4<6 z7eF}j?u)JHMWnJDWh0gsI#;5{wkc0w_Y~uL;=A^0r!}!)i$Zz(r6boRHlHUG>Ik&4 zxC?s(512`Ohde-DQPBjmB5<}^l6{M}(>vB<B&$fwMHSI9+Cz_{!!XzJe5j6EBy!j6 z>WnoZST-v-jc44;TuM-C7Z^uEu<7TH&#gjVFav_y3q6oNAMY3GoYWKT4BkO!L*OcX zt67dBejm-=hhHn3{aNLw;T;Y{zkZxfj}Ga3<3;pXZRt2?Q0<PBm{M+Yd`@vWvmYrt z8*<5D5$+<#aGZro33l1HweYdwN*0*FW@{4?V3Ej+=d2K4O!C24An|1ypXi+3F}Ha< zV>Bzrx{N4-R2GyOD@w$V`aopICC9EVvW%X{yN8*S6?O#VbT{NU%s=2uG`93GG4vvx z1!uIXjh4cau8|LSL|~oxgcI}PIhtIh_uTPr@<As{ElAk4a{A=W$MsEhV)d8D9b3kB zPdWUj>k6D^3{$PV^9Vd97dQY0%KNYrl~BVo)fk$pre&rs_00$#0j%9*<_$qMwsgFR z1GDxYlQ4E(#&(Ng5p%f4eq^)TC3eqd9J^EQVi^C4LxSLhOx<F*M>V>?x!`bqMGFNL zM;2U+j10?H*|9gB|H!01V2;|+g}}-zJ$I>Sfwj9Oe`>l<MI|oSD*r{1kyTQoDtgHq z<4$)MC^({nPJZ(4Sx{%_;=l`>IzkyNqt^%wq7uO}5*&AS?Rcf`by=Qw`6GBkUR!>= ze1h7E#n}KA*4u}4NJRI>3;t-G*E?Q(hCzFGHK>~wU(6U`4=P>ThYgF4ZWti7Lg^M2 zAutnRr!6*y9i|@K!jFSfB|h&&cOGR*D&a)x;^q)YZ;h+P+FRw|@&1BcHka)+nnLgL z@uY!BLUw2fB?f^heo~lE>QNpmdrnLsg;~J63KaC0Ja^+<A45e9G){V@%fQ#|F-6sY z-0h7xLx=UEhWjS62aZz7oGghG%>-Fx%iq*TrHSL9aVc#Ey_I_3q+`3hD&1|nXF6*x z+CGZ?(6$(FLKJdGq;i*MRt1N~MKN~T*TQqu9@dkaGw*=iP547kcJWHjJ)bTf`Y!zp zEi=n=GpQE~)bm=rGS>10Ol1+tg1*$6;$rO|K8_Qn5)z3en|ftcmV9ape*$u7pAsfy zOotOsuX|Ot>D|3#s@!K}Kbbd7DB9Kt>@&bTFWRZHZ9sf$-4Q&6n3+TW?oPyER`Ogx zF2OTP&ats7b@ViD6!J{8*d=0BgPPrt^5eR~wAin3XZpjBXao7nF;N|tpfy~Vf`Y49 zLR))tdxJ4pse9bPxs{h86`%^VWmflm15`!dF)xYv!UkpqpIU$lIfH-Oa<qiB$>Zo_ z*jzFz^dKh~9K-HOM?;u#+fKVnGDMWMfXZiKuk}fCq3zmgCs^$02hfR-Y*2~MQLBQg zxX2htY!YbSpO_ySM{f9l1z~oN813~#s#u+vNjw!O!*!~G8RDA{JtCMN$WGM|D7?W^ z<RI!f2#^oixF^qdg`ALId<9`v4z2Z%qVd~t3wj(MJdIwp=x}Tn78qrr=6LvQLB))v zD2)%}%<LKkQ;XSg=!>ED_+#q`xtoOuAo_@EkiDocZ8>}de#K5ZT8=0Q<0Z(aBX0G< z{=fwiDL{#EKRJ}COL@cOYA2>OcN+VZQ6DRU)5Q~natqbvHp{wneZh;KK{%(Zp?K+# zxab%K=uRpgQTz3!ijg!NoG8%Se6=a`*Otxo&*=STwbQ~7+7^}9#?lHcMy!xnrcbA3 zX~hcGT<`58_TzIoyA^6Uwi;7-y^5m_juKzKLozKUpBS8`xfn>8M3;lVm6sFejhS~S z5ScqgPl5Vc=jjv6u$a8HVv;^{LUWjH4oexA`}0T#<e`U#TS6%n2yv@$bI=6#*`3I( zPR{Vo4ul8cv=5`&pD&HdhhHz|jidX)7SjhU-tULnb}6HHU{1KSclB21@TxKuRZ#hy z?4==DVO>wBN6L(2@ah>VhN><l{iug44kLOEk8kP>8(HC7>Cg3m?i2~b$W_{L*v}_j zWu99D^KaoltZ;8!13zEG8gD+0p<iZ=>jULN37L>O?#W+e(Oy{r(1e_Vu*wSjECcSA zpHPiVMAY!m=Hj%=rJry38kOh;i{60|E{xC1hmIwtH>F6F*2CJYxI^DMDdS?1)2Ljj z8Z07A>rQ+=suN`K$VpN)GiBCLdR;KH5rxJ$8J+)piS-b^Y`OXb_!Zunf&76{+w>Ev zMZW@gpM*e4rI&(Ullx?GNB16DLE+hYd1Eqxw!1%%pm@3yL-V5cJiEXu7QOo!H}1Ke z8VqNNNUB^Su|XXs6Z=5JBB{*9jPMLQw_l`YPb!-HOZvCI!Yz)y8AFa%nnxw^Jblpt z>F<dowK?IxSY_K8YwwJDpw}|5uIOydsf4%ke?A+nm&6XI=hY$0V*lvl2so;Fq?wdY zH-zl}o*L>!w)gJ4M_%H2f)9drtkhFQ$e(=>i7LremOl2~;huVdz3#GF8pf4`K|e;f z-;pOjuix;l9re@OA?Fd5Vy>!rLG)@r4kScce*bN$XAT$>ptnb>8OqEx%0WAqEJX++ zWHfK09*vyZft~s6Ft>5@g}j8w^P4Wt`$S&FX`RXRjuUy2dlhxi^QR%PkTD1*g(Z_N zP}~iXdgc>D3K5-wmN3#buu8UM3kq*<=&*3M*VRj)O~RrZ)RB_GOpuqy7K3KxB`^;~ z>8?L1-J?(#1YS@98`HIo^%<G2T+X791_)7lZ6y29h<NUD<<0R{X!N0S5Tn4`Qw%Rf zrTJ*Y^F4RHrn5sU>TKa~@vT15wh0!8vI^*|3!$ecrll0Z=cXTO=tzxV(tOljGfR3f z#<Zdgw8<G~rU53`t{!TPti|BG>>Bf^R$>`@ipk~Y!E~ih(YvqREt5*UYzF#JBOLAN z<QMNLfaz{UIS9h~oFVG##MmKk*24|`kOvmylSn!g_x*U<j*}AWeu+gR+&Szge*W}X zukkQc8g#ur!s!|Ct{yDx)2@!F_(Ja)3KRs@KOfADL|=gJVT<kp^&BR;`6nKUKW!Nt zpC2sTb1yv9_u%&)o^P32iI#i5)_xVFkh#{w8XMdwOJUNjg?^8`64ecx0(PvgKk_*d z$Q|_f^fKBta9dcQBW{q~IlsBqN%&A8ee8r9<6Ug{Wd^kp_LJ=kAJfoJ{H@jaVl`)7 zUMSCfO+60<1yp<Hds-O0oLJ#P+QTkgk82#T3`x<67<tyz^Z_2$m)D<=6z=0kdQ-#W z^nRX2C3VaerX*W@jBAWudks(sm?qqK^TJctEg~*^6cK5=ujxQJu_=Or%<ib8m)K#B zil2P&NtFrev!{2m229e66JHMXtK}|a!<eND)-JKOO}uGEuk!g2^I;VLecY$k>LiHv zI+HcaVw(rIry1rE_0dz<tcakRh&h7&`@L?kr1|q~6l-&-H5ZH8!u=L^hNV!Q&jKnA zAe*!op*6}jBJ9OeIL6)<=*5(ak_ab{I7aqAwT@>yQedIZwgSMk*qO%?bd7xdV516i z*;hnQ7^b**4G?@b`uy@kg2{!3g*M)K$+A4Do5gp~aS{zkq=w_!KrC?@*0_eugUpmZ z?=R;L$nJ5kblolZTE0_){zRtt^yNZK*q784V{GT(#{@6bj}>1j8s=7fed)39nlx55 zIbh8@umO;>NPh^M_4v3^$S55}-c(JQmGjA%GKYDasy%!`)8m$YDJ<4xQb8=o^t&ya z>sY3~@ry8!*jg&Z0rrGl{hZa$tdUC8lm(=;4^?x~af8LlTnJ!ic|Jvqno-lZn&!#m z_vG%kBnd^xD-@ekATU@~`<w-W@>5LxJd=5}-+bipSjPrsrEie$<B#A`+HTv8_S)L7 zXPJ*DXN+DJLhH}GIJX*oPL(m@K??(wh?HrseVk<z@h1O#M6&39!8>nx>*fwKrcMb~ z&4b!|E%Q<yLRt%Qj*RR5%fa)mJ7y183Y`0``C##3PpY|x!}dAA1{%x+P0sq`zAxKK zKUEC|5g^(Vq;M*vB7S_svNHE*#2RsF%J9HchWuX2WVd<e!8k1jk;fRm4@&95I^p32 z(rfYi)XHC<vMYuqm=bZgiV=qpX06xq_^iE&Y*;sJT`IM%syWRI8_0Xu=SCelO|Z6{ zdb->gK`;hS7&BU3Q<!aw;O4Wu@yW^WGd<;o0VSczXEF43K6O+WNb2BIxJp$QLGz)P z8Rk{H4O6JC*G4rg8%w(p`i0s_dGB`W{p(d;P4P=ATI0J+87&)&Rw%f=q;zFe*H3P{ zSI_hYgL>XzK8f5iw;+wSr8a9mF~84OrU0?Y_#J2v*AOOPqtGsS7QP_W+((FVP#&9# zOI}qTMB&{K_aIdcIdHjvKmW~ixonqMc_sFXgUW$9x(>M`FG+5r$`oLksnERF2g>J+ zruA5ZQ=R}B=ZB}tB<T%yUrIIy6bNNMf@<@h6Ie@2ISn+j4x;59bk?`1mnJo9ue83K z9t~uhWYTvr&<<f+C)TeR+8vS<hBf@~oWNclMFduE9a(5Eee{~0&fGdU-J_|Rg7y>5 zgu_Ky^DJz*TNA{R+tL_G)S0pT>Os3|ru{Q=JVKb^tc@7d(kai7V`BFevS`^vV?n?1 z4LJsW{<a8n<MbU%?=P<@6fWZoeSG4AY>CWL0v<!N8VpepQK8Q8Pzp1wCN#BAVbJPU zv3R%m$|DJ@yRG!YLRt7XvdR_`52Yf}difa4RS5*~JqYvsC}+K~L1sUjyHIX)^>tw? zfKRhsPK%&kzmZb^@^aXr$TK!A_!FVc=M5Uwj_gEluP`ckyjRi>4c{-><wQRt%b-`L z<V}!TanP3cB-rk^<G-}+_PmRHN$n*3ehEyL%-Lxta>=NX@Zv*pzRRL<mT=U7f^93f z%c<bNsu$^K_A^VY06uercHT_-1p_yRNTA}76Yn{jnF#aiY=lKRyscH)vLqNMwuL+4 zI&W9TzLH0ut3*zvG8}N6ik)kGQNXq>lY_%CFSuZ#3s5?}=EfD>{)46Zf8wu}m(WsI z*81*~RdD!`Qsr;H&)d`@zdK_&SbuqkL2N(0)m*op$$#yQz4aP?xBLFg_5n%K0g$qE zaB=;XEC#^K0^<EQ={Z<=*?Ip}dXC%t9e=5yZslqVrvHn76SDCu6$oU5%?#}RTY3&Q zfU>cxsiiI09c&BWG-CjYxVc(Fl7@&va=Jh=atMF_c41a9HWhVnuwjODgvHju)Yul1 z#N+!xfSj8hBm;~p*v!lel6d5oKls+)`MX8`r#}>^q^tk{{Z{kF3;Mn0&nz?#RK+w! z<nF0k*@0b{A)yb(_J8+MbA5B0|8jNza-jbuAI;A((`CDLq2Ks$|M|;#>-G3OgUmnc z{Mhp&kIc;zH4y*Ct;_n`v0q2NmA?J|R^z7p>-dkhzI}5*A~9}q&;0&<lUe3lMw)L_ z-5kFuao^;nxye)W{qrpc4Lc9-?a^O*Z)@oP;!y6JOR4^Jl;7a?`ykw0?C*o1E-fRe zDfxX6L>+7)8DRbzDZu?LQs7Txz|Hxa+<-X0hgIm_Q2*Dy-)#LWTz=KOLFU)ik6b^$ zu)jGD!QfYaAs75>x}v|NJNk|L{vJ{A*Dl`n_%;{Qzu?I4aRE0;oghQQ3E<+nN#Jyw zfr;ZsE-1*p-*Q3SY~4g#{LBUQr>sslX_OSKTyC-e-NwfNAj}KANwx%e5{(`x0+aws z0_A{8Koy`GPy?t5GzOXgO@U@WFwh)m3A6&*0BwPGKszgYHy5Bi&;jTObOyQrU2KhA zEP*bL#-?DPE6@$-4)kKU?cq)KD0av&Krn{5>VA0TZdv9pGHm}n7u1cM{)p812h!qJ z=H_6?6K0Sn+;llOzY+0U496|g{NC!nLj2F{_XBVcFMu1OWVpB>543&5`$i|S{sNtg z?RU^2TR%Yu@%}NB76`=gPt3KyXYk_Sf{fu0{N*5+gCPjp-=O*z1i!~|{6z3K-S*F5 zj=u_{*pJW;b`B8xZ@G+owY=2TCSM7G98Wak>W1mPrXE{QEbv{}CMjAwH8f{a9%TdQ z^Dqmc?z;HH#Xk*;g~NW_3s4(JV#6&dK<BAln9-~<u#wYm-fe1bhS*qU>ragBzP#tJ z`dHqy@X)52`VqCov&^fDmc{)Gzr}TBXcT(%5S&D1<xI6I!SZ|_xz+3D^gA1@{RFH) zM=Y#?mjjUG)FOJgxQ>2amGp2Jf*~kb?H5@E4WxtRpO=f`N7D}jU;8+9^@ln2wdyvx z&$pJ_PS<+bh`SF6X;HdwsN=4NQEJv0mN<vY%0{?|FT=ujPbcAFDRIZ}C}R_0;b5gC zXz3(se0`)vA09qM!^kkN)>1F3r;~*DRNrOCbFh6*>>U0R9d<-~_@qN*b`jpV2xBsz zcv<0cF{e6Ak~ED<-#Gs`A7i*|p1T_4DU@+qYl@do9;SSOeVG)VV~}^EHd_R%4m@V+ z&M`1c5QSIf9rqnXRy+-?gx5eZZ#<tH@jvjNX-k0_8XX$p`ceU)nDbYubor3{1U7;v z;vwX2X0FDQ%TjmvRODT}Me<`KmjgYP!=*&5bqNd$mg1lk<M%v=7NKE{|BtwL46kh2 z_Wxt6V_O~D=-9T|aXPkb+wRzQ$F}XHV_U!MK6~%8?>*<-d(Qp8_`g`sQ)^<aIYw2@ zIcwDTd<#ceDsJbx#H2=@L}KBZgsmJnU#0P;F<EO~c?(1vJ$UVAa5+(414I5ouDOn8 z2!NysW0bPmF9`6gC;A2yj4@)c=c2;8BfYQHxcOAVrXx+7c|<)1e!Eups=XqS5kmWf z##Kes*82^fLQS10erM-fw2@LHIt{-ju;*Y~%duz-lWW!{K@n@g9C8toq5EeO2F`G4 zkmr*tCIWgOfim@!Wr!tMW{=TC9_e?d#akS-w3Ixq8jEXnLi~~U8nB?vXgseje(Z)z z@T)diweGa`j>NVvKf^c9xiVKga>P{`NS<Ll;NCfGjw`%9P(SzgVbUVG6YaiHRk*iN z)y;{Ax~MKlpEH$U2SyRB!|;;?bkK~MuxY<=!~H0|sC9dL5qCEd;{SdDIGKuAdX)ev z-vP52!D1+KQ6^=l%9aH$8>@~~8jy3o1v`pwwOL}NzrPJvBa4vAkD7lP&VhY^r3<K= zn2ANS$kY3H2$}Q<*x5&!Gov#~PDTk^6|Zf|f<8sp3t<IN`=qR7PJGgBOJDA)m9zXk z$Pk#OFaj7=0?(H%y<hfRuFlej+nz`pgKdK>=loDT{yRAv9`uczxR4@nJlz2wo#&{0 z!&=XyexkWn=<(XkxfDav8%J-#4^G$O%AtxGHc(63!U~kGZ}&qTj7pilRB~SxQ!jYE z$Kw@K3d0ksE?Y(&4WNCw9o4=^C;RZIl}5<bzPLYh@|XNzz=g&r+K8thR2wYK<Y!5H z4o9GU_rgC13|wG3!r@T1Gv@dKSKIxy3A`Gj5=aMQ8JW7W7VDBZg985AOl1(f(R$Z- zWKnXXE9(OD0@}w+U8B%^mVkOXc~;q`c2V7DGtFj5?sDB=BOkgDdO|6y?2FLLC=$tM zx9gzHU{Hpd;)DivDpHo*Nn|p!DW}KgnY+wy7yj>ud504;ArsgakdU`FVrgyQw^t%y zZe$g($luNoL3a4Ykdb!xMn`RJpyPTqA_z_=(;R#SRzjf#<sI6UM+i-sj>cRc2%06W zu-IH*w`K2VF?^=g)JRLq1z$8>&PDjSUKlS%+)^PgAC67Wsgh!Jwm$^U6+CM75V!G$ zP(L@#E#~qSM;8H5OnpW^njR)pqW2w;NykCi4l=9<l04FaGT<OqzSw{Md_NA~czXxc z-w)IX9QGDFaPab@&)7-MIhmCk`+Inwgum_b%FlUx$myTVu&GI65{ncT3oV#hgX1En z(ktwY_HoGU=+i4qNVHq{wgjX3%nND9i%#uo#%5JN$BH1A)YlF!uboND-lgL#e~ii_ zmo}raj`=$TX})51z62KpvtNj0D&|ZNeL`Mn?GIqX$o{O=+15JkR7Zr`b=x|v--|Q1 zwpPfMf$BG>i}aBbWn}2rzCI3(<|Y*43+!#NNL$XCs-RX*{w07)gMKH4bwET~Y7N=H zfmCg!RHS<%SAqH<aFL|u(j1p!zPMCzFLb(v0_0j^JQ9-nhqHk@1TD6Y%=6AF5Ii`o zVg1waD#Ln2|2~+a4@F-e<Yz;87iJh^z#WgES2?5;`~#9rpYdXb_UkmBxueFHC$0s+ z^@fu4K#X8`-<iTU^FB0AnsYuB_k7v;Fc^+}VG6I~^fQ1V;Q)h~jbNa(9&7B#cDvT{ z$H;^YzEv5QdxkLHZFi2a($2l@TRW>u`|ngPrfYg6I*MrIz*54{H3V$anrS5IR=`5F z0a4bANNoU>y%T2-BdNeaR|=(|Vt_qy9mO5E5xem}G-jLOh4{Lqq9P^7e{#Lnt%Vq+ zq*0xccrN#-Ew7k;g$1)TXum+2mI3%S-GZq(*cE%$5RsxfU5m15-y`Nwi#*edFfVV$ zq{{43Y}qUtX}AqMP3Yp1AlOyXnpj?osSzO*s2d<Cug#{)<7m7MrR^Xz*wE=vu3eb7 zGJ1c!MT4|Bap>~ntMaB)+YPr1iVeiK@JB>HFU`l3m`bILV+0l@mayf5j9{-PMUjO$ z?zoj{`I76JfZ`OA=Si&S3*2u%R_|%*V4pzRwQxLu7qVQtk*}>h($@!?QmHZLm}E>R zbHPBakJ6F&_YE2>3nZ?Yr`}lBEG+oPATk@xYXS_B1}_siq6kEPfFX)1M;{nB=%39I zOzf)ZF;y{bGb{KQp2FgmRH~THP9HHuKy;l#>TDZZZ}z3o(sX*UiT#W^QlPrh-`t_o zxNy{dip<M3K@N(%3#*uenRB~s#P1MWSew5|atreiktWGgv~MCU+_ZK){Duq}vUygD z{=#O5qmT!5Mb;L1;O;!{gtR8OMpCZvim3FE{C;OMuAl0HHKX}*yy}P>LN+oa9$UTM zdTE<(&9Of*(D{`cABEPqopy(8YRCV$J6WdjalYm=MDiCT<_@H!8L$PS8uUJjdu6hP zM2V5U?U$GERAPIOuXP8o-zAaX!s*bs51&_l5@X}GF1mnrZItN;pc&uV&>60*cyxZt z)O%jJjr7SGSB@ysnpB?8Wgo&6a&t?fy}s#6-klC-&{GrE%G`oQciE=Af-K)CmOH;| z^(@5_Or4Xq$J>^%rqd-#b9>XV8N(rbA1A7NjJRCzYMwdbWVg}Vj}q9ZO~xO`AGs7~ zG4z_GjTzm571WMGg)w$1<*iJXglN46(~b>j%vIhMgd?)Z9dfdpX`^Q%U{|qcUu#8; z`EpZZGFMI1yfm~85+BJRv+o*5CiCKdL3m^Ez9B+N6W-}%hS{|dm{O3$es6RMs4T+3 zN;_3ii@_7bhVv5Z@u7`by1n@cumj9~A4thXt1VpTh*m0WD!dFLl6e6KWjsr_U0DT9 zWujkW7c8jEL7My7iyA!xosB(wvmmpLn?Dxprqz7ThU4zL`+PL`l4W6*zx(y>hr(S> zmLqi^ChKcO{rXuW7Yyd8p#gt(y3)6>;yDwMm$Sk|O7JJG`c#hyx*IHPy2cO{3e==v z4$5aR4$kN2z6?U;WIPJ@1l+)IWM_UJuW%GzT4=3--rA$$T!Ex8tXVVDCNMGTX(M#k z{)Ds@P4vtFhJt|Hm)umU`0#b~r~$xgrzjbm<DqKGa-|~djx^WZjIrm+%}qt0WL*45 zpA35w$)M`KJvgV_I9Qz9lek?0B8Q$WbkFAt-cIo*sqQl<70nS$5tYq^sq4mdtc2YR zB$!O=g!;akig%5ER%bSyF<VQC>S-wztlCivTJDJfdH|7VKhz90<8QmH=A-KQe)aY0 z310{Y2w5{sX^sY;yvqi}^j_N^*s&*9?y57-Nkvd}7dI`wh%0K8Dv~d4(Xxdv!C^|W zQ^>6mV011{pr;Av`DM(V4BKzHqtFpEvz#gOQrFuAURu9hg1mE#XGo;G1&Hh-KKYDm zS-L{T(s<v^9Snv_%;beR=B|;Kx7yeAYk1*$fLwCxwjEfzn;K|!lL5ll!$sJj5jO%Z z-I61AMzG&ctYM(qM=g_00tajTxCxuN9fZr0pLl$xx}TLVbDW_!s_(HzVc@N@jw@hK zZMWz#1Cllfi#b)*4)VpJ8u2EO%URsm1TVcCrPe!mdi6|JLwpi9XvIvxSMw+?NiIt{ z2#l$Jc<xSVk>1@WiW)g%R*RWgG&3Y{6#XIOD2~433ww>E_YE_NamE;zBjdbQ%C*9; zfY>7R+8K4=fp@i`plMaA5dEuk4`}cNprM)Lq6N2>0oeor+j`2xQ8W6_9i}5^ub8kk zeB95=Hzz+WK!FX`@5DeV>yy5r#d2jw&CTOY*RUou@ueL4E1e4WxTnKj<nFMpM@mt1 z#*-H{D^I2cGsvdcYB04;WP-~RpSXM(&jeQ=8+*I{VcJTiC7SBZqg^a|v2Y^gzApeb z<A-;>|FOhpsF7XC#VDkz44z>@CSP2t&OMlPRnk}&6k6Qw4-6Vtlc|L;H!9T9rEM=z zP*^o?)UYwLYBt|Q+1Yo1L9x@ke+(tE{w4nLZx7Vi8Cm}_PWvjaC8=PH&Qti+eWNb3 znM8`XX8NZQsi^M^I%_2hjMgNOT(IF=nG%$uw8qcRy!Dec6`+SsuFLAfoprm&5Cw5r zfRUo#Svl5%J7*+A7>zVW6P;_*&h4|%I4#s1t6T3qvdGTk4qS9nhvj9Nq^!!AG$b{* z23Q)5+C4;;#U+}d4lPN{MnD)JD+v-1i)&EdR-aLM-bi^~z-ZPbKbt<~$t>w><js7m zD0MQ~DCMxVFy`y3cqctaJ6$j3K7B!(!ez4A=O}dy(yU8X)yV5Jbcj!(E=*%T{gl>c zGMBi-Gl@HYB*naMH1ZA@n(I9`j>Di-jjSY%Q$SIyr3hIcHLW1)$oEyw@d!+dzmft9 zg}%>$if)=2IVoA`iGI9K!=^$$6>-ji?;f8Qo*rnTq|!t6n@LI092fB(T-9!vs|yiv z7{2aHxA_GhoKt8v&n_-MBLX}iatlJ(Eop!68$C!TwrSZgkv2k+Lo24M;Su`}QIwix zAGTLb<?fN(7XFKyJPm~7E>@(lnRYoHw=APN)4?=BYZi8Tr^l0<p(EF`IHZq(=sAFn z!)@y$qt{5*<Ep{U2q*Y%20M;vu}NyCYlxkf!nV#OF!TC6Obf@0*0)`e^=!(~{!{^M zUpMiQ`y`rlSJTl}gN!O7)OppDtV3yS&-GkW;^)|<>T1z^G@;3W%=NR+6XFNNzMZQr zkJsEY`fvQ1-)iVXrs=o|#v`Jf8c3N!=6MdX0U1){kPGSk?#UVX<i;*DtBiv+6KWcD zxp`d0Ubvxz7<zmLI;`Qhqf1u?YI-F{HL4&%&f#G<pobn9;mR`cEDxf(RFn~l99`^> zm)?kdV~fo3tAK1s(9x<R7il@VwP#cOxW2tFeL=ed;Q(T|JanzWxxdW8XmblqY|s<d zvg_VvB1Ec*tkX7=c${8q+;cN}&T62owS%oK+)jXqm3qD&a;)(vM_`nx$k%qy648I~ zR{wz|yst7z+_H%KRlt%TNe6ZXFs|6LmH*u*>|%}N>vyKON>W(OySxfPXby-Wr7!E{ zvoQ-!=oLHIu-d>~B7VR!{$A;8clXVHebHrriqULu<$6mm8P|YMfNNq@<DT4U?!nq$ z+Mph={CZg32>cXm)14OHi3m%Uel)YC22Drhj{;6}_egi3c5u*k#@7Z2)!4A=E(?<w zAf!E>%z~9IB_s0tB0vp*@D-?IMn;OnR?Di&T5c=B2)%+DkhDtvlfM6#g!f;e%>OG? zWaIGF;j7)hhZTK<3V#d7{)rW_F)-sZGyKO`5$ms%`2UI(vHp#4lmAWb>2K2%AA}L> zuW<Qa2_ptpCZ_+HFgjJ2uv_i_)Ul(oi8_>kGxlr~jx=O@BHl-=#a`qtD`zeanOGyr z{`vLUojW-*MN4<+YmEpByo9^Sjoo)VV0X*{*}VMDE4Pnjxi$SS_dR8a2%K2`py(kE zL<-YU`lDJw831j>MKwO5)FCSGsB3)EY)zzxDjzuZ`E(qM_PJK~BduV2kgA+f9z>rV zdhhs{kE}Q#0wu*enK-{BSeggQiekbsBLFcBL%02VAo5rWiAF#F<CE*dZ5p4@Ck6C( z#=*<h$~V_uo%5g(J#7nBO;X~|Z8!;a%0KBat!qEUhG;%tev^0uHMXoDf&1PWb}sF5 zVLLhOx>X$;am^n{k3lBsTNzJJc+~w0QX&EI9TqSm4{hqF+)6Tnz2H4SG8p)V7#2lt zj)^?#!Uct*<-&6G$mYB+c|m?v=<0AV%p~9$CD+m_VLWX$4>{rYPr5#`g7Toq?ES^V z)!Jw&x^f8f=0_hZ;&jKt8l<?lccOK0907{7(@IcS0by|w%drHjgMg~w5F#Pq_{*VG z^MHOb(ZC>3XjRl^W2gm~Hz#8%9`yF305r|Js2wNCxzHfKy1m`h4~|Dh=WB}vs#W}y zM5Q)F^;i=O5`>{!@EvSaoRPO7gyhOWTSh0tZj{|EWHxB`YWVnCZ(0QD5b`ET;DRC= z4xW+Q=g8I?cSw=D`_LwL1Q1S$U@8jZiEU0zZO+y(;XAor0);yGcR~djlyuZ$vQ(eU z?|V|DW+AnC@;`w_ah3SKkN=3I;FDE6R+J)Ca6sfG&|xnp{x%bv7FeWpFVn%SCzYD7 z7m6D}k6Dbdiz2s4YFeBYPgpR5lFwBiiQlZhhh;hGUKD;ZLiqUpVAN(QG$@K}jnZBS z3Z;||!(R|8i9#Q!j=d75Mi{C%y@bQt1<hL8Pm`dDX<NkNKv0A*h?9SvlHB3NKWp&? z^U8VgfYeq6FrwYkVZ2%l8f78+YLbKB5^uG1%Q{Knv?KrPpnXkIPPIm4xb9TmdVl6A z;uIzdZ#;T=a{a7v@wHE2GgOA(C!zd;&Dc|W!6;v-YOJ<+@g~KX;ytN|I@-F!5U6RS z&uM~H+;V&zgrlIL6tYD6`D8W9kaIJm05R5kCayh_aRWipRy0sJN}-YZa|%k@aMVp< z+?Bp3mu8BirFkW7y6`BIkt%$5&de11_55?`eTwb1X0VouLfaH5&vl5*S;{W;?uss2 zk8CcCZuWi2<KcEbu3_^G^bE|z;O=KW!wT>Od`s!U*cQWaCpl#a$m!%m&+5ERC^=J; zdxVA&((#LlqGTaXnnG7_I@PyDkrE>%^KKXqnvz+uK3Ww4Ke_3{C;CbVxvuh5n7UI@ zvbky_*@RrWIl@p`SyRDd6A5a4TqS1$!edbJ_ge)gWwQC35VM(E6EUgld1hmldl;)- z<yCzl<J&aoD?}}|HG6SyU15v+oIxSG^jS_rpK{8+>Mm+)2e$SX(2RzjI8d59Qtej$ zbT0Z7uq9cRi3mn3;7EO!LzPo#fLX>M{5{awuN^9z#~#@lvb-{{n#zRfmRszr8#-_n z7nf!0SeK|jnj+yfJ#TKQra)>w$0vp_dg`^;MWN8OCF8-<DQvH_e72!LTdnx?)4sdJ z{R!xtDewekEpf>+9+jp$?&xIkx{8xDlAtCZkLK=A=Av;u3ND~e<hOyiN*{S-O3lzS z?<1kxH%9iE(mnk;2<c7&i1We}#;Pu)xn2#+`vwtf2q=a>p|}c~A*Kw*t=G;fMWwuc z4#&8jV9GnaV-k`umr{pe50~(HDo9!g_$}DKX-rr<p-kBaVllFU)0hk=24C7o7rNpk z4r+5~U3SYq7X(1NRRJv~&U>bZJ-QE)>y<MN$2YPs0z<v#!!JVnfQ1V9vq4%-VKMk( z@M9*QO<7QG(0oNS@%+hEjt>Pt=yIb6qOOi?WK>K{xIZ@d+r3b>KNY*nSMp14z6#_m zh6E7SgsWD*@5kbfwZ#OrN29US208BSUcRq$D)7%opX>QZka0jk%Vgr;tN9Y(>iW=A zFt_P?C~c)YjsOKEcso0)?E}+XQ-@Q`{e<9S>0%7zS()NWg1gZkUAK?>#VJ#3Jc2H! zCPMiJP}E9vlvc2~#`|u>a4KLKq8>gW73Ll;y9mws_!I*@qEn~{dF~VR=1}reC_m~_ z+%YBCNFAsuz$KjGmr{DJ1aX!77aJdQg4Ib1i`FFjxK^lnOSdFlx6Xyik{<&;g<?fw zRWY@u3PR?Wc#vkwm~|I<WPi#*jS8(ZOUHt253IAT@%>`F<n$O+lPIO?p;HQ+AhUq> z(-$41S2L1(tlkF2bT2tUl`w2(sem$(lxj>9Gay=-bG$E(UmdG1@4(4i#4hZ*^(|WF z%Jz+^=<)r^t{tnh9aCF#@>x=E5-w7uGch0(S;jq@?We9dA)89;))E^}=hK01)n$85 zG`Zg4IOl2(7A?(>4@v$_>gr|kQjoxOn>^~J0*1rVjq^sq_LP8B8w2FTy7cq6s`l13 zX?q<5*E-U(jkl4tATO)){Q;ys@KJ?svAF#eFE6*5OJTEBd?$$jC?Ti!m%AsAj`B^K z(yJj@mR0xb1GoLKelKOrmjp@1oz|#@m1j3X`~!<YiNx`?LuNp3K+eq^)(KB%m&?W^ zUY-qNHZqdylY@~Yv<a`~C-YlM5~AS|a$3x^jMb;YXWGUEHI_8-fk^c1T9cN?fy<uL zgVVLO^G=VBEN+}bDYoU-FOe)=0?9-PawBD96Yb;JHUk*03UfAX!#uj1B80ENA1gGz zxJx$6G=-Ps!1GN9Tbe|3s)^(}bRV#Oh&fddjJvk&r46&kL@`#A8fA6*I^898Qw>a1 zd$z85v}w_^c00%uLCiX+t}W$rIi}%VPaj%tlA*G)n73);x$QdK26Y=X1i!WokN8$6 zKV)Qz>3FoYE{%6BXKO?6-o>ccH)iv!u8+b+528V&r(1oq+L+@+AjzO`>hN%TtxRi; z>fGoU;rvvZX1}t_y=ipX$*cMqe5%J8!t<`E;Tm{si4H!`m}lsyVe9VhdMycUW5d(E zQ7f*yF0DBN@_FTaPf5ox>T7{Vq{9MxPz1{6Xm-`2=K2?>m5uMT3D<d#a4VHNqgXR? z5)tm$Lu;l+tB)kLJoTHxLU=4Ww}V0CJTeQQ)jzYTN%jRJsP}Jvd+r4aCR5815YTuD zFjE^+Lw|dv3Kw<AyoE*+=IB^EUTfB`Sge`U6PgH6E=oSozkQ1@Y4~^t4<gPCbgA<v zAUpNLQCN_Ds|%YDtLsH3IUK_V!wo8tk_z5B2Q;#+10svoVez{Q?gpGdDxv`o)51PP zY6YL;^f|gQl9wnrWWvGHoG#;2z`C`nAXEsFRcCe~+$qHUq9`5z#e}%ljcK&G%#x$~ z%XMT!6RzGUo?(KB(<yYvu*G}c=yYqw75=$D^GK9=TV0&xBG$P&t2^nL3<gY{aD5>y zaeD)DrIvu1@^rskf||gv!ZAeq!S}{$jxpK}xUbaL$Yj9bz&`8aCltS#>gHnB7g_L9 z5D6VY&S0!x|E}HXDWjm{v2bqMqOx&Yo4Y*0sI+mTLgDG68L~XG^F%EhH?RTZaOH{l z1|6e7LHds`_-wyr4*Wjj{M%&nFE!Y|6Rm%;tN%p;_D|0BzY(n;FV(*k3qCggBlZ9H zx$|Fo27jI9{%^{#zb1<D*?uKZ;D5-?ehdcwSEBWQxzG=ymEoTft^dm<|7S$&uS@>j zus?{_kClS{$C1QxpN5#-s&Hb*u?n3&oI<!R-yp2Sy;ws*gAw*xgU|rB1>P6rL4I`5 zc)<9@bRAwR2@A<#`BtKRc~L`BTaS_Z#L!CIitW<Q_Rzh@xzo~xb4kwx5trP!(v{~@ zMV$hQNc}kNHQWl%^#m!Ux}<9at#hr9d+NH@d4#xAVMS$C{K?bNy##YOa3}Y?R1f#? z88P?S^I31frK-)2)dI!3l7|NKLg)G$8r9cI#dyvEyLG46TWxdT*JB?E0cIG7g)50E z-26LR*3FyK;B(Y@dF)-_ROEio*YSd|(_-Q%i!wdo)Wh4Hr;nA985CC?91z_$oa$ND zvmNOrS+t)LM=oJ`N46PePk}opzg1h4mne{N4@73o)!Vju%@cKU-w3I#>7?!-a@e#6 z!-1;k>)PU&Hixb3w2d<j&!|o%UXhC-b>`hKJPV$`Nq070lsZ}!zYryO_4X{i3Nmjf zj9Ka)NDGBHdI~n{HZ`rDUhrI~WgOX*!M)ht_sVo=lT%eBb+#1ZoHC*u_Ex~xnOqX< zf@B=7VHAnCt_`%b!bHr%y0^=wib)xpso;a^JFHyR9xex!B(;=qY+hBpNStg_xCVdY zue)<DyD+kCr+fhHowPe#bs~wz=_p#$nn`FY2X1tnG4`RPSBt0g^5&hk=`7gs6xg!@ z3aW@3EL!yvu!ql!Qt~QU0gA+Q%t@y6rqjxI=vW_I$>rKUJ$mephL7y~Q4iXwvy7?k z;)zAePSg-H!q5q@Ut04N^+UcqLXqp%ook-%`dYvn_UrH)=b>$-JQ_R6v$V5r6D<ej zKv}@7BS2_z<=oj@2qn+_o@!Rpi7CWxO1<MfU&Bzu>7KpHEd%zninlHlf*^c(p>Hc$ zGugHSIh9z}pr4`A*$|3(YEGJ{R3JtHSTM|4>InXq`y9~pv{1IFuamu>E=cRzkh%HC zWHHBu)E+CxrL<saLsA;?Vu(s{o7I;<-vy{?aZ5)kaKc(}@_7X1)P;)E3@ul1DBf;7 zg5M%SwnM&pPH}*CeQBpH$8MVJh>%B+E+rB>d4m%w7K_zdizL<%Lrx|}v2F4qY!2sE zm6TJ_Mzn7^?$4{-JrED%gKr!BVd-o`04{0&RUdKA{`5ef>ulf~t)1RLHSKcFXW@Bc z*{)IC^Km8kl5<5+GC~4#1$7VaCEIA+x0?<kxF>KMr1v-0*Ehl2jUYJQFpGvy4^?r* z^OR;)OK|AL+X0iWUm{Fu^mc&*QP(N8RAPlqJB$|9iOpslD1BVn?YKkDfdB@XR%c_C zop)O*67p^tYC!5~fe>e_LJ|&yZ^N-3+d1HKkWg<EWbFBbJmu3OcD`yrA~yOeh-<Yx z<LPsko$@X0gt+JL{E!Kqk3!is#-(+{CF{%N&pG;Hw^?D&wNN37)RM(V+C_a5_;~Cs z4YXvsBUaj(RwNMO`?SI@6_8&kmOD$#7;|BChe;e%W7bYaOCL@$B`l-PjT~&C%jp&) zsyYAxNl$tt`c9I=OO?^ug2USLgRE=EFdx*(s{}8Sh;crL;@B@5Buu@GGF5H01QgDv zzvlSajLqrPX9~LxFSHtBj5YkV0EC$Q%HHlMn<9hpZL)*^3lKVh;RSWZ-NMZ?wdcdp z>1pYP4gvgt%8|I5?B%f5lBCG|p32_g+(c);3b=_siXiDk1#a>2p}2@kU{g->)5TE` z<P<H<I*?rtrZsuSXyZ9aukl8Q?ab-U)F%l-=w<scWAc0sPr_Igj6wZ2h-j2T2_nNh zZ;M!GBFuiwc&uacZN|z4Gvskq3c;kVa;7<PP#y&%v2lYAQ-M+49JbSaJm}R+WovPO z>}GQ5TH_+sR)3ODTzDl7M{=k!?9o2Md2pZ6DPEgJMENPnqG`26jb}pGs#+9Vco0@A znYD*h0pN{g*9sPJCTBBB9iv$)9!Vk{{A`DMwQ2O2DLm$V9{SPp+XIA7`5be#w$-Kh zk<e@1?q`A;RcmVX47eu!eQzkDKzAsJYB4maXxdC5mJL+8<@w?R2x)IaU)dYzyn&1& z`ZV@sbe9E?Y7V^f7?{`CLr5}0xd1DA{fPa*C<=+}tpyvY0<$HYG3m^=2Q9}i-@v3) z<zYZZrnGlI!<5>UzrGkz&wi@RWABlYd{-TspDK&4Pl2F^QEIKBL8m4SImi=qj8O7i zP|buK*aOndcBHJhv}?ZKi|4N*q2SCYc)&nqNTEoR4yWX-!0N9^+OL)Dp<I5h?f($s zuL=(4fjYgW#&81QhM9>7unD_r+>WFjp`n3G%?zyOut`xKd0ooNY5xT6fbTin!4nlM zbkW}Gh4C@%JfXLfl9-)|;2&`inRF+6+a0<{=T)@d$A|F<xiTTdYb)gpIvDxxa*AE- zwX=VjJ!*_AFPRb+o>5lo1c6~d|AS{#+_V&A8=}I@G;f<YZgyvZI)XpJTbLZB9POP( zd=!j%l&WdB-u>XQ(t@KZ`7P9$>)P3!a?t=Fod!B(eS@2^hc~zveh>;pEv}pe>bjN; zNacoHQ)-vmaa((dOWvhst4sSvkunnRF1>ocCCG|T6wZ7hn!MS#1A!F@HIchD5Du`) zPkYIp03~S8s^~@A`^yDBBBxJ3+Bg?*cwWcJ;1cxJVY3$0Y@!7+&107AN_Lel#SSLl zxe+TenHK>^M65eea?p{YH@7O(C8~g~2%K!1vJMP<4e_I2oOn{zjt+@5-TW*U5){uD zbO@DF+cM+*G~GZzL))MM2LhN31d1JCAjN|lFn^pQT(Be8D}*1IHE1u9kY$4fS+=&1 zI?P%%yrR@BC1`9DtSQNZpp%I<!pNW3vt}${^-tT#9;Tbh<-SOw8PA+$KA^B+vWC(= zRurp2f~!%4NG5tLee+XE8s(w)SE9%`2SZaFQD_rXok<)6t*(N=@XpaFyeY<nu93N; z0Kdkv-Y=tXj?_F-EgF=9&<|~(IWww>z&Oy&B54`O>{VaHM-RBM7e%3eV|4{`$L~>% zlkkwLpr8uo1o0tEZPE<;{w|w!^!;bj4nR-)^TkwfEVRoHocLR`Ck?51Vx*{Z;GXWi zUF$=v$Ld7&6WK>>-@wv|tHYzAM)xLXrHk5EhpmmdY>}FT2h@AtOXn$zi`<m#9AUgQ z(7MYXb05Sx?Lz`JB$d><r8~hz<l6Yi%9@*<nV1<fsQv|JLJ^fSYJu}GN-6E$98ye^ zEVH6((Pk@{C1donQU9IQAoB}z#@WKmnCl}tm#Mws!9<(dC=dHdpG8?&CHZtTqq;uQ z=aF{P_&raE)i5Vez4}Ey+0>J4s(T1@*%8R_rK&JrB24eoVQ74r8%f|6&b-nMk>>33 zL);_z(I%@Dyrjt^%$C*QPxtm<Y0>2rAj!Z_RelvQ(9+@Qqvj&}MtK0oq@7d4)5Ft) zOGAD(MQ33Wj9}QPleECsN?&ys{2pEzg8G)N(g);F$(Z7qZAGp><a&~0fk9F#(#Iq_ zi}iOSP~(mXh6v*<n0l!$C0j153b56G90=$jfcO)pfL?GZuG{!qWqwM;LwQL?ppHL> zvVEOl<9kj5Apx+jGh7y!!dq;6EG~Z^y6uAqm0%Jf@fflbUjd-Oio8{`$i4e<3JnF# zo~$R0%I^l;>cty?c+>^X8iX+dMg@fgSp4?6VXXFqeU(V`1a!~P+<7&vd|LoO$RU!| zJdY{K!{cL+0;)lnk`9Wz?*5JoeON*8ek;;)VYJnwF-ZiWUr2U)<p3W2%B3A@B&-_J zc&=+`M2*>^&DLt|f9rBxVxOr;^|i>Q_5#cZmwP}&Q*l7PQ0>A~Kn~2<Bc9-Tb|fd* zVofbyxr>R~E}mAf(fV6lEvd37Y<MvsqY@1DlEtkLHI=sqhiJK4j{Pb3G7nE6j?s7v z<RiSb_Sp5ZYEGX60xySY8Ke66YA--gnY4RC@;q#yjxA>YgPtzVLVn)ScU)*F1rWy$ zCN^U{z%^JFZcSGog+(7$cu>S*y6q47rthwr-U-)!3kDv}ddqgK`pzXL)>|oPXJw#O z+W=75;~ovj)Cluh%sk2oH>$%F$70yf`Nwjy18HUO?;wC@eT{OY)4Dc$a*{RA_bT4p z`o_gBa`~k1F^X-TUehWY_V^=S$*Ff-o1VB+Babi-6xF>KT*k42^d{h!5*m%ATV<7c zNhs#5uW0i=k|(lLa>oKw^Y5JO{LFR{8EtX0!12z-y{xVjPwb0^?Gh2(*-C3NtVlg$ zgX%!qE^5_`*+-eBO-xyenutrYJPI}=<p$G%0Xdo=cK~B5DByUs9M(U6vQz;jYxqq* zhcjohIb^B(0qYfynZXLJ$$pV|@oGx7i*n*um22WUh}+v25D~-}I63K`H@K9;g!T-l zqk`sV(yoiKLj`};3Q%L5BeOM1#tiQCu>LaZ>vfmi=4yz8TKw~KP88e^K=IGxMJYc$ zi8e0RyPVPP0bJRIK-Z;9qCBka*2kNV+s&ZzNaG*WWtPhhaqn|YD8%p(IRX$%au3wW z6h197dji1g@C_|T1Rb)p0no9Y<kxnT<3)~uW~1rOD{O)8V%|J50SQF3u*1ACi}Cx# zWY#LKUMt+_9y!FS_+Z{46=F@+&npO1PK~I$$dxG1lYhKujzq~4!K_AcKV>s|5^d%s zIPFmcZhN=*nYgDDUJ?olYH{-_{18U!*BrR@8>jqA#$-=1ZoG*r4kvoi{{(e|2I^=4 zY-AVlO_03^o%s72_AD~2-&kEb4w?c$hVQ;?$ibKAXlVu6Oq~!tcL)s1NPYy$<R1fg zl5r(aL+00N?wC9jOMyWpV;R{#Nc5A=8@?3O<oJOpZo3fP+Fg%&_ym2_RBTE+)kM=0 zFWQuzdR}n8KxSqb;Znm~TQD$JhCYj(+~Qw#-+ol2paYtSEknmI=J8yNbW?x*e3^ev zdmx9I;++G~6o3Q-_tyHY<!sC!fMv2`hoqkQISt1<qa+)Fv53F$$9$5vNNQj+V8`ZU zpaOPq7myt=@*oCk`U|mKhSYREsGFn|sjkZju0}5_h?OtRjs*+470}Ke!1RnulY-Mj z-c}+_Eiq%Qn*(zNSV2jl&%oQXzVkbhf!}9{E5#M#btd8ltgKOf5Guaz=b__?01W56 zg<T9j%UT(}_dOSegT)Aweh+=0?7bPTw1gk)1l`3Rn{s>Xbyhg5K-v!2i3t}|XZ8d` z2(<D<+o}wYMWXRh+(SJ`3WQ6roR;K!ux4j%eC=CvxzvTos+$5txj*5i8p#i$2BY%# zvPNL#YIV&6CEJoMT2(iP;r%aitwZ&(Cx(I`5*SDCDAne7!XlB;a49{vPbS4tg^c>_ zybFZ<BjjM<J2-AFhbi4=Z6)U4)13xs{j(5E)Q;!q?x^~T@_7n#6G2+h!V?~M*$_^) zXcD1a;FL%{Nka*k5oZQT(CXmVG|;G_y8&YRED_l;T<{NqQa!kbpnr4MoYk<O0AH|) zAIFFEn5{jR8%S=bxt!*j{0vS84#C<}faOR%VWf+IUr^h+O%-UQtAIHg$3<85+UB!x z!TzO~L;TjrS<Kp)ak`x@$_C)x*j+i^*}0SZ!aYgo#gtwJaj(`S65XTi(nZt8@Y-T* zgmKQ-#!W>}9OyAsl}7jL0%8|GtcZTLco}dRrLYy68(vC2g1l{x?qsx*J4R3Bo0pS# zby!~SK42kIznr?KIE!A(^aY2Ea~^BgixmP*ZLL>^IU4?&AqMKpi)=66o<$dH@w5+s z8I>vIWxQe$YO(sXfMZZ%n=Xd^AXoif_4)D{!^OP<Zg#hefT_3t@+u*8mVXAkT1b?k zc-t)`y6gp)Sh=<#DSjXcW-M{*_E(v4Pup$cB||BF!pavhkW!texZseaicy(YF@NM6 zd5&bR3>kF`7Re;|0#NicYlaK%Xq1C6;%?P9bc<~P7THusz^~(QHrAGLF8-Tz+O22V zJNF(qiZfa=N9ckO2dw#K_f^5TjjO#p7WKrqFWGzQ+C)lJRaO*r*D7_6Y;-cCc*C;Y z1^@sJJYXe1cU|If-C<N?1&OKF0Y&*Vz>AYgR;kRYGJIH2coQ~+OW4**N*oA{5ep6} z-0q;S$!5suOK$-16F|b7S$fY&Lm1-TLp0asEF=mo2=l=M^z^logwzr(EU)De2N*81 z<~^=&(7^g$;HgcvtZSsNwE9tKEg-HvR<UQFldfcZTpBs~JyKFL#QDXr0ceci2j7!m z=8Keoq52IG=jcp3@;#Alem?Ew_rQRjZ1G3xxPJ;9>BE_UH{k9f5f5JV-}y`moz#<J zph2i0cT&TffB@I_)}7!@&3EWV_qh8*uNEMw$0DS!zD!acAZ;aoGtv(4v#u2Qu03HQ zm!i{{&!^$5ESU%S#!?uG_DM2z8GNC*2k7R2L_{&;tNYy1$A`)!QPn*KB1W=F*yE#_ z0*tVR)VE=<<c>JixRrqS-7fiw_cZ`0#DW9#;BivsWHtL>9rE><RH?=#zS3)%VeYF{ z^_cGJl?1fKZ=ANleNS@iz4}8JJ=#|~!9w$%P!i5wyp4ef8NBWX@j97yFB@oOCDO)< zB9S;K)U4Bzs(r$-8V>>0j&?Tuw5480^nxgZuE^fYwcJ+pF!M|xLS(Xfwapu4R(5zg zvAAo{>?5x8Z!f+S0q;bKXSjSj!yAzD82ZQPVtG_7SrMG5j}uo5N$y~!*E;B=pDS~T zl{E&odnh8bUyl7MH5I6oY-O$X%5-A!I-nD17_V62%<-je@Z)tWqs#fvGtE#6-StsE zV^YBwT;lJ-`r1JX=nN~Wz0A;p>bq<(uu(n`up?<BMJ6!@z?HL_T+PM}Ax=)M2#^Wb zcl}&mS@}>?R&=SI|Hr`YuOy1!W0C)N%94rhf1xb@a=?G3EPqe<`2Ul${FMOm*EsXP z<Sc)sh5UZxpPZ$bxRR{0^8bMNvHfDgX#W}F_a9@He*l>OK4!`C!Q=h`PX1Q@4Y>R* znExB@_<;?6q__OG^$&pXZ+m|`{_n`;U!lRjmw&?~f5$C9@Xf!$g}>0rk8i)@mcN(S zJ}}JRe(jIFe>>~9|Njlp{A15=zxHv&e~usiO3?Z5m7*jiDIl-<d!_u14*c)rCEKt0 zgMV5rtbZkz=~@3EFaLDiFRt|OuK4#$e|OVg&-vw;zq6>nIp*W@n-l+U4*3g@`DZIt z6PH($kow&r|C9!1`!$2|PyO-NX3EC&hdcggi+?rc|6BQAL#!XJ|Ib^-ua5WEj{UDN z$$#F3e<hZEP^tLT40H_mY;-L6EUavQkdhxR;rkna`myn^l;p>^KPkzN^Zq#|$w2pS zVv>Bc{ImkJg1=x@u@78Tl2)2lmiB`|{=g^oK5$h7D@#kgU(l-YU!c{08&_rfU2yid z=fZD}`qd}?9gqF1{`EJXeNeC;K4bq-=l=Nooi6>0&;B)8`f=Vr^%?vBM3yprRKXu) zsW7eB2U%+T7iRZ^)BP)9%JzH0-=9YR*6{xwqyI%8`ZEEM^`n{m#axz;TN~S-t>j-? zO!rUCrKe;5$0qWpjiR)Qe`ApUi(S8x7XLnf`m1t(x9i_d$Yx}rWB*G=w!<IfY_y%C zWWp+Wwv;p54ae-(VLe<qa41rCFfbb(5<WsdNUSa?e{xM+G{Qarq<<G3IUzo|uS3zb z>Z#@JdP!rY#Kx)lal6MUPuZB(@o9E@rBnN@@|)QB(&JsDSLepPN9Pzm*LMUMFoP#P zy_PR8db;1qr(W$oaVPOjr15u7j0#Nc91O5J1JRORT>Z?t*Dhr}$pW2O_v2%PB=!49 z#WK+_O5_qr{uo}rzvf&7_emttJa3S_ci7HhzB@lwTD>$v_jd2XybC=@WPXWHNuU{w zK0*0Da1i?1!#K0U@N#cdaSwk9k3tM(H}ifD?Te3UF$OWZnpn?GZXBc<+sa8x!zP#+ z-K2t$n2t!p740Y*-(eBJ%Ljt+9Q-JVUhTaoLq&}G2|O4+*sqsho37eF7#}F)Yj04m zoId9#!7_YDgwkNG-f#NM)j_HP{s`tFvih|2e#v5V!2#&F-F~8^nRIaUnBOJSa#OEj zF@`PflDBhiA7&YUTsIv6n8P0f9tv;_;T0nv9nA(PjLiTQW~6jDx)vvOs+_{;_YY&p zEL|^_E}7?qaA$&?SRZ3NA#obG$HiemGdTopxsm%-Tn-uL+=OK2qnPd4lvkqT+QmMP z*p>2O71cMah<vCq>7tgp*-0@Bg32c-tE$7yek`a-5(qy8iNe+80b0+CUrv_R0dm6h z47Wqc20tEfom@9EB*jv41ml4WvU@!!fpyns6hv~FneBmSD{DN5xlPXW<!A@0zM2DD zdWTqHSm}0?3G`S@R$ER<>CnV+o(Z_5kl+{r&s5AK_DC?*?Z7>jy93C6nm7F>5T*%= z#?O38^I-^kJR))Ff(QDWfA~N&JMp>1;cj7*{ITP_TsrGyt?}jwnx~brmhiFj&(LIt z#yUB@@N80s!5Yy3*PuvWk_)(d;;`$du|d7}0!R_wNOnEWfx>#+!XvYZ3pOiU`+jPj z<iwCR@kfgm#lzh9P5q`#peAe@-ELbBpD-h8E$^4;_hWc`v~9WzkHO~)*zF70!_`u$ zph>(*c{Rfa&qJY?Lh6XTg~w#HC6^~Wt?&_RO1pG$G`pVrP-2k+3`{vW(na9AJHv3J z@M2tFJaTlmYcw`X=P#K|Kd_MN^6xAP(q%Kp<nv9S^S?mH?=>fYrw^1w0YeF83z<NM z3Qt+{KzUbB?yP?neesU#$bX7A(l+AiLoN@5PPxEd?pw1OwVK^1#E;^_n=8QtK{oWf zWo<&}z<YqA@AhxrB7A#oj8{TM-!6uPEtVml6>k1q8<%#dO;c1<H#f_|p1P(PQSYC# zLEt$5c2PH)s2%X^pB>~mH}-(FJ_W-{k5juvjVc-V9w>weYcR<*C@jx|iB$Uyne{V_ z+^l~*2>sHupDWbS&*#;|??)X$Xfuv?7t4gl4L%OK%_n(v$+JZ}&0XH1a&wtGt@WF^ zoySJH)MZ8u;cj+bJr@4*IEsQHhe3vT;kTpuA&wG^3JiI1@L7vMMF=iq_;{$%o!+&+ zn7z5`TVdFVKrY0j;OOM!L~iDkB3nlv;lb8~1Ah_HZ#9ed=*o}6oI51SY}k&rJ(5S5 z)^WZ$yC%<HR0@;|i}snnE|_Zo&aEta?rWN<C>n-3FW9^>B2y4dpe_`_K*rU`kjoBi z_3Ba&eJ6-~WE{`>@yIH(9~JY!>?IYAb(EE{YZlk`{iKGo67RMU)L{?_dNUxIL<E<A zOJ6c=Hsid{s&Wrni%Ue>L0Su9&NmLXN9>uR^)NvTX5rp?-HTzX@xqP<W{@N-L8y;H z7No8p$w*G=TQj<B>HUdDV<GR;Guzv1_#HNWF5--xzN2u4Tgt!)zqy8*(io})NtJhK z%`jq;tKbY^(R-82PFj$!56SCG298>JcG}T5r%Y4x8BrXrpgMJJG>PghiCck(8T_3P zthy0<Vkgkm=pLwNa(dDS`~>b$+!z92$osQQ2kzsYkkSI601EgABU8+$hl_5?@ClN= zQzFN6a4W14Y-!px`X3RDC1za!pkBfv#Jd5pm=Oet{27MosfuxcMS?;z<aixl2+*&) zp{VDnfp$}426-bK7j)YCp-I7yGWHrN$ff5apjKPleKG*W!g<E*ZGlI=-ag5Nj^o1` zz^xDw?Fnv#41tq42GCEBg}enzHm^yoIpw#)E@ISy;6Zxe>%qn=9ebk}NgxY7u1vvY zNnNPw#dTmSKKMRSgv}TNp@5b8nDKo@tZD9*H~LJ65s12ixT(nWbEe<7)MTTd&pe=X z)ro<wy?AB?^gw`?4egPylRX{0?gF(jJG-t<QU}I8ful46o^q(NF_MtBch!}cu0zQE z&caPX1}8BkLPb@{3=nmA{TpZBPFu%vYI{^?grI^#<WQ`g{!BqJe&jMMvfcA=w8yT3 z_FRn75-1&n=570HlUgpYN|PF43YY|;*7CYjLFaohFq(QC78y&w>FUqjP*}6t7Tky_ zDm-*3_;lO1xzHRAYSPZ>lo2`@GI4vtPl$v$@W|2qBDW1edk?R6^ezWkSMws7vgh_A z&*o&!J+8URy!>5$hG|G=g6t89wWucUKI%&GC0W=Uu5A|+apfDg4f6wqJ=ot!DIIDh ziIGW&aYi>lis)QU0kR^=BD~5<AD8`EUrLkPw0v3JN3`<gYx5B9{WQLv$<Cq#0!x3L zPI(v4{w}GI+zK7=2-3AS9R>hUN?TM~-GF%<!P7|spRO^Fz$>QhDflK=EehmbAD~?N z2_1&CqYGx(C*>%5F<@xvLs!?sRK=B}B<++-#BL@<p>m6Gd17E*N9mBF&{{=qy}-B` zos1w#jP^Mo4Lhq`5@2>&fHr$0DuY$T(t)Esz-k^7Ah<A+c{lUW%JQ(a<M~B>x6v68 z>dW<84RXhQ-`9nmQZA=@&B(?Xo}_9=)NbZ8UyR%pu%;<eO}bZP4;&IPIzr_P`cQt( zp{AUhClfBl;0Qpr=#97n-Ux>!;lRhPU0KW0M-G~pK`dpNaRiPRYBXe&U30~0&0=Ao zB8W?7HfYyIt=8qW&(akRPOi=Eqmg7+`0X~<aAeRE+t_60<^^qX@6oxB#1$)>A<`3$ zPms`>#pUfv0YhSlaZ&bnTvdMjNuoD6?dwQ=VQNy@a`f)rsiMTU&nP9?-D2LjU7Iu3 z31NKTUszZ#om;rzAifCu-NctEENI!nr@<&sWuf%sGMcte-T6HTGR{-;T~ECj<MH3k zKxb5byOu*)9c1n@h4moH2DZ{eNf?n_QbiAS;(q9KL+9P1zy0)*yu^jYV^_nXf~uUz z{e2S`@d>j<$`4kC6Py=VM}E?-&$2Ev=qya_KGYngAK#2Hc=lXrzYjUd*SE%(0Sa~M zzKOcw=#1B40L<Scw#L>4(KM9?LiXF5kkj-+e;v*FSyEayUOKEtY-f7UlF-4V=PG?K z0Xvyxb;^Ekv&qrZ=H6#!LSxj8n3;UsM32}6asZJp{C+Qq{@XVJN7FS(FWq>B-6_^< zVN>r<J3c-DM6|tC<Jok6jyjWy2Ij>T^uX3wvgxI~Ywa&mSx<(_;91uu*Mj8t@+%>; zMFQ*+^MZ{q%ampusgLPjy>h;;Zhg;3q1^jC5H%vx?mFlE#PwcYpJs!WR^l{YF*k&Q z84tTjbtk!d9W28tOgz#}<WdFvA(lu`UI5|N8Z!Mpv652NSZd4WxpNA&x-K1~{4~Bo z(w-JHLyytADZKBTA4dsJ5!Qf;<D<p1kg9IJtiic#Vcf+o**A@4f(FlxP>Y*5`#EA| zRWTma47y>HA!s6FH<7|pX^Ci6Wm(jMwZg5Nd|nX$)fbv-&w(_opkzJV__nd#2AV(K zw~oux(bH@Sl{-@{gAI~MbKe7rM&No)=>|akqFU<6by8wVVT}xjst&ac>)^7b8=ZPx zR>)Qx-`1U*Rhm3J>{J|wYhjI<jr=6UE|2^e)40{p@jON+8&f)?M=gjf$QwEU!%)vL zMmka}y+1yP@=QXbZqWDeeys{)Op_B6su=jCH5UzXoC;y9^;mT@@pV9*miU@MrnKey zTp^{X$xmrYS+5?DvWUjv=lH?>(JIfkt$UrGlqiEtfBUN`-wEEc5eHxB4%R2PbGX}) zSeeU~fLn}6U9;G=p()9<HA_0NZVt6+S$!I6T4t2yHNXlN*wlbFkDsvQs{D=#b9-1I zaW|wCyM#^(%Irhr64jtE3X^^oHXXt|u=-(`$WLbREmWE4!x@G?9gF$pd|Q+I*RLBI zSl`q?H{Q4Zc;M88#DifNxDTh@%4MQI4$~LGU6jtDN2KOzLXy%#c7uJo+GL!55YjqT zm{|d|tt?Ux2oatTt(vttaXqQJNPMxoKwou&xq-WF-~p^8cMf85S2k{ydrTDT2X4Im z@e*?L=r}1drIXu^SkpR6%1SJSOCpv@1{C*FJ&^S>BGmt?7n$W`8wc~iV0X7LBIzvu zO_Z2+=X(_ivcLBtb{CJ?l@X(?X1U`EY-8U{TfMUxS;~_#%ePw%i-`KUJ68=M;Oy~P ziMv)S7BC<2nC*@w-F*27ij~#^BMk-1gdU?(liqJv3k4w*Uu)coV+{oqVzl5?TzmHJ z_pSAL1y-6Tug3>*F49}F=&&7`s87kGc7i!RW727Oca84!Vpf6DkCr!>&H2vy|A64> z7dFDniVAxA`M_~zhRSgnr8{Az%0{O#FF1gaGx_5v9n58hQ)-V?p}Be3@ahV)PHly% zTxriJZUM#*Ee-z-JQ5^9;~(S5zXsL+>0pzdg^u|zgUzpsrm`9f7#{pTFA;-Ra^<Mz zA~UD!8h~$!NgE0;n~JNEz*=eaZ^7sTMfGF+0mQsf{U>QWTp_5@i6Dvfkbo99JqR{u z`&W9a<K~eJR&9`4%QI$qmx<%nMRdNO=4D8G+3&wS?6=-~+Ks2%@xUc!2HLQt9}Hv7 z+MX_-wqPpta7sid-9W*$Gemg$v_{0x^3*2x_)*aq7T~FcjMT*3ffpUQi9UIRk8JHO z95tAx!j{82+;R-1hEA{19_+u}cxAMyER-kBV!pW%xhPav7MaJE>4&j2i_erLvL1$F z&epq@=PTw_n8qTTYA2n<#}6Ohl%y&Tc_F;M1H<$%q%n<^gQ{nEwzk3b<g3yv`zGj> zR@;^Vtpx1pf~pcE_SN)ESJU08M5bL6OQrQ&!&`SsdvSA*n}6XRF^M|$Q1cys!N(dK zQ-Dvro|sXO|4dF<r5^ER5j#f|0-r-fTi{BUBHFjeq~tN!Vjz;xXeX*SD;pJFNzc;m zAVu(dc-Xptv<Wz;bDOm9Ms+G*n~OVGc~m})yY0>hypasRrwCP8E$M>t827X#3!3ZY zY>i%CVk|nGcDmS??M8jnNM^B08Czi<5u_};DP#MTY)r#>%(bw-$5BRn0`0Vw+ub`6 z1C`1!s|}88i2aZW@d^tBr+Uco$oYg_)|dQ}Xhp?A^->>a4R(javxl7}&fVft<AHI& zY9{w!(g~ZNXt;J(Q|6#Op4#v{f+VB5K&8GeVqFj#*URg4XlkWY^JcVq{52-R1smvK ziB323TOve>P0w@)KflHYdHS%>D9Tu^Xcxbv#A81X<6YbXkN^$O>|D}U9tDu+przk_ zEaQoRT(ofhz6W^7Ge?8ZbUHO#y(7xTBNO?yzdR}g4=n)#$-&yimwWL4So_B)UAk>c z8&2D{ZQHhO+qP}nT4~$PTxr|RmATU9yY_R=clL93?N+C%ef2MX%{ih)+-=@*jo$m9 zFSfm1M8A^N!^DjjhVa_%*)}v@O9e2Z;p1SOPn<hrF_VSXR903tNy<bW<XpNeY}KmX zsnI1e7NbJA2@jOO{l%tn+2Y39k(jwpTa3v4y;+<y!WzvPbWMTG_xtMck$bS#0No{U zpvJkCY1UVrjs=A?m((rIl_ZT276WdOL!|s`?#6YAj^`XRj3g%{f&L(hYcRKZMl651 zBBm?EUC~|K{*kLK-E)Zg4MH=xB5*H8o+Xm_3(`0e*oF`Lyy?ycQ<6Sf)I<^^$z{hu zoap^Res))T<~hEI6^OSf5PfK<!N4B>VW@z6pXh~ZY`>hm_h3J^!<&3_Mpc*ec(Ke8 zgEX!*DLH-gD4X~IV@5wyi2iP>y=QTst!d_>45sID)<K$lxxwh7_cJv1C$Ra%&t&gI z*EtcDij?<B74y8Tk;%DsSF}&(pkTW{!(n7!ymx6T!2J~GupOd%+QHp}#}1t)o-Vhx z4eVPua|}px+?iV$kjW}^A?*_REWO;{*3R<nqi73#xnaeSBWz|A_(26g$H&eXuL*mP z+N8U7e}qO}W`Z^!e>r{Obw*CBi5mV0olYCVy@UoL;U~o};M_s$&d@w%P(4BZxaSo0 zerBny5BOc+-;a;TSGvfnk}0~Mz$KE4`NJ2$w^mQS4i?A2y~S;HG7}$q^By<WfB+>p ztb)6*H^!}4hS5JhX+I0<kzWNCYwSmO_}AblG;heTkya4058kVzel^DuZqT#F`|FTz zGDUPP!f0J+(CV8HBjKX)VBO|BZUI=+n1%ZHCSi3>+=AUo!^jGBvqgsUFZl9%w)7bR zodLCqh2gMT6-HN1u}9Ik<YK^Pw#a%=4-&3oy!FXyGd70o%$97r)a|||hqps)qcY56 zNV0F1J_p2zqX5m-wL*nTNzdG^mn=WZsu-ToUWuZfA7D!B105Cb6whJ5+$UeZ3mzY+ zgY@_D92B1l%<3ED!9a$*k54<glw{SjBrR(SahvBbTj4U*f1g<dOC8i`FYgk34PE$u z)ZgjMM6|ydNW&C~xV&rwMgi%^a~%AB#svxbUQYhe*N6WM(}8qEGxN>OJozCT!11U| zt00<!zG}Cl;(BnBxMyG^!NcJ3WP$M^@JfL{@QqeesE>q$zo16hS-E*3>1KFw5twix z{-dw^ZI7*Eib0|eQ?!Nh<E#lfbBLE0@CgJsv6=?v9bFg`V(i}>O#h&w|F;JdGY8Y( z&Lv|L14APN6O$vzpHs55E3gx@vQxmfs(up8UkQe9gp*s_pUh`J(5%_vo!Q|h!|<Ji zc7Z|ryl?&8Q4I{tOk84STxaOZX(nW5DHP`*V4+r*R#pTfFfcK)C`eQXO;B`{qL~-( zQI-N?8kcfDg0_~rk@}{#N*0Vj>zASYUeloXpad(<OF1J&GnimtV5aa0p<rOLV9H=( zU}$1uXpY0<eXkGSGgo-O->-h}uFjf&Jl($U$4}i4_ts^NoiH@`d99kpT}$r5f5Vwy zGg({J+a8t!Ow<dT`hnCx>tu30j*`TA_NlAZ?^@(;tR0Tua$i30UmLEOe6<hC?7!dk zcz?ySYh(7wzm0zo8|9Uo`ftb7QseNG;hTKV%y$%6?c0mRjo8%e{?_=;F|hu$9qY`n zop&Dvb?$9?cEb5s99S{kSh9d~nuc@ZD}c8$|ANSV<FzB(6sz^#l74$9e|QWAfS(<O zynlZump!}ApBS3#2LSM4Yoz!$5A+{~{Qutr{oh)V{>y*#-&{!?1b=lt|0ERu>4g57 zMEX}==l|Y_^pA$YfA08K7x!O`NPp!55ODs75sB%q75{IHNdL!W{&6QU|Cc-I-#MrL z?`%ka75tyK^<OrmKk1+UYp>v+zlDubZOQb_@x(tiq?-ftC1*q-Isu@=<PBm^mTX=% z&$ogr;3$x24<JCcG&O6#&*3x;4b8?pS3;o7NFC!>BBIR8Jhz`$vvo+GUpF_u9=Vk- zquR$^TD>`b=j?E%4o5n6g&$rSv*cBE`DT@$HSqX&{pzaVJ@7{QWKvar9P8rsLHE77 zXuxXURvw$Zl@rZ_dAPkF>KlT2k{S`sx>Hy>+}1M}WpTqrj(6vZEKAhyY<&evkuG{8 z`8{(7I1SKsx+?5>!}{1iTS<$b{R?CFw``jD_2wK**ttWPS52|->Q@TMo8_;`hO3Qz zyeL^ldX(&(x2r*v=|KxrS>a{p`Ic+AWrmklQ~l7<A;LF6_0z_39-Oktr4(b=qoB@X za(bi)>sOK#{d{M|bt&hM#xX^`KI!dWBC54Uz@(7zuO2+Ciw-AY*~Po>Y1y1{r@d71 z?QqZ+eaez5euKMF(q+{Ae{4<lB*EK@e~K8RmpRxfO^$z^b%4@5D8iq0RkrQjv~Q(6 zq+I%qOv5;9N`BYDPUHRj3~!&w<@zZKHMuYC&GD?eDGTf`NIsV83!1|gBYY;}!S2k_ zZ;6u;BuFnaA9%DOZ%mD>A&3KG-6V<jIUO&=I!qIIo8j7MvWVIn%|P(Fls;BrBaoNy z*<f;cG4w;M((?57ysJ~~H)7i*DPl0>$;8B{W~#WXZ-rx#gfhKE^WrgL$2t{4H*&gd z8>;l1)%PGrvkUM7_fr3|@w(a#A}s*dWmRDrrxNs*y3ZX{!A4zx)Tf+<)~{%9i@TP& zpmJ~#!b#&FQ1p=LckDLzzkUx@>FZHa$hukmSRh(yT`LtuGWYD{*3t9*;9SQoL;{PG zO}yq)UYwL06OGGd?SilS3RgW>yJ5n$PF7ZDL)Y7>?8k<jlBvF=saS2Kr(c$!0#$of z2BK1F!t?qN!fsHnh=iSTYQi7;C1M2C1-DDVbp>QnWA(}+znm*qJnPcld|^6?@)QSa z7l&yTL_|z2v^|>Ds!MI!#z2(1z_?=^s@qwNrB?DXP(9N(ub{!-9!s~KD^;w28~#9! z5}V62yI{tO&mA2kDf_Uez4Tk7rTpfYwVb%SlDJ_2j_0&!v?@!9Wv39t*&)U2wWL?4 z3(v&xXmy&;xXYhF4l9etFOghx)Q?Uav-GY-PAa`Py2AK~wdr>by8bvMY~N{mnN|Mi zeLsEoq(JlbxJW4W=VnU<{G)_eN5UrowDls-TU%Uv%xaT<JhhLo&N%qW?m9`QIp(*F zj}=mbw}gkPNm5E$q^UOENK$CUO^u!_(%O6|s?%X=@1vS`?H=wvSACFyGk7fmw1MGQ zeMBRnTZj`^$W|}QFOC*^3o>J4>+Zc^Jm7mVn-?8^Q_UQ5GjnHTa?dZ{w~7ee!a{1? z(>W)$=b;X3p<k5KhusuFzbI9+mXf9YPal=g%tJS7@z}@%<OJBw-Y%#(B@cj3&sC~p zUTaCo!L%sOpgLKeuvM5cfKQqD`Iq!J#6Wpcj1@ypV+T{kfMg1K?RZ%N(Py#Vg+{K> zY(CAm<@yTk=H|b9-}^z%P7^CO_`&_4@>RqG_Q~kG(KdXf2SrqNV~v$z>9rJ8&|nM_ zwE#6_q2R~^Q^1vqmfIWC%q;w{D{y=Jm*^TUEqgbVakfVd@H`*jOT-jTswFisYBFyj zQ5q__b1VHKfW=E<qLh>0n7wxI)L|?OipQMF;dR(l4qZa;8#=V$2L1cik??q#skqXX zL1BRwPMr*b633aZ790<%^C1q*QlO3^`<rXxR4da*^}G$AZIBYFc-8I1f<BuXz`TkT z&n<e{jL^DFdDdKcbhEo|tPDa#X1UHE8{QrtAzz3|h`}DltI-9%hs4g#YR;bMImGCC zQRCbwvc=r+MkW5l)$v@3Y%ebDy(i(La{&K>JQH>obu4D**tYu6VQiM<^^zraQ+#W+ zD#1RWB8V6guH9|nA;6IU)a?=H1a3<^g2`KUsDWdh!lLbR>j(WjW?|KVEaJ6~jD}99 zFaqV!?Gc#9pa1hoB-VF*{tW1+`oLD74HjU&pLY)mr&jB-#I&ZziEF+dOSeD2v%p~= z@wM7-!4(Z%mFE~L>1Yl)cmsV!V}pMs5T)ipGLkgImaH%3$Fe;RqG@g6PcxPT2(j0V z@njk;aCHY_3<?HcX^DZcbS$wWK=uoi+K`4JAKhuRC4wRiA7KECPgk#4mMv@s5kUS! zF}fG>vM01B-}8&51$=-WOGLXP+p{>7E*nc%Cp%$Q9;2C$=yE7Yyb`fJ0JLZ~am#_C z+n?nKt!s)FiZP@$J1h{@E)u*=LgJE8Iy1GjHQ8Lhbtn>BgA2CIY=7Wd?C$C~x)YXY z{;?YIr+3Gao!TM?x61O?o-=PAH`HE#bh-|h2Hj(AP(G59iOLo~no9B!fbP$mHI_U1 z7bueZJpIZSO0uNVl!-9JYTiHwkCy0xK`JJV1w_nl1P2=M23IE25&>OY3^4oZBKFGP z4ZpB8A11|^BBpyxO7>_}jzKTk-{&Z)mZ-Uql_+U~ixSQCSd?7m>I$R>Dlpp(Y2~)~ z7&e-KSyq@5S^;eTB*{vY?RV5ecO&$_qVF9IHWW*dMN~?C^^$+yvap)Y9_P6x^geor ztmls-HyOwXl=agO<r1$iM43VhTYt2>opn9xs86cXxC;ct7ZBAh8_oIqgL_#f@Wlz5 z@jq3s>CEVa_X}1)XhKdp<Z7)z*0sy2SCg1rgCbI$jsQ(7^Ex@-9@!5m;527(mI9Df zg$e8Hi(}xD#mS}36hD*KraX}_JB0a}RA;@9L+`WEfvcmaP%@4}M=$_5*jzqTi(^9a zg<nhro-cCOM02d}g0H$FS=T$Rsh08T<jd&~aEOh$i5cwW<#Z8O2C$&hR4dctN+hl^ zj6OQ55lA#jN;uVzJdMWQ7ItTMSZTy8?t-NEW0BtDNXNZcF3^~GG2<)FB&ajS=}k;l zo0rR;Zc1eUnOp0$=F-{&hB1@)WhRdA0h^{#I%$8_OW3dirU$k$rnyoBrgWrd_BjMi z6$@(WVW^`^#R;IE`+&?E!nS)?NcF)Lx*!Mvb{VK#VumP%qRNAbr-3>$MwpL`puih@ zM8%m4FsOzQ28jjrBc*~z_4w0TgwuS7zbPe;4!xCm3yFjLyrU9`KKPkKCFlfZur@w) zH(g7DlJ{YrpofJX>Dfzpv=o?X7WFKs)2l{Ncm9(xW$vBoh^4p-n;+gINGB(@?OA`1 zbjac}cY@d<At4u~apWG7I{}$l*n-ir{>O1509q3IxeQkklRf%5L}~nQOrx?13@8JD z4Vz#4gP`&d+KGFz60ljR)S5IWI;H(VMgEep4wt;}dZ33X=~oeKz{D{e-u8|#K9{*t z!wmqEZ;BJI1PMmo*3BdabF7k@`^K~qRve|ng<MTcvsm009SU-*HbV6Z`bFK&G|+3Q z^NEI=b+Pd7nXVwq8u;_D<DKF7qAGI6?v!J11UtxrsYvo4&fE^2KygiGB-4*c4udF( zKh^b2=A;Z4WAO)Pq^UO3XP}Q4oOE6(34T3-7MhgFKAa2(KK+Td0VM<sr)<8PGm5Jt zVjGtU5fs`DI1PK)*TLnJq`*~9Ul63%O_8DC%iH6k6seObL3;8sP5z3Apg!g+bUSmv zswhyW%K8C_?>N^ws==@p1w!1%!o*SqZnuy$k$FG#)v>5H;sp-gXcD$cXpqMTktc3| zge~4^?VBg4#%Z3$QZ;4T2DVODFjg-?_=~v%zWsD3!9Y=&|B3WQQmhDSgUM^r304R< zkF>4JLy0Y9+uDPi9?<)75IF;I%Jxiau(M*rKzDk;)2Egw=sBlnf2$>t2AV9|o@kBU zRP#i|F)QR?v_bKzYF^oRfurG-&KYek@C}c{?C7O|`uL%Zq#1yL?6oO^(H_Hz&htuw zM{RK8fzAL1TH!bXIS<{@YB51Th+7hhP_kP;0!{$v7^jZP04f<sP@z@mdMRW~I|+7a zATr-UNzk@liV+<<RV(6(#^<R*<6>rThLVOL9(840efw7xK?IhftQ8E5p24hm*-wPv z3XktMdL>T`>$oBV2O#OZKYrO`yFkNh$i9RC`jn|RX1E%Z*gUY$y;Rnb_SE*}60J0o znREcFpUMCW%qONFPrKOy8RG6x{sZLi+^HX_Qg}iGG7ofY6kG_QlSb*&m(Ou;Z<A2s zU!I^jLdF4SN6~$Uv(I{ShHwiB=cg}oo#n?U)O{YPF)U-045R^+b^3pXcVGVLET@F* zgSIB*jnb>L<b5!`+Y9)c9Zv&y1Atku#9DIuB}H$^uPRQQA^`O|pvla>?$7yIp__X) z>?p`$SN6IJPjmH>H5rTp4Nn#Og)@V4=z%+-vQc)^5Oi#*8Yo1WCh-AG7}LI4v@%N) ziqRYT0$!)vVR$aItw*Ioys>nH;e7(Psaatw$S!q@@H4~u@q-KNitGred<NZ+YX#nD z?LASkXNvYNtMKM$P6)J|-fPf%4m(~gkaFM$obP@2BNk_8kB}kud=Z~|!n_Rm)Im{p zxQi%6eW+0gR|$jY$3dA{DBbL$pT6w#xXWZ_z8dHA9Ia>%>E~jWtduwzV6VSBG_mJ1 zcm1%-RvQB{v;7FOPXILiJ_P?7cW37Q1jG~qM>MuL5Omu~uFfzZjY=_REs<m=3X~4M zCz(~3o>EHe!2rM?kA4=eLmGgz^mkdCOcR#Mgb#!N5Ss}B4%T|STrwgo&iGPZgj5@* zw#h*nmNr3(^~F3p_)7T@AcvSD@94DbjWRqbSM8M5_9q%C!=$czSJ0po^;&7F!@qcX z+{apYWowldDF9>49J&Xvb8?MdBB7AxFh0;Bo72`76`g`Ly1k_w&;<d*<;SP?W5Tbt zzIox?LxWPM?_Tq;iPFQ}?G!Nkoh50PQ=PF+d&|kRUhnVoEXTp+gZ!&eXFAx^)<;f6 z38kE2glM!qhcik?p%J!9678Aj@x>7QCbNm3UN@7O>h`CZO35UTN~2Cz$t(RkqO;M> z828@98=Nn|E_{cj0B--aCsbEPUQ;#VJZaQE2|~P*Yi<~`XbkLcO#Yui0A%JJ$9Tgc z7-JA9dCX(ahG*<8`Dq*y1iqCAE|GVkg&oWdY?SpaV7R%y=q&D<)h&g!OzGPo7Wrcm zvDq~*3g7z2$jFvN=^m{WFvaQ(_`S*}(-7B@WZX(64YCUM=qOO7JFsTK1FJr}aK`+E z{OMmtlhf4>!GKmr?3wV{qC*_Gl!^{RiwdB>7OX{GnsFSoM`+`z8SOi%MPv4Yl{!go zp%_=V1XH-9CJweE&(EhorFpiin)938Fm}u13b%@-0fcAhHo(ttAK!m0B0$Xp7U?k1 zK2f)$J+s8$t87zQ_EmKAxHj=`(h#rccODL_+oeL(s?IKey%;KkPXl$W`CN3|tCt#{ zhbQWDe2+c|6kZ^1UA*mG3oa(SIKmSbf=Rf7VG)>{0;0yND%Z(fg<3!es^K@R+O;5- zo+-tGB_Zh#Pi3^2chh+K(%dF5Z;(T<8i6jj3@j%LOWrOtNsPj#M+zl$6X1cB&ksZ| zuG)=NA3=aE6BK41am!STge7<m#x$kk?#2n}rf|#0%1^&@68zqVtVS!14rIv>m-vk{ z&V1nwp}-gVX}DL*DgMK@S1x7{+Y<T0-VwM#GG+s{y#BSD-$Wko107%|7#JSLAH;<a zPU{IEd2Yw$AbQwMPB)lNJ#AAK0UYZ^q2L~|>5j+}o|y6?V1vVDObV%#JdL3HSO|3W z9PyGo)Zd0-DQX1Y)h`}GL3HI~O4Rykz<oWBV7y?8b-R{U0@%vjfc^!k{QgHY$}5Km z;!+`(6J?;0I7~zfoJ-i#kGQHAz6_F@T3K<9q!Y<RYab%HUa5gdXf79(PgVI&+`%>p ziiPgoaw=mqIo3R7^hM;TxK|aLj4RDTjDh}A3kgBM7n*n_0UtovY^>`s$Ms)Kj{YYI z_m#&j*kM?1r+Ppux@-1lSx&u_r`JF{*B-eNn%4F{yIZXSJqPrUc1F1uUBF~=-8(PJ z;$(1`8JN&GVTUdTpXE`c&f%{2+u+p+&I5O?gT6z9NzgN_SVA-$YVYXwNIZ|IzfFkD z<vV93+8-OrxS1KAy?Y<0)CTt06e*VGL`6vKV6arif2aF4sdei-p443jPu+^y6n=D# z(NX_U>T^n3*OTh1K;Qq3n5DP&OB1DXNaP!ALMad{tI$h>&}*@J6-%;smf+jIJ&@(* zYXNFMR80cKJ28w7A71ySDB%o~1E@Elgt>{t!~WG_&r7m~P!e(Qk=*01EqM@(hqA;E z!aj%w!WVML8%$mfm49Nh{^=(LB1q`P<lTsmHKcZM4Sx*sHsbIz(+4|Xlm%YL`V-uR z4~o;NtH*iTFDksdUQk$kD7y@rnN&Sso?jbg21jFY^h^tVP8i11anLEcl?^DTq=zr? ze3L7E?}+Z+m?qAHOiNSTa`)syQi!_i>~i}cVEsl%y+)wYxCq%a5r@`5FDcyAf;dYO z8n4XX!P40Np8e$`F~)YtA!FdQu<}sZ3YS_!c?J%3ru2z6uk;WszL%Zy`W-=Px}a<? z7t5va(%PqpVfXKX4a%I+s=Qytad$f_kQ|!6Z$QJEglRm<r3dAEY^xvUbvAgY-Jp@* z4bwvgfgh^@hh3as*83psDM4HsUInp<KF$=#;BZIZz&IX^6aR+3{>@zQPe;ptn+!$& zU#0^YB}rjbHELm5B}RsSwO?@lhb!ivBgg(eChpIqroU!3ITLXH<<j`4sQE8o`*#-m z4^97np91vH{pBRpl@#Uv>@O?%ZwmMizr#NZ_@msHF?4qMhsc_M@xNRj1f2h(k>~sy z4gNP#Sbw9re+!%X8+2wM;QULdO)thy!1*8Hvi~H}e<RiZQ##h)`WpWRO8-?f0q1|9 z>AwW;|09zAe^&V)PR;WFl2iZfmHSUl&BFO-0M-ANQ`cxqCm*%`gHu1)Rt+iR0Qq>^ zZ{SjPr>(-tQ~M;i7@%wFs;DQL^08QbzD?6J4~U~`!HL@pi=bx!KoTUz(=#*f4^0Zc z-27HA{PO#JSRS)4Iw*vQS}KE=9}A!2Ga-W+r118r#PeI@&HXjjLwm5@f+N3R1@@X7 z*7<hR9zSTJ4o)A)HYVrQWxXwB?UJ9dQF%3MFZ)h3%O6(AYrUI}4Eue4?r)*#Z5%`- zY4Vj+(LGK>(Ku(2I0{A<XGsD7dU43t($zPw-}FBG`#X16lg%udfThCwN8LB)@{oix z`To_~;{wI;pZ|?`dhx+LQ_&*+yX^67>z{WP7PG<yi$_(KPl{-BEZ)fAY=3fvuRE_? zJHhfqM<f%>I<%Mk<$3SP<chX4cM0>z44Y{nc!!?^`BF`)<T0^98z{Hh!WwY=VUp(- z@DH;&VT*=4=UpuJK6kQ6A`XqbLBf@NJxYgSlXRVj>+mQcE{H|L57FMN9L`tGFd`M- zNn+jeL+n4Z{YpB{#_zt4&2A)z8j2w!DcR)Jf25#x-T7R>9cy%nzgk-7te|^ZY}8h^ zbk&s1uO2S7y(NuPDnIxB{N`l!)vbVQCSgf;vf&~>+K#R7<I75T+E_8)@Wshq_Nr*Z z2|FoW4mWd&b>nuVLZ0l5Uh?JHY*{?+xC)g>(#Z>@iOv`+?P{!i^TH@;<KB7Tic{ES zcv>9jvNl2(9X2TV(EBAF&fM(S8yvyg2kGn6wG!@xA&EiYf*<mByo4k77AWYaU)D&! z*<faNFfzk_%S^2+N3@bq3r2({PyQuAL_YI_d%Qk%;bag3OGMnw<IgIH2P5F|fK$Q4 zlA-2aClNM$jcPU^SSAS)5rl&){o{``Ai>zjYNS|#B#Nk35n|HVHptoMnVK}_DT}9! z=61PE*hWHVqejV@dW~T761-9f@TI+<)gUrcgBp;z=z(pT+<v!tT(%c5-gpgZa0@zW zJ@GP~Rb^V?E_dEk@4vR1>wq_L$QsISGnBVihedoxa?`{&!V|@AV>)c2H!-8}ElNLf zCiY2c8hp&E28^j1xZ=suK9LabejaAn8uqej<<)T45GYq!rIB<A%)H~1yIDa#Z5Y+j z>s_OOmt|}(x?g4tOa9#Dzu&#i{9=!{joYg<s{`PbI%x43GfE2e_D)6#WPK8|7X8IE zMrOsyrrCh}f@?*Z@H_%&S?9e9i3%nVFd>Ln+an>jC<{!PK5JdYISN@qSOUP5e0m^Y zu{o2#T*Ld|%6Y@NXwVvFk<gtm)f}#+$2mt$Tr+P@<uKT=?Mizol0@#MP>`C3JORVW ze~9rh{VU*W#=q~lFF)h!h`WFWBKiYP`o={&f<S~3?+92*7sdH0I8Q%g_kC;RP<+3( z(j06=O?Jg!fU3oRgo1~u*o2}g-v>zi;E`^|7O7XU3WJUd0g7Ss=@*EFny<Snz5Cs} z_O?5p35uQIJUeU&quiOnyLj@ol@?fD4IP)^#m1XLGUOpa)S~yAi5UdpZp}Iyb3`L{ za+7=hnv|`oA-(0q3!$vq3!!c|DXVAI5<L!KkOiiDWLjYxPuykkq6?<SnHQ7YYHiJv z-zIRK;Ps3`gu!Bw_Y$K_II!o#p-Y&yFuT1n3oRn?xmwr`tbjJ(rL%|qkcPg&nTG$1 zhfP{St9!;Rt~Pp^pq9ut8w$DJR{3myy&Y__#Z{qC^M19olM~{K&dXHpPxRi*AQL~1 zhiO-n<6^!&{JGNs?47OdsTh`y?@5!7o-y@YLw_5?5z%ZTPKoAK=t1TJ7(a0}#%2Vi za}89h=JSBmiORsy^j=JBdNmzFBsfiwTMp;~u~*y?y)mv>zdeU53&m)mNxzy!3$A6M zA8|m^(a=K)ApJn^c!j(HKW(yD8nGw|Ea2LzyuGFlboKFcESeJ0r-TEOBH0KndqFU; z6i>SBd%r&<wPc|I+$%O6!OmZcB)N<ZP>oi@%gmC+TrjNbtgfowqA)m!YzUDHML+mv zyg>?WChs@r9Oi^2PVwV8Z-qKn2*$UDK2h(62e$>!%f^d1k*Tr!+<LV3PlDVi#nvyq zm}YUb3+*#B@m|Q@WiomU;Zo`;ObFMgk?qWZjn3QD!MFN9lggrM8mz1>I<u;^O3_9+ z7L5r9OZiLofFmJZo1~O53JVF00+l(TWHn`)13v^<AzJ3y{Re)g@)AVAVNIrMf&Vo3 zCr`*0{vK)fMHqA*vxIvJyz2sSVrP<`sPqipmrA0(3|$gq(yh(2OALVEx$LsyVt_yI zNXbtmZQ4{H4tFDXVt5nt-%YnCqSXhOi$Ny!ZAiBemN~S#61Bo5D3hQz2EqTbu*H(r z3ovf)B@<vz=!a#Jm;z)?BY}*IDB>oB22Km)^lZ6zvHa9(n#v!Z{79UCjcm{4cO+yP zz?Kjt09>SU?SE<G%P%GUaiqhD7-Fo~vd~VX9F1;j23I0zQt@-@GNE-z2@f5d4oIGN z3Nt^WiGuh69XIMwbI0LhYd(hj62P`P(smb%C6fY5hV9g{I)-6iSc)#Anvsc<#PWoY z=)L^Da&lVD7^04hS?@FW)~J%;CnS7LZ?JQxexl6tcYPkG>Ld7tHoPyHG~0#cWSUN* z8RZk%Y*2NCT{r$F<fC2B-D1Y$-F^RXWcEh5+?h9uU_0V88WuMBg%y!XCt>Mmx%uGP z`o|0wZB>~DZS+<B<Ryv{1V(T9L2Y6}mI~{D(c8?z$bn=Z&eD&d!Q1Rf=wbSONQ<PX zTT99=5hf*D_MV)Q>8Mm^wI7@kL6&Z!OIW`gI|s)IrvkLB&B8iI-y9BdV*Om)8ug5) z^3s)AJd0{g5<v9NIeVKSia_m*RELAoy#blo#yQHM4v5yrf3a=iCeZq7z;Yq;S;1%R z4@6n08$U0kqiZh0lYLVnETU$+u>C&6(gpL*M;FZ58bh<WZ-Qu`*T#Wn@JGVZt`RKv z2@wHq`YtDFP)BTPIMqn5@YS{7c@90W@rv=7StH!YP`Er;8-E@_=(*$S)Ud)l>t@!v z87^i)bRL6~4R*M&S&VIp<>Y2&fhassASOb{hrH^0$byS7j$*fJt@%i?V!$wCH;fo7 zm6sTYh8li#l-q5eo3uH=@H<i8d(=&W6EOrNrthY<vMI0=K7F<izbB!r9LGh*%Gzj+ zGq!L_3-VL{B2_{W?F%9+qHwY>$|s@JM)Ak%M$b9G!Q&n<TjLT9S1@`lLqP3I$S3Xo z<SKtBF==q?V$W-*`jq%ZG`^u!qSLg22Bj2P@{ty<<mYn1fSof{vrS#Bfzd^hUct-K z_iCI&97G@oSe#fT#whs+VGCIY0o%$zUoiZdpIzr#Afj}!Qyl&DdaQ=jk0aUhLyh6a zHdZ$+NG6SD+@*%2Ft8Qt@@J`L)@HkO9D``qpLjJZOgR{Pt&G&+(m{0u@B$B`!XGpy zjs0PnO&Q`ykwYF&s3A^wGEJtgi3I*R$iTl`n@A`Iwc*9Q<z!k*j*bx**b6deiN`S! zzj!BuPx40=Q>ADcQWf=w1cM{Ctp}sk7}e>wuY+S{4e%Ez^nazzK1w=)%bp^&lack) z*io^rH;S&Yt`jYm^8YxN$3bGe9sD^a<pr^_&HN_D$G1Svk9UsmxKC`GJse_sUJUO@ zg6LVmNywDV^YaqsPT_!@oM-<gzPC9u_2ZP{S8}P=%2eZa*6arRHTdVR<))NUyhIxU z#yu?8#rDec3->6IT$D#8zWv61FNtO@dl$%|mhfC0#7XlwqYlo#;wd4LtRpS|hc&=h z{-||71?SorOAHH67#A>f`Pr6s0IL}b9cxvrb%o+LPb@M1^06F5l#hvfgbz;kbTgDn zp8>Q<4vzEvrQFmRFlMOqFV6wBLBqK{<Q^Y1aZ1#P<yw};0~fwdKIlKW9bjtK;)v%% zF&cZEAjt*_C7|(~`<cV{o}RPV&}I<~d&X*^!AAv#)aET<=1sPZL1#l;;3BhQy<OuH z`;nk3>vctTb&sK$;qlES3u{W`pjnS59%k?on>+dHQVwZ+=-S%EKj9KGi1Dbiij5ew z=V$%jp5ABupF<VB*`2Vx*ywTQi`%C{iTQ41JeyPUoH?}aDmbc$T$jlno~I|FLm`#k z<<gWMb;+1=nG<9D2@YdLR^X}%_ZfmIO(4T2tvq<#=@&Ab1IrMefr0>vjb`NR_};h) z22`m7lFvW9#Q+|n2m*LEp+I4^_Um!ZA4y?pQd*Or>0CKwcu%){Q<qdJTn|X7ln!!F z#?|#g&o^cCDkUI!P3zWOV21(7vY~x?>1PEDhL5>53)c_p>Y)1%`P~=WOP<ZX2uNFK z4nLWs#P*v=gjpS<4jG^buIMvq;<hQFg}7(@hxXL(x-}*BY<ww{brd}I9Z+keW_{?J zY-&W-9S}7iqy56Klx9IqhsqTzn+yGrmSCF%=7^XGmz1!-uz^i1MkbD^R$fGBV%CQm ztlZjuI^S<WdreA0&WyM8Nyg(1@A0YN`Ta0sCSM{3zbqOq=Ynp?U!$h_ism5whIx{E z`x33Jkq~0?OtE+H6VQLaGF|~Kus{X&!x-m>WQHo12*7IgGy;FZzh=?eEn&BWs4+TV z62qbWY3<@*JIMU$F3bJd3E@E1>65^=qN3j=rnDHs{R=p=+N^#25T5Kq65Q=h8chSs zdK*;=#Y?cCXNbVugYd<1<sOA{+%PChF=TD~vw&Fl!lc%La29>+SJh4X%bHaYcC4^_ zN1kpLbD2X@74y+><Z7J|VbcZ<$5dQL4zq=JeIDxc4c4p|4P_DlThx%MD|m1G<@X}- zh~m{2<AF+kU9}P;(vpA*$${&7+s1DIF1$D;ILux6@__`5@}Yqs9xWUGM~AJ@u($0_ z@lI(gPn6u8m1|v&^}vBf{N@iJU)QokxR%YWDdC4JC>1Xme7nXweYtpvoh?vhkYFU@ z-ES?Y4-^;s7(tUZI|`-A;5gAXq8`IwFnz_OHQljW-ExZ#(g!^~Kkd#aCwFDCHk6|C znqnO#f%j9VgevZNrI6uA+&E3JUgVT6Zo&CJDyr!_I}+^#?ps`I#ViDG+i>bv9<_}v zN5b?ekHytDy-m+L<OILIC3bfplWlL!JHIewaQFOz%3WH9lfa+GuP)WcBYjJU6;)gd z6>aX7p_T?vU2!=zTNp`ULV_i5vqpwg=KykxMj0n0ksihoTcSiPI}!~K*@MI^I3e9# zt1jqQ2UH6R_~f?k>Q1rK_C>q?K3k`3xwJp2!xU^(lo&$ZVKj%IX+AKQub@W!hNu}i zua&Td9=C8v8q!L%MgTvVa>vZ#8A_{%WwRKXy7*fbrm5maRTG=>?;(0&?^8+pV^iRh zB#bXcw_^xactkYIeCENCMkYndv^4ow;;Q_@{eNG4&MH3^nZCjn5KD9kDe)`$D8dc7 z%p36wW-5ZQ2J`55#XW~7>Iq+NNn!k)X2+JQL<?!fF5L$}&W>W}1oV3d1NL(wl>^tM zK+r9jf-frNL#vukS}8qpp=xOr!s^l?!J;W~sq{>m{?Y+&?*zu9_J2d88d#=cDyM<6 z&{2aL2e|?4lWXGGCPnwci4!XZ!XlDZiPO_>YlnvNV=L>BXg$-pYMl`IEl77<Mwc`^ z4b`ETtwh4hfI`W^ynjsd@M)?=Co5vrde&xb?Fyna2`qbWk9FmY$pm#et~=y46dEF^ z?-=!I$A~i)939EL6=kfUZYl;d3YjJr{<7ua4W-luo;pN5?Jyii>Ub~_?H(X@T>`PT zf&=2$@*ziSv=t_fUot-P6WL9sbq+i9$`1Ba#Li)a%6-x<3wr1Cq63+s`F7t&Sv|>K zspRyoK1?MUZF^w%^l>`n9fMN8D<FgAw;T1>rYYNaO%H6w&6}2AW`Syq#i3G}Bi(a{ zy=}2lPi{S<`er{oO<DU@^ud?4vxeOb(Jtv$^2{c$C$M4dXF_H{fq{j`;{!<>RfgYi zU2t>QBpNsYn3HhD&a=7P$bOcxCC-uahLXy(_vWOCGq0=OZ=KN**rFq{1m$4Na3@tP zcV!(tv!);t_#&4$TSr$I@f$CkUb3kp=+Gtts>`IK6D~a2Ag=D_4LLvh<JkywhgLZs z8tO)E_l$%9Qtkb`9`*-5r?Xq$CsPo~^zV;Cu|ZqsE*s$nSGO^tF|w)0E}Mz-oV$-W zSvzV`Jd!rf&%>M5i!*LnWD`rjAweDZ`AIlTNz?_#;DmL7ph}sNmDo1tIM`s)N#Nq0 zoYR~cy&qd-0N|1S?Lq+}SU(;G0gaR}fUCasLF}aENJz8nhX{I1ASek{rIqjVJeLCZ zK)+A3xp#sop2g;oJ|&AbzajcdIzj5mO-@~fB+J!}4oEbI_;{Eulg*gN>A!ZN-c7CC zfBP3{QLK7j-la;vT03Xb-8rbsXloMhgy{6TBBDunnMLZ_0R_@X*WdU2mU<qm1;b@j zU(dkLZ8Nw(Kd<@*_8-Y>{9keJU#Rl`6t{{?$_Z=8|AkxG{}uN#{uLMWM|5lH^q+|J z@3{5P^?&kSMuxv+w0{G>f0O(EkHA?{K~z{>{U5;j-xTn_0B7cZ@?KWPe-`jB#F>%d zZ}}kq2KfGs^8SsWvNI6;iSYP`xcF~K_Yd;@GhX0Nkj(#q=J;Q4`@d_B{{*Z2S>w-I zf1zXo=Kp_0$Nyuge|RqI|3jYpA4UJG@R*bNe*n7Nn`xWviPv9H$5XeOZbps}0R-be z7dh;>lTB8d>`qC%dHf0}T-cdWDiT!MHudv61JniG8k^{eGyohamwKe;PwbFwcX*<= z?vLO9#D=S_lR-C$kYzu&VH2~9i5QkloqYP;6CyBuI=|joQK!QB#ksgV-hSry`qoTe zBAW_P8gOCxkMr**n6F;vf8JvX700eYSPCI84kMA#!Oimd#ISjvy)TkMsixWCSsob@ z57A5)sM^})eZt$@H>4>jT9~wM9U5ytrLnhiL?;!dtk*Xg@0@^dZlc8z`oWV&5pyLD zy7TQ-G|{#_b?~yVwrwhJ?_XxyqJ?J4v1=3VRd{Y7iI+Og?P}ZM$f@evy}z%Vcz3zi zo7o#mqC#phxk;CCoyDnYrl=E@Ut=h^eMss(9H%Y~0Dm2QGA!CIF6b2<)7@1Doik&y zh;N?wuJ@<y>*{tkiG)d#fG^FFRiEy}Kopa#lUO2Az1*zo<U|fhjIp350M|uR+-+s6 z7?O!adF^ldSs2vTUFwYzp;Wwmul2+nA|y0yeUdgNYi5+4H-FmFEE3c`=Q-P$k^s`K z-!akaiKm1)0)YeRP_0eZb--!RuOyWr_1{l*O$R^okj2P2jf&wSIeN4#3xWD~NfiT} z=|{4ZU)s*nl1Ui`fn^k%V)*x(`bi}zOrrAdPYL|u0xCKs_t9zH+S=Tt2ITEGf-$%% zms{}_-h!CLrTJM?RN)^-<mv8YgV>2&)`U#NJR+ckX}@o;E(R|9F1>v8?)ZU2%vCx7 zM|^#I1FO>{6;x|)yK0DSBH<a`)zwl{;|*4bbLL^<JXx^n-_zliP%Ips?H7nPw#=Xe zK}2TNzyI{32*2PZC7`a%R?hty2GkRP=`dkcK=EUmS4XhnQ4+D=_ce1xurk*5CSq5_ zfkt~h$ONJH_+@UO7W4z=lb8KW<;G{&8cX42dzmefi6do4G#bs}N6dk;;b8(Wj5~th z=F=<R_5E)vn1PATHrLbPlDWXr_ybE7J=a)!+2~?^K&uJhE!aHiQYsIcM2q^=Lt!8T zd(63$$6V_J#M{@QHhWJMb-z*R(rYVr)P&d6n4sgpWJR^j%1gL<b=m;kvft_gwwo|7 zkrGt9s%c{qJ`x3AwHA}OiU<OcJ@pL&Y{75ddw8=0_~!`yRpG|Vd*<?P@YZCSY)Oo6 z?xYXtZSo&tm9m*M04__uk(VkQR>O8;5-fH@-jjCr3lb-a?KmqL0B5gUUSRxO6com* z(}yT$y-;khYhE;x+Csa9V!XO&0>-Bcs$yIzk(e*dHNF#O+%Z~Fdw8`-j+9JF9ZLX2 zYwY0pd{U$myNdYh{%<AaFrqA1ra8!9`3M!B$IH%CA;IzBH^21@#H40VM3*;S%eI1~ zU>0piUGcTQm(ESrZ<j15yk@!GVS=b}`6<4FIic6+WjmK|NlhU~Bd}7xcGUbxvT1m; zalwK|jTGTd*$Ui{8btcNiyTwZ3HoUvr9m4}EV9)Y=ReeBGDZ&NzBb#I874*n2~>DI z<_ez0OS$ko8sZoXc`%Xqy;c#ONaD3;nJ~+*A{pel=j8jjaq&?x5#clMFLYu!WCBUe z7Cb0>t>$(uc*zN}omrSoZ8UZe5;|D*Dgp37HY#VO*~(VMmoYI))}ek>6$4nxaF7C> zrhcpe8^s{?4DLid0uCunkdhX7R7IeqN(RJt7URk~A}z?PN5Uys0L_REiHkq%lVb%* z1%%XJ{Dhx|lm!4uYF<iiHlfJ+lWYvF@3G7@4V+cRhwEk{5o;v@1_!?u^3Fq^2;-sj zi#{3wH_L=%HI&UzJapw}E@FDo(TsK2!Agcoe?Zh?>O4~pH(E96&3ZccWKHAAsSIGN zJg`M=KHw@@w3yBzz%VPeAVa+Nz)7z9V@k6vWJ%-(?C%k-x;#E@FuF@(5Crvp=Z^3= zepj*}5@HDad+F!%H$Y%AQ<#2d0ft|1HjTLr7aC}N2K1U*qY0&;dsiZ9B$PV~aQ8JQ zPnPeAqjRnx$`E;0d&@t~Ri#Ph*7$Z)Q8Cyoa0#4t*Wo_3o|KFqB0tec2%tNZAkDMd zNIh$BdYe9rTUh%_J_x)av6%+RHzFP@@XZ96>z!?Ps8~j=Ios;O-pk&Fz+{{DN@<-M zNfI*c?etu?CbYq=Hh4L4mc-J4s`2l-IxpqD*+X>W;9`vVos+h4Gxy@_^juBYv440H zwReP-<9e-%Vf`T+wH>b0?r^!N8VP&!+pp@lq)-@k&9)vhBvL2UXutS6z;gV$Cb{*< zxr*%rnWoQN2qfTH^UsVQD)rJ+w*#|W<W|55_m6}`1DWu1S66QCXI9R-G)@-_r>V>j z?DvgdeHv5Jd-2?})Z1n6IOW9i-N}j|ssk9?2jI~B8Va01E`QG#WsV_D`L_1o_e>|w z;7KLC8BhYn5&(*i&J%a?iAzg3&de5Fs{lKy%1^O!wAycc%=m@#q|BU0@oj3;bMmKv zcAzs1>S_u<z+8JYe|Ii$75F6pnc%E$t?wC-t6kF?V#X9^P6WX#lxX)y=FVPkk8G(Y z^`p>0G%Vk%{u%Fs!(nGOx_AJM7gxfQFUn;$bb3Up?~);LKv{~px_iqg0UWprqcfIU zE@W@x=PtU>bk)7T=}m!+NBod4T%uA6+q(PtP^`Wm#o>9sFmlFpDmNiBp9KLRX9Q6D z3QSOH^s-yJ^Th$@Ibdvpa%)2P+-<W)phMp|oDd_(v)3a55HuZP5<hd`7jT2o98AN$ z&PkoX>o80$UHwNp#;CdW<mGSfOWnr|G-97rc<QUa(i}*Fdxf!g+e$zrc;=u7@V&jD z3w)zxeSDH&h|}|WXud1t|8ic}%?W)Gf&))Fgl!lsF+_{(2ynUm%+C~Xoya1k*<c*= zNnd7W_c6b2YSrOjtmqyakN$^;$*cfd}c9?(r8K-4rR<N?^#vNdww8orp=HLlTb zfe5L|+=kU{!%|L$D}CxD!92v(+aj{g1p5VGe9HsD@_{i0kNg3Oa!iglh9<1tB;Krd z>(XtlOR$kCfW+q?A&dw$B2Z>;QC~i+rh2u3Q^SZ8BNI#bD^I^q(XtV_tBM0^typNj zYEB!X6ipan8WNK<ccp*j@dw|@pEr%qNP3SHF(g(8hYvaHW#);at&1EX9o+6ZjN+jF zgQx7sg>Rgt8BTF2RW7VCL<p0BG?O#cl^&-%knp)9`3GkoKN2;e!7d6pIcN{gR;s%R zMliFhKH4)5x*?~20O#^4uXvQ2A*bjaOw2;9$N@tlNLT1^BfDD*OM0wEg;R*AjgA2R z&u8JCCSSuG(okZt*O<AEhg;1g5f4TMuw6qLvxhvv8q$?gP%yKYTw57J8i2d${c|PL zvINSc4T3zMsR`!+^k7y=Gt2K`MCChzg5-91Y;!j5I4>9F%SkwU?^$~fxF6iU`;{N} z$l$pXo$H&0nnneqp%RE_;P?+?3Bktv?e%H2>xzRrKvT3Tz;m*QrQO^21<{>;6Wb;< zI!r1T+KBb2>0Vn%ET0mp2318j%&lH~Yu+DBW0y!nNB60Y5l?IAIN;{hMn;jVw3iL1 z?(Crks7KI!kcpv0sZI53t=smZ{(Y#u3ZhhlA~D5^O=Po*w!Jr}vU3{I>0P~`#mz&k zDG#GM3lu1tZ2AN3sB3JGV!1|$S(G5|0FO<-ZQc!h2Wm)Qk5MqP&@wKycWRFWbkIVT zNH7wCc{-TSs98jc_uc|a#9WQExb>m!X#(%tx2CPT@w$-`E!RwA>rl!)21ZFE+z5iL zFXbiB4%+>5I`gzS`QvEIaDRsNJNhoFYz2A&=W{O&{b<~{4_G-_zA<fjPI{;iAWF?F z+?Fgbz}R5_Tud~kQ9L30?WwZeE;a)XGj>eNiFNav7Q%qZqy;AwBJQ9ZH?(`~=;@|J zq+pALDpzS#PT%+i<WtKDK-9rN8l!&&vovoU{uqNqe8G@}?8577YvkuKK@le_tH{JO zLILMJLFOg{4uD7d04wEu@u5IC5Vn=!6n_fKxAB;<#>)T(x~RUHh)0aMa9%8j&IgRt z*BpVVgy6>elZs<kwPt$#1F{^1fpH0kmod|z;huJQZ>ioo9*5-(S^7S>k*HeO2iwS} zx@Iq;`b&}U`7yJGW+eiR@|Zz{l2Jo`!li*}j%UN$CU|T44SK?-N8tMc18%$9RBnbv ztThQ_eH40amL@JHpm!%<^Kuvn4z$~qsOpAW&u+<7m-3`y8sSIf9AqiUUgC=sDM>7Z zeK~#nc3UoO*dTa4zyLt*I;vgmHZpwOJtz9hQZw^qKzc4>>b@97H_kNM+1}O}MO+N6 z8Y#nF*=)4F9d}reGhbP`&uLy0$PRXui^?b!RPQxLbN;;M!P)54`6vh@KgDkl<1mcy zOwQJg7)z7B7;I@e6Pk6;$YqDD>l+RfdXYW4F$rnSn@yEmMEatAq0j~#R7kmyW~k&I zkl;b#xCTf;)LE4Hy_$MccxcwKULFIE*%7WJ3Ykzn@ZO$m#X_Xfrqs5voi=3DC`y^@ z!7(T{1#K5^76+kC91OQvHR#7AWjy8{;DBlOQZo`j`lEt@CR~|s4#-0j*BEWM=g#>( zWG7kxAqpiiy{6JvKo>o{dQf?{JN*(Y3e6%;b<`hJeFk6gcrw;iq+@es={R*qS%={E z;^M<_H=>>Zs+@|P8{Q!b@W61Ip^&;7IDtO~!i?o<ajMQEcy3IIfNXgutVHe~nEgsT z4BYu44D|(^R#}%L2YuFcUO2XAqllR8s7rP^D~)*0h+_GK)6IGF5DsII(`VwYt3EJN zB)jMk3lSC!0PlF%>(XjzZ;I!srK#sB!CEN&!haV5eS_#G4@V*co#bryT5B2c10j_f z7&r4-)c%P6s^(rT5J+-~#0Q$1lm*k!Bt|y4NSgw&8DQ(0aT@fARW0G$&tPLiDZGIQ z;mDC1+i6xmb6?-k=SV3nMk#jn)YCahXfv(^Has$fb0#q~6Nqk_t{o^*+rw_>{hA?n z9n|<a?Z#%sC8BvfImo$VT$ZFQd<8W;&PCXJH(NYkBdl(JphJfnDWPf<@7z~zLFE33 z_{RB+ctL24`gyKoIG#K?MaStM-xADd4q3{biGW^9zNi9uQvcPbe%2NE`EXXJqO922 zOF(E=Y2b*dr0j=3f7$w>t-nr6pnPvi5<OXv;;fik4hFRgc6X{h=MXLSb^upCpX;J4 zE~j#oTuj%->kHNfU}j5nPWLB(&eP975;QDnj-_iDh)E`SKG|pU0L(zF$Q)lx1w+=A z0&bS$-IT{#!J66W>txbH4}n*XhuF&pSIKWZ;cjx~=mPDGOfI=8;{mDVOe#qeue2!y z4}f%EU3|8oQEg(+8>uvLUxrq()mcomu{<zVOiX7V!%K)+@s!)U&?{V=cE~@*u%X;v z*N6kGdK&Qq8{b=@VuCZsQO~Wg11}vQ-h|Y^TCf0OO`FD^unUP_P&3friLF7jF^CRO z#H?Dalrli2St<syx9-im_EOoZzC?9&c*`*xm4}5P!uK*4Nqt?^H%q7863Si4Z|XQr zN9*VMH6@ubR4phrIJb9hZ~}?|ZZDq6Z>}BI6#JRq%&aTXLeA#a(a{b-dZ58NG@1_w z5#($$b3<PHz5E?@kq8#aUkgL~)dKbI%+MvyfQEkUT;euwTMnzvle3}u;MdH}-njee zD;=#kGQ{ubd8+G*Ij}U@$fGw}ew;@*$S8tMLlh{hVep8;j1+A)<##1Pa^CYABQlZ6 z19aHTYdvd=kDi;(_h~k6avU9cGZ&pE%DYN<smiP^lZv=zYgmh{eBf!y(iVm@5>UJ1 z_v54~W5aS?*P|N-c54GJ#Mjn4!R0Q2fOB-x=sO|vdxL$XA#kFtOt2y3lr>GB%W3Sd zG(#Hk`-9IoQ1ZNNbV93S+BDNeHbGEh9S}h!!H9)%jr1W_`UaHdT<|Bz!<d_p3`9RX z>Rs*dh*Rt~exM0<{@B~(J+%pa#~$QTgPN{bi5O}SjV9Db&lsY5dYVaX>hpGfIz^U! z+`D>*|2>Xbf55Jx$pC+C%ZHHD&ExS!2YHOn-iBJBc59~w+R&xR^L(K#V?vR9<P$An zxjXFDGtw{asm%_#sDD|=4@=kju&8e~tbT6a+##+HN}glnqGe+4QoLx&3+NZ92z+XZ zouOXK^E-GlB*C<1Rg4;F^;newO4+0~r2T44iz}COfOWY~vEBNUK3$V-JlTA1FFuB* zt$Xf%Q<@AYw>Ti_(aWKkp%qX~sgX~4Y)<rXekFq;a*_Gjg`xwzr^ow%Jei^u?|O2= zwUd;xQMg5i`MT(kP-TDg-bc}|aaM3(?(7xR3#_#;D)$@tSWLfQokKP7ZqbG*(@{S? zGdVGsxwC&+z#IYyKWK{5Cz_ldj)Fx?{gJLh{s)~L(~_y7^)xuuF$|e=)c?oYTR_FN zE$hO#ySoMm+PDOFcXyY@-8I2A!7aGEYj6wh65NApg3D|6-shZs&$<8o?;GO{Mz1xs zYR;Ogn;u`+S2csL1<!%{RUZkGNsA*&y|Mf$pJth(#2OmsBe%C=yp^zfx>R7?0i%)D zOFm=*tKyT|!J5%0S_s{$4|aLBk``c_1dVcrn`H+g_VS*pHq=ETI;f^Rm3nbONw48d z>Nn@oYRxaWC779=cONAtU;MV8dgS-6X0+y)ey=(&int-x#@jr90A6?&ek@^P=TyC* zi&eeSy%H5okoOZKt1bVW?XlTd=kl>bUZ3BOe3o6kO2d6L>9(rPE$Jrdq>Lj~wA-K4 zd1!gDGuMRgB!c79L{ykxEiaQi1kUDN;#jEQC$Mlf{3)`PwG_<>DDJXtO)dP*M_^o1 zP}D<3bS~7GrOG?$_i9bMlTMPN1P{l$Iu=EXe4ih@HW{Y9o?m`+ff>gHk^Lvc{U=QO zzs0z9#O1{073dX!b|!8v@(%WLj4I}qZi*(h!2d+N|8nmAC*sY{@h`xc1?0f`PssZZ zFc%<hZ|YzMw6_EW)Bh77ep~C?y8c9r|F<iYQkIaA68>w2ph)iu4m@&<^5$m1w=nqs zu}GFbkmJ9>@}N-KfAfO=7cT!t+5naRVUa9v`1;=$sikOQZEpH6=WGcFXFHI7XS+8T zpNs{dq$aM#1ajgvS1`6Sf6oK}<$!Q=Hg^G-0WB;T9UNU4oz0ES{=Ld*4(c#<uy;3i zb~QI+bah~IG_#=nQ`Q?quHa^8Vh-}({<B5e-oil)XzB{`=l(O=|371OgW_D1u`+{# z%Bz^WIJh~Rn!A8f8vkcZ4ZuHhL4dkdxIl9LI-5TX`PRk_YWvGW9OSY6mwz%h56Axu zXJ=OG$=90=IIVRVPRoW*H5@;h(uRFukj24>?nN9%gqJYN0Ed@*|8(v*G2GUC#p#gM z1A#O9+;%2uZf>5%m!=)i5#sX=`lajTdG~Zy(09%u+wWS<A;4$k*6DGP=Gp)8;HP5c zyl&dg%}wdJd-J3rpWina-{&8*hS_<32Cu$XH#c$BPJK=p&5Hs4b&~zMmA8@Qgl8`g zr?WoI7ca-Jz7LzfXB~bA8^E6V@%43H(r(=D-)A}SuSw32o3^j=g>tX0EiRHeKC)gc zxU3eh3CxE*7Ng&+-S361wr|dx*72q=liKUcUR2u~26UfZ_m>Y5ocCl+S&yT};gO%g zUW}r)_*wftuIEZ)JSmFc3zqr`8aH?gVp;jwpT1^7q>w7Y-;#JEJ>MKX{aQmcUG*uw zhZ)9t8NIz5s1CS%@C`}TF?28@4cNu)j@*D%>+9;S7I?Zy>3SUZ4_O$zM0mI}?GpLq zEby&2tm}Sr@h4)z)P5_<Pzvcil}hTZRu$lK8dmVR_!*-x>>1;DH0~M0e0{c0_US~k z>+vr8RyUgDR<{Bmc%vKbHMxO4P%UtGI7FIx{zY~2nDJyM?g@ow+{bGwB0J#Xp}5*G zz;*4FO_Gh}wC1e!mv8#RXljZ9Q_*kt8pLWVvi*)`R)n$pj!9eh*Ts#_=h=*E2gB$4 zoB9pEr=A!Gh?FV%s&EgT9!2s}u7Ky8ajw_r@l(`uzsq?`qP!K@b)rZE`|7KXl_}Sk zHWCAepAG?@uil5o^(J<=tjox3Gsl;W2CtKb>%K0X^Oq3cUa+yu_LCiIa>Z|Bp4i6R z`|D>qE*-i804cYIduV+=JRPs+)vwSh?ZqqoiLTP6mnq-Mj><HLHUcK6t5sG~eoK&l z`*3D6IfF$}+?^8PvPr*?|D)se*O21NaeZ~y!_BCogXgishHbvf;|cB0G_wWzsx%7- zd&4fT2eySv;;yxq)0;iafIGrp6$?|6d^Lex5IVY+b(4k_lbF`G13V+0ezE5l4=yGL zQYs*p8Q82Gxm!s%Q&4QdM0=7?WSdv-ETSQjwXF<(PNG)R7dj0w(}UD|y$EqrGF%4W zN&QAfmik`iHp8Jnekl7U$p=Qfo>lIG5bsIPF8onq#Bml!I%v(`5JRP{O_}F00PfAp zNDHDB;(&vxZvq?z!m8=-L>wLm+G$99&T3Trx7kA7GEBF>uCv}pC~~`epOYtAITFar zzO#MKdhVB$xn+Lt-+F&mc^45!aOTw#VQPLwOn*ePKGf03e~D$SBkLyT8!8blhIzB% zVZA=2s8!WaA&V#?$3y(#K6b-kO4<XOUw2~E_K*@vp1EnT!XaC5AXR>l1xFtnBL>D@ zV<*v3xSs$;j}*pMvw>F>`~BuJEq*LM8uL4%i<EedB9X$9IOdL+Q9{*<8UmJ0)LLse zruY(yln6?MU0F=*UOy#bOcE46;fSB@O-ge55DN@#`}n|s7P`A;7dMuCq51SX(U~(T z^ghJjEa|2Z_(IV2S$8B9xLn5jV2Wr+$!pYE#zeW=kkvm_7xBLN<Ww0uv)Ld8V>m!= z$bPZKc=x$g-zw=>2?iU7%_b^yGr^}QI*a+-u~r}wCoa2<&|RNdrW1CIZeJ0csA-Kf z%k?@UU?1&dCtRAtmNA1nIm4%P2bD)^5dS;G1Xok|y|o55H}+ebFy>?noB-DXd?`=` zI~i7IezVmlSp!RpM&X-EM`OC1*%{RlFF)N7S;dxJi!^&F3|DAnzdoc=5qV3vPmXgv zkcaSwMWQ*724$^iyDVsrVX1z(LMY;^54;iq=As|mbFL^FVLmzvxXDoaEY9wGd`)l3 zZwk5aUt(|6?jks$5h_?(%1kv@vpBC~W8_>3auM3(^CNl(o5zv%KBF_-i2~asMZYE0 zB=cxikqsgl3CdN0_OD0qshqATQIu;1Lo!*OsRko>6mQlEo7m}SiG%<m16J8`IX&n1 znHQKEs}O?4;dbiOh3ja8wlw1WmMxE6$x(;3OhWeNSL27eH5gpsA(P#K^!W<bmL>Y7 z2N-CU%!LnEROxBR_<CcvC}IUHLf{eWsV*#RefN!oiDxLh4xgPz^dv{bqVB)7N>9Do z^BysCY4ISqXAp+NIWT`~jp!d=OUr=LpSaQuYYiedeeau|f%MzKIj^`?TFKrg!`yyT zwWcGAjq5YdS#oCUu#!1nZyI60fEWIEb9<R+TpP#&4B7=dJA}EexT6aB;HY-P3i%-F zj8isKNH?NebE&Z<tOF-bD#Jk-y0rty=GxK-ln!1~NN~*$J$ANq4Wvig(a0vLzFd7x z74jiWG(KO=?M!;I+t@gjG`kbGqLDfI?71=<d&*aRZWmxeO)DY*HnTaSTAG%2Mgat+ z3A4-W2aZew+JvdK&Rnu}74l(B&Fo(rBaoTaDqiY1iFt)SA24$&`I@ZS*?hPy#1AsJ zHA?@vcCyUM3Hjhq)@Y0T3)ACxVgX4j#D0I;7MUo-@vM#`AN#=DqX~wyK*_7A5}#R< zhsM$d%pW7kawQ#}e$fQfbwJ}hYpcs<%xEb!vD6)Aoo#Q*)_%HYXIpdokbYxqE0UwU zGzi0_Z_$TONQQQHZD$*}IkINE0P2xl{kgIP)-xQ*V~R;F|CNoiTeU89eW|;#v884a zS{z-}NfyC}%1h;uYJ_PH(wqNv7V-P2emCwg?DX}(rMVLM-8>A5p<tMa(X7yvYe5kV zWR{PRt`VIVxE=|kq9_r22J-hJn5jAAI;#lgM1?sV3wGL`z&1@rB(-9t!C$#v9q+#J zGE5p({5WA^2kI-Zr|YLG%S;9R$c6LvOJ~2^4v$JWV*}^B@FzyKnk|ybtNb3N0r{z3 z7V(2Yy%=&&XoR+ZA}Xw75oI?b=NKn-`}o`e1piha4@^`TuC4rSu`O_}Xw@pCcjFi2 zRFKA9y6TmWLT8BC+JQkQn&h}aX9yeXkj2$$ZMA&UuI_tWuOFhFVg}Pj^YH*=xskr% z6oPI`hkK2x1hDVqh7dgq2?-8+*06vLaK#VfkBs*-gK(_GS5!=UeP4aaYgapb;X$NL z8c>e3v=Y(I1bbz?-Nf_m`qA2<CwF)V!tti1Te3mMR6!oK53UqD6wOi}wgC>EPrn2^ zpH4yoJ6%k>D02et#vQf@Av<^iKQMvwS93#n`wKuk7=PJ8A30q4$s1j0^M!|(a!tTC z4)QC<y;-`0ZZy)f@+$J04UTZNRLWVgwP+@LAoM&@LSq-Dysv6Q?5(D)Ku)%Zq9uED zrIm)0Ds=S5fb?AQ?#dxd(ZKeUEep!LNH(QW5n;WXpvjSNQMU!hPUObW+FWs>3=OKs z7rSjqIeA4(Ou_9lVFeLP2RWhek5)F@&`}QTU`8Pnmy~B`Qy((!gk|KeNGV`+4n@#i zxd*=sZPELoKY1wCysQ;cAe2rssvnAWI|HzDKYA^hmRdEu@9a>6;)8+$#T`j&Li(wq z&H#r#W<Uo6r!(PFTIQZ)SQej$PWDp;9UQq!EhMK8px$_ToJ<F~WSA1E6?M0(S0?9) z`K*G@jG=g1_%RyZVpV0n<nuHmwkUd@N3)tm;;PD)L<5~qDI8g`A)R76-`QCl_=t<k z`VSWB$)}6nSdYFmlpa3R;*L%vQ#souZaB<6dFC_*Qh1xmRLqxwZD=_vf~w{kZkSDr zk0#d14Ak}p<6S~G^;UA`R){}gq4lT1SlEf*aTYC5hE|2=!nS>~P!{I|h{5r4STIM2 zW~*!UlL!C6AqzIEX2Vt^s~SR$+7I6^so@w10T*I131c%oc;}m1o2&v?i-2y7o)FzY zi9@`PKK8{}7pDVZe+1}3fPu)%dYKQ6KOd?lPW?5sKvxVs^uEnqGdkif0SjuCJRg84 z7Y@oF0HwS~#D?_^2{hpu0Y~l(O;^D-Ft1za*Vi<RDLlHe0Xoii;sact+zS|b3sXmf zp_L5KzEHUM{(6~ZcG_yHwHmx5g-RM@S%nG2DR3}W;jE6V<U3JT$C@D9sxu+8H<93T zQ<k4%9gxOMtrD9ZE?@s53cQ}<g~1#@{<^uSeqQrIEws{OSL-5Uwq49<xnmt);@UeU zhjwnf(u3V2ytrvyJXC7-_^Z+Cg*U9~_wJEH;#PuvsiyTWT+ZH-Ig1_Z<W{>4U)P7N z^?ot24k{>|OO+ZeD4dfCNJQ$MTOFy~dWXHXR4IUp;k8Zc&|1i2%ZJ3;<e;m5V3S0( z2hVW*hYnL2jEDVSvHi-o-ne*dw^8fztzN|*;OXC`Ru1)X^&CO1%hBcQ@b0!9s%U#i z<zVZvd7JV>(D$eBkM6S#SicWJ%OOwJj@SC}e56KRLC329?&{G0OBNnsS3bigHCt0? zF;GkfUKSRiUp{IjLTMX)zHc7mH{l`tcpS!NBobb{sAf_fNf-v!Y9J05T5bd7udrj9 z`y3%KLYD8_I={Nnr!-0&f|^nh)Zj91I1_9l2a$zjv3MDfOTw|P@C_WH+~4nO<hFL} z6U6UXZ32SqmwU*J77ze(kk()Fc|_1+gX(&X)cB^<x;RUbffwnZcVm+zx>R%Xp-LB} z%vNf_iEKz_jAERdRMc<_s(2j*1+#+$twit;&aPFgRH$i<Z0<up_O`r9lq*L*8b3aA zkE@9?S-o19x!pMB(pD;ZwAUaJR&7tWRTx{`XGwtrBs_c=-ZeS3a#k!!Au$q&ml8L0 z<?tpB7%se35~qRCNvn<iieB-@)Z=J{N0?y5%IoEnfo7gl!swv3#m>-1l3|;TZk?WA z+^3X?63G0G4$Qtp3XgQT@=nWKIb5CvvjTN?12!9hH&oSr*wja5evluCn3grS)=hsc zFS6juEdoi0IV!FfIwzpKZkHty(CPOZszqxUeNM|S)-FO69zWc0Y4ia}f5?oj01d;E zz7N<Kg8S1MJbsAwxmq5QLG~0b_9GU6$e9Nkf~Q9v=J_#$cXuj0e(#xK{c!qe4<^)o zS&{%NzvHIoy78nzanLK{2eB7T{4*llW5G6CKtK;!cK$Fh%uz|_sRbjPz=;}DlOtxy zSD6zGVl5s@IT*Qv)R+IqFMMS8*=h<5<Gm?@PqL{oWNRCBJyq8IS%pw{Fz;rr8I3zG zU<gD3apdZW@4|!}QfrJPa+dPpaE{VAHu0QK!?AJ%%!$O6lkf0M)VN`ESu-B3M<h4p z6Fx2_>1^V$UU9MBuFY|yzt#|qaPx@}%jtSSn-iE&heleS1(_Bt2+rPXLgKkPjnQK{ zW#ftNLi3xPkt_7E_Pbhoe-w?MV-XFvkYxQrUN0d(h8)KH5;LNZXl$Be61}Pk^T`ws zOmk~I`NS^N2?r_0b|De_Ce;~lyk?Aj!eoz!+rp|E!zD83Q-4fd4xc<XQkZu$yJRB^ zlI>9%LJwZ3C|iVM)GUz$j=Auc<$l`ADF)m;9hei*9R`igV324;CmhwundrLjEw%h5 zBY1SW@h9eZ%LQ!ty)5$7Njde+d)tVW5RUFGD>%kN{UZ1lYGY`cgPfK#A)-Y{e8`0O z0VvWE+Ddtx4lrVDbcZJR9G+f?S(+xHrtz!D$~2+|vD(7i%D(d|$~wasY+)(QTI<eS zMBv7I6jMJm0&Cv~iTI0P7B`I4tjaewry-!kKA{XT)5ZsX7!q-oP*N&TrXL6_$<Ra| zu-(Yx(Le~VV8EnN<}+m{wu@rQm9pE$!hL3N5gHz3ST-+=;D*_b1mnZ<&#(}!w@`Su z5lwFKz^<G1ekoNPvQiIYNXWo7KboPO|JW#@MJ(Ohyo{4J#Y}o_33Y$+O5P*`bJSOB z3ZpKDCsmN?OG87gdq0Lk-_zXob7DLOab@s(dV{Mg_o&~dU!#$YZ@hFQ${4GG6&oWu zPDly-7x^u^^>;GLn1_Ic8NBLoIgjQBb(U(c3;*QhZ9MAWz+dluS9W*EUdU9SeV7Hl z+>;hIU-e=|-PJ$g!XM_G(3jhoa9uc~1?Z7eHl=j{k|A_23SG-zYuVH8E=7iGp^C#v z<4o1}T&q6sD?|vp%Oo)7^nX7bD~J%)#7OZfGq8n&q`7j@BV$w_5>NfW2Gr<8!{?6R zthdeZyy-5Tl#nV~BtM-jRW0Lm%cGIg|G`=4xNWwkE6C<d1Q}#}<bsimKV=5bb7;7_ zJcEX%MMO_oO!bv9Tvu0{H#VqSD=mM<p<U5%^_{()OL4arAiv@39C412i(x5$Y>zsT z$h~flG8&M8R&6|!7A<-1eQmLsMEJH`+%D-L8bf_Ajnb10J}aW|OT0*9nyLYL^=@`3 z*yk8F{_p6Yaclx9`ZPvw7J-=*no`y>z*^R@Lt*+H_zpY{9!=o^j|)VmNIG<$3&i?~ z@vRj0t#0j^?F+;;H`hDp^KASa?x$XB!TCXwJE|BSZT|X8#J~!&i3mfoiL$a3%&yG= z*xZ>xK-m`Z^cF6Ip7DdnP71pgaEjPJ=yNZ0*Jek&^+%+x?mbQgpB-EwGI*yQTy^X^ zoV|X4k{09OAYdxGC-G(wp!8(K@ru|bHWFzLfc2O5lVaiYkHEsV=(t2|%EhwXNs-d# zh%;2Fo|%m?%scv;-v_8ch!o}Y7v@`5A9LBn#UndSk1-@Wl*91dNtw%c-xvhcz^J;3 z()b6tizz(y1HR0p4{W8(g^D!97*Z5+5cx|ZN%CAmc8ST@I5mbEs+jVRJKj;L=r9C- zxJ2X=o|fRhlr>wCK11p1&WEzU#q8=n#G2RpdPmi`#D0$1#mu~kvT_GK!3f07dYd-8 zlcF{>WiN0kThI{N2Z%s|lJ5;NqzD-hsl)6NDlB3cjWERe#<nYXDO*-(y-nkf4li`H z*$)Ur`+_NW8PyV;NbE1o;hr{%*%h8(<h2@M7-+Ry$px~(;;%cZB=YPokmMGlbWVS1 zvrO)5%r3D=L~8q;lzw)?V*hPi7S4(X-b-1!3W8sE;$x^qotKD4rg=Uqcj8bO^hk<N zAIJIGL3;=fk0k5@>&F>?>IQt-Hlf-HG6W0xwC}f_A_c6yx#|bd-WKJ}f`rRNi6_wb zOOKU1uY;B<2#Z^LDLYbV^H!N0f*8l_B2)ODej8!fk4p_Yl~^^2FvI>2Ccg#&aJnV4 zD4_X>svCFETZ(h5icc7{fHA)vT)ZDut=S+`ncOv8BIdE!s)rd;Ko4RpVs^bd!0q`t z2w-O17U%L8c9a_3x<IT*g{z7&L^Fkvf9?msi4Hj0J$)pXR|T1`pquOh)N?gM^Z_KA z`C}_cfEN45C(yA%7tw7&c9A_LyRCy189~j{_zRZ^a=aa0uGu^#NIPq<6k(Sb#}s>< z;U*eBjO`PKYL&*l?{*5_ZX4)<OT-n8jWNXI<N--$#OAku>cw~;2YOrvzE5u9!Z?AR zaBR~|q)9r04QNFQvJK&duH&JUrCg93r7qtcxj!FbmhESHPl}KQe&h7_eTQ(fVkGw5 zZ34Bs9G-0?vXt!K;8ktG?(^kSdl?Xs9eS|{f#3VT2Gf3&%H2v}9`KjbAnkxty@NA; zt5qe(tpx)q=6?_wr?awY$1=;mlWV7u!)zY=1!_M1z^5PA!Oe3jpIW9v`=*1F#|wln zYH4|57BPQ?V98)s7goJ-<(I4Jo}!i`pXl}i=?nelybbF3q3RM$`wcvUfy+2uD$YZ3 zktTik@bIDkWbDG#&&1f{!j*q2dp<ZTm^PTYyA@s)TTxtbQFM{OvJ-yRvLVjcvct+y zU8%V-uE!<<_mo~^v%N7+*zwBMk4~DrXjTAbc6#<VT8hYgrC;TDLWfg&Ves>XPM8k- ztSUtY#4LkHf`)3J(@}l_D^i|N@99oG>;c|WN=;=xeN;az!kWgoL`je=lXCqV*N&2e zgG2AscV93;Uz@P}yxT}J<cPv%1;Xc3IwJJmDQ`DaQ|E!c;{+ZzJSaR$F{mpp_BDk! zT`t33>w&~g(mo8?ke;|)x$;+}TD`0Glh?YJSu{kdq&(67bscLxXBpr<%zTcB=NIZ~ zp|Y4V%e^9GnF*@_a_dN}bJOH2{oGmF8REjsJXOKEXcs%|eWx!-J5b#@s6y^n`;eRq zy$GY?`;!wkUtH6?G$z%9xJcx|Y>&|vyH<+%SJ+z5()_$JU2s&VMuMppWA994r*$^= zO6xcazW2F^d`LnMu8Z?nrp89^OUit-azoI>8hGJEXR2!M@db1?Q9_$~OU=B6n=NzO zN(N<u$<psEg!~mZ2Qyae3J2wo1%w~itEX#UhTU7zr<ye?PL3paEU?NQjbD}6?1iS+ z)`^8&`sQPtrA9j&utX_n5$F$_Ngl<7!XkKQedjxfc#8Mk;?R9v+QML61ijZxnX3p= zL~osQ0>c*hVRjmM*G8dK3O&~dk+$k*`QZ{b?T$%`jH#Hy&h|d9aKxZCOjq_(Wtq3^ zR^%`8voh)4vW_nQ_*~-_m)l~kr?8fxdb4;)V(K*ClG!$d;cfBIY;M6&MUkR<Yk@i9 zp4`gz5nymjeGReBeEYfL``%PX+9O8<YJh;+PMw<rH4k?jB3`#-wU1m=eeCEKgv7;j zp3hj?E+Uf=RCdQ1a;u$XDt=V?ck^QdOyx_X)URpJyydC%Ut2z0U!2pKzC4sGuKQk( zk0y=DZw*~1jiGkwkH4PUN;xKtjnf^gcdV*yca^1MFMj=<^suj!uwu1{qVLXRpqnsE ztFM~?+ceS0(OP&vl|Hum0xE@g+R5*Cwlq;MW|8V7{AdMLsiR!%<SlxiXVNPIm@0?I z3d4p_)`p<(QbFMl1a)m6#!vwak%XN`qZ3Eek9Cpqc1zl}1m7dI-Ad~^!hg2vRw6L4 zDk40}J5%b!HvwJh+zW!$$#Fv}`>e;XUAouNbaLi5W#bWGLW5(>M}}2OxPqbKa2OH- zKT=V0nMr%GQXC-%H5`=z)b2CuQrFIA_inXoj2rOEN0(+e<0kab`E8MneWGezE6nOC z$zo;lY+uq(83#w|EUH+qeO>n>+w%~QPJr(vbiaGP->^+0*d3lD?8QlF)mR7Ym;!ZC z&m)twUk?lS1euhm?VDjM0hU0*u-2I-f{*lOs!U3Wh>xForzhJ9j$&WPR#{yX>jbSD z)90;wW~a#t-jN(OenY_uv|UnYUiq|*2H$&@W5w#AJkr$uX`A_@=$bIKOVgUremgJd zf4XzRuc30-YS|TckIyO=1(1gingCKC=SUmW$OL{K@l)ZRNJgH_z%5%)U7tt#RCw*^ zgO52?(J`tDK~&{<^r3nT6Y)~{A($5^JOEqF{p8D4?{fBzxccd*{DqYK_|SZJU4IqI z%^x&`G3Ksz_tN@-os5D6pAMF<>f6Blbp6@AryBPX)-yn@U+%B%?zQ>f2H=<eT)vV< zPL$OZplxvnjsg}S-)4MqYG>@yLd>xlog8dvZF(|c46I_z)~GgEzS3s;;j8P<894@$ zHcXPz<m-&PHIr<3Sj%L0-{5^elRQZ<xV>@({Iy)jm<?87xO@fty<f=K)u>M8-LRJ+ zt_YgaTjmQKEuJq26(L%@--?8tjM-XFdLZ$p_#i=Gg{uW1h1~R@>F|N0AW8R0`zu!w z4UeE=M5Y&TbP=8WZT7q?s3=X@$+)2)?h<n!O><nxlATuL4;-^>;AV=s7rWn@Nv+;d z1r?*;*Ex(W(lVqB`ch+LIE*#V>VaD2bQCsHvqkJc`ls!$?Cv9SHQO5Y*peAR#$l-{ z1L@fJc~h!x-`BiHkA&RGXn5z5(MWHHBznUKxCA}_X+5SbSRR>XPAdbe1lvpDL~2~k zK3p~$h7)J8<J#CxRlH4g$<H&y8TiUAXId5my@k9u%2<0xZKi}c)P`-QvTAAR$FX)= zYwm}!jN;-oRlLbbV%Iaox@~l0dL9fsQM>J?hrct<W93~R_hyJ?n+5$mZ1YymJ9(20 z1Ru_{(BXSLhOE~n&A*B9M-Hv0F?tTg0T^F9#d3f0(bD?zVRysuU>{h}HY3OtRq{i~ ztT7bM29;(uhzgZv6t2p2Cj``iilYkl*x&hXi+P|D;le&i3Ni1GaM?p8hNcZyg7cv1 z`#>cgaAZgd@z8Y~qbcRxekB1nrwBYlM|(%kdq_}_-*X%jXvDH>q!;G;?wh}uP|EM8 z_6HBfP$L^ny)Wm@8{h69ZVpK|zC9m}T7Ftz9^w789=4I`y%lkx-IIP{{BS=E+ch8S zrqZpe8CMBmY&sR|skos>=s<g)8>6Y*H{N)VOhT&Ts%tA5b%x-U_O-2PV<w>LMi<eo zHK@9y(KY*ihFe$L!v&aG_^KCEPd`^#ao);4P$y>2F+ftwWD~(KVL;<HA5!Fd@z{cf z<nPmB>q7b>a65aeMdpz^;u%+wD?lP#6PhW^>_WB{>2-B<Z$sB(Fz!HAsb|C~-5nMh z*4d<@nGPs(&=2|f>2PkXUL%wWl<unnyR3w6xiOz5w*-lpD``7k4BM)6ts*(eKVRWM zAd<P<fqEwmN~RKBj5_Ls60^HXIXbP=9=f%)vZx{F;o}+%D9Z{h$B`VNI<S6Z$<4FT z8cHPl!;D0apXwo4uH~9>jqZdE+UiD9iswu5c|OD%F>mQh0vk~ifS2fO@d-+(_-8of zuv~U_xEVR1HB5@!&>2-EGv(XIBV9cMVbAM$Hl@=MDoZr4_q|k9Xxb+(((qs-{8P_g zLiljo48vz#?&;pQI^K(9;$|6C^n?^E+KS!~eVgcOcf63+AACH4fxe&4K6e!Bd-ft> zA8?cT$%cN(*5!*T&RwmF-YsD1;>Sa|lcgUgjc{Zk7#ppV@iRQht+s49?mBM{GPj_B zzFST`X?w^^d6{P<pzZ9l7{MbSvkY%ogvR16c$h4&UnVY<h8(>9;v#sM!k@J<l>8NW z!q#%mm5C^Cn+C<|PSSklm72*YmHNQwtmDQU{EDAm$__C%!^5}AQ%JFXMsvE9{=PBB zbfS@-nYh0fQnOk7V8vqSoCPMLRQ5WsH_vWhmV25eLDI$)ft_wa?v7_5(ksFBY}B!A zi3>r?v802%;6xs;4~iAcu>jzx5?zqzArxY{$0-Z9(1QI^yGk>sCn40&AklhGJgZFr zjKnEz@;XoKBQ$UDIm6}oo$Ly%MCp_761lG=xL};6F?{e>+`<uO<>D&t>|*yKKLf1N za<$Wop8AWd;(#;Cd~UI@fcE%@9=T@$78$72c=9T}EE?xfKNGCu$d4tenuwE>Jl6s& zk*0Odd{q-)yT6@c=Bs5F*v6L|XUi8G646MMV#%b@Yi28$W!-#HE^8a9a+K}<c5<D^ zokNG)JX#k#_9y^|G;OQ%Z$FUv&IjtS`t*@C8Wmc7rG>wLfUo=XN6THipf9PZr(V&w zM<Bwz)6fv!_D9N4w)vmO+J0-koms-lUAx=b+Wdf4&sdZcKHl>K*&J?eL+68U#;v<k zJZ<_ayWfn@7ugFZK-@*Cbyhvq&-z2_XHOL8p1@f-B7G(=oAp}#C-N|l>;!6*RNR2? z(>;%U{k(&c0WB%l<N{~EU(W9C?n%4i2u@@fi+zd2#^ZPu&VEBgvwF;l>d&%Pg)wcy z?B$wO0d~BPME0}V68n(rD~p-T&Prq}S3aAsrGIYCu5z<AO|&7_jU6M=2L-;<*3g7Q z_wjo#v;)qP0?{DH?8pen7;QSkfa(4cHdEUgeO#p|RpCe7jNpbu|5P2FN$T2DeY_Cz zI`m_!%kRO^^0%|of8&0|^0#=3e-M2CTfd=yIqq=qF!6xm8vfzD^EYoId1F^+pr`H| zdl*E$eY^h6w+G~-^JmP(zx;Dp|9_$f((kgWF@uS~rDcv5o~PMVY{pMwe}@K>QHKuy z?{@GvuI@hx!rWwRf4B+(B>qMa{yWu{jfafwe<BEj@)NO>ff&Vqa)tjg3drL~!j1*B zo&ODo802FHn*Dc|tbZdB|Dm3Y<<FEwWUT*Fn8^Q^+X3;3IseHg{=eDS|D4~5h4tU4 z<9}}Kul@fcawI1wh#C&Gbv1VeNZ1;?nv0o(yzb2Zmf>hXPuFEdqR#)e+DM(QQK>R~ z3_B&E_-^USqMp#wuJDFD12#PP9Z^CKM4(2ZVL)54Wj8)rB3#5oa(Qr%5feYZp`oP} zXM0-hk9JMli>FiUFq(`@Z8{6u$(`uSA12R7DTY66;eQ8Y*J|zmI9YGdnN%AwA%{-X ztdFaH!TuHJl{Kl7JP;8d7Xx0Yoq4>9a`|vI&xOmHVeXXm&f49JU-nvy`CZshYo<Pv zWtZ5ifUH{sP@s}?;L4VXJ<6Y9VedzEN<hHxo0<HNG}2@2g7El!NzBPe2cD`;QAKUX zW%3<@tuhH(G&E_t<`|&}UKhEx&E-{V*_!m|Y_!=dPA$(<`uyw9!>5W@>^NyzZSG%V zZ3Rth6;78|uA05-Gk2D|TBXt!b&8zw*zo2X9>3^7?NHT@CE84_P2B%BUgb-83Cttm zdVydtX1yiowU029)KbM6P40dsEy5QpuEV+*EJd<iN_Nx4E}w^-H=?1_=gQznLRe`j zbI_nlI?q|NZq&xo2WHTmtl4nkW0tcDg#SF`%RL*9A^zFN8+GA}v?Y+@59G8Ik-SYt zHoSa=`(oBbFq1h#5c1+m0C`~QkxT{n<X^O-XI32OAK#O34ECS`=33~9)ILc`wuBwF z76?IW#Ot{)jAa0$6ik39{=O9ja_*8H1IG0tLTGW&(q&ldSCiK2gNu@>LsMBjbg$$o z>J^+bjf)wAGb9P}mkdhhR&JQKgb1Wu#5L6~T}W-E5cquX&a><jLd1kw?7y!jm6s<e z0@N&Fe`QNpa*21WR=mpj@h1@{2%WQ8DeB9Lbr=oh_|ttMKgeyCGu$twj)1yMq7mi? zL+1=Au0GG;2|#F=%<$LKeY_6%Wi)4_`W}$8l;mCxmJr4l`B=2fkuRyAZkL7BKP;OW zEEX^=`VL`-2hI%%gGLo!1ig)|$^b@uT(~4YK}h*Hl^$Q{5^izJ3o824baA&Q1V7q} zB+E~=pK$wPa6CJl7wdL-U~90<_sB|1_sBzF9<037;ER@1B#w>XXUYQSW^|&PBDZ2) zQ$N)iH12`E22?v(dKc?1V4MD4lUrWD;@VMOAUcDOH{qq5?^(0@%-rRAJ0^4ZR|&(k zsfRZGS(C`ma@)Tf9xz7{iKS*k-KzA<XoVfMLF{oFMpGD?*KVtReuX>9mVDtp@q18& z^tn<isW_;u(x4w}RbE5Q<!~g(IzC`~p(R`?ExDYJoUF4R5pM?4v6I458L?VYO9xJt z=R$p-lj>GZ4`5Bog@PlKk4TXQQxr~B+~G$gsZV^wU?$<nQh~^ld})EY`;{$q87CIu zD~55C^qi6yGt8Iqj!*#i)~rtp)=kZbCv8NDGpBvR;9#O2s{e`ojBj)YD-ThTGkMQi zj+-5C@ky-rlJ8+Cq|dM$7z~N0Bw#_px%i5_l{!{0-Mi)D^qy#>zwM|*ZwF+!t-v#p z0ssce*W*;Gd)i>ppV|y=b(pbUx}4d@n5YzwX$f(Itvx5W_U@z-x0Y!6Fk#7i-tEgh zmm?5JyoO1?w--ViNpS~88h@;ZkaQWHWU_FEhoa^>KX@1Cux<v57D5*CLB!4uBW{+s zU|SB4@_8ZKNZ(8Id3~jFLJ=i!uZXh#%}$ifb<a)o{Nwe|;&sMxx0YnOTXDzvs+VSK z#&Q5HWvBqEI01JX<8TvjUCqH)s6o?XUbZ8qJ$cmm&6^;3GQ)=``DOQ2U=++%GXR5i z;5vuL>oOtdu-+6--VceLCIMFjbF1(-mR0(|`;F#4Cs!Oc3(6m(^?G6hNy9?Y%*wB# zVQnw^_}4w$78G%3ry9*_@|Nfr2`%RHN=~)Fbhb1JbD!qY6m>t*`6m1JBddaaxkD=# z#i8$XZD2%O9OMrz3~eR`^ZMG&^Omx@f*cP?DnZ;zeqPjCzaI5}s2$f)uNohhwHL-e zY(+~}PdWtzvbc;IWo*A}al|H%hDTKZX3LzhF9SM@($BlR6hoRG`U+~2#)RJmdQdff zE60%<q)CJkg=iQMZ!rJq)-Zs~-@cNUzphCr$}q<m2`#f&N!S-pk?*ZK8Aj>^L6Ads zrozEez^)!u#+Z`4KB_BQ(Ha9Kmy0MZTExIve!3i~*OgMfc8_W6ru)I8Gc4lS>POY{ zv-`a-ZxJdF66&^)GZ_wgJ<$b171Jg*a2AbNdwJb3_l1^$NtGB#m~3W1ifo*7(pTDo zs^`ww5}tInQyQmVd*{0LvL|<3QfLe=znB4I5&^A}MqM-0UT3geZRe({xkw$KF^-AH zi7P}6i=z`(ri0}c)@nH58C*a|{n<gSwlc-M-kUi-=v>xC@2XWNu|^q~m)CqL@2lYB z9)l6o;Q<SH&e{sTL4c$pP8@;oTh*CBaa%pXwMu{Jfx9Un<n8l8W#*2LFOHR;Yw7s? zP!y<82GdD?w{OaRhDYZW83prxgMDD;j9MdoUM5S7RIoFb;8TEVPMFktqX%z?#AzO& z)o!+VLG;VbzPy}zN(1@(jJ2jEj!GSG&IW+lsZjK1)JVy~HDYM1E=C)Iyn|0SXze77 zqRq|$hA{8t>$FNp3Kh9UYl?UFTk5u(IU)n&Q9FajPuX3vQV9zlovDSDX-u7Ahzq?{ zCJJmW^(9R!NAfrZ`VXKgmglIjy+3a)4irgb1k~FhQ5E$5G}?)0K=#tD(5R6)*hbN* z!IOee112P2Kn=4~A|pb7sAXx5{ABX!^a+o|;MD6k-b1J)g?n6N(JHNMF}5-<9?Zwq zbrv*!mO^64jV}s}$LJbS>=t|;jJV}_jgR($4t%vSs^er3pUR=g9P*boqlFW1@wnBJ zB@XepogI^}c9NLkDUonW43f+Te|Mw43~segd%?O}$97^QY`J?9(qQ^4ZKTCn#ML+& zQZ$&=tuR&)Z!j`lr&``CuOtoVmGro8eJ_B>HEJ3K%c21?+#9hpgr!}h<$(FzF(y^m z-^?%KThZ^VE#cQ_cZCbtIKiJ)oy>YuYU07lrnQp<!6RW!8?z;bSim(s((^kz&2dt7 zo44G#K!8^T&G>0KxFeUBd)xXre><Bl&%Z^!VdzH4pX6*~ULiO7_Jh8Lj7RGXRl@u; zyel;~@qrP0RE)>-BezQ?N!u5WIhIrXAUSE3nIO7hqfgDq>i9Y_UFkHeZA+ZpRTdpU znawPm)wXxiIB;v>6z%Y{$svC6nzksluLllxbDmHKMExo6NpHdzq`@YBfUcfMGtS?n zAJ${tR2R%uD*_oyfAM{V;$se%@7e#<Y|A~T@;IrktNn_q=1L*ruAh5MBYqxD&UKMw zMu>1+NB@LoNmFvi-)w-&$$-=C9Z)eu%<HbT(=(f;it<Ta&5NSPv7pF3dE(s%xqN|z z>3(B(vuLBJxr@%yXxqHNJRCkuIA7<<`XHftJx{K9jn4czN;IVfg^)BfU}Bskp4@T7 zATq2C^JfIDh{o5)*-5n}_xZAizKqP?Ghzz;{#<PmXLSAy?@wE1>9-SJ^3{U_Qf%?= z6)pXGC=y0DMldT4zrI!4+sj+SjzGa3X)om>3t#v?`V^N0YZ3ogn3NI@f(3YP=whPl zX;b7ttX2~0vVZDr?y5^2tuCF|{kVCrIc$2#LBI*7qFY(64qiK}ofpG{*~{!;=8s7$ z$Z${qCw5fTP#S`q=#BETqq7bOnNao7IyKQfi9SG69m8h+7}r%{=!V3Xdic?$Hir01 z403qSPXA?DqwcIa&<$-ep4%7_adj>Km?h;yZ97$CRB?{=Ppdw}yrCA(sgLX&3n;7S zIMWa!X>f8>YWcms>gzaoVE$E^OX|-9J#{G8U^JZY+7sR+VVpt!+HMW<%rmLxUSPRM zL^P1(nS-CuI`bBdo5*)qMiV|$D&(*_wnQ`ael&l11m$E5NouI2u8yH~GaRJQODtBS zXU2YQ4%++)zEOZqTY=obZc8%RebTk=J~uKJI{u)n_c(3UruiE(MS0s!Y{QtyK+lhL zZh%|V%%_%#IjjC1k82~wSk%3(`cxp@h3qA)r}byr+~bIK>~orkQM1el*We~7*9FaZ z{6~&-PU$q1n-AbZL2BBijXR-dtL~?Kr8aHi#~7z(@0e+R+HlrI-7v5Au$~nLhT>?r z2Zy_)bT8y=GJi9j_He89wkmfQV|IM4*MijTU;}d`mN$X%eh#w6++gy5xx<l_wkmAA zr}{h&Zk;mG(@U%-%FGp7=ceohACMs2L=P=YH~CfDP@7E=-hxmVlQg~1beQR<72~?q zgj0$Sk~TF~Y7jp6MeTQ=s)5z3Q_+Cwq1O-_UOreyd|z5TF{g_$V<q6kWq=R%aWrt& zNRyRR51@NFK9D}SZfo$2_|mHA!nM6h=q$K{`h}7~q$utF3-MP^)1wZEj<fhd8Sxpj zm-+*FH^0Fib?C4Z*6%H{)Lt|EtN7oAO*7c0l{@qe@eM=^Yqu{74A9D(zsk2O1w)fG z!MD5@M@8;gh%LxgHjtxRqZLfE!n_;q--FIIX|)?0G;NJD?bBrl)YF>Ece3q%k2b`2 zTG{Pa4fXRQ{3bE#aDRupRzW_J&!UaGfyb7U1|)S3FDm%qge;QY7h`_2HSARI*-WiR zqu=7#vhid2?S-B}y3s3$(Fwn_l5LoG^-b&VrL7BL1X@o+^2oun#_e(CWp|)N_z&LM zT&`C5S6GSAE}N0B1qGtP&LGo<tHqOM+kusQ8aVM7W^>pli{Pc2w^V4^Cl6Y2XFmle zNz4?2d3Nq^8Vb}J>3>&Bb#+R>EmmrOShvhdZeZHerXDL3+RDoDs{m(Up?f2<N}W+W z)Yu-pYi(wip|>TA;PeLhIL5ELcft<;n$qZTa$hEArMVL_1x6M3<C9~>n(JUp&-PUD zccYl<sG`E^#7{jWC849!o89ZtyUWsucql(F2!XkIPJ>=`QTUPV?R8eRP)o2V;7X55 zqV4w5W-4M`VN5Y@@oTlla?5cL98`HYuTc>1rMWme@W^?Zj}RN@7W|oAdR^d;Fe$B8 z10D~X?UYHmGY{z42tr#WiuNfcA;3Rs_64tsJ9wSBLmV^zWXH+gY2M*$g#?~4Glo@T z6`h;s%cWLz7k}$6rL8|mlu17IUo)cnqF2w5(!o(!49|fS#@QHgA?!cq6!f{YnP<i< z)<;@iIK?zIu*#g|`r9JjYd09RcU))cJ$)YO*haSexFTg96GynWgXDx{(G<(ZeHi<J zqD4z{(bJCBi)O#hK{3o}eGRS@C!1L0LH_+?bFDI_<HgllNvK_i2N4pghWwy(a>&Vx zQK-L7_-mNvOI0==Zlho=Ms4D=a|P2DPH;&M)}%+b#8+^u+(bcNM+2yvCB3-QTm{n} zZ?{L|xH_(#!xcV-F#6R}gn=~v1&+4Zy+nikv9ELciJScda*irVPJSOH2E6OVwTEQ# z{7cMEc>L1w<^p)@gmyR1U@mTPIPX$MQcN?(O$NNP&fkF-<=Ucn|H#;LHx@&&Mtq!~ zi4vEiR#J7L1#fp}8huALMY7*nT0B?cJ*5ap`6ZEzj8MQ;8=NNVQW9kD5}F4qJ?8Mm zvbXTvK^GLBIz;2}>8XqScdbHxx^=IT7QO^H<d9Y-?;!`$!Ow(d(J^TuRXGzZqfrF@ zvm1xqsG55mY1*`Tc6K}wq!nL&PJF*E`C<EERK}bM1=_}$D}fXyEzZE?<LAM2O2KSt z$t?f;ApOBo4+9W1e}GM=6;vQu+q*UtA5Z~UOM-r{2kt1y_*jt_pJ?2zm?dP~7xpQZ zoP8y|9**6lx)NxZylBybMiEQf!yY$(_H@nNDyimpnQE;b%6-F{M8gKAuzjpUj(VWe zW7?50w^Km)Dp)M=Q<7$|yDHDG<T29_;c^^IJ!-{5)uHLV4u$3VAzmbR%*PO_G~@RK z)%0#Sgb11g+n}@nEltQ{Rm$jl^}u#dxdA`PFvV}Tn7Ie!eP2uHm`!S+B#WL+)Q+m) zJ)lg`;fSW;N*ZZjAQOzG`zfTc_rgra*sWapDIZ_A#jb)dKG4Li-r-P&4yAuiARuvN zKlDv2>}(GEO^t{+U!K>){8Ut6UH5r^TRRk2vnkXbuI$Y2llgpX*~CB-v0j%>vc9tR zypz#9EyDMHaYW*ua!3`^7YH9c+-wcCPYX@g4LTnJPKND${~KiR4@BzU0XP?csDqom zD;Xz1$=KN(RN(lNn<xu3bJ6`1vjau;WBnssA?W@`C+PkMnh3i81GM`St@~G?#6Ka< zzm)w8Rg<;@<qZFWo2l||8D;eUKKD(_Uvoh$NHaH6bLaPxN^)e9Rt_$%E~d^vM^`c) zCKhH|fGCKT=?ZkP7XyK3@73JQ$yCi9$ym8Sfb0hjmJgt1gOFKHT7bNR*&idsKD^bL zd04qvnK@XQ*%_JHX_%R5XaQ<KS6lP<|CQ{?1Y&porNP1Zy`{aIqpgE089NgvnWM3* zsg<p{ySXhHJMCM(pEqjhKS#U`qXh`NxmtmynSc`b7`s~0{(XCI#{2s@{FBxRP*jp9 zWBL2gH~aoI^q+~0wA9o^WhCj<fOh6Cj37>}vHgD#X+aiv3-<W05S9Ombjim0H%cw* zKO#-)vU0pZ)qnqEV`C$Gd%d;&tCs^5I`e<?`nx1%GOj-!pg-@NAo4B;2j?GcZ|#5T zcq_g2{5j^W_BMu@41|#WQF`n9hm`*+`>n<fVky6^>hJeAxo>Tt)pNZ8*Kg&w5*O(8 z?>qQouiPLU|F6D3HGoFx|C3`3B1qc{+q(e&L5%*dX`cReW`EfJ@5lRw!i$;z`Pe#u zfL>7bjbse?|2W6Leq3N=WqDgG2N@?fxBeSCZ0SM<Iy+$()3<jis1YD)>?mapw6p@% zxIjDwkcPLOw|Rg!b~Vsc*xu6CoQxTu>S}JM@dnca<bf_OAX4`qT<<p{0JQ#+z|P46 zc*`}VYVP_TAPf)#hyx@5k^m`yG(Z+02T%Yg0+awM09Ak*KpkKVFaekXOdag(i~(i< zbHE>q09b%H)9&T~3lM=BU<m*MYyh?ZJAggF9%ye4Z~!=fu8si5w+|Uxa|_o$OU`f0 z2RH$o0WJU+(EG_A-~#jnxHuY{ngd(`u2#<Gpu2|yzzyI9@HTgLp#8&oZ!~St=LqXx zKS#WM)V<mJ&xXG&|DUM(e?2hKf3TxD*f=@<1-NJ0X~HTIS}q%COSRj-lURprmC8(5 zl3sO;a#}@ZiinVB8^BYIJ|O!bQbM6DSoR(Q9AiL=eYV|cV2Rqvb_jb#nmNU}H0AT| z731~g>-Bm3=<4_rSN^or@;oREV%Dvf0ggo@m}#w+EFYzbSOnqP0E=+#*e&v$3{Imp zcw;)U(uwWw3N}lQeU(uIgyfnYjPf@1rW?+=BXh?C>=Hk0(|Rq8H7^<`JslA-EzkJp zjN;Xf4V^CkzOH~DbvBDkkDCpG)l0ll7?iyxr;9VBDq_>yEqKbdGxk0-4`&Al;Vw7E z(?F-Kh#O&Y@;tPC2Ls{hy!Vfk=+o#<PTN?ITwmAgHjXxH%qgFncErTw9G&x?eRpqY z`rA+RPu9eZho^<%UtbYIjdf86BX=@)P-%#y`O;k(?mKdJdYFJeMHIpnLfb-HB|SYQ zzDCA6E!DZL+z_8Hox867-q$5$wncWwa2Z&w;bQi%W!w*{?BlEDs#RUmO~t+8bcb6B z<S{0!olRwP$6JXe?jfqlU&1-TYeM!RU5P#I)~oTTL9M}DVjY9oF&<^Xcto^_s%7Sl zj;rj(F^Q@vT;gm)O~t0}%>t#v;9x{!nyO!{nLo*{sN;So*?SMm6=>6ayGe?E42RFs zJms?0Cmx0~4>0J^YP(CJ!j$DtcGqp!%`ou#iG59P_)X{G$YJybK;Nuww_|ht*(2N| z6?gK{@4j_!d^F%4mkQ59eQr0D-$O}Px>mh`#FcGs8{ZQPUC!B3p|kS$%qwo4a=F{l zJ=skl<yndja2i(wTN5E{O9X}8@p0jJ$MsTZ0v28+Mw5g*sPHZmA~^vq3`dF#aVUKR z9L9RUZ6S*GfN!9xXyG$e$lg76U`^t%0-~}2%Bs3^wyg+EA99Q07dg}q@SfmZ+-lVh zu>Dr1zkEdA^#(<W9F4AGnRF}S(*HI}3<TaagPX!W=NAseA!O|=f1CxP(@nYxJc~7k zEL$6M1VEQ}n)Hd#eHIIF(u#w!+T*gxMQ*r%$yNfh;|d5%M#MI8KHB$gAaI%>;YY(* zUdo`rGL(LW;v?%MO!FkmnG|8+m|14W%eT+3aQMNO_HlT&>01S>HHUU`HKci9OO9$b z`b5Z1w{D^H_&ZOVLT1FDUn>Vtcj@W6!=P3_dWdnBJ%WSdJ`?NoaBY@{b@{GPl5MF4 zI$}&Ra3^6@T_j#@t-yXyuASH#%6~);S}I26{_1pYi_VP9D$`byqK}b3Ud@JuTW9Na z-z9<rpP3l>tPKUwfoOM>3fx$x89ZA(dBAuA>x}f;_j*3?>pv1J2M;1>`KFHX)Y`-V zNmOv24W!Yfmw7BYFOby3;^Uofh9pDE5%1yM;NQVKE?PwicNwYU-10qBzalvB�?i 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�$W;Igw!{^1IDv+^Yn!e9 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 HHl_R@i@NEa literal 0 HcmV?d00001 diff --git a/doc/fusermount3.1 b/doc/fusermount3.1 new file mode 100644 index 0000000..1455742 --- /dev/null +++ b/doc/fusermount3.1 @@ -0,0 +1,44 @@ +.TH FUSERMOUNT3 1 2011\-10\-23 2.8.6 "Filesystem in Userspace (FUSE)" + +.SH NAME +\fBfusermount3\fR \- mount and unmount FUSE filesystems + +.SH SYNOPSIS +\fBfusermount3\fR [\fIOPTIONS\fR] \fIMOUNTPOINT\fR + +.SH DESCRIPTION +Filesystem in Userspace (FUSE) is a simple interface for userspace programs to export a virtual filesystem to the Linux kernel. It also aims to provide a secure method for non privileged users to create and mount their own filesystem implementations. +.PP +\fBfusermount3\fR is a program to mount and unmount FUSE +filesystems. It should be called directly only for unmounting FUSE +file systems. To allow mounting and unmounting by unprivileged users, +\fBfusermount3\fR needs to be installed set-uid root. +.SH OPTIONS +.IP "\-h" 4 +print help. +.IP "\-V" 4 +print version. +.IP "-o \fIOPTION\fR[,\fIOPTION\fR...]" 4 +mount options. +.IP "-u" 4 +unmount. +.IP "-q" 4 +quiet. +.IP "-z" 4 +lazy unmount. + +.SH SEE ALSO +\fImount\fR(8), +\fImount.fuse3\fR(8), +\fIfuse\fR(4), + +.SH HOMEPAGE +More information about fusermount3 and the FUSE project can be found at <\fIhttp://fuse.sourceforge.net/\fR>. + +.SH AUTHORS +.LP +FUSE is currently maintained by Nikolaus Rath <Nikolaus@rath.org> +.LP +The original author of FUSE is Miklos Szeredi <\fImiklos@szeredi.hu\fR>. +.LP +This manual page was originally written by Daniel Baumann <\fIdaniel.baumann@progress\-technologies.net\fR>. diff --git a/doc/html/annotated.html b/doc/html/annotated.html new file mode 100644 index 0000000..4e02e63 --- /dev/null +++ b/doc/html/annotated.html @@ -0,0 +1,76 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.8"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>libfuse: Data Structures + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Data Structures
+
+ + + + + diff --git a/doc/html/bc_s.png b/doc/html/bc_s.png new file mode 100644 index 0000000000000000000000000000000000000000..224b29aa9847d5a4b3902efd602b7ddf7d33e6c2 GIT binary patch literal 676 zcmV;V0$crwP)y__>=_9%My z{n931IS})GlGUF8K#6VIbs%684A^L3@%PlP2>_sk`UWPq@f;rU*V%rPy_ekbhXT&s z(GN{DxFv}*vZp`F>S!r||M`I*nOwwKX+BC~3P5N3-)Y{65c;ywYiAh-1*hZcToLHK ztpl1xomJ+Yb}K(cfbJr2=GNOnT!UFA7Vy~fBz8?J>XHsbZoDad^8PxfSa0GDgENZS zuLCEqzb*xWX2CG*b&5IiO#NzrW*;`VC9455M`o1NBh+(k8~`XCEEoC1Ybwf;vr4K3 zg|EB<07?SOqHp9DhLpS&bzgo70I+ghB_#)K7H%AMU3v}xuyQq9&Bm~++VYhF09a+U zl7>n7Jjm$K#b*FONz~fj;I->Bf;ule1prFN9FovcDGBkpg>)O*-}eLnC{6oZHZ$o% zXKW$;0_{8hxHQ>l;_*HATI(`7t#^{$(zLe}h*mqwOc*nRY9=?Sx4OOeVIfI|0V(V2 zBrW#G7Ss9wvzr@>H*`r>zE z+e8bOBgqIgldUJlG(YUDviMB`9+DH8n-s9SXRLyJHO1!=wY^79WYZMTa(wiZ!zP66 zA~!21vmF3H2{ngD;+`6j#~6j;$*f*G_2ZD1E;9(yaw7d-QnSCpK(cR1zU3qU0000< KMNUMnLSTYoA~SLT literal 0 HcmV?d00001 diff --git a/doc/html/bc_sd.png b/doc/html/bc_sd.png new file mode 100644 index 0000000000000000000000000000000000000000..31ca888dc71049713b35c351933a8d0f36180bf1 GIT binary patch literal 635 zcmV->0)+jEP)Jwi0r1~gdSq#w{Bu1q z`craw(p2!hu$4C_$Oc3X(sI6e=9QSTwPt{G) z=htT&^~&c~L2~e{r5_5SYe7#Is-$ln>~Kd%$F#tC65?{LvQ}8O`A~RBB0N~`2M+waajO;5>3B&-viHGJeEK2TQOiPRa zfDKyqwMc4wfaEh4jt>H`nW_Zidwk@Bowp`}(VUaj-pSI(-1L>FJVsX}Yl9~JsqgsZ zUD9(rMwf23Gez6KPa|wwInZodP-2}9@fK0Ga_9{8SOjU&4l`pH4@qlQp83>>HT$xW zER^U>)MyV%t(Lu=`d=Y?{k1@}&r7ZGkFQ%z%N+sE9BtYjovzxyxCPxN6&@wLK{soQ zSmkj$aLI}miuE^p@~4}mg9OjDfGEkgY4~^XzLRUBB*O{+&vq<3v(E%+k_i%=`~j%{ Vj14gnt9}3g002ovPDHLkV1n!oC4m3{ literal 0 HcmV?d00001 diff --git a/doc/html/classes.html b/doc/html/classes.html new file mode 100644 index 0000000..72b04ed --- /dev/null +++ b/doc/html/classes.html @@ -0,0 +1,60 @@ + + + + + + + +libfuse: Data Structure Index + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Data Structure Index
+
+ + + + + diff --git a/doc/html/closed.png b/doc/html/closed.png new file mode 100644 index 0000000000000000000000000000000000000000..98cc2c909da37a6df914fbf67780eebd99c597f5 GIT binary patch literal 132 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{V-kvUwAr*{o@8{^CZMh(5KoB^r_<4^zF@3)Cp&&t3hdujKf f*?bjBoY!V+E))@{xMcbjXe@)LtDnm{r-UW|*e5JT literal 0 HcmV?d00001 diff --git a/doc/html/dir_042d006301a0f6339943f03c22d70dab.html b/doc/html/dir_042d006301a0f6339943f03c22d70dab.html new file mode 100644 index 0000000..9701569 --- /dev/null +++ b/doc/html/dir_042d006301a0f6339943f03c22d70dab.html @@ -0,0 +1,55 @@ + + + + + + + +libfuse: fuse-3.18.2/doc Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
doc Directory Reference
+
+
+
+ + + + diff --git a/doc/html/dir_13e138d54eb8818da29c3992edef070a.html b/doc/html/dir_13e138d54eb8818da29c3992edef070a.html new file mode 100644 index 0000000..bc2dacf --- /dev/null +++ b/doc/html/dir_13e138d54eb8818da29c3992edef070a.html @@ -0,0 +1,81 @@ + + + + + + + +libfuse: test Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
test Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +

+Files

 hello.c
 
 readdir_inode.c
 
 release_unlink_race.c
 
 stracedecode.c
 
 test_abi.c
 
 test_setattr.c
 
 test_signals.c
 
 test_syscalls.c
 
 test_want_conversion.c
 
 test_write_cache.c
 
 wrong_command.c
 
+
+ + + + diff --git a/doc/html/dir_151531ba243f27a508f84a018a5044f9.html b/doc/html/dir_151531ba243f27a508f84a018a5044f9.html new file mode 100644 index 0000000..b7cd404 --- /dev/null +++ b/doc/html/dir_151531ba243f27a508f84a018a5044f9.html @@ -0,0 +1,102 @@ + + + + + + + +libfuse: fuse-3.17.4/lib Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
lib Directory Reference
+
+
+ + + + +

+Directories

 modules
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Files

 buffer.c
 
 compat.c
 
 cuse_lowlevel.c
 
 fuse.c
 
 fuse_i.h
 
 fuse_log.c
 
 fuse_loop.c
 
 fuse_loop_mt.c
 
 fuse_lowlevel.c
 
 fuse_misc.h
 
 fuse_opt.c
 
 fuse_signals.c
 
 helper.c
 
 mount.c
 
 mount_bsd.c
 
 mount_util.c
 
 mount_util.h
 
 util.c
 
 util.h
 
+
+ + + + diff --git a/doc/html/dir_2379d157e40387655d5d09cb33050082.html b/doc/html/dir_2379d157e40387655d5d09cb33050082.html new file mode 100644 index 0000000..534d5af --- /dev/null +++ b/doc/html/dir_2379d157e40387655d5d09cb33050082.html @@ -0,0 +1,69 @@ + + + + + + + +libfuse: fuse-3.18.2 Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
fuse-3.18.2 Directory Reference
+
+
+ + + + + + + + + + + + +

+Directories

 example
 
 include
 
 lib
 
 test
 
 util
 
+
+ + + + diff --git a/doc/html/dir_23ec12649285f9fabf3a6b7380226c28.html b/doc/html/dir_23ec12649285f9fabf3a6b7380226c28.html new file mode 100644 index 0000000..da5da2d --- /dev/null +++ b/doc/html/dir_23ec12649285f9fabf3a6b7380226c28.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: util Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
util Directory Reference
+
+
+ + + + + + +

+Files

 fusermount.c
 
 mount.fuse.c
 
+
+ + + + diff --git a/doc/html/dir_3141c10b43d1ded6ee3c902c2fe1c48b.html b/doc/html/dir_3141c10b43d1ded6ee3c902c2fe1c48b.html new file mode 100644 index 0000000..f2e5a49 --- /dev/null +++ b/doc/html/dir_3141c10b43d1ded6ee3c902c2fe1c48b.html @@ -0,0 +1,81 @@ + + + + + + + +libfuse: fuse-3.18.1/test Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
test Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +

+Files

 hello.c
 
 readdir_inode.c
 
 release_unlink_race.c
 
 stracedecode.c
 
 test_abi.c
 
 test_setattr.c
 
 test_signals.c
 
 test_syscalls.c
 
 test_want_conversion.c
 
 test_write_cache.c
 
 wrong_command.c
 
+
+ + + + diff --git a/doc/html/dir_4d35850f380bf2b0e1e23612de44a403.html b/doc/html/dir_4d35850f380bf2b0e1e23612de44a403.html new file mode 100644 index 0000000..e9dfbe7 --- /dev/null +++ b/doc/html/dir_4d35850f380bf2b0e1e23612de44a403.html @@ -0,0 +1,69 @@ + + + + + + + +libfuse: fuse-3.18.1 Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
fuse-3.18.1 Directory Reference
+
+
+ + + + + + + + + + + + +

+Directories

 example
 
 include
 
 lib
 
 test
 
 util
 
+
+ + + + diff --git a/doc/html/dir_567612448af13c8fdbe428427cb913c5.html b/doc/html/dir_567612448af13c8fdbe428427cb913c5.html new file mode 100644 index 0000000..d77f6af --- /dev/null +++ b/doc/html/dir_567612448af13c8fdbe428427cb913c5.html @@ -0,0 +1,75 @@ + + + + + + + +libfuse: fuse-3.18.1/include Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
include Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + +

+Files

 cuse_lowlevel.h
 
 fuse.h
 
 fuse_common.h
 
 fuse_kernel.h
 
 fuse_log.h
 
 fuse_lowlevel.h
 
 fuse_mount_compat.h
 
 fuse_opt.h
 
+
+ + + + diff --git a/doc/html/dir_5cbcf3cc0c552e5807bf52f5541bdba8.html b/doc/html/dir_5cbcf3cc0c552e5807bf52f5541bdba8.html new file mode 100644 index 0000000..c04892e --- /dev/null +++ b/doc/html/dir_5cbcf3cc0c552e5807bf52f5541bdba8.html @@ -0,0 +1,75 @@ + + + + + + + +libfuse: fuse-3.18.2/include Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
include Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + +

+Files

 cuse_lowlevel.h
 
 fuse.h
 
 fuse_common.h
 
 fuse_kernel.h
 
 fuse_log.h
 
 fuse_lowlevel.h
 
 fuse_mount_compat.h
 
 fuse_opt.h
 
+
+ + + + diff --git a/doc/html/dir_5dc95f8183277f5c6f5c244c43efe208.html b/doc/html/dir_5dc95f8183277f5c6f5c244c43efe208.html new file mode 100644 index 0000000..d63f75f --- /dev/null +++ b/doc/html/dir_5dc95f8183277f5c6f5c244c43efe208.html @@ -0,0 +1,99 @@ + + + + + + + +libfuse: fuse-3.18.2/example Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
example Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Files

 cuse.c
 
 cuse_client.c
 
 hello.c
 
 hello_ll.c
 
 hello_ll_uds.c
 
 invalidate_path.c
 
 ioctl.c
 
 ioctl.h
 
 ioctl_client.c
 
 notify_inval_entry.c
 
 notify_inval_inode.c
 
 notify_store_retrieve.c
 
 null.c
 
 passthrough.c
 
 passthrough_fh.c
 
 passthrough_helpers.h
 
 passthrough_ll.c
 
 poll.c
 
 poll_client.c
 
 printcap.c
 
+
+ + + + diff --git a/doc/html/dir_5fb9f92dc673d37c57217e3feec2be83.html b/doc/html/dir_5fb9f92dc673d37c57217e3feec2be83.html new file mode 100644 index 0000000..039e2bb --- /dev/null +++ b/doc/html/dir_5fb9f92dc673d37c57217e3feec2be83.html @@ -0,0 +1,108 @@ + + + + + + + +libfuse: fuse-3.18.2/lib Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
lib Directory Reference
+
+
+ + + + +

+Directories

 modules
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Files

 buffer.c
 
 compat.c
 
 cuse_lowlevel.c
 
 fuse.c
 
 fuse_i.h
 
 fuse_log.c
 
 fuse_loop.c
 
 fuse_loop_mt.c
 
 fuse_lowlevel.c
 
 fuse_misc.h
 
 fuse_opt.c
 
 fuse_signals.c
 
 fuse_uring.c
 
 fuse_uring_i.h
 
 helper.c
 
 mount.c
 
 mount_bsd.c
 
 mount_util.c
 
 mount_util.h
 
 usdt.h
 
 util.c
 
 util.h
 
+
+ + + + diff --git a/doc/html/dir_7015ddbf9bcaeb01169723f1ad3dc4f8.html b/doc/html/dir_7015ddbf9bcaeb01169723f1ad3dc4f8.html new file mode 100644 index 0000000..884a58a --- /dev/null +++ b/doc/html/dir_7015ddbf9bcaeb01169723f1ad3dc4f8.html @@ -0,0 +1,81 @@ + + + + + + + +libfuse: fuse-3.18.2/test Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
test Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +

+Files

 hello.c
 
 readdir_inode.c
 
 release_unlink_race.c
 
 stracedecode.c
 
 test_abi.c
 
 test_setattr.c
 
 test_signals.c
 
 test_syscalls.c
 
 test_want_conversion.c
 
 test_write_cache.c
 
 wrong_command.c
 
+
+ + + + diff --git a/doc/html/dir_73f848c5a0a9108732ea2e7217c7aea5.html b/doc/html/dir_73f848c5a0a9108732ea2e7217c7aea5.html new file mode 100644 index 0000000..f14850d --- /dev/null +++ b/doc/html/dir_73f848c5a0a9108732ea2e7217c7aea5.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: fuse-3.17.4/util Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
util Directory Reference
+
+
+ + + + + + +

+Files

 fusermount.c
 
 mount.fuse.c
 
+
+ + + + diff --git a/doc/html/dir_97aefd0d527b934f1d99a682da8fe6a9.html b/doc/html/dir_97aefd0d527b934f1d99a682da8fe6a9.html new file mode 100644 index 0000000..6e863ef --- /dev/null +++ b/doc/html/dir_97aefd0d527b934f1d99a682da8fe6a9.html @@ -0,0 +1,108 @@ + + + + + + + +libfuse: lib Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
lib Directory Reference
+
+
+ + + + +

+Directories

 modules
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Files

 buffer.c
 
 compat.c
 
 cuse_lowlevel.c
 
 fuse.c
 
 fuse_i.h
 
 fuse_log.c
 
 fuse_loop.c
 
 fuse_loop_mt.c
 
 fuse_lowlevel.c
 
 fuse_misc.h
 
 fuse_opt.c
 
 fuse_signals.c
 
 fuse_uring.c
 
 fuse_uring_i.h
 
 helper.c
 
 mount.c
 
 mount_bsd.c
 
 mount_util.c
 
 mount_util.h
 
 usdt.h
 
 util.c
 
 util.h
 
+
+ + + + diff --git a/doc/html/dir_98164a96c6a79e43c7a77c344d27e55d.html b/doc/html/dir_98164a96c6a79e43c7a77c344d27e55d.html new file mode 100644 index 0000000..6fe6fa9 --- /dev/null +++ b/doc/html/dir_98164a96c6a79e43c7a77c344d27e55d.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/modules Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
modules Directory Reference
+
+
+ + + + + + +

+Files

 iconv.c
 
 subdir.c
 
+
+ + + + diff --git a/doc/html/dir_a1e8bb6aacb6fb222eb29390226a8aec.html b/doc/html/dir_a1e8bb6aacb6fb222eb29390226a8aec.html new file mode 100644 index 0000000..75ffdfe --- /dev/null +++ b/doc/html/dir_a1e8bb6aacb6fb222eb29390226a8aec.html @@ -0,0 +1,69 @@ + + + + + + + +libfuse: fuse-3.17.4 Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
fuse-3.17.4 Directory Reference
+
+
+ + + + + + + + + + + + +

+Directories

 example
 
 include
 
 lib
 
 test
 
 util
 
+
+ + + + diff --git a/doc/html/dir_aa785a52b370a0eb18aa670afa0403e9.html b/doc/html/dir_aa785a52b370a0eb18aa670afa0403e9.html new file mode 100644 index 0000000..0666126 --- /dev/null +++ b/doc/html/dir_aa785a52b370a0eb18aa670afa0403e9.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: fuse-3.18.1/util Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
util Directory Reference
+
+
+ + + + + + +

+Files

 fusermount.c
 
 mount.fuse.c
 
+
+ + + + diff --git a/doc/html/dir_bea620c7bc056d32632a5444abf88a37.html b/doc/html/dir_bea620c7bc056d32632a5444abf88a37.html new file mode 100644 index 0000000..6464f7b --- /dev/null +++ b/doc/html/dir_bea620c7bc056d32632a5444abf88a37.html @@ -0,0 +1,108 @@ + + + + + + + +libfuse: fuse-3.18.1/lib Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
lib Directory Reference
+
+
+ + + + +

+Directories

 modules
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Files

 buffer.c
 
 compat.c
 
 cuse_lowlevel.c
 
 fuse.c
 
 fuse_i.h
 
 fuse_log.c
 
 fuse_loop.c
 
 fuse_loop_mt.c
 
 fuse_lowlevel.c
 
 fuse_misc.h
 
 fuse_opt.c
 
 fuse_signals.c
 
 fuse_uring.c
 
 fuse_uring_i.h
 
 helper.c
 
 mount.c
 
 mount_bsd.c
 
 mount_util.c
 
 mount_util.h
 
 usdt.h
 
 util.c
 
 util.h
 
+
+ + + + diff --git a/doc/html/dir_cfafba98a580ce4b62f8a6fa96d7cbb0.html b/doc/html/dir_cfafba98a580ce4b62f8a6fa96d7cbb0.html new file mode 100644 index 0000000..429e231 --- /dev/null +++ b/doc/html/dir_cfafba98a580ce4b62f8a6fa96d7cbb0.html @@ -0,0 +1,99 @@ + + + + + + + +libfuse: example Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
example Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Files

 cuse.c
 
 cuse_client.c
 
 hello.c
 
 hello_ll.c
 
 hello_ll_uds.c
 
 invalidate_path.c
 
 ioctl.c
 
 ioctl.h
 
 ioctl_client.c
 
 notify_inval_entry.c
 
 notify_inval_inode.c
 
 notify_store_retrieve.c
 
 null.c
 
 passthrough.c
 
 passthrough_fh.c
 
 passthrough_helpers.h
 
 passthrough_ll.c
 
 poll.c
 
 poll_client.c
 
 printcap.c
 
+
+ + + + diff --git a/doc/html/dir_d44c64559bbebec7f509842c48db8b23.html b/doc/html/dir_d44c64559bbebec7f509842c48db8b23.html new file mode 100644 index 0000000..65716f7 --- /dev/null +++ b/doc/html/dir_d44c64559bbebec7f509842c48db8b23.html @@ -0,0 +1,75 @@ + + + + + + + +libfuse: include Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
include Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + +

+Files

 cuse_lowlevel.h
 
 fuse.h
 
 fuse_common.h
 
 fuse_kernel.h
 
 fuse_log.h
 
 fuse_lowlevel.h
 
 fuse_mount_compat.h
 
 fuse_opt.h
 
+
+ + + + diff --git a/doc/html/dir_de6a3d84c7bdc16a0560151553ce19f4.html b/doc/html/dir_de6a3d84c7bdc16a0560151553ce19f4.html new file mode 100644 index 0000000..09bb063 --- /dev/null +++ b/doc/html/dir_de6a3d84c7bdc16a0560151553ce19f4.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: fuse-3.18.2/util Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
util Directory Reference
+
+
+ + + + + + +

+Files

 fusermount.c
 
 mount.fuse.c
 
+
+ + + + diff --git a/doc/html/dir_de8f9914a0c6d3fefb7eee74461f97ba.html b/doc/html/dir_de8f9914a0c6d3fefb7eee74461f97ba.html new file mode 100644 index 0000000..08b7654 --- /dev/null +++ b/doc/html/dir_de8f9914a0c6d3fefb7eee74461f97ba.html @@ -0,0 +1,99 @@ + + + + + + + +libfuse: fuse-3.18.1/example Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
example Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Files

 cuse.c
 
 cuse_client.c
 
 hello.c
 
 hello_ll.c
 
 hello_ll_uds.c
 
 invalidate_path.c
 
 ioctl.c
 
 ioctl.h
 
 ioctl_client.c
 
 notify_inval_entry.c
 
 notify_inval_inode.c
 
 notify_store_retrieve.c
 
 null.c
 
 passthrough.c
 
 passthrough_fh.c
 
 passthrough_helpers.h
 
 passthrough_ll.c
 
 poll.c
 
 poll_client.c
 
 printcap.c
 
+
+ + + + diff --git a/doc/html/dir_e1dbc8ba94a86723d4c32227b7c46099.html b/doc/html/dir_e1dbc8ba94a86723d4c32227b7c46099.html new file mode 100644 index 0000000..73483a3 --- /dev/null +++ b/doc/html/dir_e1dbc8ba94a86723d4c32227b7c46099.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: lib/modules Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
modules Directory Reference
+
+
+ + + + + + +

+Files

 iconv.c
 
 subdir.c
 
+
+ + + + diff --git a/doc/html/dir_e68e8157741866f444e17edd764ebbae.html b/doc/html/dir_e68e8157741866f444e17edd764ebbae.html new file mode 100644 index 0000000..6ab75b5 --- /dev/null +++ b/doc/html/dir_e68e8157741866f444e17edd764ebbae.html @@ -0,0 +1,55 @@ + + + + + + + +libfuse: doc Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
doc Directory Reference
+
+
+
+ + + + diff --git a/doc/html/dir_e7cc7929bd1ffaa820e10e01d9c95abd.html b/doc/html/dir_e7cc7929bd1ffaa820e10e01d9c95abd.html new file mode 100644 index 0000000..a321111 --- /dev/null +++ b/doc/html/dir_e7cc7929bd1ffaa820e10e01d9c95abd.html @@ -0,0 +1,77 @@ + + + + + + + +libfuse: fuse-3.17.4/example Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
example Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + + + +

+Files

 notify_store_retrieve.c
 
 null.c
 
 passthrough.c
 
 passthrough_fh.c
 
 passthrough_helpers.h
 
 passthrough_ll.c
 
 poll.c
 
 poll_client.c
 
 printcap.c
 
+
+ + + + diff --git a/doc/html/dir_ee79e556e893a265551a6396eb768ff4.html b/doc/html/dir_ee79e556e893a265551a6396eb768ff4.html new file mode 100644 index 0000000..556a7f2 --- /dev/null +++ b/doc/html/dir_ee79e556e893a265551a6396eb768ff4.html @@ -0,0 +1,75 @@ + + + + + + + +libfuse: fuse-3.17.4/include Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
include Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + +

+Files

 cuse_lowlevel.h
 
 fuse.h
 
 fuse_common.h
 
 fuse_kernel.h
 
 fuse_log.h
 
 fuse_lowlevel.h
 
 fuse_mount_compat.h
 
 fuse_opt.h
 
+
+ + + + diff --git a/doc/html/dir_ef89b861fb26f7108388f05c496b561a.html b/doc/html/dir_ef89b861fb26f7108388f05c496b561a.html new file mode 100644 index 0000000..2b5ccef --- /dev/null +++ b/doc/html/dir_ef89b861fb26f7108388f05c496b561a.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/modules Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
modules Directory Reference
+
+
+ + + + + + +

+Files

 iconv.c
 
 subdir.c
 
+
+ + + + diff --git a/doc/html/dir_f353b27f473de27f9d371ee54fafa676.html b/doc/html/dir_f353b27f473de27f9d371ee54fafa676.html new file mode 100644 index 0000000..7014bd5 --- /dev/null +++ b/doc/html/dir_f353b27f473de27f9d371ee54fafa676.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/modules Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
modules Directory Reference
+
+
+ + + + + + +

+Files

 iconv.c
 
 subdir.c
 
+
+ + + + diff --git a/doc/html/dir_f77c7566d5b54b3a8f6688c1c1718be3.html b/doc/html/dir_f77c7566d5b54b3a8f6688c1c1718be3.html new file mode 100644 index 0000000..a42cb56 --- /dev/null +++ b/doc/html/dir_f77c7566d5b54b3a8f6688c1c1718be3.html @@ -0,0 +1,79 @@ + + + + + + + +libfuse: fuse-3.17.4/test Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
test Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + + + + + +

+Files

 hello.c
 
 readdir_inode.c
 
 release_unlink_race.c
 
 stracedecode.c
 
 test_abi.c
 
 test_setattr.c
 
 test_syscalls.c
 
 test_want_conversion.c
 
 test_write_cache.c
 
 wrong_command.c
 
+
+ + + + diff --git a/doc/html/dir_ff8734a6ed1553e4cdaef764f37a71c4.html b/doc/html/dir_ff8734a6ed1553e4cdaef764f37a71c4.html new file mode 100644 index 0000000..fd561e7 --- /dev/null +++ b/doc/html/dir_ff8734a6ed1553e4cdaef764f37a71c4.html @@ -0,0 +1,55 @@ + + + + + + + +libfuse: fuse-3.18.1/doc Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
doc Directory Reference
+
+
+
+ + + + diff --git a/doc/html/doc.svg b/doc/html/doc.svg new file mode 100644 index 0000000..0b928a5 --- /dev/null +++ b/doc/html/doc.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/doc/html/docd.svg b/doc/html/docd.svg new file mode 100644 index 0000000..ac18b27 --- /dev/null +++ b/doc/html/docd.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/doc/html/doxygen.css b/doc/html/doxygen.css new file mode 100644 index 0000000..009a9b5 --- /dev/null +++ b/doc/html/doxygen.css @@ -0,0 +1,2045 @@ +/* The standard CSS for doxygen 1.9.8*/ + +html { +/* page base colors */ +--page-background-color: white; +--page-foreground-color: black; +--page-link-color: #3D578C; +--page-visited-link-color: #4665A2; + +/* index */ +--index-odd-item-bg-color: #F8F9FC; +--index-even-item-bg-color: white; +--index-header-color: black; +--index-separator-color: #A0A0A0; + +/* header */ +--header-background-color: #F9FAFC; +--header-separator-color: #C4CFE5; +--header-gradient-image: url('nav_h.png'); +--group-header-separator-color: #879ECB; +--group-header-color: #354C7B; +--inherit-header-color: gray; + +--footer-foreground-color: #2A3D61; +--footer-logo-width: 104px; +--citation-label-color: #334975; +--glow-color: cyan; + +--title-background-color: white; +--title-separator-color: #5373B4; +--directory-separator-color: #9CAFD4; +--separator-color: #4A6AAA; + +--blockquote-background-color: #F7F8FB; +--blockquote-border-color: #9CAFD4; + +--scrollbar-thumb-color: #9CAFD4; +--scrollbar-background-color: #F9FAFC; + +--icon-background-color: #728DC1; +--icon-foreground-color: white; +--icon-doc-image: url('doc.svg'); +--icon-folder-open-image: url('folderopen.svg'); +--icon-folder-closed-image: url('folderclosed.svg'); + +/* brief member declaration list */ +--memdecl-background-color: #F9FAFC; +--memdecl-separator-color: #DEE4F0; +--memdecl-foreground-color: #555; +--memdecl-template-color: #4665A2; + +/* detailed member list */ +--memdef-border-color: #A8B8D9; +--memdef-title-background-color: #E2E8F2; +--memdef-title-gradient-image: url('nav_f.png'); +--memdef-proto-background-color: #DFE5F1; +--memdef-proto-text-color: #253555; +--memdef-proto-text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); +--memdef-doc-background-color: white; +--memdef-param-name-color: #602020; +--memdef-template-color: #4665A2; + +/* tables */ +--table-cell-border-color: #2D4068; +--table-header-background-color: #374F7F; +--table-header-foreground-color: #FFFFFF; + +/* labels */ +--label-background-color: #728DC1; +--label-left-top-border-color: #5373B4; +--label-right-bottom-border-color: #C4CFE5; +--label-foreground-color: white; + +/** navigation bar/tree/menu */ +--nav-background-color: #F9FAFC; +--nav-foreground-color: #364D7C; +--nav-gradient-image: url('tab_b.png'); +--nav-gradient-hover-image: url('tab_h.png'); +--nav-gradient-active-image: url('tab_a.png'); +--nav-gradient-active-image-parent: url("../tab_a.png"); +--nav-separator-image: url('tab_s.png'); +--nav-breadcrumb-image: url('bc_s.png'); +--nav-breadcrumb-border-color: #C2CDE4; +--nav-splitbar-image: url('splitbar.png'); +--nav-font-size-level1: 13px; +--nav-font-size-level2: 10px; +--nav-font-size-level3: 9px; +--nav-text-normal-color: #283A5D; +--nav-text-hover-color: white; +--nav-text-active-color: white; +--nav-text-normal-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); +--nav-text-hover-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); +--nav-text-active-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); +--nav-menu-button-color: #364D7C; +--nav-menu-background-color: white; +--nav-menu-foreground-color: #555555; +--nav-menu-toggle-color: rgba(255, 255, 255, 0.5); +--nav-arrow-color: #9CAFD4; +--nav-arrow-selected-color: #9CAFD4; + +/* table of contents */ +--toc-background-color: #F4F6FA; +--toc-border-color: #D8DFEE; +--toc-header-color: #4665A2; +--toc-down-arrow-image: url("data:image/svg+xml;utf8,&%238595;"); + +/** search field */ +--search-background-color: white; +--search-foreground-color: #909090; +--search-magnification-image: url('mag.svg'); +--search-magnification-select-image: url('mag_sel.svg'); +--search-active-color: black; +--search-filter-background-color: #F9FAFC; +--search-filter-foreground-color: black; +--search-filter-border-color: #90A5CE; +--search-filter-highlight-text-color: white; +--search-filter-highlight-bg-color: #3D578C; +--search-results-foreground-color: #425E97; +--search-results-background-color: #EEF1F7; +--search-results-border-color: black; +--search-box-shadow: inset 0.5px 0.5px 3px 0px #555; + +/** code fragments */ +--code-keyword-color: #008000; +--code-type-keyword-color: #604020; +--code-flow-keyword-color: #E08000; +--code-comment-color: #800000; +--code-preprocessor-color: #806020; +--code-string-literal-color: #002080; +--code-char-literal-color: #008080; +--code-xml-cdata-color: black; +--code-vhdl-digit-color: #FF00FF; +--code-vhdl-char-color: #000000; +--code-vhdl-keyword-color: #700070; +--code-vhdl-logic-color: #FF0000; +--code-link-color: #4665A2; +--code-external-link-color: #4665A2; +--fragment-foreground-color: black; +--fragment-background-color: #FBFCFD; +--fragment-border-color: #C4CFE5; +--fragment-lineno-border-color: #00FF00; +--fragment-lineno-background-color: #E8E8E8; +--fragment-lineno-foreground-color: black; +--fragment-lineno-link-fg-color: #4665A2; +--fragment-lineno-link-bg-color: #D8D8D8; +--fragment-lineno-link-hover-fg-color: #4665A2; +--fragment-lineno-link-hover-bg-color: #C8C8C8; +--tooltip-foreground-color: black; +--tooltip-background-color: white; +--tooltip-border-color: gray; +--tooltip-doc-color: grey; +--tooltip-declaration-color: #006318; +--tooltip-link-color: #4665A2; +--tooltip-shadow: 1px 1px 7px gray; +--fold-line-color: #808080; +--fold-minus-image: url('minus.svg'); +--fold-plus-image: url('plus.svg'); +--fold-minus-image-relpath: url('../../minus.svg'); +--fold-plus-image-relpath: url('../../plus.svg'); + +/** font-family */ +--font-family-normal: Roboto,sans-serif; +--font-family-monospace: 'JetBrains Mono',Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace,fixed; +--font-family-nav: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; +--font-family-title: Tahoma,Arial,sans-serif; +--font-family-toc: Verdana,'DejaVu Sans',Geneva,sans-serif; +--font-family-search: Arial,Verdana,sans-serif; +--font-family-icon: Arial,Helvetica; +--font-family-tooltip: Roboto,sans-serif; + +} + +@media (prefers-color-scheme: dark) { + html:not(.dark-mode) { + color-scheme: dark; + +/* page base colors */ +--page-background-color: black; +--page-foreground-color: #C9D1D9; +--page-link-color: #90A5CE; +--page-visited-link-color: #A3B4D7; + +/* index */ +--index-odd-item-bg-color: #0B101A; +--index-even-item-bg-color: black; +--index-header-color: #C4CFE5; +--index-separator-color: #334975; + +/* header */ +--header-background-color: #070B11; +--header-separator-color: #141C2E; +--header-gradient-image: url('nav_hd.png'); +--group-header-separator-color: #283A5D; +--group-header-color: #90A5CE; +--inherit-header-color: #A0A0A0; + +--footer-foreground-color: #5B7AB7; +--footer-logo-width: 60px; +--citation-label-color: #90A5CE; +--glow-color: cyan; + +--title-background-color: #090D16; +--title-separator-color: #354C79; +--directory-separator-color: #283A5D; +--separator-color: #283A5D; + +--blockquote-background-color: #101826; +--blockquote-border-color: #283A5D; + +--scrollbar-thumb-color: #283A5D; +--scrollbar-background-color: #070B11; + +--icon-background-color: #334975; +--icon-foreground-color: #C4CFE5; +--icon-doc-image: url('docd.svg'); +--icon-folder-open-image: url('folderopend.svg'); +--icon-folder-closed-image: url('folderclosedd.svg'); + +/* brief member declaration list */ +--memdecl-background-color: #0B101A; +--memdecl-separator-color: #2C3F65; +--memdecl-foreground-color: #BBB; +--memdecl-template-color: #7C95C6; + +/* detailed member list */ +--memdef-border-color: #233250; +--memdef-title-background-color: #1B2840; +--memdef-title-gradient-image: url('nav_fd.png'); +--memdef-proto-background-color: #19243A; +--memdef-proto-text-color: #9DB0D4; +--memdef-proto-text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.9); +--memdef-doc-background-color: black; +--memdef-param-name-color: #D28757; +--memdef-template-color: #7C95C6; + +/* tables */ +--table-cell-border-color: #283A5D; +--table-header-background-color: #283A5D; +--table-header-foreground-color: #C4CFE5; + +/* labels */ +--label-background-color: #354C7B; +--label-left-top-border-color: #4665A2; +--label-right-bottom-border-color: #283A5D; +--label-foreground-color: #CCCCCC; + +/** navigation bar/tree/menu */ +--nav-background-color: #101826; +--nav-foreground-color: #364D7C; +--nav-gradient-image: url('tab_bd.png'); +--nav-gradient-hover-image: url('tab_hd.png'); +--nav-gradient-active-image: url('tab_ad.png'); +--nav-gradient-active-image-parent: url("../tab_ad.png"); +--nav-separator-image: url('tab_sd.png'); +--nav-breadcrumb-image: url('bc_sd.png'); +--nav-breadcrumb-border-color: #2A3D61; +--nav-splitbar-image: url('splitbard.png'); +--nav-font-size-level1: 13px; +--nav-font-size-level2: 10px; +--nav-font-size-level3: 9px; +--nav-text-normal-color: #B6C4DF; +--nav-text-hover-color: #DCE2EF; +--nav-text-active-color: #DCE2EF; +--nav-text-normal-shadow: 0px 1px 1px black; +--nav-text-hover-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); +--nav-text-active-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); +--nav-menu-button-color: #B6C4DF; +--nav-menu-background-color: #05070C; +--nav-menu-foreground-color: #BBBBBB; +--nav-menu-toggle-color: rgba(255, 255, 255, 0.2); +--nav-arrow-color: #334975; +--nav-arrow-selected-color: #90A5CE; + +/* table of contents */ +--toc-background-color: #151E30; +--toc-border-color: #202E4A; +--toc-header-color: #A3B4D7; +--toc-down-arrow-image: url("data:image/svg+xml;utf8,&%238595;"); + +/** search field */ +--search-background-color: black; +--search-foreground-color: #C5C5C5; +--search-magnification-image: url('mag_d.svg'); +--search-magnification-select-image: url('mag_seld.svg'); +--search-active-color: #C5C5C5; +--search-filter-background-color: #101826; +--search-filter-foreground-color: #90A5CE; +--search-filter-border-color: #7C95C6; +--search-filter-highlight-text-color: #BCC9E2; +--search-filter-highlight-bg-color: #283A5D; +--search-results-background-color: #101826; +--search-results-foreground-color: #90A5CE; +--search-results-border-color: #7C95C6; +--search-box-shadow: inset 0.5px 0.5px 3px 0px #2F436C; + +/** code fragments */ +--code-keyword-color: #CC99CD; +--code-type-keyword-color: #AB99CD; +--code-flow-keyword-color: #E08000; +--code-comment-color: #717790; +--code-preprocessor-color: #65CABE; +--code-string-literal-color: #7EC699; +--code-char-literal-color: #00E0F0; +--code-xml-cdata-color: #C9D1D9; +--code-vhdl-digit-color: #FF00FF; +--code-vhdl-char-color: #C0C0C0; +--code-vhdl-keyword-color: #CF53C9; +--code-vhdl-logic-color: #FF0000; +--code-link-color: #79C0FF; +--code-external-link-color: #79C0FF; +--fragment-foreground-color: #C9D1D9; +--fragment-background-color: black; +--fragment-border-color: #30363D; +--fragment-lineno-border-color: #30363D; +--fragment-lineno-background-color: black; +--fragment-lineno-foreground-color: #6E7681; +--fragment-lineno-link-fg-color: #6E7681; +--fragment-lineno-link-bg-color: #303030; +--fragment-lineno-link-hover-fg-color: #8E96A1; +--fragment-lineno-link-hover-bg-color: #505050; +--tooltip-foreground-color: #C9D1D9; +--tooltip-background-color: #202020; +--tooltip-border-color: #C9D1D9; +--tooltip-doc-color: #D9E1E9; +--tooltip-declaration-color: #20C348; +--tooltip-link-color: #79C0FF; +--tooltip-shadow: none; +--fold-line-color: #808080; +--fold-minus-image: url('minusd.svg'); +--fold-plus-image: url('plusd.svg'); +--fold-minus-image-relpath: url('../../minusd.svg'); +--fold-plus-image-relpath: url('../../plusd.svg'); + +/** font-family */ +--font-family-normal: Roboto,sans-serif; +--font-family-monospace: 'JetBrains Mono',Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace,fixed; +--font-family-nav: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; +--font-family-title: Tahoma,Arial,sans-serif; +--font-family-toc: Verdana,'DejaVu Sans',Geneva,sans-serif; +--font-family-search: Arial,Verdana,sans-serif; +--font-family-icon: Arial,Helvetica; +--font-family-tooltip: Roboto,sans-serif; + +}} +body { + background-color: var(--page-background-color); + color: var(--page-foreground-color); +} + +body, table, div, p, dl { + font-weight: 400; + font-size: 14px; + font-family: var(--font-family-normal); + line-height: 22px; +} + +/* @group Heading Levels */ + +.title { + font-weight: 400; + font-size: 14px; + font-family: var(--font-family-normal); + line-height: 28px; + font-size: 150%; + font-weight: bold; + margin: 10px 2px; +} + +h1.groupheader { + font-size: 150%; +} + +h2.groupheader { + border-bottom: 1px solid var(--group-header-separator-color); + color: var(--group-header-color); + font-size: 150%; + font-weight: normal; + margin-top: 1.75em; + padding-top: 8px; + padding-bottom: 4px; + width: 100%; +} + +h3.groupheader { + font-size: 100%; +} + +h1, h2, h3, h4, h5, h6 { + -webkit-transition: text-shadow 0.5s linear; + -moz-transition: text-shadow 0.5s linear; + -ms-transition: text-shadow 0.5s linear; + -o-transition: text-shadow 0.5s linear; + transition: text-shadow 0.5s linear; + margin-right: 15px; +} + +h1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow { + text-shadow: 0 0 15px var(--glow-color); +} + +dt { + font-weight: bold; +} + +p.startli, p.startdd { + margin-top: 2px; +} + +th p.starttd, th p.intertd, th p.endtd { + font-size: 100%; + font-weight: 700; +} + +p.starttd { + margin-top: 0px; +} + +p.endli { + margin-bottom: 0px; +} + +p.enddd { + margin-bottom: 4px; +} + +p.endtd { + margin-bottom: 2px; +} + +p.interli { +} + +p.interdd { +} + +p.intertd { +} + +/* @end */ + +caption { + font-weight: bold; +} + +span.legend { + font-size: 70%; + text-align: center; +} + +h3.version { + font-size: 90%; + text-align: center; +} + +div.navtab { + padding-right: 15px; + text-align: right; + line-height: 110%; +} + +div.navtab table { + border-spacing: 0; +} + +td.navtab { + padding-right: 6px; + padding-left: 6px; +} + +td.navtabHL { + background-image: var(--nav-gradient-active-image); + background-repeat:repeat-x; + padding-right: 6px; + padding-left: 6px; +} + +td.navtabHL a, td.navtabHL a:visited { + color: var(--nav-text-hover-color); + text-shadow: var(--nav-text-hover-shadow); +} + +a.navtab { + font-weight: bold; +} + +div.qindex{ + text-align: center; + width: 100%; + line-height: 140%; + font-size: 130%; + color: var(--index-separator-color); +} + +#main-menu a:focus { + outline: auto; + z-index: 10; + position: relative; +} + +dt.alphachar{ + font-size: 180%; + font-weight: bold; +} + +.alphachar a{ + color: var(--index-header-color); +} + +.alphachar a:hover, .alphachar a:visited{ + text-decoration: none; +} + +.classindex dl { + padding: 25px; + column-count:1 +} + +.classindex dd { + display:inline-block; + margin-left: 50px; + width: 90%; + line-height: 1.15em; +} + +.classindex dl.even { + background-color: var(--index-even-item-bg-color); +} + +.classindex dl.odd { + background-color: var(--index-odd-item-bg-color); +} + +@media(min-width: 1120px) { + .classindex dl { + column-count:2 + } +} + +@media(min-width: 1320px) { + .classindex dl { + column-count:3 + } +} + + +/* @group Link Styling */ + +a { + color: var(--page-link-color); + font-weight: normal; + text-decoration: none; +} + +.contents a:visited { + color: var(--page-visited-link-color); +} + +a:hover { + text-decoration: underline; +} + +a.el { + font-weight: bold; +} + +a.elRef { +} + +a.code, a.code:visited, a.line, a.line:visited { + color: var(--code-link-color); +} + +a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited { + color: var(--code-external-link-color); +} + +a.code.hl_class { /* style for links to class names in code snippets */ } +a.code.hl_struct { /* style for links to struct names in code snippets */ } +a.code.hl_union { /* style for links to union names in code snippets */ } +a.code.hl_interface { /* style for links to interface names in code snippets */ } +a.code.hl_protocol { /* style for links to protocol names in code snippets */ } +a.code.hl_category { /* style for links to category names in code snippets */ } +a.code.hl_exception { /* style for links to exception names in code snippets */ } +a.code.hl_service { /* style for links to service names in code snippets */ } +a.code.hl_singleton { /* style for links to singleton names in code snippets */ } +a.code.hl_concept { /* style for links to concept names in code snippets */ } +a.code.hl_namespace { /* style for links to namespace names in code snippets */ } +a.code.hl_package { /* style for links to package names in code snippets */ } +a.code.hl_define { /* style for links to macro names in code snippets */ } +a.code.hl_function { /* style for links to function names in code snippets */ } +a.code.hl_variable { /* style for links to variable names in code snippets */ } +a.code.hl_typedef { /* style for links to typedef names in code snippets */ } +a.code.hl_enumvalue { /* style for links to enum value names in code snippets */ } +a.code.hl_enumeration { /* style for links to enumeration names in code snippets */ } +a.code.hl_signal { /* style for links to Qt signal names in code snippets */ } +a.code.hl_slot { /* style for links to Qt slot names in code snippets */ } +a.code.hl_friend { /* style for links to friend names in code snippets */ } +a.code.hl_dcop { /* style for links to KDE3 DCOP names in code snippets */ } +a.code.hl_property { /* style for links to property names in code snippets */ } +a.code.hl_event { /* style for links to event names in code snippets */ } +a.code.hl_sequence { /* style for links to sequence names in code snippets */ } +a.code.hl_dictionary { /* style for links to dictionary names in code snippets */ } + +/* @end */ + +dl.el { + margin-left: -1cm; +} + +ul { + overflow: visible; +} + +ul.multicol { + -moz-column-gap: 1em; + -webkit-column-gap: 1em; + column-gap: 1em; + -moz-column-count: 3; + -webkit-column-count: 3; + column-count: 3; + list-style-type: none; +} + +#side-nav ul { + overflow: visible; /* reset ul rule for scroll bar in GENERATE_TREEVIEW window */ +} + +#main-nav ul { + overflow: visible; /* reset ul rule for the navigation bar drop down lists */ +} + +.fragment { + text-align: left; + direction: ltr; + overflow-x: auto; /*Fixed: fragment lines overlap floating elements*/ + overflow-y: hidden; +} + +pre.fragment { + border: 1px solid var(--fragment-border-color); + background-color: var(--fragment-background-color); + color: var(--fragment-foreground-color); + padding: 4px 6px; + margin: 4px 8px 4px 2px; + overflow: auto; + word-wrap: break-word; + font-size: 9pt; + line-height: 125%; + font-family: var(--font-family-monospace); + font-size: 105%; +} + +div.fragment { + padding: 0 0 1px 0; /*Fixed: last line underline overlap border*/ + margin: 4px 8px 4px 2px; + color: var(--fragment-foreground-color); + background-color: var(--fragment-background-color); + border: 1px solid var(--fragment-border-color); +} + +div.line { + font-family: var(--font-family-monospace); + font-size: 13px; + min-height: 13px; + line-height: 1.2; + text-wrap: unrestricted; + white-space: -moz-pre-wrap; /* Moz */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + white-space: pre-wrap; /* CSS3 */ + word-wrap: break-word; /* IE 5.5+ */ + text-indent: -53px; + padding-left: 53px; + padding-bottom: 0px; + margin: 0px; + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +div.line:after { + content:"\000A"; + white-space: pre; +} + +div.line.glow { + background-color: var(--glow-color); + box-shadow: 0 0 10px var(--glow-color); +} + +span.fold { + margin-left: 5px; + margin-right: 1px; + margin-top: 0px; + margin-bottom: 0px; + padding: 0px; + display: inline-block; + width: 12px; + height: 12px; + background-repeat:no-repeat; + background-position:center; +} + +span.lineno { + padding-right: 4px; + margin-right: 9px; + text-align: right; + border-right: 2px solid var(--fragment-lineno-border-color); + color: var(--fragment-lineno-foreground-color); + background-color: var(--fragment-lineno-background-color); + white-space: pre; +} +span.lineno a, span.lineno a:visited { + color: var(--fragment-lineno-link-fg-color); + background-color: var(--fragment-lineno-link-bg-color); +} + +span.lineno a:hover { + color: var(--fragment-lineno-link-hover-fg-color); + background-color: var(--fragment-lineno-link-hover-bg-color); +} + +.lineno { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +div.classindex ul { + list-style: none; + padding-left: 0; +} + +div.classindex span.ai { + display: inline-block; +} + +div.groupHeader { + margin-left: 16px; + margin-top: 12px; + font-weight: bold; +} + +div.groupText { + margin-left: 16px; + font-style: italic; +} + +body { + color: var(--page-foreground-color); + margin: 0; +} + +div.contents { + margin-top: 10px; + margin-left: 12px; + margin-right: 8px; +} + +p.formulaDsp { + text-align: center; +} + +img.dark-mode-visible { + display: none; +} +img.light-mode-visible { + display: none; +} + +img.formulaDsp { + +} + +img.formulaInl, img.inline { + vertical-align: middle; +} + +div.center { + text-align: center; + margin-top: 0px; + margin-bottom: 0px; + padding: 0px; +} + +div.center img { + border: 0px; +} + +address.footer { + text-align: right; + padding-right: 12px; +} + +img.footer { + border: 0px; + vertical-align: middle; + width: var(--footer-logo-width); +} + +.compoundTemplParams { + color: var(--memdecl-template-color); + font-size: 80%; + line-height: 120%; +} + +/* @group Code Colorization */ + +span.keyword { + color: var(--code-keyword-color); +} + +span.keywordtype { + color: var(--code-type-keyword-color); +} + +span.keywordflow { + color: var(--code-flow-keyword-color); +} + +span.comment { + color: var(--code-comment-color); +} + +span.preprocessor { + color: var(--code-preprocessor-color); +} + +span.stringliteral { + color: var(--code-string-literal-color); +} + +span.charliteral { + color: var(--code-char-literal-color); +} + +span.xmlcdata { + color: var(--code-xml-cdata-color); +} + +span.vhdldigit { + color: var(--code-vhdl-digit-color); +} + +span.vhdlchar { + color: var(--code-vhdl-char-color); +} + +span.vhdlkeyword { + color: var(--code-vhdl-keyword-color); +} + +span.vhdllogic { + color: var(--code-vhdl-logic-color); +} + +blockquote { + background-color: var(--blockquote-background-color); + border-left: 2px solid var(--blockquote-border-color); + margin: 0 24px 0 4px; + padding: 0 12px 0 16px; +} + +/* @end */ + +td.tiny { + font-size: 75%; +} + +.dirtab { + padding: 4px; + border-collapse: collapse; + border: 1px solid var(--table-cell-border-color); +} + +th.dirtab { + background-color: var(--table-header-background-color); + color: var(--table-header-foreground-color); + font-weight: bold; +} + +hr { + height: 0px; + border: none; + border-top: 1px solid var(--separator-color); +} + +hr.footer { + height: 1px; +} + +/* @group Member Descriptions */ + +table.memberdecls { + border-spacing: 0px; + padding: 0px; +} + +.memberdecls td, .fieldtable tr { + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +.memberdecls td.glow, .fieldtable tr.glow { + background-color: var(--glow-color); + box-shadow: 0 0 15px var(--glow-color); +} + +.mdescLeft, .mdescRight, +.memItemLeft, .memItemRight, +.memTemplItemLeft, .memTemplItemRight, .memTemplParams { + background-color: var(--memdecl-background-color); + border: none; + margin: 4px; + padding: 1px 0 0 8px; +} + +.mdescLeft, .mdescRight { + padding: 0px 8px 4px 8px; + color: var(--memdecl-foreground-color); +} + +.memSeparator { + border-bottom: 1px solid var(--memdecl-separator-color); + line-height: 1px; + margin: 0px; + padding: 0px; +} + +.memItemLeft, .memTemplItemLeft { + white-space: nowrap; +} + +.memItemRight, .memTemplItemRight { + width: 100%; +} + +.memTemplParams { + color: var(--memdecl-template-color); + white-space: nowrap; + font-size: 80%; +} + +/* @end */ + +/* @group Member Details */ + +/* Styles for detailed member documentation */ + +.memtitle { + padding: 8px; + border-top: 1px solid var(--memdef-border-color); + border-left: 1px solid var(--memdef-border-color); + border-right: 1px solid var(--memdef-border-color); + border-top-right-radius: 4px; + border-top-left-radius: 4px; + margin-bottom: -1px; + background-image: var(--memdef-title-gradient-image); + background-repeat: repeat-x; + background-color: var(--memdef-title-background-color); + line-height: 1.25; + font-weight: 300; + float:left; +} + +.permalink +{ + font-size: 65%; + display: inline-block; + vertical-align: middle; +} + +.memtemplate { + font-size: 80%; + color: var(--memdef-template-color); + font-weight: normal; + margin-left: 9px; +} + +.mempage { + width: 100%; +} + +.memitem { + padding: 0; + margin-bottom: 10px; + margin-right: 5px; + -webkit-transition: box-shadow 0.5s linear; + -moz-transition: box-shadow 0.5s linear; + -ms-transition: box-shadow 0.5s linear; + -o-transition: box-shadow 0.5s linear; + transition: box-shadow 0.5s linear; + display: table !important; + width: 100%; +} + +.memitem.glow { + box-shadow: 0 0 15px var(--glow-color); +} + +.memname { + font-weight: 400; + margin-left: 6px; +} + +.memname td { + vertical-align: bottom; +} + +.memproto, dl.reflist dt { + border-top: 1px solid var(--memdef-border-color); + border-left: 1px solid var(--memdef-border-color); + border-right: 1px solid var(--memdef-border-color); + padding: 6px 0px 6px 0px; + color: var(--memdef-proto-text-color); + font-weight: bold; + text-shadow: var(--memdef-proto-text-shadow); + background-color: var(--memdef-proto-background-color); + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + border-top-right-radius: 4px; +} + +.overload { + font-family: var(--font-family-monospace); + font-size: 65%; +} + +.memdoc, dl.reflist dd { + border-bottom: 1px solid var(--memdef-border-color); + border-left: 1px solid var(--memdef-border-color); + border-right: 1px solid var(--memdef-border-color); + padding: 6px 10px 2px 10px; + border-top-width: 0; + background-image:url('nav_g.png'); + background-repeat:repeat-x; + background-color: var(--memdef-doc-background-color); + /* opera specific markup */ + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + /* firefox specific markup */ + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-bottomright: 4px; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px; + /* webkit specific markup */ + -webkit-border-bottom-left-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); +} + +dl.reflist dt { + padding: 5px; +} + +dl.reflist dd { + margin: 0px 0px 10px 0px; + padding: 5px; +} + +.paramkey { + text-align: right; +} + +.paramtype { + white-space: nowrap; +} + +.paramname { + color: var(--memdef-param-name-color); + white-space: nowrap; +} +.paramname em { + font-style: normal; +} +.paramname code { + line-height: 14px; +} + +.params, .retval, .exception, .tparams { + margin-left: 0px; + padding-left: 0px; +} + +.params .paramname, .retval .paramname, .tparams .paramname, .exception .paramname { + font-weight: bold; + vertical-align: top; +} + +.params .paramtype, .tparams .paramtype { + font-style: italic; + vertical-align: top; +} + +.params .paramdir, .tparams .paramdir { + font-family: var(--font-family-monospace); + vertical-align: top; +} + +table.mlabels { + border-spacing: 0px; +} + +td.mlabels-left { + width: 100%; + padding: 0px; +} + +td.mlabels-right { + vertical-align: bottom; + padding: 0px; + white-space: nowrap; +} + +span.mlabels { + margin-left: 8px; +} + +span.mlabel { + background-color: var(--label-background-color); + border-top:1px solid var(--label-left-top-border-color); + border-left:1px solid var(--label-left-top-border-color); + border-right:1px solid var(--label-right-bottom-border-color); + border-bottom:1px solid var(--label-right-bottom-border-color); + text-shadow: none; + color: var(--label-foreground-color); + margin-right: 4px; + padding: 2px 3px; + border-radius: 3px; + font-size: 7pt; + white-space: nowrap; + vertical-align: middle; +} + + + +/* @end */ + +/* these are for tree view inside a (index) page */ + +div.directory { + margin: 10px 0px; + border-top: 1px solid var(--directory-separator-color); + border-bottom: 1px solid var(--directory-separator-color); + width: 100%; +} + +.directory table { + border-collapse:collapse; +} + +.directory td { + margin: 0px; + padding: 0px; + vertical-align: top; +} + +.directory td.entry { + white-space: nowrap; + padding-right: 6px; + padding-top: 3px; +} + +.directory td.entry a { + outline:none; +} + +.directory td.entry a img { + border: none; +} + +.directory td.desc { + width: 100%; + padding-left: 6px; + padding-right: 6px; + padding-top: 3px; + border-left: 1px solid rgba(0,0,0,0.05); +} + +.directory tr.odd { + padding-left: 6px; + background-color: var(--index-odd-item-bg-color); +} + +.directory tr.even { + padding-left: 6px; + background-color: var(--index-even-item-bg-color); +} + +.directory img { + vertical-align: -30%; +} + +.directory .levels { + white-space: nowrap; + width: 100%; + text-align: right; + font-size: 9pt; +} + +.directory .levels span { + cursor: pointer; + padding-left: 2px; + padding-right: 2px; + color: var(--page-link-color); +} + +.arrow { + color: var(--nav-arrow-color); + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; + font-size: 80%; + display: inline-block; + width: 16px; + height: 22px; +} + +.icon { + font-family: var(--font-family-icon); + line-height: normal; + font-weight: bold; + font-size: 12px; + height: 14px; + width: 16px; + display: inline-block; + background-color: var(--icon-background-color); + color: var(--icon-foreground-color); + text-align: center; + border-radius: 4px; + margin-left: 2px; + margin-right: 2px; +} + +.icona { + width: 24px; + height: 22px; + display: inline-block; +} + +.iconfopen { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image:var(--icon-folder-open-image); + background-repeat: repeat-y; + vertical-align:top; + display: inline-block; +} + +.iconfclosed { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image:var(--icon-folder-closed-image); + background-repeat: repeat-y; + vertical-align:top; + display: inline-block; +} + +.icondoc { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image:var(--icon-doc-image); + background-position: 0px -4px; + background-repeat: repeat-y; + vertical-align:top; + display: inline-block; +} + +/* @end */ + +div.dynheader { + margin-top: 8px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +address { + font-style: normal; + color: var(--footer-foreground-color); +} + +table.doxtable caption { + caption-side: top; +} + +table.doxtable { + border-collapse:collapse; + margin-top: 4px; + margin-bottom: 4px; +} + +table.doxtable td, table.doxtable th { + border: 1px solid var(--table-cell-border-color); + padding: 3px 7px 2px; +} + +table.doxtable th { + background-color: var(--table-header-background-color); + color: var(--table-header-foreground-color); + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; +} + +table.fieldtable { + margin-bottom: 10px; + border: 1px solid var(--memdef-border-color); + border-spacing: 0px; + border-radius: 4px; + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); +} + +.fieldtable td, .fieldtable th { + padding: 3px 7px 2px; +} + +.fieldtable td.fieldtype, .fieldtable td.fieldname { + white-space: nowrap; + border-right: 1px solid var(--memdef-border-color); + border-bottom: 1px solid var(--memdef-border-color); + vertical-align: top; +} + +.fieldtable td.fieldname { + padding-top: 3px; +} + +.fieldtable td.fielddoc { + border-bottom: 1px solid var(--memdef-border-color); +} + +.fieldtable td.fielddoc p:first-child { + margin-top: 0px; +} + +.fieldtable td.fielddoc p:last-child { + margin-bottom: 2px; +} + +.fieldtable tr:last-child td { + border-bottom: none; +} + +.fieldtable th { + background-image: var(--memdef-title-gradient-image); + background-repeat:repeat-x; + background-color: var(--memdef-title-background-color); + font-size: 90%; + color: var(--memdef-proto-text-color); + padding-bottom: 4px; + padding-top: 5px; + text-align:left; + font-weight: 400; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom: 1px solid var(--memdef-border-color); +} + + +.tabsearch { + top: 0px; + left: 10px; + height: 36px; + background-image: var(--nav-gradient-image); + z-index: 101; + overflow: hidden; + font-size: 13px; +} + +.navpath ul +{ + font-size: 11px; + background-image: var(--nav-gradient-image); + background-repeat:repeat-x; + background-position: 0 -5px; + height:30px; + line-height:30px; + color:var(--nav-text-normal-color); + border:solid 1px var(--nav-breadcrumb-border-color); + overflow:hidden; + margin:0px; + padding:0px; +} + +.navpath li +{ + list-style-type:none; + float:left; + padding-left:10px; + padding-right:15px; + background-image:var(--nav-breadcrumb-image); + background-repeat:no-repeat; + background-position:right; + color: var(--nav-foreground-color); +} + +.navpath li.navelem a +{ + height:32px; + display:block; + text-decoration: none; + outline: none; + color: var(--nav-text-normal-color); + font-family: var(--font-family-nav); + text-shadow: var(--nav-text-normal-shadow); + text-decoration: none; +} + +.navpath li.navelem a:hover +{ + color: var(--nav-text-hover-color); + text-shadow: var(--nav-text-hover-shadow); +} + +.navpath li.footer +{ + list-style-type:none; + float:right; + padding-left:10px; + padding-right:15px; + background-image:none; + background-repeat:no-repeat; + background-position:right; + color: var(--footer-foreground-color); + font-size: 8pt; +} + + +div.summary +{ + float: right; + font-size: 8pt; + padding-right: 5px; + width: 50%; + text-align: right; +} + +div.summary a +{ + white-space: nowrap; +} + +table.classindex +{ + margin: 10px; + white-space: nowrap; + margin-left: 3%; + margin-right: 3%; + width: 94%; + border: 0; + border-spacing: 0; + padding: 0; +} + +div.ingroups +{ + font-size: 8pt; + width: 50%; + text-align: left; +} + +div.ingroups a +{ + white-space: nowrap; +} + +div.header +{ + background-image: var(--header-gradient-image); + background-repeat:repeat-x; + background-color: var(--header-background-color); + margin: 0px; + border-bottom: 1px solid var(--header-separator-color); +} + +div.headertitle +{ + padding: 5px 5px 5px 10px; +} + +.PageDocRTL-title div.headertitle { + text-align: right; + direction: rtl; +} + +dl { + padding: 0 0 0 0; +} + +/* dl.note, dl.warning, dl.attention, dl.pre, dl.post, dl.invariant, dl.deprecated, dl.todo, dl.test, dl.bug, dl.examples */ +dl.section { + margin-left: 0px; + padding-left: 0px; +} + +dl.note { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #D0C000; +} + +dl.warning, dl.attention { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #FF0000; +} + +dl.pre, dl.post, dl.invariant { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #00D000; +} + +dl.deprecated { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #505050; +} + +dl.todo { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #00C0E0; +} + +dl.test { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #3030E0; +} + +dl.bug { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #C08050; +} + +dl.section dd { + margin-bottom: 6px; +} + + +#projectrow +{ + height: 56px; +} + +#projectlogo +{ + text-align: center; + vertical-align: bottom; + border-collapse: separate; +} + +#projectlogo img +{ + border: 0px none; +} + +#projectalign +{ + vertical-align: middle; + padding-left: 0.5em; +} + +#projectname +{ + font-size: 200%; + font-family: var(--font-family-title); + margin: 0px; + padding: 2px 0px; +} + +#projectbrief +{ + font-size: 90%; + font-family: var(--font-family-title); + margin: 0px; + padding: 0px; +} + +#projectnumber +{ + font-size: 50%; + font-family: 50% var(--font-family-title); + margin: 0px; + padding: 0px; +} + +#titlearea +{ + padding: 0px; + margin: 0px; + width: 100%; + border-bottom: 1px solid var(--title-separator-color); + background-color: var(--title-background-color); +} + +.image +{ + text-align: center; +} + +.dotgraph +{ + text-align: center; +} + +.mscgraph +{ + text-align: center; +} + +.plantumlgraph +{ + text-align: center; +} + +.diagraph +{ + text-align: center; +} + +.caption +{ + font-weight: bold; +} + +dl.citelist { + margin-bottom:50px; +} + +dl.citelist dt { + color:var(--citation-label-color); + float:left; + font-weight:bold; + margin-right:10px; + padding:5px; + text-align:right; + width:52px; +} + +dl.citelist dd { + margin:2px 0 2px 72px; + padding:5px 0; +} + +div.toc { + padding: 14px 25px; + background-color: var(--toc-background-color); + border: 1px solid var(--toc-border-color); + border-radius: 7px 7px 7px 7px; + float: right; + height: auto; + margin: 0 8px 10px 10px; + width: 200px; +} + +div.toc li { + background: var(--toc-down-arrow-image) no-repeat scroll 0 5px transparent; + font: 10px/1.2 var(--font-family-toc); + margin-top: 5px; + padding-left: 10px; + padding-top: 2px; +} + +div.toc h3 { + font: bold 12px/1.2 var(--font-family-toc); + color: var(--toc-header-color); + border-bottom: 0 none; + margin: 0; +} + +div.toc ul { + list-style: none outside none; + border: medium none; + padding: 0px; +} + +div.toc li.level1 { + margin-left: 0px; +} + +div.toc li.level2 { + margin-left: 15px; +} + +div.toc li.level3 { + margin-left: 15px; +} + +div.toc li.level4 { + margin-left: 15px; +} + +span.emoji { + /* font family used at the site: https://unicode.org/emoji/charts/full-emoji-list.html + * font-family: "Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", Times, Symbola, Aegyptus, Code2000, Code2001, Code2002, Musica, serif, LastResort; + */ +} + +span.obfuscator { + display: none; +} + +.inherit_header { + font-weight: bold; + color: var(--inherit-header-color); + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.inherit_header td { + padding: 6px 0px 2px 5px; +} + +.inherit { + display: none; +} + +tr.heading h2 { + margin-top: 12px; + margin-bottom: 4px; +} + +/* tooltip related style info */ + +.ttc { + position: absolute; + display: none; +} + +#powerTip { + cursor: default; + /*white-space: nowrap;*/ + color: var(--tooltip-foreground-color); + background-color: var(--tooltip-background-color); + border: 1px solid var(--tooltip-border-color); + border-radius: 4px 4px 4px 4px; + box-shadow: var(--tooltip-shadow); + display: none; + font-size: smaller; + max-width: 80%; + opacity: 0.9; + padding: 1ex 1em 1em; + position: absolute; + z-index: 2147483647; +} + +#powerTip div.ttdoc { + color: var(--tooltip-doc-color); + font-style: italic; +} + +#powerTip div.ttname a { + font-weight: bold; +} + +#powerTip a { + color: var(--tooltip-link-color); +} + +#powerTip div.ttname { + font-weight: bold; +} + +#powerTip div.ttdeci { + color: var(--tooltip-declaration-color); +} + +#powerTip div { + margin: 0px; + padding: 0px; + font-size: 12px; + font-family: var(--font-family-tooltip); + line-height: 16px; +} + +#powerTip:before, #powerTip:after { + content: ""; + position: absolute; + margin: 0px; +} + +#powerTip.n:after, #powerTip.n:before, +#powerTip.s:after, #powerTip.s:before, +#powerTip.w:after, #powerTip.w:before, +#powerTip.e:after, #powerTip.e:before, +#powerTip.ne:after, #powerTip.ne:before, +#powerTip.se:after, #powerTip.se:before, +#powerTip.nw:after, #powerTip.nw:before, +#powerTip.sw:after, #powerTip.sw:before { + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; +} + +#powerTip.n:after, #powerTip.s:after, +#powerTip.w:after, #powerTip.e:after, +#powerTip.nw:after, #powerTip.ne:after, +#powerTip.sw:after, #powerTip.se:after { + border-color: rgba(255, 255, 255, 0); +} + +#powerTip.n:before, #powerTip.s:before, +#powerTip.w:before, #powerTip.e:before, +#powerTip.nw:before, #powerTip.ne:before, +#powerTip.sw:before, #powerTip.se:before { + border-color: rgba(128, 128, 128, 0); +} + +#powerTip.n:after, #powerTip.n:before, +#powerTip.ne:after, #powerTip.ne:before, +#powerTip.nw:after, #powerTip.nw:before { + top: 100%; +} + +#powerTip.n:after, #powerTip.ne:after, #powerTip.nw:after { + border-top-color: var(--tooltip-background-color); + border-width: 10px; + margin: 0px -10px; +} +#powerTip.n:before, #powerTip.ne:before, #powerTip.nw:before { + border-top-color: var(--tooltip-border-color); + border-width: 11px; + margin: 0px -11px; +} +#powerTip.n:after, #powerTip.n:before { + left: 50%; +} + +#powerTip.nw:after, #powerTip.nw:before { + right: 14px; +} + +#powerTip.ne:after, #powerTip.ne:before { + left: 14px; +} + +#powerTip.s:after, #powerTip.s:before, +#powerTip.se:after, #powerTip.se:before, +#powerTip.sw:after, #powerTip.sw:before { + bottom: 100%; +} + +#powerTip.s:after, #powerTip.se:after, #powerTip.sw:after { + border-bottom-color: var(--tooltip-background-color); + border-width: 10px; + margin: 0px -10px; +} + +#powerTip.s:before, #powerTip.se:before, #powerTip.sw:before { + border-bottom-color: var(--tooltip-border-color); + border-width: 11px; + margin: 0px -11px; +} + +#powerTip.s:after, #powerTip.s:before { + left: 50%; +} + +#powerTip.sw:after, #powerTip.sw:before { + right: 14px; +} + +#powerTip.se:after, #powerTip.se:before { + left: 14px; +} + +#powerTip.e:after, #powerTip.e:before { + left: 100%; +} +#powerTip.e:after { + border-left-color: var(--tooltip-border-color); + border-width: 10px; + top: 50%; + margin-top: -10px; +} +#powerTip.e:before { + border-left-color: var(--tooltip-border-color); + border-width: 11px; + top: 50%; + margin-top: -11px; +} + +#powerTip.w:after, #powerTip.w:before { + right: 100%; +} +#powerTip.w:after { + border-right-color: var(--tooltip-border-color); + border-width: 10px; + top: 50%; + margin-top: -10px; +} +#powerTip.w:before { + border-right-color: var(--tooltip-border-color); + border-width: 11px; + top: 50%; + margin-top: -11px; +} + +@media print +{ + #top { display: none; } + #side-nav { display: none; } + #nav-path { display: none; } + body { overflow:visible; } + h1, h2, h3, h4, h5, h6 { page-break-after: avoid; } + .summary { display: none; } + .memitem { page-break-inside: avoid; } + #doc-content + { + margin-left:0 !important; + height:auto !important; + width:auto !important; + overflow:inherit; + display:inline; + } +} + +/* @group Markdown */ + +table.markdownTable { + border-collapse:collapse; + margin-top: 4px; + margin-bottom: 4px; +} + +table.markdownTable td, table.markdownTable th { + border: 1px solid var(--table-cell-border-color); + padding: 3px 7px 2px; +} + +table.markdownTable tr { +} + +th.markdownTableHeadLeft, th.markdownTableHeadRight, th.markdownTableHeadCenter, th.markdownTableHeadNone { + background-color: var(--table-header-background-color); + color: var(--table-header-foreground-color); + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; +} + +th.markdownTableHeadLeft, td.markdownTableBodyLeft { + text-align: left +} + +th.markdownTableHeadRight, td.markdownTableBodyRight { + text-align: right +} + +th.markdownTableHeadCenter, td.markdownTableBodyCenter { + text-align: center +} + +tt, code, kbd, samp +{ + display: inline-block; +} +/* @end */ + +u { + text-decoration: underline; +} + +details>summary { + list-style-type: none; +} + +details > summary::-webkit-details-marker { + display: none; +} + +details>summary::before { + content: "\25ba"; + padding-right:4px; + font-size: 80%; +} + +details[open]>summary::before { + content: "\25bc"; + padding-right:4px; + font-size: 80%; +} + +body { + scrollbar-color: var(--scrollbar-thumb-color) var(--scrollbar-background-color); +} + +::-webkit-scrollbar { + background-color: var(--scrollbar-background-color); + height: 12px; + width: 12px; +} +::-webkit-scrollbar-thumb { + border-radius: 6px; + box-shadow: inset 0 0 12px 12px var(--scrollbar-thumb-color); + border: solid 2px transparent; +} +::-webkit-scrollbar-corner { + background-color: var(--scrollbar-background-color); +} + diff --git a/doc/html/doxygen.svg b/doc/html/doxygen.svg new file mode 100644 index 0000000..79a7635 --- /dev/null +++ b/doc/html/doxygen.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/html/dynsections.js b/doc/html/dynsections.js new file mode 100644 index 0000000..9b28156 --- /dev/null +++ b/doc/html/dynsections.js @@ -0,0 +1,199 @@ +/* + @licstart The following is the entire license notice for the JavaScript code in this file. + + The MIT License (MIT) + + Copyright (C) 1997-2020 by Dimitri van Heesch + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software + and associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, + sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or + substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + @licend The above is the entire license notice for the JavaScript code in this file + */ +function toggleVisibility(linkObj) +{ + var base = $(linkObj).attr('id'); + var summary = $('#'+base+'-summary'); + var content = $('#'+base+'-content'); + var trigger = $('#'+base+'-trigger'); + var src=$(trigger).attr('src'); + if (content.is(':visible')===true) { + content.hide(); + summary.show(); + $(linkObj).addClass('closed').removeClass('opened'); + $(trigger).attr('src',src.substring(0,src.length-8)+'closed.png'); + } else { + content.show(); + summary.hide(); + $(linkObj).removeClass('closed').addClass('opened'); + $(trigger).attr('src',src.substring(0,src.length-10)+'open.png'); + } + return false; +} + +function updateStripes() +{ + $('table.directory tr'). + removeClass('even').filter(':visible:even').addClass('even'); + $('table.directory tr'). + removeClass('odd').filter(':visible:odd').addClass('odd'); +} + +function toggleLevel(level) +{ + $('table.directory tr').each(function() { + var l = this.id.split('_').length-1; + var i = $('#img'+this.id.substring(3)); + var a = $('#arr'+this.id.substring(3)); + if (l'); + // add vertical lines to other rows + $('span[class=lineno]').not(':eq(0)').append(''); + // add toggle controls to lines with fold divs + $('div[class=foldopen]').each(function() { + // extract specific id to use + var id = $(this).attr('id').replace('foldopen',''); + // extract start and end foldable fragment attributes + var start = $(this).attr('data-start'); + var end = $(this).attr('data-end'); + // replace normal fold span with controls for the first line of a foldable fragment + $(this).find('span[class=fold]:first').replaceWith(''); + // append div for folded (closed) representation + $(this).after(''); + // extract the first line from the "open" section to represent closed content + var line = $(this).children().first().clone(); + // remove any glow that might still be active on the original line + $(line).removeClass('glow'); + if (start) { + // if line already ends with a start marker (e.g. trailing {), remove it + $(line).html($(line).html().replace(new RegExp('\\s*'+start+'\\s*$','g'),'')); + } + // replace minus with plus symbol + $(line).find('span[class=fold]').css('background-image',plusImg[relPath]); + // append ellipsis + $(line).append(' '+start+''+end); + // insert constructed line into closed div + $('#foldclosed'+id).html(line); + }); +} + +/* @license-end */ +$(document).ready(function() { + $('.code,.codeRef').each(function() { + $(this).data('powertip',$('#a'+$(this).attr('href').replace(/.*\//,'').replace(/[^a-z_A-Z0-9]/g,'_')).html()); + $.fn.powerTip.smartPlacementLists.s = [ 's', 'n', 'ne', 'se' ]; + $(this).powerTip({ placement: 's', smartPlacement: true, mouseOnToPopup: true }); + }); +}); diff --git a/doc/html/example_2cuse_8c.html b/doc/html/example_2cuse_8c.html new file mode 100644 index 0000000..6fc84e0 --- /dev/null +++ b/doc/html/example_2cuse_8c.html @@ -0,0 +1,409 @@ + + + + + + + +libfuse: example/cuse.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
cuse.c File Reference
+
+
+
#include <cuse_lowlevel.h>
+#include <fuse_opt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This example demonstrates how to implement a character device in userspace ("CUSE"). This is only allowed for root. The character device should appear in /dev under the specified name. It can be tested with the cuse_client.c program.

+

Mount the file system with:

cuse -f --name=mydevice
+

You should now have a new /dev/mydevice character device. To "unmount" it, kill the "cuse" process.

+

To compile this example, run

gcc -Wall cuse.c `pkg-config fuse3 --cflags --libs` -o cuse
+

+Source code

+
/*
+
CUSE example: Character device in Userspace
+
Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <cuse_lowlevel.h>
+
#include <fuse_opt.h>
+
#include <stddef.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <errno.h>
+
+
#include "ioctl.h"
+
+
static void *cusexmp_buf;
+
static size_t cusexmp_size;
+
+
static const char *usage =
+
"usage: cusexmp [options]\n"
+
"\n"
+
"options:\n"
+
" --help|-h print this help message\n"
+
" --maj=MAJ|-M MAJ device major number\n"
+
" --min=MIN|-m MIN device minor number\n"
+
" --name=NAME|-n NAME device name (mandatory)\n"
+
" -d -o debug enable debug output (implies -f)\n"
+
" -f foreground operation\n"
+
" -s disable multi-threaded operation\n"
+
"\n";
+
+
static int cusexmp_resize(size_t new_size)
+
{
+
void *new_buf;
+
+
if (new_size == cusexmp_size)
+
return 0;
+
+
new_buf = realloc(cusexmp_buf, new_size);
+
if (!new_buf && new_size)
+
return -ENOMEM;
+
+
if (new_size > cusexmp_size)
+
memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
+
cusexmp_buf = new_buf;
+
cusexmp_size = new_size;
+
+
return 0;
+
}
+
+
static int cusexmp_expand(size_t new_size)
+
{
+
if (new_size > cusexmp_size)
+
return cusexmp_resize(new_size);
+
return 0;
+
}
+
+
static void cusexmp_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+
{
+
fuse_reply_open(req, fi);
+
}
+
+
static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+
struct fuse_file_info *fi)
+
{
+
(void)fi;
+
+
if (off >= cusexmp_size)
+
off = cusexmp_size;
+
if (size > cusexmp_size - off)
+
size = cusexmp_size - off;
+
+
fuse_reply_buf(req, cusexmp_buf + off, size);
+
}
+
+
static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void)fi;
+
+
if (cusexmp_expand(off + size)) {
+
fuse_reply_err(req, ENOMEM);
+
return;
+
}
+
+
memcpy(cusexmp_buf + off, buf, size);
+
fuse_reply_write(req, size);
+
}
+
+
static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+
size_t in_bufsz, size_t out_bufsz, int is_read)
+
{
+
const struct fioc_rw_arg *arg;
+
struct iovec in_iov[2], out_iov[3], iov[3];
+
size_t cur_size;
+
+
/* read in arg */
+
in_iov[0].iov_base = addr;
+
in_iov[0].iov_len = sizeof(*arg);
+
if (!in_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+
return;
+
}
+
arg = in_buf;
+
in_buf += sizeof(*arg);
+
in_bufsz -= sizeof(*arg);
+
+
/* prepare size outputs */
+
out_iov[0].iov_base =
+
addr + offsetof(struct fioc_rw_arg, prev_size);
+
out_iov[0].iov_len = sizeof(arg->prev_size);
+
+
out_iov[1].iov_base =
+
addr + offsetof(struct fioc_rw_arg, new_size);
+
out_iov[1].iov_len = sizeof(arg->new_size);
+
+
/* prepare client buf */
+
if (is_read) {
+
out_iov[2].iov_base = arg->buf;
+
out_iov[2].iov_len = arg->size;
+
if (!out_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+
return;
+
}
+
} else {
+
in_iov[1].iov_base = arg->buf;
+
in_iov[1].iov_len = arg->size;
+
if (arg->size && !in_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+
return;
+
}
+
}
+
+
/* we're all set */
+
cur_size = cusexmp_size;
+
iov[0].iov_base = &cur_size;
+
iov[0].iov_len = sizeof(cur_size);
+
+
iov[1].iov_base = &cusexmp_size;
+
iov[1].iov_len = sizeof(cusexmp_size);
+
+
if (is_read) {
+
size_t off = arg->offset;
+
size_t size = arg->size;
+
+
if (off >= cusexmp_size)
+
off = cusexmp_size;
+
if (size > cusexmp_size - off)
+
size = cusexmp_size - off;
+
+
iov[2].iov_base = cusexmp_buf + off;
+
iov[2].iov_len = size;
+
fuse_reply_ioctl_iov(req, size, iov, 3);
+
} else {
+
if (cusexmp_expand(arg->offset + in_bufsz)) {
+
fuse_reply_err(req, ENOMEM);
+
return;
+
}
+
+
memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+
fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+
}
+
}
+
+
static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+
struct fuse_file_info *fi, unsigned flags,
+
const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
{
+
int is_read = 0;
+
+
(void)fi;
+
+
if (flags & FUSE_IOCTL_COMPAT) {
+
fuse_reply_err(req, ENOSYS);
+
return;
+
}
+
+
switch (cmd) {
+
case FIOC_GET_SIZE:
+
if (!out_bufsz) {
+
struct iovec iov = { arg, sizeof(size_t) };
+
+
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+
} else
+
fuse_reply_ioctl(req, 0, &cusexmp_size,
+
sizeof(cusexmp_size));
+
break;
+
+
case FIOC_SET_SIZE:
+
if (!in_bufsz) {
+
struct iovec iov = { arg, sizeof(size_t) };
+
+
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+
} else {
+
cusexmp_resize(*(size_t *)in_buf);
+
fuse_reply_ioctl(req, 0, NULL, 0);
+
}
+
break;
+
+
case FIOC_READ:
+
is_read = 1;
+
/* fall through */
+
case FIOC_WRITE:
+
fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+
break;
+
+
default:
+
fuse_reply_err(req, EINVAL);
+
}
+
}
+
+
struct cusexmp_param {
+
unsigned major;
+
unsigned minor;
+
char *dev_name;
+
int is_help;
+
};
+
+
#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
+
static const struct fuse_opt cusexmp_opts[] = {
+
CUSEXMP_OPT("-M %u", major),
+
CUSEXMP_OPT("--maj=%u", major),
+
CUSEXMP_OPT("-m %u", minor),
+
CUSEXMP_OPT("--min=%u", minor),
+
CUSEXMP_OPT("-n %s", dev_name),
+
CUSEXMP_OPT("--name=%s", dev_name),
+
FUSE_OPT_KEY("-h", 0),
+
FUSE_OPT_KEY("--help", 0),
+ +
};
+
+
static int cusexmp_process_arg(void *data, const char *arg, int key,
+
struct fuse_args *outargs)
+
{
+
struct cusexmp_param *param = data;
+
+
(void)outargs;
+
(void)arg;
+
+
switch (key) {
+
case 0:
+
param->is_help = 1;
+
fprintf(stderr, "%s", usage);
+
return fuse_opt_add_arg(outargs, "-ho");
+
default:
+
return 1;
+
}
+
}
+
+
static const struct cuse_lowlevel_ops cusexmp_clop = {
+
.init = cusexmp_init,
+
.open = cusexmp_open,
+
.read = cusexmp_read,
+
.write = cusexmp_write,
+
.ioctl = cusexmp_ioctl,
+
};
+
+
int main(int argc, char **argv)
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct cusexmp_param param = { 0, 0, NULL, 0 };
+
char dev_name[128] = "DEVNAME=";
+
const char *dev_info_argv[] = { dev_name };
+
struct cuse_info ci;
+
int ret = 1;
+
+
if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+
printf("failed to parse option\n");
+
free(param.dev_name);
+
goto out;
+
}
+
+
if (!param.is_help) {
+
if (!param.dev_name) {
+
fprintf(stderr, "Error: device name missing\n");
+
goto out;
+
}
+
strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+
free(param.dev_name);
+
}
+
+
memset(&ci, 0, sizeof(ci));
+
ci.dev_major = param.major;
+
ci.dev_minor = param.minor;
+
ci.dev_info_argc = 1;
+
ci.dev_info_argv = dev_info_argv;
+
ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
+
ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL);
+
+
out:
+ +
return ret;
+
}
+
#define FUSE_IOCTL_COMPAT
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t no_interrupt
+ + +
+

Definition in file cuse.c.

+
+ + + + diff --git a/doc/html/example_2cuse_8c_source.html b/doc/html/example_2cuse_8c_source.html new file mode 100644 index 0000000..406ab6a --- /dev/null +++ b/doc/html/example_2cuse_8c_source.html @@ -0,0 +1,395 @@ + + + + + + + +libfuse: example/cuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse.c
+
+
+Go to the documentation of this file.
1/*
+
2 CUSE example: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8
+
9*/
+
10
+
34#define FUSE_USE_VERSION 31
+
35
+
36#include <cuse_lowlevel.h>
+
37#include <fuse_opt.h>
+
38#include <stddef.h>
+
39#include <stdio.h>
+
40#include <stdlib.h>
+
41#include <string.h>
+
42#include <unistd.h>
+
43#include <errno.h>
+
44
+
45#include "ioctl.h"
+
46
+
47static void *cusexmp_buf;
+
48static size_t cusexmp_size;
+
49
+
50static const char *usage =
+
51"usage: cusexmp [options]\n"
+
52"\n"
+
53"options:\n"
+
54" --help|-h print this help message\n"
+
55" --maj=MAJ|-M MAJ device major number\n"
+
56" --min=MIN|-m MIN device minor number\n"
+
57" --name=NAME|-n NAME device name (mandatory)\n"
+
58" -d -o debug enable debug output (implies -f)\n"
+
59" -f foreground operation\n"
+
60" -s disable multi-threaded operation\n"
+
61"\n";
+
62
+
63static int cusexmp_resize(size_t new_size)
+
64{
+
65 void *new_buf;
+
66
+
67 if (new_size == cusexmp_size)
+
68 return 0;
+
69
+
70 new_buf = realloc(cusexmp_buf, new_size);
+
71 if (!new_buf && new_size)
+
72 return -ENOMEM;
+
73
+
74 if (new_size > cusexmp_size)
+
75 memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
76
+
77 cusexmp_buf = new_buf;
+
78 cusexmp_size = new_size;
+
79
+
80 return 0;
+
81}
+
82
+
83static int cusexmp_expand(size_t new_size)
+
84{
+
85 if (new_size > cusexmp_size)
+
86 return cusexmp_resize(new_size);
+
87 return 0;
+
88}
+
89
+
90static void cusexmp_init(void *userdata, struct fuse_conn_info *conn)
+
91{
+
92 (void)userdata;
+
93
+
94 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
95 conn->no_interrupt = 1;
+
96}
+
97
+
98static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+
99{
+
100 fuse_reply_open(req, fi);
+
101}
+
102
+
103static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+
104 struct fuse_file_info *fi)
+
105{
+
106 (void)fi;
+
107
+
108 if (off >= cusexmp_size)
+
109 off = cusexmp_size;
+
110 if (size > cusexmp_size - off)
+
111 size = cusexmp_size - off;
+
112
+
113 fuse_reply_buf(req, cusexmp_buf + off, size);
+
114}
+
115
+
116static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+
117 off_t off, struct fuse_file_info *fi)
+
118{
+
119 (void)fi;
+
120
+
121 if (cusexmp_expand(off + size)) {
+
122 fuse_reply_err(req, ENOMEM);
+
123 return;
+
124 }
+
125
+
126 memcpy(cusexmp_buf + off, buf, size);
+
127 fuse_reply_write(req, size);
+
128}
+
129
+
130static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+
131 size_t in_bufsz, size_t out_bufsz, int is_read)
+
132{
+
133 const struct fioc_rw_arg *arg;
+
134 struct iovec in_iov[2], out_iov[3], iov[3];
+
135 size_t cur_size;
+
136
+
137 /* read in arg */
+
138 in_iov[0].iov_base = addr;
+
139 in_iov[0].iov_len = sizeof(*arg);
+
140 if (!in_bufsz) {
+
141 fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+
142 return;
+
143 }
+
144 arg = in_buf;
+
145 in_buf += sizeof(*arg);
+
146 in_bufsz -= sizeof(*arg);
+
147
+
148 /* prepare size outputs */
+
149 out_iov[0].iov_base =
+
150 addr + offsetof(struct fioc_rw_arg, prev_size);
+
151 out_iov[0].iov_len = sizeof(arg->prev_size);
+
152
+
153 out_iov[1].iov_base =
+
154 addr + offsetof(struct fioc_rw_arg, new_size);
+
155 out_iov[1].iov_len = sizeof(arg->new_size);
+
156
+
157 /* prepare client buf */
+
158 if (is_read) {
+
159 out_iov[2].iov_base = arg->buf;
+
160 out_iov[2].iov_len = arg->size;
+
161 if (!out_bufsz) {
+
162 fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+
163 return;
+
164 }
+
165 } else {
+
166 in_iov[1].iov_base = arg->buf;
+
167 in_iov[1].iov_len = arg->size;
+
168 if (arg->size && !in_bufsz) {
+
169 fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+
170 return;
+
171 }
+
172 }
+
173
+
174 /* we're all set */
+
175 cur_size = cusexmp_size;
+
176 iov[0].iov_base = &cur_size;
+
177 iov[0].iov_len = sizeof(cur_size);
+
178
+
179 iov[1].iov_base = &cusexmp_size;
+
180 iov[1].iov_len = sizeof(cusexmp_size);
+
181
+
182 if (is_read) {
+
183 size_t off = arg->offset;
+
184 size_t size = arg->size;
+
185
+
186 if (off >= cusexmp_size)
+
187 off = cusexmp_size;
+
188 if (size > cusexmp_size - off)
+
189 size = cusexmp_size - off;
+
190
+
191 iov[2].iov_base = cusexmp_buf + off;
+
192 iov[2].iov_len = size;
+
193 fuse_reply_ioctl_iov(req, size, iov, 3);
+
194 } else {
+
195 if (cusexmp_expand(arg->offset + in_bufsz)) {
+
196 fuse_reply_err(req, ENOMEM);
+
197 return;
+
198 }
+
199
+
200 memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+
201 fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+
202 }
+
203}
+
204
+
205static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+
206 struct fuse_file_info *fi, unsigned flags,
+
207 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
208{
+
209 int is_read = 0;
+
210
+
211 (void)fi;
+
212
+
213 if (flags & FUSE_IOCTL_COMPAT) {
+
214 fuse_reply_err(req, ENOSYS);
+
215 return;
+
216 }
+
217
+
218 switch (cmd) {
+
219 case FIOC_GET_SIZE:
+
220 if (!out_bufsz) {
+
221 struct iovec iov = { arg, sizeof(size_t) };
+
222
+
223 fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+
224 } else
+
225 fuse_reply_ioctl(req, 0, &cusexmp_size,
+
226 sizeof(cusexmp_size));
+
227 break;
+
228
+
229 case FIOC_SET_SIZE:
+
230 if (!in_bufsz) {
+
231 struct iovec iov = { arg, sizeof(size_t) };
+
232
+
233 fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+
234 } else {
+
235 cusexmp_resize(*(size_t *)in_buf);
+
236 fuse_reply_ioctl(req, 0, NULL, 0);
+
237 }
+
238 break;
+
239
+
240 case FIOC_READ:
+
241 is_read = 1;
+
242 /* fall through */
+
243 case FIOC_WRITE:
+
244 fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+
245 break;
+
246
+
247 default:
+
248 fuse_reply_err(req, EINVAL);
+
249 }
+
250}
+
251
+
252struct cusexmp_param {
+
253 unsigned major;
+
254 unsigned minor;
+
255 char *dev_name;
+
256 int is_help;
+
257};
+
258
+
259#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
260
+
261static const struct fuse_opt cusexmp_opts[] = {
+
262 CUSEXMP_OPT("-M %u", major),
+
263 CUSEXMP_OPT("--maj=%u", major),
+
264 CUSEXMP_OPT("-m %u", minor),
+
265 CUSEXMP_OPT("--min=%u", minor),
+
266 CUSEXMP_OPT("-n %s", dev_name),
+
267 CUSEXMP_OPT("--name=%s", dev_name),
+
268 FUSE_OPT_KEY("-h", 0),
+
269 FUSE_OPT_KEY("--help", 0),
+ +
271};
+
272
+
273static int cusexmp_process_arg(void *data, const char *arg, int key,
+
274 struct fuse_args *outargs)
+
275{
+
276 struct cusexmp_param *param = data;
+
277
+
278 (void)outargs;
+
279 (void)arg;
+
280
+
281 switch (key) {
+
282 case 0:
+
283 param->is_help = 1;
+
284 fprintf(stderr, "%s", usage);
+
285 return fuse_opt_add_arg(outargs, "-ho");
+
286 default:
+
287 return 1;
+
288 }
+
289}
+
290
+
291static const struct cuse_lowlevel_ops cusexmp_clop = {
+
292 .init = cusexmp_init,
+
293 .open = cusexmp_open,
+
294 .read = cusexmp_read,
+
295 .write = cusexmp_write,
+
296 .ioctl = cusexmp_ioctl,
+
297};
+
298
+
299int main(int argc, char **argv)
+
300{
+
301 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
302 struct cusexmp_param param = { 0, 0, NULL, 0 };
+
303 char dev_name[128] = "DEVNAME=";
+
304 const char *dev_info_argv[] = { dev_name };
+
305 struct cuse_info ci;
+
306 int ret = 1;
+
307
+
308 if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+
309 printf("failed to parse option\n");
+
310 free(param.dev_name);
+
311 goto out;
+
312 }
+
313
+
314 if (!param.is_help) {
+
315 if (!param.dev_name) {
+
316 fprintf(stderr, "Error: device name missing\n");
+
317 goto out;
+
318 }
+
319 strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+
320 free(param.dev_name);
+
321 }
+
322
+
323 memset(&ci, 0, sizeof(ci));
+
324 ci.dev_major = param.major;
+
325 ci.dev_minor = param.minor;
+
326 ci.dev_info_argc = 1;
+
327 ci.dev_info_argv = dev_info_argv;
+
328 ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
329
+
330 ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL);
+
331
+
332out:
+
333 fuse_opt_free_args(&args);
+
334 return ret;
+
335}
+
#define FUSE_IOCTL_COMPAT
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t no_interrupt
+ + +
+ + + + diff --git a/doc/html/example_2cuse__client_8c.html b/doc/html/example_2cuse__client_8c.html new file mode 100644 index 0000000..2eb578d --- /dev/null +++ b/doc/html/example_2cuse__client_8c.html @@ -0,0 +1,214 @@ + + + + + + + +libfuse: example/cuse_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
cuse_client.c File Reference
+
+
+
#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the cuse.c example file system.

+

Example usage (assuming that /dev/foobar is a CUSE device provided by the cuse.c example file system):

$ cuse_client /dev/foobar s
+0
+
+$ echo "hello" | cuse_client /dev/foobar w 6
+Writing 6 bytes
+transferred 6 bytes (0 -> 6)
+
+$ cuse_client /dev/foobar s
+6
+
+$ cuse_client /dev/foobar r 10
+hello
+transferred 6 bytes (6 -> 6)
+

Compiling this example

gcc -Wall cuse_client.c -o cuse_client
+

+Source Code

+
/*
+
FUSE fioclient: FUSE ioctl example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#include <sys/types.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <sys/ioctl.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <ctype.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include "ioctl.h"
+
+
const char *usage =
+
"Usage: cuse_client FIOC_FILE COMMAND\n"
+
"\n"
+
"COMMANDS\n"
+
" s [SIZE] : get size if SIZE is omitted, set size otherwise\n"
+
" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+
" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+
"\n";
+
+
static int do_rw(int fd, int is_read, size_t size, off_t offset,
+
size_t *prev_size, size_t *new_size)
+
{
+
struct fioc_rw_arg arg = { .offset = offset };
+
ssize_t ret;
+
+
arg.buf = calloc(1, size);
+
if (!arg.buf) {
+
fprintf(stderr, "failed to allocated %zu bytes\n", size);
+
return -1;
+
}
+
+
if (is_read) {
+
arg.size = size;
+
ret = ioctl(fd, FIOC_READ, &arg);
+
if (ret >= 0)
+
fwrite(arg.buf, 1, ret, stdout);
+
} else {
+
arg.size = fread(arg.buf, 1, size, stdin);
+
fprintf(stderr, "Writing %zu bytes\n", arg.size);
+
ret = ioctl(fd, FIOC_WRITE, &arg);
+
}
+
+
if (ret >= 0) {
+
*prev_size = arg.prev_size;
+
*new_size = arg.new_size;
+
} else
+
perror("ioctl");
+
+
free(arg.buf);
+
return ret;
+
}
+
+
int main(int argc, char **argv)
+
{
+
size_t param[2] = { };
+
size_t size, prev_size = 0, new_size = 0;
+
char cmd;
+
int fd, i, rc;
+
+
if (argc < 3)
+
goto usage;
+
+
fd = open(argv[1], O_RDWR);
+
if (fd < 0) {
+
perror("open");
+
return 1;
+
}
+
+
cmd = tolower(argv[2][0]);
+
argc -= 3;
+
argv += 3;
+
+
for (i = 0; i < argc; i++) {
+
char *endp;
+
param[i] = strtoul(argv[i], &endp, 0);
+
if (endp == argv[i] || *endp != '\0')
+
goto usage;
+
}
+
+
switch (cmd) {
+
case 's':
+
if (!argc) {
+
if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
perror("ioctl");
+
goto error;
+
}
+
printf("%zu\n", size);
+
} else {
+
size = param[0];
+
if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
perror("ioctl");
+
goto error;
+
}
+
}
+
close(fd);
+
return 0;
+
+
case 'r':
+
case 'w':
+
rc = do_rw(fd, cmd == 'r', param[0], param[1],
+
&prev_size, &new_size);
+
if (rc < 0)
+
goto error;
+
fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+
rc, prev_size, new_size);
+
close(fd);
+
return 0;
+
}
+
+
usage:
+
fprintf(stderr, "%s", usage);
+
return 1;
+
+
error:
+
close(fd);
+
return 1;
+
}
+
+

Definition in file cuse_client.c.

+
+ + + + diff --git a/doc/html/example_2cuse__client_8c_source.html b/doc/html/example_2cuse__client_8c_source.html new file mode 100644 index 0000000..7e6096c --- /dev/null +++ b/doc/html/example_2cuse__client_8c_source.html @@ -0,0 +1,188 @@ + + + + + + + +libfuse: example/cuse_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioclient: FUSE ioctl example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
40#include <sys/types.h>
+
41#include <fcntl.h>
+
42#include <sys/stat.h>
+
43#include <sys/ioctl.h>
+
44#include <stdio.h>
+
45#include <stdlib.h>
+
46#include <ctype.h>
+
47#include <errno.h>
+
48#include <unistd.h>
+
49#include "ioctl.h"
+
50
+
51const char *usage =
+
52"Usage: cuse_client FIOC_FILE COMMAND\n"
+
53"\n"
+
54"COMMANDS\n"
+
55" s [SIZE] : get size if SIZE is omitted, set size otherwise\n"
+
56" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+
57" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+
58"\n";
+
59
+
60static int do_rw(int fd, int is_read, size_t size, off_t offset,
+
61 size_t *prev_size, size_t *new_size)
+
62{
+
63 struct fioc_rw_arg arg = { .offset = offset };
+
64 ssize_t ret;
+
65
+
66 arg.buf = calloc(1, size);
+
67 if (!arg.buf) {
+
68 fprintf(stderr, "failed to allocated %zu bytes\n", size);
+
69 return -1;
+
70 }
+
71
+
72 if (is_read) {
+
73 arg.size = size;
+
74 ret = ioctl(fd, FIOC_READ, &arg);
+
75 if (ret >= 0)
+
76 fwrite(arg.buf, 1, ret, stdout);
+
77 } else {
+
78 arg.size = fread(arg.buf, 1, size, stdin);
+
79 fprintf(stderr, "Writing %zu bytes\n", arg.size);
+
80 ret = ioctl(fd, FIOC_WRITE, &arg);
+
81 }
+
82
+
83 if (ret >= 0) {
+
84 *prev_size = arg.prev_size;
+
85 *new_size = arg.new_size;
+
86 } else
+
87 perror("ioctl");
+
88
+
89 free(arg.buf);
+
90 return ret;
+
91}
+
92
+
93int main(int argc, char **argv)
+
94{
+
95 size_t param[2] = { };
+
96 size_t size, prev_size = 0, new_size = 0;
+
97 char cmd;
+
98 int fd, i, rc;
+
99
+
100 if (argc < 3)
+
101 goto usage;
+
102
+
103 fd = open(argv[1], O_RDWR);
+
104 if (fd < 0) {
+
105 perror("open");
+
106 return 1;
+
107 }
+
108
+
109 cmd = tolower(argv[2][0]);
+
110 argc -= 3;
+
111 argv += 3;
+
112
+
113 for (i = 0; i < argc; i++) {
+
114 char *endp;
+
115 param[i] = strtoul(argv[i], &endp, 0);
+
116 if (endp == argv[i] || *endp != '\0')
+
117 goto usage;
+
118 }
+
119
+
120 switch (cmd) {
+
121 case 's':
+
122 if (!argc) {
+
123 if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
124 perror("ioctl");
+
125 goto error;
+
126 }
+
127 printf("%zu\n", size);
+
128 } else {
+
129 size = param[0];
+
130 if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
131 perror("ioctl");
+
132 goto error;
+
133 }
+
134 }
+
135 close(fd);
+
136 return 0;
+
137
+
138 case 'r':
+
139 case 'w':
+
140 rc = do_rw(fd, cmd == 'r', param[0], param[1],
+
141 &prev_size, &new_size);
+
142 if (rc < 0)
+
143 goto error;
+
144 fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+
145 rc, prev_size, new_size);
+
146 close(fd);
+
147 return 0;
+
148 }
+
149
+
150usage:
+
151 fprintf(stderr, "%s", usage);
+
152 return 1;
+
153
+
154error:
+
155 close(fd);
+
156 return 1;
+
157}
+ +
+ + + + diff --git a/doc/html/example_2hello_8c.html b/doc/html/example_2hello_8c.html new file mode 100644 index 0000000..6534efc --- /dev/null +++ b/doc/html/example_2hello_8c.html @@ -0,0 +1,265 @@ + + + + + + + +libfuse: example/hello.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using high-level API

+

Compile with:

gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <stddef.h>
+
#include <assert.h>
+
+
/*
+
* Command line options
+
*
+
* We can't set default values for the char* fields here because
+
* fuse_opt_parse would attempt to free() them when the user specifies
+
* different values on the command line.
+
*/
+
static struct options {
+
const char *filename;
+
const char *contents;
+
int show_help;
+
} options;
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--name=%s", filename),
+
OPTION("--contents=%s", contents),
+
OPTION("-h", show_help),
+
OPTION("--help", show_help),
+ +
};
+
+
static void *hello_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->kernel_cache = 1;
+
+
/* Test setting flags the old way */
+ + +
+
return NULL;
+
}
+
+
static int hello_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res = 0;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
} else if (strcmp(path+1, options.filename) == 0) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(options.contents);
+
} else
+
res = -ENOENT;
+
+
return res;
+
}
+
+
static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int hello_open(const char *path, struct fuse_file_info *fi)
+
{
+
if (strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
+
return 0;
+
}
+
+
static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
size_t len;
+
(void) fi;
+
if(strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
len = strlen(options.contents);
+
if (offset < len) {
+
if (offset + size > len)
+
size = len - offset;
+
memcpy(buf, options.contents + offset, size);
+
} else
+
size = 0;
+
+
return size;
+
}
+
+
static const struct fuse_operations hello_oper = {
+
.init = hello_init,
+
.getattr = hello_getattr,
+
.readdir = hello_readdir,
+
.open = hello_open,
+
.read = hello_read,
+
};
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --name=<s> Name of the \"hello\" file\n"
+
" (default: \"hello\")\n"
+
" --contents=<s> Contents \"hello\" file\n"
+
" (default \"Hello, World!\\n\")\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[])
+
{
+
int ret;
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
+
/* Set defaults -- we have to use strdup so that
+
fuse_opt_parse can free the defaults if other
+
values are specified */
+
options.filename = strdup("hello");
+
options.contents = strdup("Hello World!\n");
+
+
/* Parse options */
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
/* When --help is specified, first print our own file-system
+
specific help text, then signal fuse_main to show
+
additional help (by adding `--help` to the options again)
+
without usage: line (by setting argv[0] to the empty
+
string) */
+
if (options.show_help) {
+
show_help(argv[0]);
+
assert(fuse_opt_add_arg(&args, "--help") == 0);
+
args.argv[0][0] = '\0';
+
}
+
+
ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+ +
return ret;
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_ASYNC_READ
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file hello.c.

+
+ + + + diff --git a/doc/html/example_2hello_8c_source.html b/doc/html/example_2hello_8c_source.html new file mode 100644 index 0000000..17a4d8b --- /dev/null +++ b/doc/html/example_2hello_8c_source.html @@ -0,0 +1,254 @@ + + + + + + + +libfuse: example/hello.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
22#define FUSE_USE_VERSION 31
+
23
+
24#include <fuse.h>
+
25#include <stdio.h>
+
26#include <string.h>
+
27#include <errno.h>
+
28#include <fcntl.h>
+
29#include <stddef.h>
+
30#include <assert.h>
+
31
+
32/*
+
33 * Command line options
+
34 *
+
35 * We can't set default values for the char* fields here because
+
36 * fuse_opt_parse would attempt to free() them when the user specifies
+
37 * different values on the command line.
+
38 */
+
39static struct options {
+
40 const char *filename;
+
41 const char *contents;
+
42 int show_help;
+
43} options;
+
44
+
45#define OPTION(t, p) \
+
46 { t, offsetof(struct options, p), 1 }
+
47static const struct fuse_opt option_spec[] = {
+
48 OPTION("--name=%s", filename),
+
49 OPTION("--contents=%s", contents),
+
50 OPTION("-h", show_help),
+
51 OPTION("--help", show_help),
+ +
53};
+
54
+
55static void *hello_init(struct fuse_conn_info *conn,
+
56 struct fuse_config *cfg)
+
57{
+
58 (void) conn;
+
59 cfg->kernel_cache = 1;
+
60
+
61 /* Test setting flags the old way */
+ + +
64
+
65 return NULL;
+
66}
+
67
+
68static int hello_getattr(const char *path, struct stat *stbuf,
+
69 struct fuse_file_info *fi)
+
70{
+
71 (void) fi;
+
72 int res = 0;
+
73
+
74 memset(stbuf, 0, sizeof(struct stat));
+
75 if (strcmp(path, "/") == 0) {
+
76 stbuf->st_mode = S_IFDIR | 0755;
+
77 stbuf->st_nlink = 2;
+
78 } else if (strcmp(path+1, options.filename) == 0) {
+
79 stbuf->st_mode = S_IFREG | 0444;
+
80 stbuf->st_nlink = 1;
+
81 stbuf->st_size = strlen(options.contents);
+
82 } else
+
83 res = -ENOENT;
+
84
+
85 return res;
+
86}
+
87
+
88static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
89 off_t offset, struct fuse_file_info *fi,
+
90 enum fuse_readdir_flags flags)
+
91{
+
92 (void) offset;
+
93 (void) fi;
+
94 (void) flags;
+
95
+
96 if (strcmp(path, "/") != 0)
+
97 return -ENOENT;
+
98
+
99 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
100 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
101 filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
102
+
103 return 0;
+
104}
+
105
+
106static int hello_open(const char *path, struct fuse_file_info *fi)
+
107{
+
108 if (strcmp(path+1, options.filename) != 0)
+
109 return -ENOENT;
+
110
+
111 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
112 return -EACCES;
+
113
+
114 return 0;
+
115}
+
116
+
117static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
118 struct fuse_file_info *fi)
+
119{
+
120 size_t len;
+
121 (void) fi;
+
122 if(strcmp(path+1, options.filename) != 0)
+
123 return -ENOENT;
+
124
+
125 len = strlen(options.contents);
+
126 if (offset < len) {
+
127 if (offset + size > len)
+
128 size = len - offset;
+
129 memcpy(buf, options.contents + offset, size);
+
130 } else
+
131 size = 0;
+
132
+
133 return size;
+
134}
+
135
+
136static const struct fuse_operations hello_oper = {
+
137 .init = hello_init,
+
138 .getattr = hello_getattr,
+
139 .readdir = hello_readdir,
+
140 .open = hello_open,
+
141 .read = hello_read,
+
142};
+
143
+
144static void show_help(const char *progname)
+
145{
+
146 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
147 printf("File-system specific options:\n"
+
148 " --name=<s> Name of the \"hello\" file\n"
+
149 " (default: \"hello\")\n"
+
150 " --contents=<s> Contents \"hello\" file\n"
+
151 " (default \"Hello, World!\\n\")\n"
+
152 "\n");
+
153}
+
154
+
155int main(int argc, char *argv[])
+
156{
+
157 int ret;
+
158 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
159
+
160 /* Set defaults -- we have to use strdup so that
+
161 fuse_opt_parse can free the defaults if other
+
162 values are specified */
+
163 options.filename = strdup("hello");
+
164 options.contents = strdup("Hello World!\n");
+
165
+
166 /* Parse options */
+
167 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
168 return 1;
+
169
+
170 /* When --help is specified, first print our own file-system
+
171 specific help text, then signal fuse_main to show
+
172 additional help (by adding `--help` to the options again)
+
173 without usage: line (by setting argv[0] to the empty
+
174 string) */
+
175 if (options.show_help) {
+
176 show_help(argv[0]);
+
177 assert(fuse_opt_add_arg(&args, "--help") == 0);
+
178 args.argv[0][0] = '\0';
+
179 }
+
180
+
181 ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+
182 fuse_opt_free_args(&args);
+
183 return ret;
+
184}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_ASYNC_READ
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/example_2hello__ll_8c.html b/doc/html/example_2hello__ll_8c.html new file mode 100644 index 0000000..27d6bf5 --- /dev/null +++ b/doc/html/example_2hello__ll_8c.html @@ -0,0 +1,389 @@ + + + + + + + +libfuse: example/hello_ll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello_ll.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using low-level API

+

Compile with:

gcc -Wall hello_ll.c `pkg-config fuse3 --cflags --libs` -o hello_ll
+

Note: If the pkg-config command fails due to the absence of the fuse3.pc file, you should configure the path to the fuse3.pc file in the PKG_CONFIG_PATH variable.

+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <assert.h>
+
+
static const char *hello_str = "Hello World!\n";
+
static const char *hello_name = "hello";
+
+
static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
{
+
stbuf->st_ino = ino;
+
switch (ino) {
+
case 1:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
+
case 2:
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(hello_str);
+
break;
+
+
default:
+
return -1;
+
}
+
return 0;
+
}
+
+
static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
+
/* Test setting flags the old way */
+ +
conn->want &= ~FUSE_CAP_ASYNC_READ;
+
}
+
+
static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (hello_stat(ino, &stbuf) == -1)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, 1.0);
+
}
+
+
static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
+
if (parent != 1 || strcmp(name, hello_name) != 0)
+
fuse_reply_err(req, ENOENT);
+
else {
+
memset(&e, 0, sizeof(e));
+
e.ino = 2;
+
e.attr_timeout = 1.0;
+
e.entry_timeout = 1.0;
+
hello_stat(e.ino, &e.attr);
+
+
fuse_reply_entry(req, &e);
+
}
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+ +
{
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize)
+
{
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (ino != 1)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, ".", 1);
+
dirbuf_add(req, &b, "..", 1);
+
dirbuf_add(req, &b, hello_name, 2);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
if (ino != 2)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else
+
fuse_reply_open(req, fi);
+
}
+
+
static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
assert(ino == 2);
+
reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
}
+
+
static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
{
+
const char *buf = "hello_ll_getxattr_value";
+
fuse_reply_buf(req, buf, strlen(buf));
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
(void)flags;
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
const char* exp_val = "hello_ll_setxattr_value";
+
if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
strlen(exp_val) == size &&
+
strncmp(value, exp_val, size) == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops hello_ll_oper = {
+
.init = hello_ll_init,
+
.lookup = hello_ll_lookup,
+
.getattr = hello_ll_getattr,
+
.readdir = hello_ll_readdir,
+
.open = hello_ll_open,
+
.read = hello_ll_read,
+
.setxattr = hello_ll_setxattr,
+
.getxattr = hello_ll_getxattr,
+
.removexattr = hello_ll_removexattr,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
se = fuse_session_new(&args, &hello_ll_oper,
+
sizeof(hello_ll_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file hello_ll.c.

+
+ + + + diff --git a/doc/html/example_2hello__ll_8c_source.html b/doc/html/example_2hello__ll_8c_source.html new file mode 100644 index 0000000..f1e9a1c --- /dev/null +++ b/doc/html/example_2hello__ll_8c_source.html @@ -0,0 +1,376 @@ + + + + + + + +libfuse: example/hello_ll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello_ll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
25#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
26
+
27#include <fuse_lowlevel.h>
+
28#include <stdio.h>
+
29#include <stdlib.h>
+
30#include <string.h>
+
31#include <errno.h>
+
32#include <fcntl.h>
+
33#include <unistd.h>
+
34#include <assert.h>
+
35
+
36static const char *hello_str = "Hello World!\n";
+
37static const char *hello_name = "hello";
+
38
+
39static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
40{
+
41 stbuf->st_ino = ino;
+
42 switch (ino) {
+
43 case 1:
+
44 stbuf->st_mode = S_IFDIR | 0755;
+
45 stbuf->st_nlink = 2;
+
46 break;
+
47
+
48 case 2:
+
49 stbuf->st_mode = S_IFREG | 0444;
+
50 stbuf->st_nlink = 1;
+
51 stbuf->st_size = strlen(hello_str);
+
52 break;
+
53
+
54 default:
+
55 return -1;
+
56 }
+
57 return 0;
+
58}
+
59
+
60static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
61{
+
62 (void)userdata;
+
63
+
64 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
65 conn->no_interrupt = 1;
+
66
+
67 /* Test setting flags the old way */
+ +
69 conn->want &= ~FUSE_CAP_ASYNC_READ;
+
70}
+
71
+
72static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
73 struct fuse_file_info *fi)
+
74{
+
75 struct stat stbuf;
+
76
+
77 (void) fi;
+
78
+
79 memset(&stbuf, 0, sizeof(stbuf));
+
80 if (hello_stat(ino, &stbuf) == -1)
+
81 fuse_reply_err(req, ENOENT);
+
82 else
+
83 fuse_reply_attr(req, &stbuf, 1.0);
+
84}
+
85
+
86static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
87{
+
88 struct fuse_entry_param e;
+
89
+
90 if (parent != 1 || strcmp(name, hello_name) != 0)
+
91 fuse_reply_err(req, ENOENT);
+
92 else {
+
93 memset(&e, 0, sizeof(e));
+
94 e.ino = 2;
+
95 e.attr_timeout = 1.0;
+
96 e.entry_timeout = 1.0;
+
97 hello_stat(e.ino, &e.attr);
+
98
+
99 fuse_reply_entry(req, &e);
+
100 }
+
101}
+
102
+
103struct dirbuf {
+
104 char *p;
+
105 size_t size;
+
106};
+
107
+
108static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
109 fuse_ino_t ino)
+
110{
+
111 struct stat stbuf;
+
112 size_t oldsize = b->size;
+
113 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
114 b->p = (char *) realloc(b->p, b->size);
+
115 memset(&stbuf, 0, sizeof(stbuf));
+
116 stbuf.st_ino = ino;
+
117 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
118 b->size);
+
119}
+
120
+
121#define min(x, y) ((x) < (y) ? (x) : (y))
+
122
+
123static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
124 off_t off, size_t maxsize)
+
125{
+
126 if (off < bufsize)
+
127 return fuse_reply_buf(req, buf + off,
+
128 min(bufsize - off, maxsize));
+
129 else
+
130 return fuse_reply_buf(req, NULL, 0);
+
131}
+
132
+
133static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
134 off_t off, struct fuse_file_info *fi)
+
135{
+
136 (void) fi;
+
137
+
138 if (ino != 1)
+
139 fuse_reply_err(req, ENOTDIR);
+
140 else {
+
141 struct dirbuf b;
+
142
+
143 memset(&b, 0, sizeof(b));
+
144 dirbuf_add(req, &b, ".", 1);
+
145 dirbuf_add(req, &b, "..", 1);
+
146 dirbuf_add(req, &b, hello_name, 2);
+
147 reply_buf_limited(req, b.p, b.size, off, size);
+
148 free(b.p);
+
149 }
+
150}
+
151
+
152static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
153 struct fuse_file_info *fi)
+
154{
+
155 if (ino != 2)
+
156 fuse_reply_err(req, EISDIR);
+
157 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
158 fuse_reply_err(req, EACCES);
+
159 else
+
160 fuse_reply_open(req, fi);
+
161}
+
162
+
163static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
164 off_t off, struct fuse_file_info *fi)
+
165{
+
166 (void) fi;
+
167
+
168 assert(ino == 2);
+
169 reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
170}
+
171
+
172static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
173 size_t size)
+
174{
+
175 (void)size;
+
176 assert(ino == 1 || ino == 2);
+
177 if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
178 {
+
179 const char *buf = "hello_ll_getxattr_value";
+
180 fuse_reply_buf(req, buf, strlen(buf));
+
181 }
+
182 else
+
183 {
+
184 fuse_reply_err(req, ENOTSUP);
+
185 }
+
186}
+
187
+
188static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
189 const char *value, size_t size, int flags)
+
190{
+
191 (void)flags;
+
192 (void)size;
+
193 assert(ino == 1 || ino == 2);
+
194 const char* exp_val = "hello_ll_setxattr_value";
+
195 if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
196 strlen(exp_val) == size &&
+
197 strncmp(value, exp_val, size) == 0)
+
198 {
+
199 fuse_reply_err(req, 0);
+
200 }
+
201 else
+
202 {
+
203 fuse_reply_err(req, ENOTSUP);
+
204 }
+
205}
+
206
+
207static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
208{
+
209 assert(ino == 1 || ino == 2);
+
210 if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
211 {
+
212 fuse_reply_err(req, 0);
+
213 }
+
214 else
+
215 {
+
216 fuse_reply_err(req, ENOTSUP);
+
217 }
+
218}
+
219
+
220static const struct fuse_lowlevel_ops hello_ll_oper = {
+
221 .init = hello_ll_init,
+
222 .lookup = hello_ll_lookup,
+
223 .getattr = hello_ll_getattr,
+
224 .readdir = hello_ll_readdir,
+
225 .open = hello_ll_open,
+
226 .read = hello_ll_read,
+
227 .setxattr = hello_ll_setxattr,
+
228 .getxattr = hello_ll_getxattr,
+
229 .removexattr = hello_ll_removexattr,
+
230};
+
231
+
232int main(int argc, char *argv[])
+
233{
+
234 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
235 struct fuse_session *se;
+
236 struct fuse_cmdline_opts opts;
+
237 struct fuse_loop_config *config;
+
238 int ret = -1;
+
239
+
240 if (fuse_parse_cmdline(&args, &opts) != 0)
+
241 return 1;
+
242 if (opts.show_help) {
+
243 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
246 ret = 0;
+
247 goto err_out1;
+
248 } else if (opts.show_version) {
+
249 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
251 ret = 0;
+
252 goto err_out1;
+
253 }
+
254
+
255 if(opts.mountpoint == NULL) {
+
256 printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
257 printf(" %s --help\n", argv[0]);
+
258 ret = 1;
+
259 goto err_out1;
+
260 }
+
261
+
262 se = fuse_session_new(&args, &hello_ll_oper,
+
263 sizeof(hello_ll_oper), NULL);
+
264 if (se == NULL)
+
265 goto err_out1;
+
266
+
267 if (fuse_set_signal_handlers(se) != 0)
+
268 goto err_out2;
+
269
+
270 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
271 goto err_out3;
+
272
+
273 fuse_daemonize(opts.foreground);
+
274
+
275 /* Block until ctrl+c or fusermount -u */
+
276 if (opts.singlethread)
+
277 ret = fuse_session_loop(se);
+
278 else {
+
279 config = fuse_loop_cfg_create();
+
280 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
281 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
282 ret = fuse_session_loop_mt(se, config);
+
283 fuse_loop_cfg_destroy(config);
+
284 config = NULL;
+
285 }
+
286
+ +
288err_out3:
+ +
290err_out2:
+ +
292err_out1:
+
293 free(opts.mountpoint);
+
294 fuse_opt_free_args(&args);
+
295
+
296 return ret ? 1 : 0;
+
297}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/example_2hello__ll__uds_8c.html b/doc/html/example_2hello__ll__uds_8c.html new file mode 100644 index 0000000..4c5a666 --- /dev/null +++ b/doc/html/example_2hello__ll__uds_8c.html @@ -0,0 +1,391 @@ + + + + + + + +libfuse: example/hello_ll_uds.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello_ll_uds.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <fuse_kernel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using low-level API and a custom io. This custom io is implemented using UNIX domain sockets (of type SOCK_STREAM)

+

Compile with:

gcc -Wall hello_ll_uds.c `pkg-config fuse3 --cflags --libs` -o hello_ll_uds
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <assert.h>
+
+
static const char *hello_str = "Hello World!\n";
+
static const char *hello_name = "hello";
+
+
static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
{
+
stbuf->st_ino = ino;
+
switch (ino) {
+
case 1:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
+
case 2:
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(hello_str);
+
break;
+
+
default:
+
return -1;
+
}
+
return 0;
+
}
+
+
static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
+
/* Test setting flags the old way */
+ +
conn->want &= ~FUSE_CAP_ASYNC_READ;
+
}
+
+
static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (hello_stat(ino, &stbuf) == -1)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, 1.0);
+
}
+
+
static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
+
if (parent != 1 || strcmp(name, hello_name) != 0)
+
fuse_reply_err(req, ENOENT);
+
else {
+
memset(&e, 0, sizeof(e));
+
e.ino = 2;
+
e.attr_timeout = 1.0;
+
e.entry_timeout = 1.0;
+
hello_stat(e.ino, &e.attr);
+
+
fuse_reply_entry(req, &e);
+
}
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+ +
{
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize)
+
{
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (ino != 1)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, ".", 1);
+
dirbuf_add(req, &b, "..", 1);
+
dirbuf_add(req, &b, hello_name, 2);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
if (ino != 2)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else
+
fuse_reply_open(req, fi);
+
}
+
+
static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
assert(ino == 2);
+
reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
}
+
+
static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
{
+
const char *buf = "hello_ll_getxattr_value";
+
fuse_reply_buf(req, buf, strlen(buf));
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
(void)flags;
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
const char* exp_val = "hello_ll_setxattr_value";
+
if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
strlen(exp_val) == size &&
+
strncmp(value, exp_val, size) == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops hello_ll_oper = {
+
.init = hello_ll_init,
+
.lookup = hello_ll_lookup,
+
.getattr = hello_ll_getattr,
+
.readdir = hello_ll_readdir,
+
.open = hello_ll_open,
+
.read = hello_ll_read,
+
.setxattr = hello_ll_setxattr,
+
.getxattr = hello_ll_getxattr,
+
.removexattr = hello_ll_removexattr,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
se = fuse_session_new(&args, &hello_ll_oper,
+
sizeof(hello_ll_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file hello_ll_uds.c.

+
+ + + + diff --git a/doc/html/example_2hello__ll__uds_8c_source.html b/doc/html/example_2hello__ll__uds_8c_source.html new file mode 100644 index 0000000..102e317 --- /dev/null +++ b/doc/html/example_2hello__ll__uds_8c_source.html @@ -0,0 +1,442 @@ + + + + + + + +libfuse: example/hello_ll_uds.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello_ll_uds.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2022 Tofik Sonono <tofik.sonono@intel.com>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
23#define FUSE_USE_VERSION 34
+
24
+
25
+
26#ifndef _GNU_SOURCE
+
27#define _GNU_SOURCE
+
28#endif
+
29
+
30#include <fuse_lowlevel.h>
+
31#include <fuse_kernel.h>
+
32#include <stdio.h>
+
33#include <stdlib.h>
+
34#include <string.h>
+
35#include <errno.h>
+
36#include <fcntl.h>
+
37#include <unistd.h>
+
38#include <assert.h>
+
39#include <sys/socket.h>
+
40#include <sys/un.h>
+
41
+
42static const char *hello_str = "Hello World!\n";
+
43static const char *hello_name = "hello";
+
44
+
45static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
46{
+
47 stbuf->st_ino = ino;
+
48 switch (ino) {
+
49 case 1:
+
50 stbuf->st_mode = S_IFDIR | 0755;
+
51 stbuf->st_nlink = 2;
+
52 break;
+
53
+
54 case 2:
+
55 stbuf->st_mode = S_IFREG | 0444;
+
56 stbuf->st_nlink = 1;
+
57 stbuf->st_size = strlen(hello_str);
+
58 break;
+
59
+
60 default:
+
61 return -1;
+
62 }
+
63 return 0;
+
64}
+
65
+
66static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
67 struct fuse_file_info *fi)
+
68{
+
69 struct stat stbuf;
+
70
+
71 (void) fi;
+
72
+
73 memset(&stbuf, 0, sizeof(stbuf));
+
74 if (hello_stat(ino, &stbuf) == -1)
+
75 fuse_reply_err(req, ENOENT);
+
76 else
+
77 fuse_reply_attr(req, &stbuf, 1.0);
+
78}
+
79
+
80static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
81{
+
82 (void)userdata;
+
83
+
84 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
85 conn->no_interrupt = 1;
+
86}
+
87
+
88static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
89{
+
90 struct fuse_entry_param e;
+
91
+
92 if (parent != 1 || strcmp(name, hello_name) != 0)
+
93 fuse_reply_err(req, ENOENT);
+
94 else {
+
95 memset(&e, 0, sizeof(e));
+
96 e.ino = 2;
+
97 e.attr_timeout = 1.0;
+
98 e.entry_timeout = 1.0;
+
99 hello_stat(e.ino, &e.attr);
+
100
+
101 fuse_reply_entry(req, &e);
+
102 }
+
103}
+
104
+
105struct dirbuf {
+
106 char *p;
+
107 size_t size;
+
108};
+
109
+
110static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
111 fuse_ino_t ino)
+
112{
+
113 struct stat stbuf;
+
114 size_t oldsize = b->size;
+
115 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
116 b->p = (char *) realloc(b->p, b->size);
+
117 memset(&stbuf, 0, sizeof(stbuf));
+
118 stbuf.st_ino = ino;
+
119 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
120 b->size);
+
121}
+
122
+
123#define min(x, y) ((x) < (y) ? (x) : (y))
+
124
+
125static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
126 off_t off, size_t maxsize)
+
127{
+
128 if (off < bufsize)
+
129 return fuse_reply_buf(req, buf + off,
+
130 min(bufsize - off, maxsize));
+
131 else
+
132 return fuse_reply_buf(req, NULL, 0);
+
133}
+
134
+
135static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
136 off_t off, struct fuse_file_info *fi)
+
137{
+
138 (void) fi;
+
139
+
140 if (ino != 1)
+
141 fuse_reply_err(req, ENOTDIR);
+
142 else {
+
143 struct dirbuf b;
+
144
+
145 memset(&b, 0, sizeof(b));
+
146 dirbuf_add(req, &b, ".", 1);
+
147 dirbuf_add(req, &b, "..", 1);
+
148 dirbuf_add(req, &b, hello_name, 2);
+
149 reply_buf_limited(req, b.p, b.size, off, size);
+
150 free(b.p);
+
151 }
+
152}
+
153
+
154static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
155 struct fuse_file_info *fi)
+
156{
+
157 if (ino != 2)
+
158 fuse_reply_err(req, EISDIR);
+
159 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
160 fuse_reply_err(req, EACCES);
+
161 else
+
162 fuse_reply_open(req, fi);
+
163}
+
164
+
165static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
166 off_t off, struct fuse_file_info *fi)
+
167{
+
168 (void) fi;
+
169
+
170 assert(ino == 2);
+
171 reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
172}
+
173
+
174static const struct fuse_lowlevel_ops hello_ll_oper = {
+
175 .init = hello_ll_init,
+
176 .lookup = hello_ll_lookup,
+
177 .getattr = hello_ll_getattr,
+
178 .readdir = hello_ll_readdir,
+
179 .open = hello_ll_open,
+
180 .read = hello_ll_read,
+
181};
+
182
+
183static int create_socket(const char *socket_path) {
+
184 struct sockaddr_un addr;
+
185
+
186 if (strnlen(socket_path, sizeof(addr.sun_path)) >=
+
187 sizeof(addr.sun_path)) {
+
188 printf("Socket path may not be longer than %zu characters\n",
+
189 sizeof(addr.sun_path) - 1);
+
190 return -1;
+
191 }
+
192
+
193 if (remove(socket_path) == -1 && errno != ENOENT) {
+
194 printf("Could not delete previous socket file entry at %s. Error: "
+
195 "%s\n", socket_path, strerror(errno));
+
196 return -1;
+
197 }
+
198
+
199 memset(&addr, 0, sizeof(struct sockaddr_un));
+
200 strcpy(addr.sun_path, socket_path);
+
201
+
202 int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+
203 if (sfd == -1) {
+
204 printf("Could not create socket. Error: %s\n", strerror(errno));
+
205 return -1;
+
206 }
+
207
+
208 addr.sun_family = AF_UNIX;
+
209 if (bind(sfd, (struct sockaddr *) &addr,
+
210 sizeof(struct sockaddr_un)) == -1) {
+
211 printf("Could not bind socket. Error: %s\n", strerror(errno));
+
212 return -1;
+
213 }
+
214
+
215 if (listen(sfd, 1) == -1)
+
216 return -1;
+
217
+
218 printf("Awaiting connection on socket at %s...\n", socket_path);
+
219 int cfd = accept(sfd, NULL, NULL);
+
220 if (cfd == -1) {
+
221 printf("Could not accept connection. Error: %s\n",
+
222 strerror(errno));
+
223 return -1;
+
224 } else {
+
225 printf("Accepted connection!\n");
+
226 }
+
227 return cfd;
+
228}
+
229
+
230static ssize_t stream_writev(int fd, struct iovec *iov, int count,
+
231 void *userdata) {
+
232 (void)userdata;
+
233
+
234 ssize_t written = 0;
+
235 int cur = 0;
+
236 for (;;) {
+
237 written = writev(fd, iov+cur, count-cur);
+
238 if (written < 0)
+
239 return written;
+
240
+
241 while (cur < count && written >= iov[cur].iov_len)
+
242 written -= iov[cur++].iov_len;
+
243 if (cur == count)
+
244 break;
+
245
+
246 iov[cur].iov_base = (char *)iov[cur].iov_base + written;
+
247 iov[cur].iov_len -= written;
+
248 }
+
249 return written;
+
250}
+
251
+
252
+
253static ssize_t readall(int fd, void *buf, size_t len) {
+
254 size_t count = 0;
+
255
+
256 while (count < len) {
+
257 int i = read(fd, (char *)buf + count, len - count);
+
258 if (!i)
+
259 break;
+
260
+
261 if (i < 0)
+
262 return i;
+
263
+
264 count += i;
+
265 }
+
266 return count;
+
267}
+
268
+
269static ssize_t stream_read(int fd, void *buf, size_t buf_len, void *userdata) {
+
270 (void)userdata;
+
271
+
272 int res = readall(fd, buf, sizeof(struct fuse_in_header));
+
273 if (res == -1)
+
274 return res;
+
275
+
276
+
277 uint32_t packet_len = ((struct fuse_in_header *)buf)->len;
+
278 if (packet_len > buf_len)
+
279 return -1;
+
280
+
281 int prev_res = res;
+
282
+
283 res = readall(fd, (char *)buf + sizeof(struct fuse_in_header),
+
284 packet_len - sizeof(struct fuse_in_header));
+
285
+
286 return (res == -1) ? res : (res + prev_res);
+
287}
+
288
+
289static ssize_t stream_splice_send(int fdin, off_t *offin, int fdout,
+
290 off_t *offout, size_t len,
+
291 unsigned int flags, void *userdata) {
+
292 (void)userdata;
+
293
+
294 size_t count = 0;
+
295 while (count < len) {
+
296 int i = splice(fdin, offin, fdout, offout, len - count, flags);
+
297 if (i < 1)
+
298 return i;
+
299
+
300 count += i;
+
301 }
+
302 return count;
+
303}
+
304
+
305static void fuse_cmdline_help_uds(void)
+
306{
+
307 printf(" -h --help print help\n"
+
308 " -V --version print version\n"
+
309 " -d -o debug enable debug output (implies -f)\n");
+
310}
+
311
+
312int main(int argc, char *argv[])
+
313{
+
314 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
315 struct fuse_session *se;
+
316 struct fuse_cmdline_opts opts;
+
317 const struct fuse_custom_io io = {
+
318 .writev = stream_writev,
+
319 .read = stream_read,
+
320 .splice_receive = NULL,
+
321 .splice_send = stream_splice_send,
+
322 };
+
323 int cfd = -1;
+
324 int ret = -1;
+
325
+
326 if (fuse_parse_cmdline(&args, &opts) != 0)
+
327 return 1;
+
328 if (opts.show_help) {
+
329 printf("usage: %s [options]\n\n", argv[0]);
+
330 fuse_cmdline_help_uds();
+ +
332 ret = 0;
+
333 goto err_out1;
+
334 } else if (opts.show_version) {
+
335 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
337 ret = 0;
+
338 goto err_out1;
+
339 }
+
340
+
341 se = fuse_session_new(&args, &hello_ll_oper,
+
342 sizeof(hello_ll_oper), NULL);
+
343 if (se == NULL)
+
344 goto err_out1;
+
345
+
346 if (fuse_set_signal_handlers(se) != 0)
+
347 goto err_out2;
+
348
+
349 cfd = create_socket("/tmp/libfuse-hello-ll.sock");
+
350 if (cfd == -1)
+
351 goto err_out3;
+
352
+
353 if (fuse_session_custom_io(se, &io, cfd) != 0)
+
354 goto err_out3;
+
355
+
356 /* Block until ctrl+c */
+
357 ret = fuse_session_loop(se);
+
358err_out3:
+ +
360err_out2:
+ +
362err_out1:
+
363 free(opts.mountpoint);
+
364 fuse_opt_free_args(&args);
+
365
+
366 return ret ? 1 : 0;
+
367}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_lowlevel_help(void)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/example_2invalidate__path_8c.html b/doc/html/example_2invalidate__path_8c.html new file mode 100644 index 0000000..7ee5e6e --- /dev/null +++ b/doc/html/example_2invalidate__path_8c.html @@ -0,0 +1,390 @@ + + + + + + + +libfuse: example/invalidate_path.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
invalidate_path.c File Reference
+
+
+
#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with two files:

    +
  • 'current-time', whose contents change dynamically: it always contains the current time (same as in notify_inval_inode.c).
  • +
  • 'growing', whose size changes dynamically, growing by 1 byte after each update. This aims to check if cached file metadata is also invalidated.
  • +
+

+Compilation

+
gcc -Wall invalidate_path.c `pkg-config fuse3 --cflags --libs` -o invalidate_path
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
(C) 2017 EditShare LLC <slawek.rudnicki@editshare.com>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 34
+
+
#include <fuse.h>
+
#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define TIME_FILE_NAME "current_time"
+
#define TIME_FILE_INO 2
+
#define GROW_FILE_NAME "growing"
+
#define GROW_FILE_INO 3
+
+
static char time_file_contents[MAX_STR_LEN];
+
static size_t grow_file_size;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->entry_timeout = NO_TIMEOUT;
+
cfg->attr_timeout = NO_TIMEOUT;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path,
+
struct stat *stbuf, struct fuse_file_info* fi) {
+
(void) fi;
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_ino = 1;
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
} else if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
stbuf->st_ino = TIME_FILE_INO;
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(time_file_contents);
+
} else if (strcmp(path, "/" GROW_FILE_NAME) == 0) {
+
stbuf->st_ino = GROW_FILE_INO;
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = grow_file_size;
+
} else {
+
return -ENOENT;
+
}
+
+
return 0;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags) {
+
(void) fi;
+
(void) offset;
+
(void) flags;
+
if (strcmp(path, "/") != 0) {
+
return -ENOTDIR;
+
} else {
+
(void) filler;
+
(void) buf;
+
struct stat file_stat;
+
xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL);
+
filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL);
+
filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
return 0;
+
}
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi) {
+
(void) path;
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi) {
+
(void) fi;
+
(void) offset;
+
if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
int file_length = strlen(time_file_contents);
+
int to_copy = offset + size <= file_length
+
? size
+
: file_length - offset;
+
memcpy(buf, time_file_contents, to_copy);
+
return to_copy;
+
} else {
+
assert(strcmp(path, "/" GROW_FILE_NAME) == 0);
+
int to_copy = offset + size <= grow_file_size
+
? size
+
: grow_file_size - offset;
+
memset(buf, 'x', to_copy);
+
return to_copy;
+
}
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.readdir = xmp_readdir,
+
.open = xmp_open,
+
.read = xmp_read,
+
};
+
+
static void update_fs(void) {
+
static int count = 0;
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
int time_file_size = strftime(time_file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(time_file_size != 0);
+
+
grow_file_size = count++;
+
}
+
+
static int invalidate(struct fuse *fuse, const char *path) {
+
int status = fuse_invalidate_path(fuse, path);
+
if (status == -ENOENT) {
+
return 0;
+
} else {
+
return status;
+
}
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse *fuse = (struct fuse*) data;
+
+
while (1) {
+
update_fs();
+
if (!options.no_notify) {
+
assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0);
+
assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0);
+
}
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse *fuse;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config config;
+
int res;
+
+
/* Initialize the files */
+
update_fs();
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
+
if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
res = 0;
+
goto out1;
+
} else if (opts.show_help) {
+
show_help(argv[0]);
+ +
fuse_lib_help(&args);
+
res = 0;
+
goto out1;
+
} else if (!opts.mountpoint) {
+
fprintf(stderr, "error: no mountpoint specified\n");
+
res = 1;
+
goto out1;
+
}
+
+
fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL);
+
if (fuse == NULL) {
+
res = 1;
+
goto out1;
+
}
+
+
if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
res = 1;
+
goto out2;
+
}
+
+
if (fuse_daemonize(opts.foreground) != 0) {
+
res = 1;
+
goto out3;
+
}
+
+
pthread_t updater; /* Start thread to update file contents */
+
int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n", strerror(ret));
+
return 1;
+
};
+
+
struct fuse_session *se = fuse_get_session(fuse);
+
if (fuse_set_signal_handlers(se) != 0) {
+
res = 1;
+
goto out3;
+
}
+
+
if (opts.singlethread)
+
res = fuse_loop(fuse);
+
else {
+
config.clone_fd = opts.clone_fd;
+
config.max_idle_threads = opts.max_idle_threads;
+
res = fuse_loop_mt(fuse, &config);
+
}
+
if (res)
+
res = 1;
+
+ +
out3:
+
fuse_unmount(fuse);
+
out2:
+
fuse_destroy(fuse);
+
out1:
+
free(opts.mountpoint);
+ +
return res;
+
}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5197
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5146
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5202
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint32_t keep_cache
Definition fuse_common.h:69
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file invalidate_path.c.

+
+ + + + diff --git a/doc/html/example_2invalidate__path_8c_source.html b/doc/html/example_2invalidate__path_8c_source.html new file mode 100644 index 0000000..a893c15 --- /dev/null +++ b/doc/html/example_2invalidate__path_8c_source.html @@ -0,0 +1,370 @@ + + + + + + + +libfuse: example/invalidate_path.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
invalidate_path.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4 (C) 2017 EditShare LLC <slawek.rudnicki@editshare.com>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8 */
+
9
+
28#define FUSE_USE_VERSION 34
+
29
+
30#include <fuse.h>
+
31#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
+
32
+
33#include <stdio.h>
+
34#include <stdlib.h>
+
35#include <string.h>
+
36#include <errno.h>
+
37#include <fcntl.h>
+
38#include <assert.h>
+
39#include <stddef.h>
+
40#include <unistd.h>
+
41#include <pthread.h>
+
42
+
43/* We can't actually tell the kernel that there is no
+
44 timeout, so we just send a big value */
+
45#define NO_TIMEOUT 500000
+
46
+
47#define MAX_STR_LEN 128
+
48#define TIME_FILE_NAME "current_time"
+
49#define TIME_FILE_INO 2
+
50#define GROW_FILE_NAME "growing"
+
51#define GROW_FILE_INO 3
+
52
+
53static char time_file_contents[MAX_STR_LEN];
+
54static size_t grow_file_size;
+
55
+
56/* Command line parsing */
+
57struct options {
+
58 int no_notify;
+
59 int update_interval;
+
60};
+
61static struct options options = {
+
62 .no_notify = 0,
+
63 .update_interval = 1,
+
64};
+
65
+
66#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
67static const struct fuse_opt option_spec[] = {
+
68 OPTION("--no-notify", no_notify),
+
69 OPTION("--update-interval=%d", update_interval),
+ +
71};
+
72
+
73static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
74{
+
75 (void) conn;
+
76 cfg->entry_timeout = NO_TIMEOUT;
+
77 cfg->attr_timeout = NO_TIMEOUT;
+
78 cfg->negative_timeout = 0;
+
79
+
80 return NULL;
+
81}
+
82
+
83static int xmp_getattr(const char *path,
+
84 struct stat *stbuf, struct fuse_file_info* fi) {
+
85 (void) fi;
+
86 if (strcmp(path, "/") == 0) {
+
87 stbuf->st_ino = 1;
+
88 stbuf->st_mode = S_IFDIR | 0755;
+
89 stbuf->st_nlink = 1;
+
90 } else if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
91 stbuf->st_ino = TIME_FILE_INO;
+
92 stbuf->st_mode = S_IFREG | 0444;
+
93 stbuf->st_nlink = 1;
+
94 stbuf->st_size = strlen(time_file_contents);
+
95 } else if (strcmp(path, "/" GROW_FILE_NAME) == 0) {
+
96 stbuf->st_ino = GROW_FILE_INO;
+
97 stbuf->st_mode = S_IFREG | 0444;
+
98 stbuf->st_nlink = 1;
+
99 stbuf->st_size = grow_file_size;
+
100 } else {
+
101 return -ENOENT;
+
102 }
+
103
+
104 return 0;
+
105}
+
106
+
107static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
108 off_t offset, struct fuse_file_info *fi,
+
109 enum fuse_readdir_flags flags) {
+
110 (void) fi;
+
111 (void) offset;
+
112 (void) flags;
+
113 if (strcmp(path, "/") != 0) {
+
114 return -ENOTDIR;
+
115 } else {
+
116 (void) filler;
+
117 (void) buf;
+
118 struct stat file_stat;
+
119 xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL);
+
120 filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
121 xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL);
+
122 filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
123 return 0;
+
124 }
+
125}
+
126
+
127static int xmp_open(const char *path, struct fuse_file_info *fi) {
+
128 (void) path;
+
129 /* Make cache persistent even if file is closed,
+
130 this makes it easier to see the effects */
+
131 fi->keep_cache = 1;
+
132 return 0;
+
133}
+
134
+
135static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
136 struct fuse_file_info *fi) {
+
137 (void) fi;
+
138 (void) offset;
+
139 if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
140 int file_length = strlen(time_file_contents);
+
141 int to_copy = offset + size <= file_length
+
142 ? size
+
143 : file_length - offset;
+
144 memcpy(buf, time_file_contents, to_copy);
+
145 return to_copy;
+
146 } else {
+
147 assert(strcmp(path, "/" GROW_FILE_NAME) == 0);
+
148 int to_copy = offset + size <= grow_file_size
+
149 ? size
+
150 : grow_file_size - offset;
+
151 memset(buf, 'x', to_copy);
+
152 return to_copy;
+
153 }
+
154}
+
155
+
156static const struct fuse_operations xmp_oper = {
+
157 .init = xmp_init,
+
158 .getattr = xmp_getattr,
+
159 .readdir = xmp_readdir,
+
160 .open = xmp_open,
+
161 .read = xmp_read,
+
162};
+
163
+
164static void update_fs(void) {
+
165 static int count = 0;
+
166 struct tm *now;
+
167 time_t t;
+
168 t = time(NULL);
+
169 now = localtime(&t);
+
170 assert(now != NULL);
+
171
+
172 int time_file_size = strftime(time_file_contents, MAX_STR_LEN,
+
173 "The current time is %H:%M:%S\n", now);
+
174 assert(time_file_size != 0);
+
175
+
176 grow_file_size = count++;
+
177}
+
178
+
179static int invalidate(struct fuse *fuse, const char *path) {
+
180 int status = fuse_invalidate_path(fuse, path);
+
181 if (status == -ENOENT) {
+
182 return 0;
+
183 } else {
+
184 return status;
+
185 }
+
186}
+
187
+
188static void* update_fs_loop(void *data) {
+
189 struct fuse *fuse = (struct fuse*) data;
+
190
+
191 while (1) {
+
192 update_fs();
+
193 if (!options.no_notify) {
+
194 assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0);
+
195 assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0);
+
196 }
+
197 sleep(options.update_interval);
+
198 }
+
199 return NULL;
+
200}
+
201
+
202static void show_help(const char *progname)
+
203{
+
204 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
205 printf("File-system specific options:\n"
+
206 " --update-interval=<secs> Update-rate of file system contents\n"
+
207 " --no-notify Disable kernel notifications\n"
+
208 "\n");
+
209}
+
210
+
211int main(int argc, char *argv[]) {
+
212 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
213 struct fuse *fuse;
+
214 struct fuse_cmdline_opts opts;
+
215 struct fuse_loop_config config;
+
216 int res;
+
217
+
218 /* Initialize the files */
+
219 update_fs();
+
220
+
221 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
222 return 1;
+
223
+
224 if (fuse_parse_cmdline(&args, &opts) != 0)
+
225 return 1;
+
226
+
227 if (opts.show_version) {
+
228 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
230 res = 0;
+
231 goto out1;
+
232 } else if (opts.show_help) {
+
233 show_help(argv[0]);
+ +
235 fuse_lib_help(&args);
+
236 res = 0;
+
237 goto out1;
+
238 } else if (!opts.mountpoint) {
+
239 fprintf(stderr, "error: no mountpoint specified\n");
+
240 res = 1;
+
241 goto out1;
+
242 }
+
243
+
244 fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL);
+
245 if (fuse == NULL) {
+
246 res = 1;
+
247 goto out1;
+
248 }
+
249
+
250 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
251 res = 1;
+
252 goto out2;
+
253 }
+
254
+
255 if (fuse_daemonize(opts.foreground) != 0) {
+
256 res = 1;
+
257 goto out3;
+
258 }
+
259
+
260 pthread_t updater; /* Start thread to update file contents */
+
261 int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse);
+
262 if (ret != 0) {
+
263 fprintf(stderr, "pthread_create failed with %s\n", strerror(ret));
+
264 return 1;
+
265 };
+
266
+
267 struct fuse_session *se = fuse_get_session(fuse);
+
268 if (fuse_set_signal_handlers(se) != 0) {
+
269 res = 1;
+
270 goto out3;
+
271 }
+
272
+
273 if (opts.singlethread)
+
274 res = fuse_loop(fuse);
+
275 else {
+
276 config.clone_fd = opts.clone_fd;
+
277 config.max_idle_threads = opts.max_idle_threads;
+
278 res = fuse_loop_mt(fuse, &config);
+
279 }
+
280 if (res)
+
281 res = 1;
+
282
+ +
284out3:
+
285 fuse_unmount(fuse);
+
286out2:
+
287 fuse_destroy(fuse);
+
288out1:
+
289 free(opts.mountpoint);
+
290 fuse_opt_free_args(&args);
+
291 return res;
+
292}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5197
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5146
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5202
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint32_t keep_cache
Definition fuse_common.h:69
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/example_2ioctl_8c.html b/doc/html/example_2ioctl_8c.html new file mode 100644 index 0000000..87373b6 --- /dev/null +++ b/doc/html/example_2ioctl_8c.html @@ -0,0 +1,294 @@ + + + + + + + +libfuse: example/ioctl.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl.c File Reference
+
+
+
#include <fuse.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This example illustrates how to write a FUSE file system that can process (a restricted set of) ioctls. It can be tested with the ioctl_client.c program.

+

Compile with:

gcc -Wall ioctl.c `pkg-config fuse3 --cflags --libs` -o ioctl
+

+Source code

+
/*
+
FUSE fioc: FUSE ioctl example
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 35
+
+
#include <fuse.h>
+
#include <stdlib.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <time.h>
+
#include <errno.h>
+
+
#include "ioctl.h"
+
+
#define FIOC_NAME "fioc"
+
+
enum {
+
FIOC_NONE,
+
FIOC_ROOT,
+
FIOC_FILE,
+
};
+
+
static void *fioc_buf;
+
static size_t fioc_size;
+
+
static int fioc_resize(size_t new_size)
+
{
+
void *new_buf;
+
+
if (new_size == fioc_size)
+
return 0;
+
+
new_buf = realloc(fioc_buf, new_size);
+
if (!new_buf && new_size)
+
return -ENOMEM;
+
+
if (new_size > fioc_size)
+
memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
+
fioc_buf = new_buf;
+
fioc_size = new_size;
+
+
return 0;
+
}
+
+
static int fioc_expand(size_t new_size)
+
{
+
if (new_size > fioc_size)
+
return fioc_resize(new_size);
+
return 0;
+
}
+
+
static int fioc_file_type(const char *path)
+
{
+
if (strcmp(path, "/") == 0)
+
return FIOC_ROOT;
+
if (strcmp(path, "/" FIOC_NAME) == 0)
+
return FIOC_FILE;
+
return FIOC_NONE;
+
}
+
+
static int fioc_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
stbuf->st_uid = getuid();
+
stbuf->st_gid = getgid();
+
stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
+
switch (fioc_file_type(path)) {
+
case FIOC_ROOT:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
case FIOC_FILE:
+
stbuf->st_mode = S_IFREG | 0644;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = fioc_size;
+
break;
+
case FIOC_NONE:
+
return -ENOENT;
+
}
+
+
return 0;
+
}
+
+
static int fioc_open(const char *path, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_NONE)
+
return 0;
+
return -ENOENT;
+
}
+
+
static int fioc_do_read(char *buf, size_t size, off_t offset)
+
{
+
if (offset >= fioc_size)
+
return 0;
+
+
if (size > fioc_size - offset)
+
size = fioc_size - offset;
+
+
memcpy(buf, fioc_buf + offset, size);
+
+
return size;
+
}
+
+
static int fioc_read(const char *path, char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_do_read(buf, size, offset);
+
}
+
+
static int fioc_do_write(const char *buf, size_t size, off_t offset)
+
{
+
if (fioc_expand(offset + size))
+
return -ENOMEM;
+
+
memcpy(fioc_buf + offset, buf, size);
+
+
return size;
+
}
+
+
static int fioc_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_do_write(buf, size, offset);
+
}
+
+
static int fioc_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_resize(size);
+
}
+
+
static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) fi;
+
(void) offset;
+
(void) flags;
+
+
if (fioc_file_type(path) != FIOC_ROOT)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int fioc_ioctl(const char *path, unsigned int cmd, void *arg,
+
struct fuse_file_info *fi, unsigned int flags, void *data)
+
{
+
(void) arg;
+
(void) fi;
+
(void) flags;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
if (flags & FUSE_IOCTL_COMPAT)
+
return -ENOSYS;
+
+
switch (cmd) {
+
case FIOC_GET_SIZE:
+
*(size_t *)data = fioc_size;
+
return 0;
+
+
case FIOC_SET_SIZE:
+
fioc_resize(*(size_t *)data);
+
return 0;
+
}
+
+
return -EINVAL;
+
}
+
+
static const struct fuse_operations fioc_oper = {
+
.getattr = fioc_getattr,
+
.readdir = fioc_readdir,
+
.truncate = fioc_truncate,
+
.open = fioc_open,
+
.read = fioc_read,
+
.write = fioc_write,
+
.ioctl = fioc_ioctl,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
return fuse_main(argc, argv, &fioc_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_IOCTL_COMPAT
+ + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+

Definition in file ioctl.c.

+
+ + + + diff --git a/doc/html/example_2ioctl_8c_source.html b/doc/html/example_2ioctl_8c_source.html new file mode 100644 index 0000000..cf12647 --- /dev/null +++ b/doc/html/example_2ioctl_8c_source.html @@ -0,0 +1,283 @@ + + + + + + + +libfuse: example/ioctl.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioc: FUSE ioctl example
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
25#define FUSE_USE_VERSION 35
+
26
+
27#include <fuse.h>
+
28#include <stdlib.h>
+
29#include <stdio.h>
+
30#include <string.h>
+
31#include <unistd.h>
+
32#include <time.h>
+
33#include <errno.h>
+
34
+
35#include "ioctl.h"
+
36
+
37#define FIOC_NAME "fioc"
+
38
+
39enum {
+
40 FIOC_NONE,
+
41 FIOC_ROOT,
+
42 FIOC_FILE,
+
43};
+
44
+
45static void *fioc_buf;
+
46static size_t fioc_size;
+
47
+
48static int fioc_resize(size_t new_size)
+
49{
+
50 void *new_buf;
+
51
+
52 if (new_size == fioc_size)
+
53 return 0;
+
54
+
55 new_buf = realloc(fioc_buf, new_size);
+
56 if (!new_buf && new_size)
+
57 return -ENOMEM;
+
58
+
59 if (new_size > fioc_size)
+
60 memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
61
+
62 fioc_buf = new_buf;
+
63 fioc_size = new_size;
+
64
+
65 return 0;
+
66}
+
67
+
68static int fioc_expand(size_t new_size)
+
69{
+
70 if (new_size > fioc_size)
+
71 return fioc_resize(new_size);
+
72 return 0;
+
73}
+
74
+
75static int fioc_file_type(const char *path)
+
76{
+
77 if (strcmp(path, "/") == 0)
+
78 return FIOC_ROOT;
+
79 if (strcmp(path, "/" FIOC_NAME) == 0)
+
80 return FIOC_FILE;
+
81 return FIOC_NONE;
+
82}
+
83
+
84static int fioc_getattr(const char *path, struct stat *stbuf,
+
85 struct fuse_file_info *fi)
+
86{
+
87 (void) fi;
+
88 stbuf->st_uid = getuid();
+
89 stbuf->st_gid = getgid();
+
90 stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
91
+
92 switch (fioc_file_type(path)) {
+
93 case FIOC_ROOT:
+
94 stbuf->st_mode = S_IFDIR | 0755;
+
95 stbuf->st_nlink = 2;
+
96 break;
+
97 case FIOC_FILE:
+
98 stbuf->st_mode = S_IFREG | 0644;
+
99 stbuf->st_nlink = 1;
+
100 stbuf->st_size = fioc_size;
+
101 break;
+
102 case FIOC_NONE:
+
103 return -ENOENT;
+
104 }
+
105
+
106 return 0;
+
107}
+
108
+
109static int fioc_open(const char *path, struct fuse_file_info *fi)
+
110{
+
111 (void) fi;
+
112
+
113 if (fioc_file_type(path) != FIOC_NONE)
+
114 return 0;
+
115 return -ENOENT;
+
116}
+
117
+
118static int fioc_do_read(char *buf, size_t size, off_t offset)
+
119{
+
120 if (offset >= fioc_size)
+
121 return 0;
+
122
+
123 if (size > fioc_size - offset)
+
124 size = fioc_size - offset;
+
125
+
126 memcpy(buf, fioc_buf + offset, size);
+
127
+
128 return size;
+
129}
+
130
+
131static int fioc_read(const char *path, char *buf, size_t size,
+
132 off_t offset, struct fuse_file_info *fi)
+
133{
+
134 (void) fi;
+
135
+
136 if (fioc_file_type(path) != FIOC_FILE)
+
137 return -EINVAL;
+
138
+
139 return fioc_do_read(buf, size, offset);
+
140}
+
141
+
142static int fioc_do_write(const char *buf, size_t size, off_t offset)
+
143{
+
144 if (fioc_expand(offset + size))
+
145 return -ENOMEM;
+
146
+
147 memcpy(fioc_buf + offset, buf, size);
+
148
+
149 return size;
+
150}
+
151
+
152static int fioc_write(const char *path, const char *buf, size_t size,
+
153 off_t offset, struct fuse_file_info *fi)
+
154{
+
155 (void) fi;
+
156
+
157 if (fioc_file_type(path) != FIOC_FILE)
+
158 return -EINVAL;
+
159
+
160 return fioc_do_write(buf, size, offset);
+
161}
+
162
+
163static int fioc_truncate(const char *path, off_t size,
+
164 struct fuse_file_info *fi)
+
165{
+
166 (void) fi;
+
167 if (fioc_file_type(path) != FIOC_FILE)
+
168 return -EINVAL;
+
169
+
170 return fioc_resize(size);
+
171}
+
172
+
173static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
174 off_t offset, struct fuse_file_info *fi,
+
175 enum fuse_readdir_flags flags)
+
176{
+
177 (void) fi;
+
178 (void) offset;
+
179 (void) flags;
+
180
+
181 if (fioc_file_type(path) != FIOC_ROOT)
+
182 return -ENOENT;
+
183
+
184 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
185 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
186 filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
187
+
188 return 0;
+
189}
+
190
+
191static int fioc_ioctl(const char *path, unsigned int cmd, void *arg,
+
192 struct fuse_file_info *fi, unsigned int flags, void *data)
+
193{
+
194 (void) arg;
+
195 (void) fi;
+
196 (void) flags;
+
197
+
198 if (fioc_file_type(path) != FIOC_FILE)
+
199 return -EINVAL;
+
200
+
201 if (flags & FUSE_IOCTL_COMPAT)
+
202 return -ENOSYS;
+
203
+
204 switch (cmd) {
+
205 case FIOC_GET_SIZE:
+
206 *(size_t *)data = fioc_size;
+
207 return 0;
+
208
+
209 case FIOC_SET_SIZE:
+
210 fioc_resize(*(size_t *)data);
+
211 return 0;
+
212 }
+
213
+
214 return -EINVAL;
+
215}
+
216
+
217static const struct fuse_operations fioc_oper = {
+
218 .getattr = fioc_getattr,
+
219 .readdir = fioc_readdir,
+
220 .truncate = fioc_truncate,
+
221 .open = fioc_open,
+
222 .read = fioc_read,
+
223 .write = fioc_write,
+
224 .ioctl = fioc_ioctl,
+
225};
+
226
+
227int main(int argc, char *argv[])
+
228{
+
229 return fuse_main(argc, argv, &fioc_oper, NULL);
+
230}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_IOCTL_COMPAT
+ + + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+ + + + diff --git a/doc/html/example_2ioctl_8h.html b/doc/html/example_2ioctl_8h.html new file mode 100644 index 0000000..f2c28c7 --- /dev/null +++ b/doc/html/example_2ioctl_8h.html @@ -0,0 +1,96 @@ + + + + + + + +libfuse: example/ioctl.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl.h File Reference
+
+
+
#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

Header file to share definitions between the ioctl.c example file system and the ioctl_client.c test program.

+
/*
+
FUSE-ioctl: ioctl support for FUSE
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#include <sys/types.h>
+
#include <sys/uio.h>
+
#include <sys/ioctl.h>
+
+
enum {
+
FIOC_GET_SIZE = _IOR('E', 0, size_t),
+
FIOC_SET_SIZE = _IOW('E', 1, size_t),
+
+
/*
+
* The following two ioctls don't follow usual encoding rules
+
* and transfer variable amount of data.
+
*/
+
FIOC_READ = _IO('E', 2),
+
FIOC_WRITE = _IO('E', 3),
+
};
+
+
struct fioc_rw_arg {
+
off_t offset;
+
void *buf;
+
size_t size;
+
size_t prev_size; /* out param for previous total size */
+
size_t new_size; /* out param for new total size */
+
};
+
+

Definition in file ioctl.h.

+
+ + + + diff --git a/doc/html/example_2ioctl_8h_source.html b/doc/html/example_2ioctl_8h_source.html new file mode 100644 index 0000000..9ae4076 --- /dev/null +++ b/doc/html/example_2ioctl_8h_source.html @@ -0,0 +1,92 @@ + + + + + + + +libfuse: example/ioctl.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE-ioctl: ioctl support for FUSE
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
20#include <sys/types.h>
+
21#include <sys/uio.h>
+
22#include <sys/ioctl.h>
+
23
+
24enum {
+
25 FIOC_GET_SIZE = _IOR('E', 0, size_t),
+
26 FIOC_SET_SIZE = _IOW('E', 1, size_t),
+
27
+
28 /*
+
29 * The following two ioctls don't follow usual encoding rules
+
30 * and transfer variable amount of data.
+
31 */
+
32 FIOC_READ = _IO('E', 2),
+
33 FIOC_WRITE = _IO('E', 3),
+
34};
+
35
+
36struct fioc_rw_arg {
+
37 off_t offset;
+
38 void *buf;
+
39 size_t size;
+
40 size_t prev_size; /* out param for previous total size */
+
41 size_t new_size; /* out param for new total size */
+
42};
+
+ + + + diff --git a/doc/html/example_2ioctl__client_8c.html b/doc/html/example_2ioctl__client_8c.html new file mode 100644 index 0000000..23ab645 --- /dev/null +++ b/doc/html/example_2ioctl__client_8c.html @@ -0,0 +1,136 @@ + + + + + + + +libfuse: example/ioctl_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl_client.c File Reference
+
+
+
#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the ioctl.c example file systsem.

+

Compile with:

gcc -Wall ioctl_client.c -o ioctl_client
+

+Source code

+
/*
+
FUSE fioclient: FUSE ioctl example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#include <sys/types.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <sys/ioctl.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <ctype.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include "ioctl.h"
+
+
const char *usage =
+
"Usage: fioclient FIOC_FILE [size]\n"
+
"\n"
+
"Get size if <size> is omitted, set size otherwise\n"
+
"\n";
+
+
int main(int argc, char **argv)
+
{
+
size_t size;
+
int fd;
+
int ret = 0;
+
+
if (argc < 2) {
+
fprintf(stderr, "%s", usage);
+
return 1;
+
}
+
+
fd = open(argv[1], O_RDWR);
+
if (fd < 0) {
+
perror("open");
+
return 1;
+
}
+
+
if (argc == 2) {
+
if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
perror("ioctl");
+
ret = 1;
+
goto out;
+
}
+
printf("%zu\n", size);
+
} else {
+
size = strtoul(argv[2], NULL, 0);
+
if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
perror("ioctl");
+
ret = 1;
+
goto out;
+
}
+
}
+
out:
+
close(fd);
+
return ret;
+
}
+
+

Definition in file ioctl_client.c.

+
+ + + + diff --git a/doc/html/example_2ioctl__client_8c_source.html b/doc/html/example_2ioctl__client_8c_source.html new file mode 100644 index 0000000..e3e3406 --- /dev/null +++ b/doc/html/example_2ioctl__client_8c_source.html @@ -0,0 +1,123 @@ + + + + + + + +libfuse: example/ioctl_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioclient: FUSE ioctl example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
22#include <sys/types.h>
+
23#include <fcntl.h>
+
24#include <sys/stat.h>
+
25#include <sys/ioctl.h>
+
26#include <stdio.h>
+
27#include <stdlib.h>
+
28#include <ctype.h>
+
29#include <errno.h>
+
30#include <unistd.h>
+
31#include "ioctl.h"
+
32
+
33const char *usage =
+
34"Usage: fioclient FIOC_FILE [size]\n"
+
35"\n"
+
36"Get size if <size> is omitted, set size otherwise\n"
+
37"\n";
+
38
+
39int main(int argc, char **argv)
+
40{
+
41 size_t size;
+
42 int fd;
+
43 int ret = 0;
+
44
+
45 if (argc < 2) {
+
46 fprintf(stderr, "%s", usage);
+
47 return 1;
+
48 }
+
49
+
50 fd = open(argv[1], O_RDWR);
+
51 if (fd < 0) {
+
52 perror("open");
+
53 return 1;
+
54 }
+
55
+
56 if (argc == 2) {
+
57 if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
58 perror("ioctl");
+
59 ret = 1;
+
60 goto out;
+
61 }
+
62 printf("%zu\n", size);
+
63 } else {
+
64 size = strtoul(argv[2], NULL, 0);
+
65 if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
66 perror("ioctl");
+
67 ret = 1;
+
68 goto out;
+
69 }
+
70 }
+
71out:
+
72 close(fd);
+
73 return ret;
+
74}
+ +
+ + + + diff --git a/doc/html/example_2notify__inval__entry_8c.html b/doc/html/example_2notify__inval__entry_8c.html new file mode 100644 index 0000000..c8b6b7b --- /dev/null +++ b/doc/html/example_2notify__inval__entry_8c.html @@ -0,0 +1,497 @@ + + + + + + + +libfuse: example/notify_inval_entry.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_inval_entry.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <signal.h>
+#include <stddef.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <pthread.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose file name changes dynamically to reflect the current time.

+

It illustrates the use of the fuse_lowlevel_notify_inval_entry() and fuse_lowlevel_notify_expire_entry() functions.

+

To see the effect, first start the file system with the --no-notify

$ notify_inval_entry --update-interval=1 --timeout=30 --no-notify mnt/
+

Observe that ls always prints the correct directory contents (since readdir output is not cached)::

$ ls mnt; sleep 1; ls mnt; sleep 1; ls mnt
+Time_is_15h_48m_33s  current_time
+Time_is_15h_48m_34s  current_time
+Time_is_15h_48m_35s  current_time
+

However, if you try to access a file by name the kernel will report that it still exists:

$ file=$(ls mnt/); echo $file
+Time_is_15h_50m_09s
+$ sleep 5; stat mnt/$file
+  File: ‘mnt/Time_is_15h_50m_09s’
+  Size: 32                Blocks: 0          IO Block: 4096   regular file
+Device: 2ah/42d     Inode: 3           Links: 1
+Access: (0444/-r--r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
+Access: 1969-12-31 16:00:00.000000000 -0800
+Modify: 1969-12-31 16:00:00.000000000 -0800
+Change: 1969-12-31 16:00:00.000000000 -0800
+ Birth: -
+

Only once the kernel cache timeout has been reached will the stat call fail:

$ sleep 30; stat mnt/$file
+stat: cannot stat ‘mnt/Time_is_15h_50m_09s’: No such file or directory
+

In contrast, if you enable notifications you will be unable to stat the file as soon as the file system updates its name:

$ notify_inval_entry --update-interval=1 --timeout=30 mnt/
+$ file=$(ls mnt/); stat mnt/$file
+  File: ‘mnt/Time_is_20h_42m_11s’
+  Size: 0                 Blocks: 0          IO Block: 4096   regular empty file
+Device: 2ah/42d     Inode: 2           Links: 1
+Access: (0000/----------)  Uid: (    0/    root)   Gid: (    0/    root)
+Access: 1969-12-31 16:00:00.000000000 -0800
+Modify: 1969-12-31 16:00:00.000000000 -0800
+Change: 1969-12-31 16:00:00.000000000 -0800
+ Birth: -
+$ sleep 1; stat mnt/$file
+stat: cannot stat ‘mnt/Time_is_20h_42m_11s’: No such file or directory
+

To use the function fuse_lowlevel_notify_expire_entry() instead of fuse_lowlevel_notify_inval_entry(), use the command line option –only-expire

+

Another possible command-line option is –inc-epoch, which will use the FUSE low-level function fuse_lowlevel_notify_increment_epoch() instead. This will function will force the invalidation of all dentries next time they are revalidated. Note that –inc-epoch and –only-expire options are mutually exclusive.

+

+Compilation

+
gcc -Wall notify_inval_entry.c `pkg-config fuse3 --cflags --libs` -o notify_inval_entry
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <signal.h>
+
#include <stddef.h>
+
#include <sys/stat.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
+
#define MAX_STR_LEN 128
+
static char file_name[MAX_STR_LEN];
+
static fuse_ino_t file_ino = 2;
+
static int lookup_cnt = 0;
+
static pthread_t main_thread;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
float timeout;
+
int update_interval;
+
int only_expire;
+
int inc_epoch;
+
};
+
static struct options options = {
+
.timeout = 5,
+
.no_notify = 0,
+
.update_interval = 1,
+
.only_expire = 0,
+
.inc_epoch = 0,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+
OPTION("--timeout=%f", timeout),
+
OPTION("--only-expire", only_expire),
+
OPTION("--inc-epoch", inc_epoch),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == file_ino) {
+
stbuf->st_mode = S_IFREG | 0000;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = 0;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, file_name) == 0) {
+
e.ino = file_ino;
+
lookup_cnt++;
+
} else
+
goto err_out;
+
+
e.attr_timeout = options.timeout;
+
e.entry_timeout = options.timeout;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == file_ino)
+
lookup_cnt -= nlookup;
+
else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, options.timeout);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, file_name, file_ino);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.forget = tfs_forget,
+
};
+
+
static void update_fs(void) {
+
time_t t;
+
struct tm *now;
+
ssize_t ret;
+
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
ret = strftime(file_name, MAX_STR_LEN,
+
"Time_is_%Hh_%Mm_%Ss", now);
+
assert(ret != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
char *old_name;
+
int ret = 0;
+
+
while(!fuse_session_exited(se)) {
+
old_name = strdup(file_name);
+
update_fs();
+
+
if (!options.no_notify && lookup_cnt) {
+
if(options.only_expire) { // expire entry
+ +
(se, FUSE_ROOT_ID, old_name, strlen(old_name));
+
+
// no kernel support
+
if (ret == -ENOSYS) {
+
printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n");
+
break;
+
}
+
+
// 1) ret == 0: successful expire of an existing entry
+
// 2) ret == -ENOENT: kernel has already expired the entry /
+
// entry does not exist anymore in the kernel
+
assert(ret == 0 || ret == -ENOENT);
+
} else if (options.inc_epoch) { // increment epoch
+ +
+
if (ret == -ENOSYS) {
+
printf("fuse_lowlevel_notify_increment_epoch not supported by kernel\n");
+
break;
+
}
+
assert(ret == 0);
+
} else { // invalidate entry
+ +
(se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0);
+
}
+
}
+
free(old_name);
+
sleep(options.update_interval);
+
}
+
+
if (ret == -ENOSYS) {
+
printf("Exiting...\n");
+
+ +
// Make sure to exit now, rather than on next request from userspace
+
pthread_kill(main_thread, SIGPIPE);
+
}
+
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --timeout=<secs> Timeout for kernel caches\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
" --only-expire Expire entries instead of invalidating them\n"
+
" --inc-epoch Increment epoch, invalidating all dentries\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
pthread_t updater;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
if (options.only_expire && options.inc_epoch) {
+
printf("'only-expire' and 'inc-epoch' options are exclusive\n");
+
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), &se);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
// Needed to ensure that the main thread continues/restarts processing as soon
+
// as the fuse session ends (immediately after calling fuse_session_exit() )
+
// and not only on the next request from userspace
+
main_thread = pthread_self();
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread) {
+
ret = fuse_session_loop(se);
+
} else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_inval_entry.c.

+
+ + + + diff --git a/doc/html/example_2notify__inval__entry_8c_source.html b/doc/html/example_2notify__inval__entry_8c_source.html new file mode 100644 index 0000000..e0c7224 --- /dev/null +++ b/doc/html/example_2notify__inval__entry_8c_source.html @@ -0,0 +1,446 @@ + + + + + + + +libfuse: example/notify_inval_entry.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_inval_entry.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
85#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
86
+
87#include <fuse_lowlevel.h>
+
88#include <stdio.h>
+
89#include <stdlib.h>
+
90#include <string.h>
+
91#include <errno.h>
+
92#include <fcntl.h>
+
93#include <assert.h>
+
94#include <signal.h>
+
95#include <stddef.h>
+
96#include <sys/stat.h>
+
97#include <unistd.h>
+
98#include <pthread.h>
+
99
+
100#define MAX_STR_LEN 128
+
101static char file_name[MAX_STR_LEN];
+
102static fuse_ino_t file_ino = 2;
+
103static int lookup_cnt = 0;
+
104static pthread_t main_thread;
+
105
+
106/* Command line parsing */
+
107struct options {
+
108 int no_notify;
+
109 float timeout;
+
110 int update_interval;
+
111 int only_expire;
+
112 int inc_epoch;
+
113};
+
114static struct options options = {
+
115 .timeout = 5,
+
116 .no_notify = 0,
+
117 .update_interval = 1,
+
118 .only_expire = 0,
+
119 .inc_epoch = 0,
+
120};
+
121
+
122#define OPTION(t, p) \
+
123 { t, offsetof(struct options, p), 1 }
+
124static const struct fuse_opt option_spec[] = {
+
125 OPTION("--no-notify", no_notify),
+
126 OPTION("--update-interval=%d", update_interval),
+
127 OPTION("--timeout=%f", timeout),
+
128 OPTION("--only-expire", only_expire),
+
129 OPTION("--inc-epoch", inc_epoch),
+ +
131};
+
132
+
133static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
134 stbuf->st_ino = ino;
+
135 if (ino == FUSE_ROOT_ID) {
+
136 stbuf->st_mode = S_IFDIR | 0755;
+
137 stbuf->st_nlink = 1;
+
138 }
+
139
+
140 else if (ino == file_ino) {
+
141 stbuf->st_mode = S_IFREG | 0000;
+
142 stbuf->st_nlink = 1;
+
143 stbuf->st_size = 0;
+
144 }
+
145
+
146 else
+
147 return -1;
+
148
+
149 return 0;
+
150}
+
151
+
152static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
153 (void)userdata;
+
154
+
155 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
156 conn->no_interrupt = 1;
+
157}
+
158
+
159static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
160 const char *name) {
+
161 struct fuse_entry_param e;
+
162 memset(&e, 0, sizeof(e));
+
163
+
164 if (parent != FUSE_ROOT_ID)
+
165 goto err_out;
+
166 else if (strcmp(name, file_name) == 0) {
+
167 e.ino = file_ino;
+
168 lookup_cnt++;
+
169 } else
+
170 goto err_out;
+
171
+
172 e.attr_timeout = options.timeout;
+
173 e.entry_timeout = options.timeout;
+
174 if (tfs_stat(e.ino, &e.attr) != 0)
+
175 goto err_out;
+
176 fuse_reply_entry(req, &e);
+
177 return;
+
178
+
179err_out:
+
180 fuse_reply_err(req, ENOENT);
+
181}
+
182
+
183static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
184 uint64_t nlookup) {
+
185 (void) req;
+
186 if(ino == file_ino)
+
187 lookup_cnt -= nlookup;
+
188 else
+
189 assert(ino == FUSE_ROOT_ID);
+
190 fuse_reply_none(req);
+
191}
+
192
+
193static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
194 struct fuse_file_info *fi) {
+
195 struct stat stbuf;
+
196
+
197 (void) fi;
+
198
+
199 memset(&stbuf, 0, sizeof(stbuf));
+
200 if (tfs_stat(ino, &stbuf) != 0)
+
201 fuse_reply_err(req, ENOENT);
+
202 else
+
203 fuse_reply_attr(req, &stbuf, options.timeout);
+
204}
+
205
+
206struct dirbuf {
+
207 char *p;
+
208 size_t size;
+
209};
+
210
+
211static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
212 fuse_ino_t ino) {
+
213 struct stat stbuf;
+
214 size_t oldsize = b->size;
+
215 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
216 b->p = (char *) realloc(b->p, b->size);
+
217 memset(&stbuf, 0, sizeof(stbuf));
+
218 stbuf.st_ino = ino;
+
219 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
220 b->size);
+
221}
+
222
+
223#define min(x, y) ((x) < (y) ? (x) : (y))
+
224
+
225static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
226 off_t off, size_t maxsize) {
+
227 if (off < bufsize)
+
228 return fuse_reply_buf(req, buf + off,
+
229 min(bufsize - off, maxsize));
+
230 else
+
231 return fuse_reply_buf(req, NULL, 0);
+
232}
+
233
+
234static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
235 off_t off, struct fuse_file_info *fi) {
+
236 (void) fi;
+
237
+
238 if (ino != FUSE_ROOT_ID)
+
239 fuse_reply_err(req, ENOTDIR);
+
240 else {
+
241 struct dirbuf b;
+
242
+
243 memset(&b, 0, sizeof(b));
+
244 dirbuf_add(req, &b, file_name, file_ino);
+
245 reply_buf_limited(req, b.p, b.size, off, size);
+
246 free(b.p);
+
247 }
+
248}
+
249
+
250static const struct fuse_lowlevel_ops tfs_oper = {
+
251 .init = tfs_init,
+
252 .lookup = tfs_lookup,
+
253 .getattr = tfs_getattr,
+
254 .readdir = tfs_readdir,
+
255 .forget = tfs_forget,
+
256};
+
257
+
258static void update_fs(void) {
+
259 time_t t;
+
260 struct tm *now;
+
261 ssize_t ret;
+
262
+
263 t = time(NULL);
+
264 now = localtime(&t);
+
265 assert(now != NULL);
+
266
+
267 ret = strftime(file_name, MAX_STR_LEN,
+
268 "Time_is_%Hh_%Mm_%Ss", now);
+
269 assert(ret != 0);
+
270}
+
271
+
272static void* update_fs_loop(void *data) {
+
273 struct fuse_session *se = (struct fuse_session*) data;
+
274 char *old_name;
+
275 int ret = 0;
+
276
+
277 while(!fuse_session_exited(se)) {
+
278 old_name = strdup(file_name);
+
279 update_fs();
+
280
+
281 if (!options.no_notify && lookup_cnt) {
+
282 if(options.only_expire) { // expire entry
+ +
284 (se, FUSE_ROOT_ID, old_name, strlen(old_name));
+
285
+
286 // no kernel support
+
287 if (ret == -ENOSYS) {
+
288 printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n");
+
289 break;
+
290 }
+
291
+
292 // 1) ret == 0: successful expire of an existing entry
+
293 // 2) ret == -ENOENT: kernel has already expired the entry /
+
294 // entry does not exist anymore in the kernel
+
295 assert(ret == 0 || ret == -ENOENT);
+
296 } else if (options.inc_epoch) { // increment epoch
+ +
298
+
299 if (ret == -ENOSYS) {
+
300 printf("fuse_lowlevel_notify_increment_epoch not supported by kernel\n");
+
301 break;
+
302 }
+
303 assert(ret == 0);
+
304 } else { // invalidate entry
+ +
306 (se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0);
+
307 }
+
308 }
+
309 free(old_name);
+
310 sleep(options.update_interval);
+
311 }
+
312
+
313 if (ret == -ENOSYS) {
+
314 printf("Exiting...\n");
+
315
+ +
317 // Make sure to exit now, rather than on next request from userspace
+
318 pthread_kill(main_thread, SIGPIPE);
+
319 }
+
320
+
321 return NULL;
+
322}
+
323
+
324static void show_help(const char *progname)
+
325{
+
326 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
327 printf("File-system specific options:\n"
+
328 " --timeout=<secs> Timeout for kernel caches\n"
+
329 " --update-interval=<secs> Update-rate of file system contents\n"
+
330 " --no-notify Disable kernel notifications\n"
+
331 " --only-expire Expire entries instead of invalidating them\n"
+
332 " --inc-epoch Increment epoch, invalidating all dentries\n"
+
333 "\n");
+
334}
+
335
+
336int main(int argc, char *argv[]) {
+
337 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
338 struct fuse_session *se;
+
339 struct fuse_cmdline_opts opts;
+
340 struct fuse_loop_config *config;
+
341 pthread_t updater;
+
342 int ret = -1;
+
343
+
344 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
345 return 1;
+
346
+
347 if (fuse_parse_cmdline(&args, &opts) != 0)
+
348 return 1;
+
349 if (opts.show_help) {
+
350 show_help(argv[0]);
+ + +
353 ret = 0;
+
354 goto err_out1;
+
355 } else if (opts.show_version) {
+
356 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
358 ret = 0;
+
359 goto err_out1;
+
360 }
+
361 if (options.only_expire && options.inc_epoch) {
+
362 printf("'only-expire' and 'inc-epoch' options are exclusive\n");
+
363 ret = 0;
+
364 goto err_out1;
+
365 }
+
366
+
367 /* Initial contents */
+
368 update_fs();
+
369
+
370 se = fuse_session_new(&args, &tfs_oper,
+
371 sizeof(tfs_oper), &se);
+
372 if (se == NULL)
+
373 goto err_out1;
+
374
+
375 if (fuse_set_signal_handlers(se) != 0)
+
376 goto err_out2;
+
377
+
378 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
379 goto err_out3;
+
380
+
381 fuse_daemonize(opts.foreground);
+
382
+
383 // Needed to ensure that the main thread continues/restarts processing as soon
+
384 // as the fuse session ends (immediately after calling fuse_session_exit() )
+
385 // and not only on the next request from userspace
+
386 main_thread = pthread_self();
+
387
+
388 /* Start thread to update file contents */
+
389 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
390 if (ret != 0) {
+
391 fprintf(stderr, "pthread_create failed with %s\n",
+
392 strerror(ret));
+
393 goto err_out3;
+
394 }
+
395
+
396 /* Block until ctrl+c or fusermount -u */
+
397 if (opts.singlethread) {
+
398 ret = fuse_session_loop(se);
+
399 } else {
+
400 config = fuse_loop_cfg_create();
+
401 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
402 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
403 ret = fuse_session_loop_mt(se, config);
+
404 fuse_loop_cfg_destroy(config);
+
405 config = NULL;
+
406 }
+
407
+ +
409err_out3:
+ +
411err_out2:
+ +
413err_out1:
+
414 free(opts.mountpoint);
+
415 fuse_opt_free_args(&args);
+
416
+
417 return ret ? 1 : 0;
+
418}
+
419
+
420
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/example_2notify__inval__inode_8c.html b/doc/html/example_2notify__inval__inode_8c.html new file mode 100644 index 0000000..c7256c4 --- /dev/null +++ b/doc/html/example_2notify__inval__inode_8c.html @@ -0,0 +1,483 @@ + + + + + + + +libfuse: example/notify_inval_inode.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_inval_inode.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdatomic.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

+

While notify_store_retrieve.c uses fuse_lowlevel_notify_store() to actively push the updated data into the kernel cache, this example uses fuse_lowlevel_notify_inval_inode() to notify the kernel that the cache has to be invalidated - but the kernel still has to explicitly request the updated data on the next read.

+

To see the effect, first start the file system with the --no-notify option:

+

$ notify_inval_inode –update-interval=1 –no-notify mnt/

+

Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+

If you instead enable the notification functions, the changes become visible:

 $ notify_inval_inode --update-interval=1 mnt/
+ $ for i in 1 2 3 4 5; do
+ >     cat mnt/current_time
+ >     sleep 1
+ > done
+ The current time is 15:58:40
+ The current time is 15:58:41
+ The current time is 15:58:42
+ The current time is 15:58:43
+ The current time is 15:58:44
+

+Compilation

+
gcc -Wall notify_inval_inode.c `pkg-config fuse3 --cflags --libs` -o notify_inval_inode
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
#include <stdbool.h>
+
#include <stdatomic.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define FILE_INO 2
+
#define FILE_NAME "current_time"
+
static char file_contents[MAX_STR_LEN];
+
static int lookup_cnt = 0;
+
static size_t file_size;
+
static _Atomic bool is_stop = false;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == FILE_INO) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = file_size;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_destroy(void *userarg)
+
{
+
(void)userarg;
+
+
is_stop = true;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, FILE_NAME) == 0) {
+
e.ino = FILE_INO;
+
lookup_cnt++;
+
} else
+
goto err_out;
+
+
e.attr_timeout = NO_TIMEOUT;
+
e.entry_timeout = NO_TIMEOUT;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == FILE_INO)
+
lookup_cnt -= nlookup;
+
else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
+
if (ino == FUSE_ROOT_ID)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else if (ino == FILE_INO)
+
fuse_reply_open(req, fi);
+
else {
+
// This should not happen
+
fprintf(stderr, "Got open for non-existing inode!\n");
+
fuse_reply_err(req, ENOENT);
+
}
+
}
+
+
static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
assert(ino == FILE_INO);
+
reply_buf_limited(req, file_contents, file_size, off, size);
+
}
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.destroy = tfs_destroy,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.open = tfs_open,
+
.read = tfs_read,
+
.forget = tfs_forget,
+
};
+
+
static void update_fs(void) {
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
file_size = strftime(file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(file_size != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
+
while(!is_stop) {
+
update_fs();
+
if (!options.no_notify && lookup_cnt) {
+
/* Only send notification if the kernel is aware of the inode */
+
+
/* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
* might come up during umount, when kernel side already releases
+
* all inodes, but does not send FUSE_DESTROY yet.
+
*/
+
int ret =
+
fuse_lowlevel_notify_inval_inode(se, FILE_INO, 0, 0);
+
if ((ret != 0 && !is_stop) &&
+
ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
fprintf(stderr,
+
"ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
strerror(-ret), -ret);
+
abort();
+
}
+
}
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
pthread_t updater;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0) {
+
ret = 1;
+
goto err_out1;
+
}
+
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+ +
free(opts.mountpoint);
+
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_inval_inode.c.

+
+ + + + diff --git a/doc/html/example_2notify__inval__inode_8c_source.html b/doc/html/example_2notify__inval__inode_8c_source.html new file mode 100644 index 0000000..c412c1f --- /dev/null +++ b/doc/html/example_2notify__inval__inode_8c_source.html @@ -0,0 +1,443 @@ + + + + + + + +libfuse: example/notify_inval_inode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_inval_inode.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
62#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
63
+
64#include <fuse_lowlevel.h>
+
65#include <stdio.h>
+
66#include <stdlib.h>
+
67#include <string.h>
+
68#include <errno.h>
+
69#include <fcntl.h>
+
70#include <assert.h>
+
71#include <stddef.h>
+
72#include <unistd.h>
+
73#include <pthread.h>
+
74#include <stdbool.h>
+
75#include <stdatomic.h>
+
76
+
77/* We can't actually tell the kernel that there is no
+
78 timeout, so we just send a big value */
+
79#define NO_TIMEOUT 500000
+
80
+
81#define MAX_STR_LEN 128
+
82#define FILE_INO 2
+
83#define FILE_NAME "current_time"
+
84static char file_contents[MAX_STR_LEN];
+
85static int lookup_cnt = 0;
+
86static size_t file_size;
+
87static _Atomic bool is_stop = false;
+
88
+
89/* Command line parsing */
+
90struct options {
+
91 int no_notify;
+
92 int update_interval;
+
93};
+
94static struct options options = {
+
95 .no_notify = 0,
+
96 .update_interval = 1,
+
97};
+
98
+
99#define OPTION(t, p) \
+
100 { t, offsetof(struct options, p), 1 }
+
101static const struct fuse_opt option_spec[] = {
+
102 OPTION("--no-notify", no_notify),
+
103 OPTION("--update-interval=%d", update_interval),
+ +
105};
+
106
+
107static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
108 stbuf->st_ino = ino;
+
109 if (ino == FUSE_ROOT_ID) {
+
110 stbuf->st_mode = S_IFDIR | 0755;
+
111 stbuf->st_nlink = 1;
+
112 }
+
113
+
114 else if (ino == FILE_INO) {
+
115 stbuf->st_mode = S_IFREG | 0444;
+
116 stbuf->st_nlink = 1;
+
117 stbuf->st_size = file_size;
+
118 }
+
119
+
120 else
+
121 return -1;
+
122
+
123 return 0;
+
124}
+
125
+
126static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
127 (void)userdata;
+
128
+
129 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
130 conn->no_interrupt = 1;
+
131}
+
132
+
133static void tfs_destroy(void *userarg)
+
134{
+
135 (void)userarg;
+
136
+
137 is_stop = true;
+
138}
+
139
+
140static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
141 const char *name) {
+
142 struct fuse_entry_param e;
+
143 memset(&e, 0, sizeof(e));
+
144
+
145 if (parent != FUSE_ROOT_ID)
+
146 goto err_out;
+
147 else if (strcmp(name, FILE_NAME) == 0) {
+
148 e.ino = FILE_INO;
+
149 lookup_cnt++;
+
150 } else
+
151 goto err_out;
+
152
+
153 e.attr_timeout = NO_TIMEOUT;
+
154 e.entry_timeout = NO_TIMEOUT;
+
155 if (tfs_stat(e.ino, &e.attr) != 0)
+
156 goto err_out;
+
157 fuse_reply_entry(req, &e);
+
158 return;
+
159
+
160err_out:
+
161 fuse_reply_err(req, ENOENT);
+
162}
+
163
+
164static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
165 uint64_t nlookup) {
+
166 (void) req;
+
167 if(ino == FILE_INO)
+
168 lookup_cnt -= nlookup;
+
169 else
+
170 assert(ino == FUSE_ROOT_ID);
+
171 fuse_reply_none(req);
+
172}
+
173
+
174static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
175 struct fuse_file_info *fi) {
+
176 struct stat stbuf;
+
177
+
178 (void) fi;
+
179
+
180 memset(&stbuf, 0, sizeof(stbuf));
+
181 if (tfs_stat(ino, &stbuf) != 0)
+
182 fuse_reply_err(req, ENOENT);
+
183 else
+
184 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
185}
+
186
+
187struct dirbuf {
+
188 char *p;
+
189 size_t size;
+
190};
+
191
+
192static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
193 fuse_ino_t ino) {
+
194 struct stat stbuf;
+
195 size_t oldsize = b->size;
+
196 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
197 b->p = (char *) realloc(b->p, b->size);
+
198 memset(&stbuf, 0, sizeof(stbuf));
+
199 stbuf.st_ino = ino;
+
200 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
201 b->size);
+
202}
+
203
+
204#define min(x, y) ((x) < (y) ? (x) : (y))
+
205
+
206static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
207 off_t off, size_t maxsize) {
+
208 if (off < bufsize)
+
209 return fuse_reply_buf(req, buf + off,
+
210 min(bufsize - off, maxsize));
+
211 else
+
212 return fuse_reply_buf(req, NULL, 0);
+
213}
+
214
+
215static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
216 off_t off, struct fuse_file_info *fi) {
+
217 (void) fi;
+
218
+
219 if (ino != FUSE_ROOT_ID)
+
220 fuse_reply_err(req, ENOTDIR);
+
221 else {
+
222 struct dirbuf b;
+
223
+
224 memset(&b, 0, sizeof(b));
+
225 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
226 reply_buf_limited(req, b.p, b.size, off, size);
+
227 free(b.p);
+
228 }
+
229}
+
230
+
231static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
232 struct fuse_file_info *fi) {
+
233
+
234 /* Make cache persistent even if file is closed,
+
235 this makes it easier to see the effects */
+
236 fi->keep_cache = 1;
+
237
+
238 if (ino == FUSE_ROOT_ID)
+
239 fuse_reply_err(req, EISDIR);
+
240 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
241 fuse_reply_err(req, EACCES);
+
242 else if (ino == FILE_INO)
+
243 fuse_reply_open(req, fi);
+
244 else {
+
245 // This should not happen
+
246 fprintf(stderr, "Got open for non-existing inode!\n");
+
247 fuse_reply_err(req, ENOENT);
+
248 }
+
249}
+
250
+
251static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
252 off_t off, struct fuse_file_info *fi) {
+
253 (void) fi;
+
254
+
255 assert(ino == FILE_INO);
+
256 reply_buf_limited(req, file_contents, file_size, off, size);
+
257}
+
258
+
259static const struct fuse_lowlevel_ops tfs_oper = {
+
260 .init = tfs_init,
+
261 .destroy = tfs_destroy,
+
262 .lookup = tfs_lookup,
+
263 .getattr = tfs_getattr,
+
264 .readdir = tfs_readdir,
+
265 .open = tfs_open,
+
266 .read = tfs_read,
+
267 .forget = tfs_forget,
+
268};
+
269
+
270static void update_fs(void) {
+
271 struct tm *now;
+
272 time_t t;
+
273 t = time(NULL);
+
274 now = localtime(&t);
+
275 assert(now != NULL);
+
276
+
277 file_size = strftime(file_contents, MAX_STR_LEN,
+
278 "The current time is %H:%M:%S\n", now);
+
279 assert(file_size != 0);
+
280}
+
281
+
282static void* update_fs_loop(void *data) {
+
283 struct fuse_session *se = (struct fuse_session*) data;
+
284
+
285 while(!is_stop) {
+
286 update_fs();
+
287 if (!options.no_notify && lookup_cnt) {
+
288 /* Only send notification if the kernel is aware of the inode */
+
289
+
290 /* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
291 * might come up during umount, when kernel side already releases
+
292 * all inodes, but does not send FUSE_DESTROY yet.
+
293 */
+
294 int ret =
+
295 fuse_lowlevel_notify_inval_inode(se, FILE_INO, 0, 0);
+
296 if ((ret != 0 && !is_stop) &&
+
297 ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
298 fprintf(stderr,
+
299 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
300 strerror(-ret), -ret);
+
301 abort();
+
302 }
+
303 }
+
304 sleep(options.update_interval);
+
305 }
+
306 return NULL;
+
307}
+
308
+
309static void show_help(const char *progname)
+
310{
+
311 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
312 printf("File-system specific options:\n"
+
313 " --update-interval=<secs> Update-rate of file system contents\n"
+
314 " --no-notify Disable kernel notifications\n"
+
315 "\n");
+
316}
+
317
+
318int main(int argc, char *argv[]) {
+
319 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
320 struct fuse_session *se;
+
321 struct fuse_cmdline_opts opts;
+
322 struct fuse_loop_config *config;
+
323 pthread_t updater;
+
324 int ret = -1;
+
325
+
326 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
327 return 1;
+
328
+
329 if (fuse_parse_cmdline(&args, &opts) != 0) {
+
330 ret = 1;
+
331 goto err_out1;
+
332 }
+
333
+
334 if (opts.show_help) {
+
335 show_help(argv[0]);
+ + +
338 ret = 0;
+
339 goto err_out1;
+
340 } else if (opts.show_version) {
+
341 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
343 ret = 0;
+
344 goto err_out1;
+
345 }
+
346
+
347 /* Initial contents */
+
348 update_fs();
+
349
+
350 se = fuse_session_new(&args, &tfs_oper,
+
351 sizeof(tfs_oper), NULL);
+
352 if (se == NULL)
+
353 goto err_out1;
+
354
+
355 if (fuse_set_signal_handlers(se) != 0)
+
356 goto err_out2;
+
357
+
358 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
359 goto err_out3;
+
360
+
361 fuse_daemonize(opts.foreground);
+
362
+
363 /* Start thread to update file contents */
+
364 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
365 if (ret != 0) {
+
366 fprintf(stderr, "pthread_create failed with %s\n",
+
367 strerror(ret));
+
368 goto err_out3;
+
369 }
+
370
+
371 /* Block until ctrl+c or fusermount -u */
+
372 if (opts.singlethread)
+
373 ret = fuse_session_loop(se);
+
374 else {
+
375 config = fuse_loop_cfg_create();
+
376 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
377 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
378 ret = fuse_session_loop_mt(se, config);
+
379 fuse_loop_cfg_destroy(config);
+
380 config = NULL;
+
381 }
+
382
+ +
384err_out3:
+ +
386err_out2:
+ +
388err_out1:
+
389 fuse_opt_free_args(&args);
+
390 free(opts.mountpoint);
+
391
+
392 return ret ? 1 : 0;
+
393}
+
394
+
395
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/example_2notify__store__retrieve_8c.html b/doc/html/example_2notify__store__retrieve_8c.html new file mode 100644 index 0000000..5b863cb --- /dev/null +++ b/doc/html/example_2notify__store__retrieve_8c.html @@ -0,0 +1,560 @@ + + + + + + + +libfuse: example/notify_store_retrieve.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_store_retrieve.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

+

While notify_inval_inode.c uses fuse_lowlevel_notify_inval_inode() to let the kernel know that it has to invalidate the cache, this example actively pushes the updated data into the kernel cache using fuse_lowlevel_notify_store().

+

To see the effect, first start the file system with the --no-notify option:

$ notify_store_retrieve --update-interval=1 --no-notify mnt/
+

Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+

If you instead enable the notification functions, the changes become visible:

$ notify_store_retrieve --update-interval=1 mnt/
+$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:40
+The current time is 15:58:41
+The current time is 15:58:42
+The current time is 15:58:43
+The current time is 15:58:44
+

+Compilation

+
gcc -Wall notify_store_retrieve.c `pkg-config fuse3 --cflags --libs` -o notify_store_retrieve
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
#include <stdbool.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define FILE_INO 2
+
#define FILE_NAME "current_time"
+
static char file_contents[MAX_STR_LEN];
+
static int lookup_cnt = 0;
+
static int open_cnt = 0;
+
static size_t file_size;
+
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
+
/* Keep track if we ever stored data (==1), and
+
received it back correctly (==2) */
+
static int retrieve_status = 0;
+
+
static bool is_umount = false;
+
+
/* updater thread tid */
+
static pthread_t updater;
+
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == FILE_INO) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = file_size;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, FILE_NAME) == 0) {
+
e.ino = FILE_INO;
+
} else
+
goto err_out;
+
+
e.attr_timeout = NO_TIMEOUT;
+
e.entry_timeout = NO_TIMEOUT;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
+
/*
+
* must only be set when the kernel knows about the entry,
+
* otherwise update_fs_loop() might see a positive count, but kernel
+
* would not have the entry yet
+
*/
+
if (e.ino == FILE_INO) {
+
pthread_mutex_lock(&lock);
+
lookup_cnt++;
+
pthread_mutex_unlock(&lock);
+
}
+
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == FILE_INO) {
+
pthread_mutex_lock(&lock);
+
lookup_cnt -= nlookup;
+
pthread_mutex_unlock(&lock);
+
} else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
+
if (ino == FUSE_ROOT_ID)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else if (ino == FILE_INO) {
+
fuse_reply_open(req, fi);
+
pthread_mutex_lock(&lock);
+
open_cnt++;
+
pthread_mutex_unlock(&lock);
+
} else {
+
// This should not happen
+
fprintf(stderr, "Got open for non-existing inode!\n");
+
fuse_reply_err(req, ENOENT);
+
}
+
}
+
+
static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
assert(ino == FILE_INO);
+
reply_buf_limited(req, file_contents, file_size, off, size);
+
}
+
+
static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
+
off_t offset, struct fuse_bufvec *data) {
+
struct fuse_bufvec bufv;
+
char buf[MAX_STR_LEN];
+
char *expected;
+
ssize_t ret;
+
+
assert(ino == FILE_INO);
+
assert(offset == 0);
+
expected = (char*) cookie;
+
+
bufv.count = 1;
+
bufv.idx = 0;
+
bufv.off = 0;
+
bufv.buf[0].size = MAX_STR_LEN;
+
bufv.buf[0].mem = buf;
+
bufv.buf[0].flags = 0;
+
+
ret = fuse_buf_copy(&bufv, data, 0);
+
assert(ret > 0);
+
assert(strncmp(buf, expected, ret) == 0);
+
free(expected);
+
retrieve_status = 2;
+ +
}
+
+
static void tfs_destroy(void *userdata)
+
{
+
(void)userdata;
+
+
is_umount = true;
+
+
pthread_join(updater, NULL);
+
}
+
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.open = tfs_open,
+
.read = tfs_read,
+
.forget = tfs_forget,
+
.retrieve_reply = tfs_retrieve_reply,
+
.destroy = tfs_destroy,
+
};
+
+
static void update_fs(void) {
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
file_size = strftime(file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(file_size != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
struct fuse_bufvec bufv;
+
int ret;
+
+
while(!is_umount) {
+
update_fs();
+
pthread_mutex_lock(&lock);
+
if (!options.no_notify && open_cnt && lookup_cnt) {
+
/* Only send notification if the kernel
+
is aware of the inode */
+
bufv.count = 1;
+
bufv.idx = 0;
+
bufv.off = 0;
+
bufv.buf[0].size = file_size;
+
bufv.buf[0].mem = file_contents;
+
bufv.buf[0].flags = 0;
+
+
/*
+
* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
* might come up during umount, when kernel side already releases
+
* all inodes, but does not send FUSE_DESTROY yet.
+
*/
+
+
ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
+
if ((ret != 0 && !is_umount) &&
+
ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
fprintf(stderr,
+
"ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
strerror(-ret), -ret);
+
abort();
+
}
+
+
/* To make sure that everything worked correctly, ask the
+
kernel to send us back the stored data */
+
ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
+
0, (void*) strdup(file_contents));
+
assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF ||
+
ret != -ENODEV);
+
if(retrieve_status == 0)
+
retrieve_status = 1;
+
}
+
pthread_mutex_unlock(&lock);
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+
assert(retrieve_status != 1);
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_store_retrieve.c.

+
+ + + + diff --git a/doc/html/example_2notify__store__retrieve_8c_source.html b/doc/html/example_2notify__store__retrieve_8c_source.html new file mode 100644 index 0000000..4885a23 --- /dev/null +++ b/doc/html/example_2notify__store__retrieve_8c_source.html @@ -0,0 +1,522 @@ + + + + + + + +libfuse: example/notify_store_retrieve.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_store_retrieve.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
61#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
62
+
63#include <fuse_lowlevel.h>
+
64#include <stdio.h>
+
65#include <stdlib.h>
+
66#include <string.h>
+
67#include <errno.h>
+
68#include <fcntl.h>
+
69#include <assert.h>
+
70#include <stddef.h>
+
71#include <unistd.h>
+
72#include <pthread.h>
+
73#include <stdbool.h>
+
74
+
75/* We can't actually tell the kernel that there is no
+
76 timeout, so we just send a big value */
+
77#define NO_TIMEOUT 500000
+
78
+
79#define MAX_STR_LEN 128
+
80#define FILE_INO 2
+
81#define FILE_NAME "current_time"
+
82static char file_contents[MAX_STR_LEN];
+
83static int lookup_cnt = 0;
+
84static int open_cnt = 0;
+
85static size_t file_size;
+
86static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
87
+
88/* Keep track if we ever stored data (==1), and
+
89 received it back correctly (==2) */
+
90static int retrieve_status = 0;
+
91
+
92static bool is_umount = false;
+
93
+
94/* updater thread tid */
+
95static pthread_t updater;
+
96
+
97
+
98/* Command line parsing */
+
99struct options {
+
100 int no_notify;
+
101 int update_interval;
+
102};
+
103static struct options options = {
+
104 .no_notify = 0,
+
105 .update_interval = 1,
+
106};
+
107
+
108#define OPTION(t, p) \
+
109 { t, offsetof(struct options, p), 1 }
+
110static const struct fuse_opt option_spec[] = {
+
111 OPTION("--no-notify", no_notify),
+
112 OPTION("--update-interval=%d", update_interval),
+ +
114};
+
115
+
116static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
117 stbuf->st_ino = ino;
+
118 if (ino == FUSE_ROOT_ID) {
+
119 stbuf->st_mode = S_IFDIR | 0755;
+
120 stbuf->st_nlink = 1;
+
121 }
+
122
+
123 else if (ino == FILE_INO) {
+
124 stbuf->st_mode = S_IFREG | 0444;
+
125 stbuf->st_nlink = 1;
+
126 stbuf->st_size = file_size;
+
127 }
+
128
+
129 else
+
130 return -1;
+
131
+
132 return 0;
+
133}
+
134
+
135static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
136 (void)userdata;
+
137
+
138 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
139 conn->no_interrupt = 1;
+
140}
+
141
+
142static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
143 const char *name) {
+
144 struct fuse_entry_param e;
+
145 memset(&e, 0, sizeof(e));
+
146
+
147 if (parent != FUSE_ROOT_ID)
+
148 goto err_out;
+
149 else if (strcmp(name, FILE_NAME) == 0) {
+
150 e.ino = FILE_INO;
+
151 } else
+
152 goto err_out;
+
153
+
154 e.attr_timeout = NO_TIMEOUT;
+
155 e.entry_timeout = NO_TIMEOUT;
+
156 if (tfs_stat(e.ino, &e.attr) != 0)
+
157 goto err_out;
+
158 fuse_reply_entry(req, &e);
+
159
+
160 /*
+
161 * must only be set when the kernel knows about the entry,
+
162 * otherwise update_fs_loop() might see a positive count, but kernel
+
163 * would not have the entry yet
+
164 */
+
165 if (e.ino == FILE_INO) {
+
166 pthread_mutex_lock(&lock);
+
167 lookup_cnt++;
+
168 pthread_mutex_unlock(&lock);
+
169 }
+
170
+
171 return;
+
172
+
173err_out:
+
174 fuse_reply_err(req, ENOENT);
+
175}
+
176
+
177static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
178 uint64_t nlookup) {
+
179 (void) req;
+
180 if(ino == FILE_INO) {
+
181 pthread_mutex_lock(&lock);
+
182 lookup_cnt -= nlookup;
+
183 pthread_mutex_unlock(&lock);
+
184 } else
+
185 assert(ino == FUSE_ROOT_ID);
+
186 fuse_reply_none(req);
+
187}
+
188
+
189static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
190 struct fuse_file_info *fi) {
+
191 struct stat stbuf;
+
192
+
193 (void) fi;
+
194
+
195 memset(&stbuf, 0, sizeof(stbuf));
+
196 if (tfs_stat(ino, &stbuf) != 0)
+
197 fuse_reply_err(req, ENOENT);
+
198 else
+
199 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
200}
+
201
+
202struct dirbuf {
+
203 char *p;
+
204 size_t size;
+
205};
+
206
+
207static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
208 fuse_ino_t ino) {
+
209 struct stat stbuf;
+
210 size_t oldsize = b->size;
+
211 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
212 b->p = (char *) realloc(b->p, b->size);
+
213 memset(&stbuf, 0, sizeof(stbuf));
+
214 stbuf.st_ino = ino;
+
215 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
216 b->size);
+
217}
+
218
+
219#define min(x, y) ((x) < (y) ? (x) : (y))
+
220
+
221static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
222 off_t off, size_t maxsize) {
+
223 if (off < bufsize)
+
224 return fuse_reply_buf(req, buf + off,
+
225 min(bufsize - off, maxsize));
+
226 else
+
227 return fuse_reply_buf(req, NULL, 0);
+
228}
+
229
+
230static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
231 off_t off, struct fuse_file_info *fi) {
+
232 (void) fi;
+
233
+
234 if (ino != FUSE_ROOT_ID)
+
235 fuse_reply_err(req, ENOTDIR);
+
236 else {
+
237 struct dirbuf b;
+
238
+
239 memset(&b, 0, sizeof(b));
+
240 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
241 reply_buf_limited(req, b.p, b.size, off, size);
+
242 free(b.p);
+
243 }
+
244}
+
245
+
246static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
247 struct fuse_file_info *fi) {
+
248
+
249 /* Make cache persistent even if file is closed,
+
250 this makes it easier to see the effects */
+
251 fi->keep_cache = 1;
+
252
+
253 if (ino == FUSE_ROOT_ID)
+
254 fuse_reply_err(req, EISDIR);
+
255 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
256 fuse_reply_err(req, EACCES);
+
257 else if (ino == FILE_INO) {
+
258 fuse_reply_open(req, fi);
+
259 pthread_mutex_lock(&lock);
+
260 open_cnt++;
+
261 pthread_mutex_unlock(&lock);
+
262 } else {
+
263 // This should not happen
+
264 fprintf(stderr, "Got open for non-existing inode!\n");
+
265 fuse_reply_err(req, ENOENT);
+
266 }
+
267}
+
268
+
269static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
270 off_t off, struct fuse_file_info *fi) {
+
271 (void) fi;
+
272
+
273 assert(ino == FILE_INO);
+
274 reply_buf_limited(req, file_contents, file_size, off, size);
+
275}
+
276
+
277static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
+
278 off_t offset, struct fuse_bufvec *data) {
+
279 struct fuse_bufvec bufv;
+
280 char buf[MAX_STR_LEN];
+
281 char *expected;
+
282 ssize_t ret;
+
283
+
284 assert(ino == FILE_INO);
+
285 assert(offset == 0);
+
286 expected = (char*) cookie;
+
287
+
288 bufv.count = 1;
+
289 bufv.idx = 0;
+
290 bufv.off = 0;
+
291 bufv.buf[0].size = MAX_STR_LEN;
+
292 bufv.buf[0].mem = buf;
+
293 bufv.buf[0].flags = 0;
+
294
+
295 ret = fuse_buf_copy(&bufv, data, 0);
+
296 assert(ret > 0);
+
297 assert(strncmp(buf, expected, ret) == 0);
+
298 free(expected);
+
299 retrieve_status = 2;
+
300 fuse_reply_none(req);
+
301}
+
302
+
303static void tfs_destroy(void *userdata)
+
304{
+
305 (void)userdata;
+
306
+
307 is_umount = true;
+
308
+
309 pthread_join(updater, NULL);
+
310}
+
311
+
312
+
313static const struct fuse_lowlevel_ops tfs_oper = {
+
314 .init = tfs_init,
+
315 .lookup = tfs_lookup,
+
316 .getattr = tfs_getattr,
+
317 .readdir = tfs_readdir,
+
318 .open = tfs_open,
+
319 .read = tfs_read,
+
320 .forget = tfs_forget,
+
321 .retrieve_reply = tfs_retrieve_reply,
+
322 .destroy = tfs_destroy,
+
323};
+
324
+
325static void update_fs(void) {
+
326 struct tm *now;
+
327 time_t t;
+
328 t = time(NULL);
+
329 now = localtime(&t);
+
330 assert(now != NULL);
+
331
+
332 file_size = strftime(file_contents, MAX_STR_LEN,
+
333 "The current time is %H:%M:%S\n", now);
+
334 assert(file_size != 0);
+
335}
+
336
+
337static void* update_fs_loop(void *data) {
+
338 struct fuse_session *se = (struct fuse_session*) data;
+
339 struct fuse_bufvec bufv;
+
340 int ret;
+
341
+
342 while(!is_umount) {
+
343 update_fs();
+
344 pthread_mutex_lock(&lock);
+
345 if (!options.no_notify && open_cnt && lookup_cnt) {
+
346 /* Only send notification if the kernel
+
347 is aware of the inode */
+
348 bufv.count = 1;
+
349 bufv.idx = 0;
+
350 bufv.off = 0;
+
351 bufv.buf[0].size = file_size;
+
352 bufv.buf[0].mem = file_contents;
+
353 bufv.buf[0].flags = 0;
+
354
+
355 /*
+
356 * Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
357 * might come up during umount, when kernel side already releases
+
358 * all inodes, but does not send FUSE_DESTROY yet.
+
359 */
+
360
+
361 ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
+
362 if ((ret != 0 && !is_umount) &&
+
363 ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
364 fprintf(stderr,
+
365 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
366 strerror(-ret), -ret);
+
367 abort();
+
368 }
+
369
+
370 /* To make sure that everything worked correctly, ask the
+
371 kernel to send us back the stored data */
+
372 ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
+
373 0, (void*) strdup(file_contents));
+
374 assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF ||
+
375 ret != -ENODEV);
+
376 if(retrieve_status == 0)
+
377 retrieve_status = 1;
+
378 }
+
379 pthread_mutex_unlock(&lock);
+
380 sleep(options.update_interval);
+
381 }
+
382 return NULL;
+
383}
+
384
+
385static void show_help(const char *progname)
+
386{
+
387 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
388 printf("File-system specific options:\n"
+
389 " --update-interval=<secs> Update-rate of file system contents\n"
+
390 " --no-notify Disable kernel notifications\n"
+
391 "\n");
+
392}
+
393
+
394int main(int argc, char *argv[]) {
+
395 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
396 struct fuse_session *se;
+
397 struct fuse_cmdline_opts opts;
+
398 struct fuse_loop_config *config;
+
399 int ret = -1;
+
400
+
401 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
402 return 1;
+
403
+
404 if (fuse_parse_cmdline(&args, &opts) != 0)
+
405 return 1;
+
406 if (opts.show_help) {
+
407 show_help(argv[0]);
+ + +
410 ret = 0;
+
411 goto err_out1;
+
412 } else if (opts.show_version) {
+
413 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
415 ret = 0;
+
416 goto err_out1;
+
417 }
+
418
+
419 /* Initial contents */
+
420 update_fs();
+
421
+
422 se = fuse_session_new(&args, &tfs_oper,
+
423 sizeof(tfs_oper), NULL);
+
424 if (se == NULL)
+
425 goto err_out1;
+
426
+
427 if (fuse_set_signal_handlers(se) != 0)
+
428 goto err_out2;
+
429
+
430 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
431 goto err_out3;
+
432
+
433 fuse_daemonize(opts.foreground);
+
434
+
435 /* Start thread to update file contents */
+
436 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
437 if (ret != 0) {
+
438 fprintf(stderr, "pthread_create failed with %s\n",
+
439 strerror(ret));
+
440 goto err_out3;
+
441 }
+
442
+
443 /* Block until ctrl+c or fusermount -u */
+
444 if (opts.singlethread)
+
445 ret = fuse_session_loop(se);
+
446 else {
+
447 config = fuse_loop_cfg_create();
+
448 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
449 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
450 ret = fuse_session_loop_mt(se, config);
+
451 fuse_loop_cfg_destroy(config);
+
452 config = NULL;
+
453 }
+
454
+
455 assert(retrieve_status != 1);
+ +
457err_out3:
+ +
459err_out2:
+ +
461err_out1:
+
462 free(opts.mountpoint);
+
463 fuse_opt_free_args(&args);
+
464
+
465 return ret ? 1 : 0;
+
466}
+
467
+
468
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/example_2null_8c.html b/doc/html/example_2null_8c.html new file mode 100644 index 0000000..31d342f --- /dev/null +++ b/doc/html/example_2null_8c.html @@ -0,0 +1,779 @@ + + + + + + + +libfuse: example/null.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
null.c File Reference
+
+
+
#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This "filesystem" provides only a single file. The mountpoint needs to be a file rather than a directory. All writes to the file will be discarded, and reading the file always returns \0.

+

Compile with:

gcc -Wall null.c `pkg-config fuse3 --cflags --libs` -o null
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
+
#define _GNU_SOURCE
+
+
#include <fuse.h>
+
+
#ifdef HAVE_LIBULOCKMGR
+
#include <ulockmgr.h>
+
#endif
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
#include <sys/file.h> /* flock(2) */
+
+
#include "passthrough_helpers.h"
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
cfg->nullpath_ok = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
+
if(fi)
+
res = fstat(fi->fh, stbuf);
+
else
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
struct xmp_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
if (d == NULL)
+
return -ENOMEM;
+
+
d->dp = opendir(path);
+
if (d->dp == NULL) {
+
res = -errno;
+
free(d);
+
return res;
+
}
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (unsigned long) d;
+
return 0;
+
}
+
+
static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
{
+
return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
+
(void) path;
+
if (offset != d->offset) {
+
#ifndef __FreeBSD__
+
seekdir(d->dp, offset);
+
#else
+
/* Subtract the one that we add when calling
+
telldir() below */
+
seekdir(d->dp, offset-1);
+
#endif
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
struct stat st;
+
off_t nextoff;
+ +
+
if (!d->entry) {
+
d->entry = readdir(d->dp);
+
if (!d->entry)
+
break;
+
}
+
#ifdef HAVE_FSTATAT
+
if (flags & FUSE_READDIR_PLUS) {
+
int res;
+
+
res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
if (res != -1)
+
fill_flags |= FUSE_FILL_DIR_PLUS;
+
}
+
#endif
+
if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = d->entry->d_ino;
+
st.st_mode = d->entry->d_type << 12;
+
}
+
nextoff = telldir(d->dp);
+
#ifdef __FreeBSD__
+
/* Under FreeBSD, telldir() may return 0 the first time
+
it is called. But for libfuse, an offset of zero
+
means that offsets are not supported, so we shift
+
everything by one. */
+
nextoff++;
+
#endif
+
if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
break;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
return 0;
+
}
+
+
static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
(void) path;
+
closedir(d->dp);
+
free(d);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
if (S_ISFIFO(mode))
+
res = mkfifo(path, mode);
+
else
+
res = mknod(path, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
/* When we have renameat2() in libc, then we can implement flags */
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = fchmod(fi->fh, mode);
+
else
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi)
+
res = fchown(fi->fh, uid, gid);
+
else
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
if (fi)
+
res = futimens(fi->fh, ts);
+
else
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags, mode);
+
if (fd == -1)
+
return -errno;
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags);
+
if (fd == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pread(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
size_t size, off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec *src;
+
+
(void) path;
+
+
src = malloc(sizeof(struct fuse_bufvec));
+
if (src == NULL)
+
return -ENOMEM;
+
+
*src = FUSE_BUFVEC_INIT(size);
+
+ +
src->buf[0].fd = fi->fh;
+
src->buf[0].pos = offset;
+
+
*bufp = src;
+
+
return 0;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pwrite(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
+
(void) path;
+
+ +
dst.buf[0].fd = fi->fh;
+
dst.buf[0].pos = offset;
+
+ +
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
/* This is called from every close on an open file, so call the
+
close on the underlying filesystem. But since flush may be
+
called multiple times for an open file, this must not really
+
close the file. This is important if used on a network
+
filesystem like NFS which flush the data/metadata on close() */
+
res = close(dup(fi->fh));
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) path;
+
+
#ifndef HAVE_FDATASYNC
+
(void) isdatasync;
+
#else
+
if (isdatasync)
+
res = fdatasync(fi->fh);
+
else
+
#endif
+
res = fsync(fi->fh);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
(void) path;
+
+
return do_fallocate(fi->fh, mode, offset, length);
+
}
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_LIBULOCKMGR
+
static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
struct flock *lock)
+
{
+
(void) path;
+
+
return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
sizeof(fi->lock_owner));
+
}
+
#endif
+
+
static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
{
+
int res;
+
(void) path;
+
+
res = flock(fi->fh, op);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t off_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t off_out, size_t len, int flags)
+
{
+
ssize_t res;
+
(void) path_in;
+
(void) path_out;
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
off_t res;
+
(void) path;
+
+
res = lseek(fi->fh, off, whence);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
+
#ifdef HAVE_STATX
+
static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
struct fuse_file_info *fi)
+
{
+
int fd = -1;
+
int res;
+
+
if (fi)
+
fd = fi->fh;
+
+
res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.opendir = xmp_opendir,
+
.readdir = xmp_readdir,
+
.releasedir = xmp_releasedir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.create = xmp_create,
+
.open = xmp_open,
+
.read = xmp_read,
+
.read_buf = xmp_read_buf,
+
.write = xmp_write,
+
.write_buf = xmp_write_buf,
+
.statfs = xmp_statfs,
+
.flush = xmp_flush,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
.fallocate = xmp_fallocate,
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_LIBULOCKMGR
+
.lock = xmp_lock,
+
#endif
+
.flock = xmp_flock,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
#ifdef HAVE_STATX
+
.statx = xmp_statx,
+
#endif
+
};
+
+
int main(int argc, char *argv[])
+
{
+
umask(0);
+
return fuse_main(argc, argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file null.c.

+
+ + + + diff --git a/doc/html/example_2null_8c_source.html b/doc/html/example_2null_8c_source.html new file mode 100644 index 0000000..6bab7f6 --- /dev/null +++ b/doc/html/example_2null_8c_source.html @@ -0,0 +1,196 @@ + + + + + + + +libfuse: example/null.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
null.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
25#define FUSE_USE_VERSION 31
+
26
+
27#include <fuse.h>
+
28#include <fuse_lowlevel.h>
+
29#include <stdio.h>
+
30#include <stdlib.h>
+
31#include <string.h>
+
32#include <unistd.h>
+
33#include <time.h>
+
34#include <errno.h>
+
35
+
36static int null_getattr(const char *path, struct stat *stbuf,
+
37 struct fuse_file_info *fi)
+
38{
+
39 (void) fi;
+
40
+
41 if(strcmp(path, "/") != 0)
+
42 return -ENOENT;
+
43
+
44 stbuf->st_mode = S_IFREG | 0644;
+
45 stbuf->st_nlink = 1;
+
46 stbuf->st_uid = getuid();
+
47 stbuf->st_gid = getgid();
+
48 stbuf->st_size = (1ULL << 32); /* 4G */
+
49 stbuf->st_blocks = 0;
+
50 stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = time(NULL);
+
51
+
52 return 0;
+
53}
+
54
+
55static int null_truncate(const char *path, off_t size,
+
56 struct fuse_file_info *fi)
+
57{
+
58 (void) size;
+
59 (void) fi;
+
60
+
61 if(strcmp(path, "/") != 0)
+
62 return -ENOENT;
+
63
+
64 return 0;
+
65}
+
66
+
67static int null_open(const char *path, struct fuse_file_info *fi)
+
68{
+
69 (void) fi;
+
70
+
71 if(strcmp(path, "/") != 0)
+
72 return -ENOENT;
+
73
+
74 return 0;
+
75}
+
76
+
77static int null_read(const char *path, char *buf, size_t size,
+
78 off_t offset, struct fuse_file_info *fi)
+
79{
+
80 (void) buf;
+
81 (void) offset;
+
82 (void) fi;
+
83
+
84 if(strcmp(path, "/") != 0)
+
85 return -ENOENT;
+
86
+
87 if (offset >= (1ULL << 32))
+
88 return 0;
+
89
+
90 memset(buf, 0, size);
+
91 return size;
+
92}
+
93
+
94static int null_write(const char *path, const char *buf, size_t size,
+
95 off_t offset, struct fuse_file_info *fi)
+
96{
+
97 (void) buf;
+
98 (void) offset;
+
99 (void) fi;
+
100
+
101 if(strcmp(path, "/") != 0)
+
102 return -ENOENT;
+
103
+
104 return size;
+
105}
+
106
+
107static const struct fuse_operations null_oper = {
+
108 .getattr = null_getattr,
+
109 .truncate = null_truncate,
+
110 .open = null_open,
+
111 .read = null_read,
+
112 .write = null_write,
+
113};
+
114
+
115int main(int argc, char *argv[])
+
116{
+
117 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
118 struct fuse_cmdline_opts opts;
+
119 struct stat stbuf;
+
120
+
121 if (fuse_parse_cmdline(&args, &opts) != 0)
+
122 return 1;
+
123 fuse_opt_free_args(&args);
+
124
+
125 if (!opts.mountpoint) {
+
126 fprintf(stderr, "missing mountpoint parameter\n");
+
127 return 1;
+
128 }
+
129
+
130 if (stat(opts.mountpoint, &stbuf) == -1) {
+
131 fprintf(stderr ,"failed to access mountpoint %s: %s\n",
+
132 opts.mountpoint, strerror(errno));
+
133 free(opts.mountpoint);
+
134 return 1;
+
135 }
+
136 free(opts.mountpoint);
+
137 if (!S_ISREG(stbuf.st_mode)) {
+
138 fprintf(stderr, "mountpoint is not a regular file\n");
+
139 return 1;
+
140 }
+
141
+
142 return fuse_main(argc, argv, &null_oper, NULL);
+
143}
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+ + + + diff --git a/doc/html/example_2passthrough_8c.html b/doc/html/example_2passthrough_8c.html new file mode 100644 index 0000000..56d54f4 --- /dev/null +++ b/doc/html/example_2passthrough_8c.html @@ -0,0 +1,679 @@ + + + + + + + +libfuse: example/passthrough.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. Its performance is terrible.

+

Compile with

gcc -Wall passthrough.c `pkg-config fuse3 --cflags --libs` -o passthrough
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
+
#define _GNU_SOURCE
+
+
#ifdef linux
+
/* For pread()/pwrite()/utimensat() */
+
#define _XOPEN_SOURCE 700
+
#endif
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
+
#include "passthrough_helpers.h"
+
+
static int fill_dir_plus = 0;
+
static int readdir_zero_ino;
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = !readdir_zero_ino;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
if (!cfg->auto_cache) {
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
}
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
DIR *dp;
+
struct dirent *de;
+
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
dp = opendir(path);
+
if (dp == NULL)
+
return -errno;
+
+
while ((de = readdir(dp)) != NULL) {
+
struct stat st;
+
if (fill_dir_plus) {
+
fstatat(dirfd(dp), de->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
} else {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = de->d_ino;
+
st.st_mode = de->d_type << 12;
+
}
+
if (readdir_zero_ino)
+
st.st_ino = 0;
+
if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
+
break;
+
}
+
+
closedir(dp);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi != NULL)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
res = open(path, fi->flags, mode);
+
if (res == -1)
+
return -errno;
+
+
fi->fh = res;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
res = open(path, fi->flags);
+
if (res == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = res;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
if(fi == NULL)
+
fd = open(path, O_RDONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = pread(fd, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
(void) fi;
+
if(fi == NULL)
+
fd = open(path, O_WRONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = pwrite(fd, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
/* Just a stub. This method is optional and can safely be left
+
unimplemented */
+
+
(void) path;
+
(void) isdatasync;
+
(void) fi;
+
return 0;
+
}
+
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
(void) fi;
+
+
if(fi == NULL)
+
fd = open(path, O_WRONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = do_fallocate(fd, mode, offset, length);
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t offset_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t offset_out, size_t len, int flags)
+
{
+
int fd_in, fd_out;
+
ssize_t res;
+
+
if(fi_in == NULL)
+
fd_in = open(path_in, O_RDONLY);
+
else
+
fd_in = fi_in->fh;
+
+
if (fd_in == -1)
+
return -errno;
+
+
if(fi_out == NULL)
+
fd_out = open(path_out, O_WRONLY);
+
else
+
fd_out = fi_out->fh;
+
+
if (fd_out == -1) {
+
close(fd_in);
+
return -errno;
+
}
+
+
res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
+
flags);
+
if (res == -1)
+
res = -errno;
+
+
if (fi_out == NULL)
+
close(fd_out);
+
if (fi_in == NULL)
+
close(fd_in);
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
int fd;
+
off_t res;
+
+
if (fi == NULL)
+
fd = open(path, O_RDONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = lseek(fd, off, whence);
+
if (res == -1)
+
res = -errno;
+
+
if (fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
#ifdef HAVE_STATX
+
static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
struct fuse_file_info *fi)
+
{
+
int fd = -1;
+
int res;
+
+
if (fi)
+
fd = fi->fh;
+
+
res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.readdir = xmp_readdir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.open = xmp_open,
+
.create = xmp_create,
+
.read = xmp_read,
+
.write = xmp_write,
+
.statfs = xmp_statfs,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
.fallocate = xmp_fallocate,
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
#ifdef HAVE_STATX
+
.statx = xmp_statx,
+
#endif
+
};
+
+
int main(int argc, char *argv[])
+
{
+
enum { MAX_ARGS = 10 };
+
int i,new_argc;
+
char *new_argv[MAX_ARGS];
+
+
umask(0);
+
/* Process the "--plus" option apart */
+
for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
+
if (!strcmp(argv[i], "--plus")) {
+
fill_dir_plus = FUSE_FILL_DIR_PLUS;
+
} else if (!strcmp(argv[i], "--readdir-zero-inodes")) {
+
// Return zero inodes from readdir
+
readdir_zero_ino = 1;
+
} else {
+
new_argv[new_argc++] = argv[i];
+
}
+
}
+
return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ +
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t auto_cache
Definition fuse.h:253
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file passthrough.c.

+
+ + + + diff --git a/doc/html/example_2passthrough_8c_source.html b/doc/html/example_2passthrough_8c_source.html new file mode 100644 index 0000000..fb7c270 --- /dev/null +++ b/doc/html/example_2passthrough_8c_source.html @@ -0,0 +1,665 @@ + + + + + + + +libfuse: example/passthrough.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
26#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
27
+
28#define _GNU_SOURCE
+
29
+
30#ifdef linux
+
31/* For pread()/pwrite()/utimensat() */
+
32#define _XOPEN_SOURCE 700
+
33#endif
+
34
+
35#include <fuse.h>
+
36#include <stdio.h>
+
37#include <string.h>
+
38#include <unistd.h>
+
39#include <fcntl.h>
+
40#include <sys/stat.h>
+
41#include <dirent.h>
+
42#include <errno.h>
+
43#include <sys/time.h>
+
44#ifdef HAVE_SETXATTR
+
45#include <sys/xattr.h>
+
46#endif
+
47
+
48#include "passthrough_helpers.h"
+
49
+
50static int fill_dir_plus = 0;
+
51static int readdir_zero_ino;
+
52
+
53static void *xmp_init(struct fuse_conn_info *conn,
+
54 struct fuse_config *cfg)
+
55{
+
56 (void) conn;
+
57 cfg->use_ino = !readdir_zero_ino;
+
58
+
59 /* parallel_direct_writes feature depends on direct_io features.
+
60 To make parallel_direct_writes valid, need either set cfg->direct_io
+
61 in current function (recommended in high level API) or set fi->direct_io
+
62 in xmp_create() or xmp_open(). */
+
63 // cfg->direct_io = 1;
+ +
65
+
66 /* Pick up changes from lower filesystem right away. This is
+
67 also necessary for better hardlink support. When the kernel
+
68 calls the unlink() handler, it does not know the inode of
+
69 the to-be-removed entry and can therefore not invalidate
+
70 the cache of the associated inode - resulting in an
+
71 incorrect st_nlink value being reported for any remaining
+
72 hardlinks to this inode. */
+
73 if (!cfg->auto_cache) {
+
74 cfg->entry_timeout = 0;
+
75 cfg->attr_timeout = 0;
+
76 cfg->negative_timeout = 0;
+
77 }
+
78
+
79 return NULL;
+
80}
+
81
+
82static int xmp_getattr(const char *path, struct stat *stbuf,
+
83 struct fuse_file_info *fi)
+
84{
+
85 (void) fi;
+
86 int res;
+
87
+
88 res = lstat(path, stbuf);
+
89 if (res == -1)
+
90 return -errno;
+
91
+
92 return 0;
+
93}
+
94
+
95static int xmp_access(const char *path, int mask)
+
96{
+
97 int res;
+
98
+
99 res = access(path, mask);
+
100 if (res == -1)
+
101 return -errno;
+
102
+
103 return 0;
+
104}
+
105
+
106static int xmp_readlink(const char *path, char *buf, size_t size)
+
107{
+
108 int res;
+
109
+
110 res = readlink(path, buf, size - 1);
+
111 if (res == -1)
+
112 return -errno;
+
113
+
114 buf[res] = '\0';
+
115 return 0;
+
116}
+
117
+
118
+
119static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
120 off_t offset, struct fuse_file_info *fi,
+
121 enum fuse_readdir_flags flags)
+
122{
+
123 DIR *dp;
+
124 struct dirent *de;
+
125
+
126 (void) offset;
+
127 (void) fi;
+
128 (void) flags;
+
129
+
130 dp = opendir(path);
+
131 if (dp == NULL)
+
132 return -errno;
+
133
+
134 while ((de = readdir(dp)) != NULL) {
+
135 struct stat st;
+
136 if (fill_dir_plus) {
+
137 fstatat(dirfd(dp), de->d_name, &st,
+
138 AT_SYMLINK_NOFOLLOW);
+
139 } else {
+
140 memset(&st, 0, sizeof(st));
+
141 st.st_ino = de->d_ino;
+
142 st.st_mode = de->d_type << 12;
+
143 }
+
144 if (readdir_zero_ino)
+
145 st.st_ino = 0;
+
146 if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
+
147 break;
+
148 }
+
149
+
150 closedir(dp);
+
151 return 0;
+
152}
+
153
+
154static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
155{
+
156 int res;
+
157
+
158 res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
+
159 if (res == -1)
+
160 return -errno;
+
161
+
162 return 0;
+
163}
+
164
+
165static int xmp_mkdir(const char *path, mode_t mode)
+
166{
+
167 int res;
+
168
+
169 res = mkdir(path, mode);
+
170 if (res == -1)
+
171 return -errno;
+
172
+
173 return 0;
+
174}
+
175
+
176static int xmp_unlink(const char *path)
+
177{
+
178 int res;
+
179
+
180 res = unlink(path);
+
181 if (res == -1)
+
182 return -errno;
+
183
+
184 return 0;
+
185}
+
186
+
187static int xmp_rmdir(const char *path)
+
188{
+
189 int res;
+
190
+
191 res = rmdir(path);
+
192 if (res == -1)
+
193 return -errno;
+
194
+
195 return 0;
+
196}
+
197
+
198static int xmp_symlink(const char *from, const char *to)
+
199{
+
200 int res;
+
201
+
202 res = symlink(from, to);
+
203 if (res == -1)
+
204 return -errno;
+
205
+
206 return 0;
+
207}
+
208
+
209static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
210{
+
211 int res;
+
212
+
213 if (flags)
+
214 return -EINVAL;
+
215
+
216 res = rename(from, to);
+
217 if (res == -1)
+
218 return -errno;
+
219
+
220 return 0;
+
221}
+
222
+
223static int xmp_link(const char *from, const char *to)
+
224{
+
225 int res;
+
226
+
227 res = link(from, to);
+
228 if (res == -1)
+
229 return -errno;
+
230
+
231 return 0;
+
232}
+
233
+
234static int xmp_chmod(const char *path, mode_t mode,
+
235 struct fuse_file_info *fi)
+
236{
+
237 (void) fi;
+
238 int res;
+
239
+
240 res = chmod(path, mode);
+
241 if (res == -1)
+
242 return -errno;
+
243
+
244 return 0;
+
245}
+
246
+
247static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
248 struct fuse_file_info *fi)
+
249{
+
250 (void) fi;
+
251 int res;
+
252
+
253 res = lchown(path, uid, gid);
+
254 if (res == -1)
+
255 return -errno;
+
256
+
257 return 0;
+
258}
+
259
+
260static int xmp_truncate(const char *path, off_t size,
+
261 struct fuse_file_info *fi)
+
262{
+
263 int res;
+
264
+
265 if (fi != NULL)
+
266 res = ftruncate(fi->fh, size);
+
267 else
+
268 res = truncate(path, size);
+
269 if (res == -1)
+
270 return -errno;
+
271
+
272 return 0;
+
273}
+
274
+
275#ifdef HAVE_UTIMENSAT
+
276static int xmp_utimens(const char *path, const struct timespec ts[2],
+
277 struct fuse_file_info *fi)
+
278{
+
279 (void) fi;
+
280 int res;
+
281
+
282 /* don't use utime/utimes since they follow symlinks */
+
283 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
284 if (res == -1)
+
285 return -errno;
+
286
+
287 return 0;
+
288}
+
289#endif
+
290
+
291static int xmp_create(const char *path, mode_t mode,
+
292 struct fuse_file_info *fi)
+
293{
+
294 int res;
+
295
+
296 res = open(path, fi->flags, mode);
+
297 if (res == -1)
+
298 return -errno;
+
299
+
300 fi->fh = res;
+
301 return 0;
+
302}
+
303
+
304static int xmp_open(const char *path, struct fuse_file_info *fi)
+
305{
+
306 int res;
+
307
+
308 res = open(path, fi->flags);
+
309 if (res == -1)
+
310 return -errno;
+
311
+
312 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
313 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
314 for writes to the same file). */
+
315 if (fi->flags & O_DIRECT) {
+
316 fi->direct_io = 1;
+ +
318 }
+
319
+
320 fi->fh = res;
+
321 return 0;
+
322}
+
323
+
324static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
325 struct fuse_file_info *fi)
+
326{
+
327 int fd;
+
328 int res;
+
329
+
330 if(fi == NULL)
+
331 fd = open(path, O_RDONLY);
+
332 else
+
333 fd = fi->fh;
+
334
+
335 if (fd == -1)
+
336 return -errno;
+
337
+
338 res = pread(fd, buf, size, offset);
+
339 if (res == -1)
+
340 res = -errno;
+
341
+
342 if(fi == NULL)
+
343 close(fd);
+
344 return res;
+
345}
+
346
+
347static int xmp_write(const char *path, const char *buf, size_t size,
+
348 off_t offset, struct fuse_file_info *fi)
+
349{
+
350 int fd;
+
351 int res;
+
352
+
353 (void) fi;
+
354 if(fi == NULL)
+
355 fd = open(path, O_WRONLY);
+
356 else
+
357 fd = fi->fh;
+
358
+
359 if (fd == -1)
+
360 return -errno;
+
361
+
362 res = pwrite(fd, buf, size, offset);
+
363 if (res == -1)
+
364 res = -errno;
+
365
+
366 if(fi == NULL)
+
367 close(fd);
+
368 return res;
+
369}
+
370
+
371static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
372{
+
373 int res;
+
374
+
375 res = statvfs(path, stbuf);
+
376 if (res == -1)
+
377 return -errno;
+
378
+
379 return 0;
+
380}
+
381
+
382static int xmp_release(const char *path, struct fuse_file_info *fi)
+
383{
+
384 (void) path;
+
385 close(fi->fh);
+
386 return 0;
+
387}
+
388
+
389static int xmp_fsync(const char *path, int isdatasync,
+
390 struct fuse_file_info *fi)
+
391{
+
392 /* Just a stub. This method is optional and can safely be left
+
393 unimplemented */
+
394
+
395 (void) path;
+
396 (void) isdatasync;
+
397 (void) fi;
+
398 return 0;
+
399}
+
400
+
401static int xmp_fallocate(const char *path, int mode,
+
402 off_t offset, off_t length, struct fuse_file_info *fi)
+
403{
+
404 int fd;
+
405 int res;
+
406
+
407 (void) fi;
+
408
+
409 if(fi == NULL)
+
410 fd = open(path, O_WRONLY);
+
411 else
+
412 fd = fi->fh;
+
413
+
414 if (fd == -1)
+
415 return -errno;
+
416
+
417 res = do_fallocate(fd, mode, offset, length);
+
418
+
419 if(fi == NULL)
+
420 close(fd);
+
421 return res;
+
422}
+
423
+
424#ifdef HAVE_SETXATTR
+
425/* xattr operations are optional and can safely be left unimplemented */
+
426static int xmp_setxattr(const char *path, const char *name, const char *value,
+
427 size_t size, int flags)
+
428{
+
429 int res = lsetxattr(path, name, value, size, flags);
+
430 if (res == -1)
+
431 return -errno;
+
432 return 0;
+
433}
+
434
+
435static int xmp_getxattr(const char *path, const char *name, char *value,
+
436 size_t size)
+
437{
+
438 int res = lgetxattr(path, name, value, size);
+
439 if (res == -1)
+
440 return -errno;
+
441 return res;
+
442}
+
443
+
444static int xmp_listxattr(const char *path, char *list, size_t size)
+
445{
+
446 int res = llistxattr(path, list, size);
+
447 if (res == -1)
+
448 return -errno;
+
449 return res;
+
450}
+
451
+
452static int xmp_removexattr(const char *path, const char *name)
+
453{
+
454 int res = lremovexattr(path, name);
+
455 if (res == -1)
+
456 return -errno;
+
457 return 0;
+
458}
+
459#endif /* HAVE_SETXATTR */
+
460
+
461#ifdef HAVE_COPY_FILE_RANGE
+
462static ssize_t xmp_copy_file_range(const char *path_in,
+
463 struct fuse_file_info *fi_in,
+
464 off_t offset_in, const char *path_out,
+
465 struct fuse_file_info *fi_out,
+
466 off_t offset_out, size_t len, int flags)
+
467{
+
468 int fd_in, fd_out;
+
469 ssize_t res;
+
470
+
471 if(fi_in == NULL)
+
472 fd_in = open(path_in, O_RDONLY);
+
473 else
+
474 fd_in = fi_in->fh;
+
475
+
476 if (fd_in == -1)
+
477 return -errno;
+
478
+
479 if(fi_out == NULL)
+
480 fd_out = open(path_out, O_WRONLY);
+
481 else
+
482 fd_out = fi_out->fh;
+
483
+
484 if (fd_out == -1) {
+
485 close(fd_in);
+
486 return -errno;
+
487 }
+
488
+
489 res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
+
490 flags);
+
491 if (res == -1)
+
492 res = -errno;
+
493
+
494 if (fi_out == NULL)
+
495 close(fd_out);
+
496 if (fi_in == NULL)
+
497 close(fd_in);
+
498
+
499 return res;
+
500}
+
501#endif
+
502
+
503static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
504{
+
505 int fd;
+
506 off_t res;
+
507
+
508 if (fi == NULL)
+
509 fd = open(path, O_RDONLY);
+
510 else
+
511 fd = fi->fh;
+
512
+
513 if (fd == -1)
+
514 return -errno;
+
515
+
516 res = lseek(fd, off, whence);
+
517 if (res == -1)
+
518 res = -errno;
+
519
+
520 if (fi == NULL)
+
521 close(fd);
+
522 return res;
+
523}
+
524
+
525#ifdef HAVE_STATX
+
526static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
527 struct fuse_file_info *fi)
+
528{
+
529 int fd = -1;
+
530 int res;
+
531
+
532 if (fi)
+
533 fd = fi->fh;
+
534
+
535 res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
+
536 if (res == -1)
+
537 return -errno;
+
538
+
539 return 0;
+
540}
+
541#endif
+
542
+
543static const struct fuse_operations xmp_oper = {
+
544 .init = xmp_init,
+
545 .getattr = xmp_getattr,
+
546 .access = xmp_access,
+
547 .readlink = xmp_readlink,
+
548 .readdir = xmp_readdir,
+
549 .mknod = xmp_mknod,
+
550 .mkdir = xmp_mkdir,
+
551 .symlink = xmp_symlink,
+
552 .unlink = xmp_unlink,
+
553 .rmdir = xmp_rmdir,
+
554 .rename = xmp_rename,
+
555 .link = xmp_link,
+
556 .chmod = xmp_chmod,
+
557 .chown = xmp_chown,
+
558 .truncate = xmp_truncate,
+
559#ifdef HAVE_UTIMENSAT
+
560 .utimens = xmp_utimens,
+
561#endif
+
562 .open = xmp_open,
+
563 .create = xmp_create,
+
564 .read = xmp_read,
+
565 .write = xmp_write,
+
566 .statfs = xmp_statfs,
+
567 .release = xmp_release,
+
568 .fsync = xmp_fsync,
+
569 .fallocate = xmp_fallocate,
+
570#ifdef HAVE_SETXATTR
+
571 .setxattr = xmp_setxattr,
+
572 .getxattr = xmp_getxattr,
+
573 .listxattr = xmp_listxattr,
+
574 .removexattr = xmp_removexattr,
+
575#endif
+
576#ifdef HAVE_COPY_FILE_RANGE
+
577 .copy_file_range = xmp_copy_file_range,
+
578#endif
+
579 .lseek = xmp_lseek,
+
580#ifdef HAVE_STATX
+
581 .statx = xmp_statx,
+
582#endif
+
583};
+
584
+
585int main(int argc, char *argv[])
+
586{
+
587 enum { MAX_ARGS = 10 };
+
588 int i,new_argc;
+
589 char *new_argv[MAX_ARGS];
+
590
+
591 umask(0);
+
592 /* Process the "--plus" option apart */
+
593 for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
+
594 if (!strcmp(argv[i], "--plus")) {
+
595 fill_dir_plus = FUSE_FILL_DIR_PLUS;
+
596 } else if (!strcmp(argv[i], "--readdir-zero-inodes")) {
+
597 // Return zero inodes from readdir
+
598 readdir_zero_ino = 1;
+
599 } else {
+
600 new_argv[new_argc++] = argv[i];
+
601 }
+
602 }
+
603 return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
+
604}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ +
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t auto_cache
Definition fuse.h:253
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/example_2passthrough__fh_8c.html b/doc/html/example_2passthrough__fh_8c.html new file mode 100644 index 0000000..9649024 --- /dev/null +++ b/doc/html/example_2passthrough__fh_8c.html @@ -0,0 +1,783 @@ + + + + + + + +libfuse: example/passthrough_fh.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough_fh.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/file.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. This implementation is a little more sophisticated than the one in passthrough.c, so performance is not quite as bad.

+

Compile with:

gcc -Wall passthrough_fh.c `pkg-config fuse3 --cflags --libs` -lulockmgr -o passthrough_fh
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
+
#define _GNU_SOURCE
+
+
#include <fuse.h>
+
+
#ifdef HAVE_LIBULOCKMGR
+
#include <ulockmgr.h>
+
#endif
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
#include <sys/file.h> /* flock(2) */
+
+
#include "passthrough_helpers.h"
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
cfg->nullpath_ok = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
+
if(fi)
+
res = fstat(fi->fh, stbuf);
+
else
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
struct xmp_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
if (d == NULL)
+
return -ENOMEM;
+
+
d->dp = opendir(path);
+
if (d->dp == NULL) {
+
res = -errno;
+
free(d);
+
return res;
+
}
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (unsigned long) d;
+
return 0;
+
}
+
+
static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
{
+
return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
+
(void) path;
+
if (offset != d->offset) {
+
#ifndef __FreeBSD__
+
seekdir(d->dp, offset);
+
#else
+
/* Subtract the one that we add when calling
+
telldir() below */
+
seekdir(d->dp, offset-1);
+
#endif
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
struct stat st;
+
off_t nextoff;
+ +
+
if (!d->entry) {
+
d->entry = readdir(d->dp);
+
if (!d->entry)
+
break;
+
}
+
#ifdef HAVE_FSTATAT
+
if (flags & FUSE_READDIR_PLUS) {
+
int res;
+
+
res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
if (res != -1)
+
fill_flags |= FUSE_FILL_DIR_PLUS;
+
}
+
#endif
+
if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = d->entry->d_ino;
+
st.st_mode = d->entry->d_type << 12;
+
}
+
nextoff = telldir(d->dp);
+
#ifdef __FreeBSD__
+
/* Under FreeBSD, telldir() may return 0 the first time
+
it is called. But for libfuse, an offset of zero
+
means that offsets are not supported, so we shift
+
everything by one. */
+
nextoff++;
+
#endif
+
if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
break;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
return 0;
+
}
+
+
static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
(void) path;
+
closedir(d->dp);
+
free(d);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
if (S_ISFIFO(mode))
+
res = mkfifo(path, mode);
+
else
+
res = mknod(path, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
/* When we have renameat2() in libc, then we can implement flags */
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = fchmod(fi->fh, mode);
+
else
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi)
+
res = fchown(fi->fh, uid, gid);
+
else
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
if (fi)
+
res = futimens(fi->fh, ts);
+
else
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags, mode);
+
if (fd == -1)
+
return -errno;
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags);
+
if (fd == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pread(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
size_t size, off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec *src;
+
+
(void) path;
+
+
src = malloc(sizeof(struct fuse_bufvec));
+
if (src == NULL)
+
return -ENOMEM;
+
+
*src = FUSE_BUFVEC_INIT(size);
+
+ +
src->buf[0].fd = fi->fh;
+
src->buf[0].pos = offset;
+
+
*bufp = src;
+
+
return 0;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pwrite(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
+
(void) path;
+
+ +
dst.buf[0].fd = fi->fh;
+
dst.buf[0].pos = offset;
+
+ +
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
/* This is called from every close on an open file, so call the
+
close on the underlying filesystem. But since flush may be
+
called multiple times for an open file, this must not really
+
close the file. This is important if used on a network
+
filesystem like NFS which flush the data/metadata on close() */
+
res = close(dup(fi->fh));
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) path;
+
+
#ifndef HAVE_FDATASYNC
+
(void) isdatasync;
+
#else
+
if (isdatasync)
+
res = fdatasync(fi->fh);
+
else
+
#endif
+
res = fsync(fi->fh);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
(void) path;
+
+
return do_fallocate(fi->fh, mode, offset, length);
+
}
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_LIBULOCKMGR
+
static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
struct flock *lock)
+
{
+
(void) path;
+
+
return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
sizeof(fi->lock_owner));
+
}
+
#endif
+
+
static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
{
+
int res;
+
(void) path;
+
+
res = flock(fi->fh, op);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t off_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t off_out, size_t len, int flags)
+
{
+
ssize_t res;
+
(void) path_in;
+
(void) path_out;
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
off_t res;
+
(void) path;
+
+
res = lseek(fi->fh, off, whence);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
+
#ifdef HAVE_STATX
+
static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
struct fuse_file_info *fi)
+
{
+
int fd = -1;
+
int res;
+
+
if (fi)
+
fd = fi->fh;
+
+
res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.opendir = xmp_opendir,
+
.readdir = xmp_readdir,
+
.releasedir = xmp_releasedir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.create = xmp_create,
+
.open = xmp_open,
+
.read = xmp_read,
+
.read_buf = xmp_read_buf,
+
.write = xmp_write,
+
.write_buf = xmp_write_buf,
+
.statfs = xmp_statfs,
+
.flush = xmp_flush,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
.fallocate = xmp_fallocate,
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_LIBULOCKMGR
+
.lock = xmp_lock,
+
#endif
+
.flock = xmp_flock,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
#ifdef HAVE_STATX
+
.statx = xmp_statx,
+
#endif
+
};
+
+
int main(int argc, char *argv[])
+
{
+
umask(0);
+
return fuse_main(argc, argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file passthrough_fh.c.

+
+ + + + diff --git a/doc/html/example_2passthrough__fh_8c_source.html b/doc/html/example_2passthrough__fh_8c_source.html new file mode 100644 index 0000000..ba578fe --- /dev/null +++ b/doc/html/example_2passthrough__fh_8c_source.html @@ -0,0 +1,767 @@ + + + + + + + +libfuse: example/passthrough_fh.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_fh.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
26#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
27
+
28#define _GNU_SOURCE
+
29
+
30#include <fuse.h>
+
31
+
32#ifdef HAVE_LIBULOCKMGR
+
33#include <ulockmgr.h>
+
34#endif
+
35
+
36#include <stdio.h>
+
37#include <stdlib.h>
+
38#include <string.h>
+
39#include <unistd.h>
+
40#include <fcntl.h>
+
41#include <sys/stat.h>
+
42#include <dirent.h>
+
43#include <errno.h>
+
44#include <sys/time.h>
+
45#ifdef HAVE_SETXATTR
+
46#include <sys/xattr.h>
+
47#endif
+
48#include <sys/file.h> /* flock(2) */
+
49
+
50#include "passthrough_helpers.h"
+
51
+
52static void *xmp_init(struct fuse_conn_info *conn,
+
53 struct fuse_config *cfg)
+
54{
+
55 (void) conn;
+
56 cfg->use_ino = 1;
+
57 cfg->nullpath_ok = 1;
+
58
+
59 /* parallel_direct_writes feature depends on direct_io features.
+
60 To make parallel_direct_writes valid, need either set cfg->direct_io
+
61 in current function (recommended in high level API) or set fi->direct_io
+
62 in xmp_create() or xmp_open(). */
+
63 // cfg->direct_io = 1;
+ +
65
+
66 /* Pick up changes from lower filesystem right away. This is
+
67 also necessary for better hardlink support. When the kernel
+
68 calls the unlink() handler, it does not know the inode of
+
69 the to-be-removed entry and can therefore not invalidate
+
70 the cache of the associated inode - resulting in an
+
71 incorrect st_nlink value being reported for any remaining
+
72 hardlinks to this inode. */
+
73 cfg->entry_timeout = 0;
+
74 cfg->attr_timeout = 0;
+
75 cfg->negative_timeout = 0;
+
76
+
77 return NULL;
+
78}
+
79
+
80static int xmp_getattr(const char *path, struct stat *stbuf,
+
81 struct fuse_file_info *fi)
+
82{
+
83 int res;
+
84
+
85 (void) path;
+
86
+
87 if(fi)
+
88 res = fstat(fi->fh, stbuf);
+
89 else
+
90 res = lstat(path, stbuf);
+
91 if (res == -1)
+
92 return -errno;
+
93
+
94 return 0;
+
95}
+
96
+
97static int xmp_access(const char *path, int mask)
+
98{
+
99 int res;
+
100
+
101 res = access(path, mask);
+
102 if (res == -1)
+
103 return -errno;
+
104
+
105 return 0;
+
106}
+
107
+
108static int xmp_readlink(const char *path, char *buf, size_t size)
+
109{
+
110 int res;
+
111
+
112 res = readlink(path, buf, size - 1);
+
113 if (res == -1)
+
114 return -errno;
+
115
+
116 buf[res] = '\0';
+
117 return 0;
+
118}
+
119
+
120struct xmp_dirp {
+
121 DIR *dp;
+
122 struct dirent *entry;
+
123 off_t offset;
+
124};
+
125
+
126static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
127{
+
128 int res;
+
129 struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
130 if (d == NULL)
+
131 return -ENOMEM;
+
132
+
133 d->dp = opendir(path);
+
134 if (d->dp == NULL) {
+
135 res = -errno;
+
136 free(d);
+
137 return res;
+
138 }
+
139 d->offset = 0;
+
140 d->entry = NULL;
+
141
+
142 fi->fh = (unsigned long) d;
+
143 return 0;
+
144}
+
145
+
146static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
147{
+
148 return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
149}
+
150
+
151static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
152 off_t offset, struct fuse_file_info *fi,
+
153 enum fuse_readdir_flags flags)
+
154{
+
155 struct xmp_dirp *d = get_dirp(fi);
+
156
+
157 (void) path;
+
158 if (offset != d->offset) {
+
159#ifndef __FreeBSD__
+
160 seekdir(d->dp, offset);
+
161#else
+
162 /* Subtract the one that we add when calling
+
163 telldir() below */
+
164 seekdir(d->dp, offset-1);
+
165#endif
+
166 d->entry = NULL;
+
167 d->offset = offset;
+
168 }
+
169 while (1) {
+
170 struct stat st;
+
171 off_t nextoff;
+ +
173
+
174 if (!d->entry) {
+
175 d->entry = readdir(d->dp);
+
176 if (!d->entry)
+
177 break;
+
178 }
+
179#ifdef HAVE_FSTATAT
+
180 if (flags & FUSE_READDIR_PLUS) {
+
181 int res;
+
182
+
183 res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
184 AT_SYMLINK_NOFOLLOW);
+
185 if (res != -1)
+
186 fill_flags |= FUSE_FILL_DIR_PLUS;
+
187 }
+
188#endif
+
189 if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
190 memset(&st, 0, sizeof(st));
+
191 st.st_ino = d->entry->d_ino;
+
192 st.st_mode = d->entry->d_type << 12;
+
193 }
+
194 nextoff = telldir(d->dp);
+
195#ifdef __FreeBSD__
+
196 /* Under FreeBSD, telldir() may return 0 the first time
+
197 it is called. But for libfuse, an offset of zero
+
198 means that offsets are not supported, so we shift
+
199 everything by one. */
+
200 nextoff++;
+
201#endif
+
202 if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
203 break;
+
204
+
205 d->entry = NULL;
+
206 d->offset = nextoff;
+
207 }
+
208
+
209 return 0;
+
210}
+
211
+
212static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
213{
+
214 struct xmp_dirp *d = get_dirp(fi);
+
215 (void) path;
+
216 closedir(d->dp);
+
217 free(d);
+
218 return 0;
+
219}
+
220
+
221static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
222{
+
223 int res;
+
224
+
225 if (S_ISFIFO(mode))
+
226 res = mkfifo(path, mode);
+
227 else
+
228 res = mknod(path, mode, rdev);
+
229 if (res == -1)
+
230 return -errno;
+
231
+
232 return 0;
+
233}
+
234
+
235static int xmp_mkdir(const char *path, mode_t mode)
+
236{
+
237 int res;
+
238
+
239 res = mkdir(path, mode);
+
240 if (res == -1)
+
241 return -errno;
+
242
+
243 return 0;
+
244}
+
245
+
246static int xmp_unlink(const char *path)
+
247{
+
248 int res;
+
249
+
250 res = unlink(path);
+
251 if (res == -1)
+
252 return -errno;
+
253
+
254 return 0;
+
255}
+
256
+
257static int xmp_rmdir(const char *path)
+
258{
+
259 int res;
+
260
+
261 res = rmdir(path);
+
262 if (res == -1)
+
263 return -errno;
+
264
+
265 return 0;
+
266}
+
267
+
268static int xmp_symlink(const char *from, const char *to)
+
269{
+
270 int res;
+
271
+
272 res = symlink(from, to);
+
273 if (res == -1)
+
274 return -errno;
+
275
+
276 return 0;
+
277}
+
278
+
279static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
280{
+
281 int res;
+
282
+
283 /* When we have renameat2() in libc, then we can implement flags */
+
284 if (flags)
+
285 return -EINVAL;
+
286
+
287 res = rename(from, to);
+
288 if (res == -1)
+
289 return -errno;
+
290
+
291 return 0;
+
292}
+
293
+
294static int xmp_link(const char *from, const char *to)
+
295{
+
296 int res;
+
297
+
298 res = link(from, to);
+
299 if (res == -1)
+
300 return -errno;
+
301
+
302 return 0;
+
303}
+
304
+
305static int xmp_chmod(const char *path, mode_t mode,
+
306 struct fuse_file_info *fi)
+
307{
+
308 int res;
+
309
+
310 if(fi)
+
311 res = fchmod(fi->fh, mode);
+
312 else
+
313 res = chmod(path, mode);
+
314 if (res == -1)
+
315 return -errno;
+
316
+
317 return 0;
+
318}
+
319
+
320static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
321 struct fuse_file_info *fi)
+
322{
+
323 int res;
+
324
+
325 if (fi)
+
326 res = fchown(fi->fh, uid, gid);
+
327 else
+
328 res = lchown(path, uid, gid);
+
329 if (res == -1)
+
330 return -errno;
+
331
+
332 return 0;
+
333}
+
334
+
335static int xmp_truncate(const char *path, off_t size,
+
336 struct fuse_file_info *fi)
+
337{
+
338 int res;
+
339
+
340 if(fi)
+
341 res = ftruncate(fi->fh, size);
+
342 else
+
343 res = truncate(path, size);
+
344
+
345 if (res == -1)
+
346 return -errno;
+
347
+
348 return 0;
+
349}
+
350
+
351#ifdef HAVE_UTIMENSAT
+
352static int xmp_utimens(const char *path, const struct timespec ts[2],
+
353 struct fuse_file_info *fi)
+
354{
+
355 int res;
+
356
+
357 /* don't use utime/utimes since they follow symlinks */
+
358 if (fi)
+
359 res = futimens(fi->fh, ts);
+
360 else
+
361 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
362 if (res == -1)
+
363 return -errno;
+
364
+
365 return 0;
+
366}
+
367#endif
+
368
+
369static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
370{
+
371 int fd;
+
372
+
373 fd = open(path, fi->flags, mode);
+
374 if (fd == -1)
+
375 return -errno;
+
376
+
377 fi->fh = fd;
+
378 return 0;
+
379}
+
380
+
381static int xmp_open(const char *path, struct fuse_file_info *fi)
+
382{
+
383 int fd;
+
384
+
385 fd = open(path, fi->flags);
+
386 if (fd == -1)
+
387 return -errno;
+
388
+
389 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
390 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
391 for writes to the same file). */
+
392 if (fi->flags & O_DIRECT) {
+
393 fi->direct_io = 1;
+ +
395 }
+
396
+
397 fi->fh = fd;
+
398 return 0;
+
399}
+
400
+
401static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
402 struct fuse_file_info *fi)
+
403{
+
404 int res;
+
405
+
406 (void) path;
+
407 res = pread(fi->fh, buf, size, offset);
+
408 if (res == -1)
+
409 res = -errno;
+
410
+
411 return res;
+
412}
+
413
+
414static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
415 size_t size, off_t offset, struct fuse_file_info *fi)
+
416{
+
417 struct fuse_bufvec *src;
+
418
+
419 (void) path;
+
420
+
421 src = malloc(sizeof(struct fuse_bufvec));
+
422 if (src == NULL)
+
423 return -ENOMEM;
+
424
+
425 *src = FUSE_BUFVEC_INIT(size);
+
426
+ +
428 src->buf[0].fd = fi->fh;
+
429 src->buf[0].pos = offset;
+
430
+
431 *bufp = src;
+
432
+
433 return 0;
+
434}
+
435
+
436static int xmp_write(const char *path, const char *buf, size_t size,
+
437 off_t offset, struct fuse_file_info *fi)
+
438{
+
439 int res;
+
440
+
441 (void) path;
+
442 res = pwrite(fi->fh, buf, size, offset);
+
443 if (res == -1)
+
444 res = -errno;
+
445
+
446 return res;
+
447}
+
448
+
449static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
450 off_t offset, struct fuse_file_info *fi)
+
451{
+
452 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
453
+
454 (void) path;
+
455
+ +
457 dst.buf[0].fd = fi->fh;
+
458 dst.buf[0].pos = offset;
+
459
+ +
461}
+
462
+
463static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
464{
+
465 int res;
+
466
+
467 res = statvfs(path, stbuf);
+
468 if (res == -1)
+
469 return -errno;
+
470
+
471 return 0;
+
472}
+
473
+
474static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
475{
+
476 int res;
+
477
+
478 (void) path;
+
479 /* This is called from every close on an open file, so call the
+
480 close on the underlying filesystem. But since flush may be
+
481 called multiple times for an open file, this must not really
+
482 close the file. This is important if used on a network
+
483 filesystem like NFS which flush the data/metadata on close() */
+
484 res = close(dup(fi->fh));
+
485 if (res == -1)
+
486 return -errno;
+
487
+
488 return 0;
+
489}
+
490
+
491static int xmp_release(const char *path, struct fuse_file_info *fi)
+
492{
+
493 (void) path;
+
494 close(fi->fh);
+
495
+
496 return 0;
+
497}
+
498
+
499static int xmp_fsync(const char *path, int isdatasync,
+
500 struct fuse_file_info *fi)
+
501{
+
502 int res;
+
503 (void) path;
+
504
+
505#ifndef HAVE_FDATASYNC
+
506 (void) isdatasync;
+
507#else
+
508 if (isdatasync)
+
509 res = fdatasync(fi->fh);
+
510 else
+
511#endif
+
512 res = fsync(fi->fh);
+
513 if (res == -1)
+
514 return -errno;
+
515
+
516 return 0;
+
517}
+
518
+
519static int xmp_fallocate(const char *path, int mode,
+
520 off_t offset, off_t length, struct fuse_file_info *fi)
+
521{
+
522 (void) path;
+
523
+
524 return do_fallocate(fi->fh, mode, offset, length);
+
525}
+
526
+
527#ifdef HAVE_SETXATTR
+
528/* xattr operations are optional and can safely be left unimplemented */
+
529static int xmp_setxattr(const char *path, const char *name, const char *value,
+
530 size_t size, int flags)
+
531{
+
532 int res = lsetxattr(path, name, value, size, flags);
+
533 if (res == -1)
+
534 return -errno;
+
535 return 0;
+
536}
+
537
+
538static int xmp_getxattr(const char *path, const char *name, char *value,
+
539 size_t size)
+
540{
+
541 int res = lgetxattr(path, name, value, size);
+
542 if (res == -1)
+
543 return -errno;
+
544 return res;
+
545}
+
546
+
547static int xmp_listxattr(const char *path, char *list, size_t size)
+
548{
+
549 int res = llistxattr(path, list, size);
+
550 if (res == -1)
+
551 return -errno;
+
552 return res;
+
553}
+
554
+
555static int xmp_removexattr(const char *path, const char *name)
+
556{
+
557 int res = lremovexattr(path, name);
+
558 if (res == -1)
+
559 return -errno;
+
560 return 0;
+
561}
+
562#endif /* HAVE_SETXATTR */
+
563
+
564#ifdef HAVE_LIBULOCKMGR
+
565static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
566 struct flock *lock)
+
567{
+
568 (void) path;
+
569
+
570 return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
571 sizeof(fi->lock_owner));
+
572}
+
573#endif
+
574
+
575static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
576{
+
577 int res;
+
578 (void) path;
+
579
+
580 res = flock(fi->fh, op);
+
581 if (res == -1)
+
582 return -errno;
+
583
+
584 return 0;
+
585}
+
586
+
587#ifdef HAVE_COPY_FILE_RANGE
+
588static ssize_t xmp_copy_file_range(const char *path_in,
+
589 struct fuse_file_info *fi_in,
+
590 off_t off_in, const char *path_out,
+
591 struct fuse_file_info *fi_out,
+
592 off_t off_out, size_t len, int flags)
+
593{
+
594 ssize_t res;
+
595 (void) path_in;
+
596 (void) path_out;
+
597
+
598 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
599 flags);
+
600 if (res == -1)
+
601 return -errno;
+
602
+
603 return res;
+
604}
+
605#endif
+
606
+
607static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
608{
+
609 off_t res;
+
610 (void) path;
+
611
+
612 res = lseek(fi->fh, off, whence);
+
613 if (res == -1)
+
614 return -errno;
+
615
+
616 return res;
+
617}
+
618
+
619#ifdef HAVE_STATX
+
620static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
621 struct fuse_file_info *fi)
+
622{
+
623 int fd = -1;
+
624 int res;
+
625
+
626 if (fi)
+
627 fd = fi->fh;
+
628
+
629 res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
+
630 if (res == -1)
+
631 return -errno;
+
632
+
633 return 0;
+
634}
+
635#endif
+
636
+
637static const struct fuse_operations xmp_oper = {
+
638 .init = xmp_init,
+
639 .getattr = xmp_getattr,
+
640 .access = xmp_access,
+
641 .readlink = xmp_readlink,
+
642 .opendir = xmp_opendir,
+
643 .readdir = xmp_readdir,
+
644 .releasedir = xmp_releasedir,
+
645 .mknod = xmp_mknod,
+
646 .mkdir = xmp_mkdir,
+
647 .symlink = xmp_symlink,
+
648 .unlink = xmp_unlink,
+
649 .rmdir = xmp_rmdir,
+
650 .rename = xmp_rename,
+
651 .link = xmp_link,
+
652 .chmod = xmp_chmod,
+
653 .chown = xmp_chown,
+
654 .truncate = xmp_truncate,
+
655#ifdef HAVE_UTIMENSAT
+
656 .utimens = xmp_utimens,
+
657#endif
+
658 .create = xmp_create,
+
659 .open = xmp_open,
+
660 .read = xmp_read,
+
661 .read_buf = xmp_read_buf,
+
662 .write = xmp_write,
+
663 .write_buf = xmp_write_buf,
+
664 .statfs = xmp_statfs,
+
665 .flush = xmp_flush,
+
666 .release = xmp_release,
+
667 .fsync = xmp_fsync,
+
668 .fallocate = xmp_fallocate,
+
669#ifdef HAVE_SETXATTR
+
670 .setxattr = xmp_setxattr,
+
671 .getxattr = xmp_getxattr,
+
672 .listxattr = xmp_listxattr,
+
673 .removexattr = xmp_removexattr,
+
674#endif
+
675#ifdef HAVE_LIBULOCKMGR
+
676 .lock = xmp_lock,
+
677#endif
+
678 .flock = xmp_flock,
+
679#ifdef HAVE_COPY_FILE_RANGE
+
680 .copy_file_range = xmp_copy_file_range,
+
681#endif
+
682 .lseek = xmp_lseek,
+
683#ifdef HAVE_STATX
+
684 .statx = xmp_statx,
+
685#endif
+
686};
+
687
+
688int main(int argc, char *argv[])
+
689{
+
690 umask(0);
+
691 return fuse_main(argc, argv, &xmp_oper, NULL);
+
692}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/example_2passthrough__helpers_8h_source.html b/doc/html/example_2passthrough__helpers_8h_source.html new file mode 100644 index 0000000..48690a5 --- /dev/null +++ b/doc/html/example_2passthrough__helpers_8h_source.html @@ -0,0 +1,182 @@ + + + + + + + +libfuse: example/passthrough_helpers.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_helpers.h
+
+
+
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 *
+
4 * Redistribution and use in source and binary forms, with or without
+
5 * modification, are permitted provided that the following conditions
+
6 * are met:
+
7 * 1. Redistributions of source code must retain the above copyright
+
8 * notice, this list of conditions and the following disclaimer.
+
9 * 2. Redistributions in binary form must reproduce the above copyright
+
10 * notice, this list of conditions and the following disclaimer in the
+
11 * documentation and/or other materials provided with the distribution.
+
12 *
+
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
23 * SUCH DAMAGE
+
24 */
+
25
+
26#ifndef FUSE_EXAMPLE_PASSTHROUGH_HELPERS_H_
+
27#define FUSE_EXAMPLE_PASSTHROUGH_HELPERS_H_
+
28
+
29#include <errno.h>
+
30#include <fcntl.h>
+
31#include <string.h>
+
32#include <sys/stat.h>
+
33#include <unistd.h>
+
34
+
35#ifdef __FreeBSD__
+
36#include <sys/socket.h>
+
37#include <sys/un.h>
+
38#endif
+
39
+
40static inline int do_fallocate(int fd, int mode, off_t offset, off_t length)
+
41{
+
42#ifdef HAVE_FALLOCATE
+
43 if (fallocate(fd, mode, offset, length) == -1)
+
44 return -errno;
+
45 return 0;
+
46#else // HAVE_FALLOCATE
+
47
+
48#ifdef HAVE_POSIX_FALLOCATE
+
49 if (mode == 0)
+
50 return -posix_fallocate(fd, offset, length);
+
51#endif
+
52
+
53#ifdef HAVE_FSPACECTL
+
54 // 0x3 == FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE
+
55 if (mode == 0x3) {
+
56 struct spacectl_range sr;
+
57
+
58 sr.r_offset = offset;
+
59 sr.r_len = length;
+
60 if (fspacectl(fd, SPACECTL_DEALLOC, &sr, 0, NULL) == -1)
+
61 return -errno;
+
62 return 0;
+
63 }
+
64#endif
+
65
+
66 return -EOPNOTSUPP;
+
67#endif // HAVE_FALLOCATE
+
68}
+
69
+
70/*
+
71 * Creates files on the underlying file system in response to a FUSE_MKNOD
+
72 * operation
+
73 */
+
74static inline int mknod_wrapper(int dirfd, const char *path, const char *link,
+
75 int mode, dev_t rdev)
+
76{
+
77 int res;
+
78
+
79 if (S_ISREG(mode)) {
+
80 res = openat(dirfd, path, O_CREAT | O_EXCL | O_WRONLY, mode);
+
81 if (res >= 0)
+
82 res = close(res);
+
83 } else if (S_ISDIR(mode)) {
+
84 res = mkdirat(dirfd, path, mode);
+
85 } else if (S_ISLNK(mode) && link != NULL) {
+
86 res = symlinkat(link, dirfd, path);
+
87 } else if (S_ISFIFO(mode)) {
+
88 res = mkfifoat(dirfd, path, mode);
+
89#ifdef __FreeBSD__
+
90 } else if (S_ISSOCK(mode)) {
+
91 struct sockaddr_un su;
+
92 int fd;
+
93
+
94 if (strlen(path) >= sizeof(su.sun_path)) {
+
95 errno = ENAMETOOLONG;
+
96 return -1;
+
97 }
+
98 fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
99 if (fd >= 0) {
+
100 /*
+
101 * We must bind the socket to the underlying file
+
102 * system to create the socket file, even though
+
103 * we'll never listen on this socket.
+
104 */
+
105 su.sun_family = AF_UNIX;
+
106 strncpy(su.sun_path, path, sizeof(su.sun_path));
+
107 res = bindat(dirfd, fd, (struct sockaddr*)&su,
+
108 sizeof(su));
+
109 if (res == 0)
+
110 close(fd);
+
111 } else {
+
112 res = -1;
+
113 }
+
114#endif
+
115 } else {
+
116 res = mknodat(dirfd, path, mode, rdev);
+
117 }
+
118
+
119 return res;
+
120}
+
121
+
122#endif // FUSE_PASSTHROUGH_HELPERS_H_
+
+ + + + diff --git a/doc/html/example_2passthrough__ll_8c.html b/doc/html/example_2passthrough__ll_8c.html new file mode 100644 index 0000000..05e3cce --- /dev/null +++ b/doc/html/example_2passthrough__ll_8c.html @@ -0,0 +1,1546 @@ + + + + + + + +libfuse: example/passthrough_ll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough_ll.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <sys/file.h>
+#include <sys/xattr.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. In contrast to passthrough.c and passthrough_fh.c, this implementation uses the low-level API. Its performance should be the least bad among the three.

+

When writeback caching is enabled (-o writeback mount option), it is only possible to write to files for which the mounting user has read permissions. This is because the writeback cache requires the kernel to be able to issue read requests for all files (which the passthrough filesystem cannot satisfy if it can't read the file in the underlying filesystem).

+

Compile with:

gcc -Wall passthrough_ll.c `pkg-config fuse3 --cflags --libs` -o passthrough_ll
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define _GNU_SOURCE
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
+
#include <fuse_lowlevel.h>
+
#include <unistd.h>
+
#include <stdlib.h>
+
#include <stdio.h>
+
#include <stddef.h>
+
#include <stdbool.h>
+
#include <string.h>
+
#include <limits.h>
+
#include <dirent.h>
+
#include <assert.h>
+
#include <errno.h>
+
#include <inttypes.h>
+
#include <pthread.h>
+
#include <sys/file.h>
+
#include <sys/xattr.h>
+
+
#include "passthrough_helpers.h"
+
+
/* We are re-using pointers to our `struct lo_inode` and `struct
+
lo_dirp` elements as inodes. This means that we must be able to
+
store uintptr_t values in a fuse_ino_t variable. The following
+
incantation checks this condition at compile time. */
+
#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
+
_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
+
"fuse_ino_t too small to hold uintptr_t values!");
+
#else
+
struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
+
{ unsigned _uintptr_to_must_hold_fuse_ino_t:
+
((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
+
#endif
+
+
struct lo_inode {
+
struct lo_inode *next; /* protected by lo->mutex */
+
struct lo_inode *prev; /* protected by lo->mutex */
+
int fd;
+
ino_t ino;
+
dev_t dev;
+
uint64_t refcount; /* protected by lo->mutex */
+
};
+
+
enum {
+
CACHE_NEVER,
+
CACHE_NORMAL,
+
CACHE_ALWAYS,
+
};
+
+
struct lo_data {
+
pthread_mutex_t mutex;
+
int debug;
+
int writeback;
+
int flock;
+
int xattr;
+
char *source;
+
double timeout;
+
int cache;
+
int timeout_set;
+
struct lo_inode root; /* protected by lo->mutex */
+
};
+
+
static const struct fuse_opt lo_opts[] = {
+
{ "writeback",
+
offsetof(struct lo_data, writeback), 1 },
+
{ "no_writeback",
+
offsetof(struct lo_data, writeback), 0 },
+
{ "source=%s",
+
offsetof(struct lo_data, source), 0 },
+
{ "flock",
+
offsetof(struct lo_data, flock), 1 },
+
{ "no_flock",
+
offsetof(struct lo_data, flock), 0 },
+
{ "xattr",
+
offsetof(struct lo_data, xattr), 1 },
+
{ "no_xattr",
+
offsetof(struct lo_data, xattr), 0 },
+
{ "timeout=%lf",
+
offsetof(struct lo_data, timeout), 0 },
+
{ "timeout=",
+
offsetof(struct lo_data, timeout_set), 1 },
+
{ "cache=never",
+
offsetof(struct lo_data, cache), CACHE_NEVER },
+
{ "cache=auto",
+
offsetof(struct lo_data, cache), CACHE_NORMAL },
+
{ "cache=always",
+
offsetof(struct lo_data, cache), CACHE_ALWAYS },
+
+ +
};
+
+
static void passthrough_ll_help(void)
+
{
+
printf(
+
" -o writeback Enable writeback\n"
+
" -o no_writeback Disable write back\n"
+
" -o source=/home/dir Source directory to be mounted\n"
+
" -o flock Enable flock\n"
+
" -o no_flock Disable flock\n"
+
" -o xattr Enable xattr\n"
+
" -o no_xattr Disable xattr\n"
+
" -o timeout=1.0 Caching timeout\n"
+
" -o timeout=0/1 Timeout is set\n"
+
" -o cache=never Disable cache\n"
+
" -o cache=auto Auto enable cache\n"
+
" -o cache=always Cache always\n");
+
}
+
+
static struct lo_data *lo_data(fuse_req_t req)
+
{
+
return (struct lo_data *) fuse_req_userdata(req);
+
}
+
+
static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
+
{
+
if (ino == FUSE_ROOT_ID)
+
return &lo_data(req)->root;
+
else
+
return (struct lo_inode *) (uintptr_t) ino;
+
}
+
+
static int lo_fd(fuse_req_t req, fuse_ino_t ino)
+
{
+
return lo_inode(req, ino)->fd;
+
}
+
+
static bool lo_debug(fuse_req_t req)
+
{
+
return lo_data(req)->debug != 0;
+
}
+
+
static void lo_init(void *userdata,
+
struct fuse_conn_info *conn)
+
{
+
struct lo_data *lo = (struct lo_data *)userdata;
+
bool has_flag;
+
+
if (lo->writeback) {
+ +
if (lo->debug && has_flag)
+
fuse_log(FUSE_LOG_DEBUG,
+
"lo_init: activating writeback\n");
+
}
+
if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
+ +
if (lo->debug && has_flag)
+
fuse_log(FUSE_LOG_DEBUG,
+
"lo_init: activating flock locks\n");
+
}
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void lo_destroy(void *userdata)
+
{
+
struct lo_data *lo = (struct lo_data*) userdata;
+
+
while (lo->root.next != &lo->root) {
+
struct lo_inode* next = lo->root.next;
+
lo->root.next = next->next;
+
close(next->fd);
+
free(next);
+
}
+
}
+
+
static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
struct stat buf;
+
struct lo_data *lo = lo_data(req);
+
int fd = fi ? fi->fh : lo_fd(req, ino);
+
+
(void) fi;
+
+
res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fuse_reply_attr(req, &buf, lo->timeout);
+
}
+
+
static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
int valid, struct fuse_file_info *fi)
+
{
+
int saverr;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
int ifd = inode->fd;
+
int res;
+
+
if (valid & FUSE_SET_ATTR_MODE) {
+
if (fi) {
+
res = fchmod(fi->fh, attr->st_mode);
+
} else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = chmod(procname, attr->st_mode);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+
uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
attr->st_uid : (uid_t) -1;
+
gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
attr->st_gid : (gid_t) -1;
+
+
res = fchownat(ifd, "", uid, gid,
+
AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & FUSE_SET_ATTR_SIZE) {
+
if (fi) {
+
res = ftruncate(fi->fh, attr->st_size);
+
} else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = truncate(procname, attr->st_size);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
struct timespec tv[2];
+
+
tv[0].tv_sec = 0;
+
tv[1].tv_sec = 0;
+
tv[0].tv_nsec = UTIME_OMIT;
+
tv[1].tv_nsec = UTIME_OMIT;
+
+
if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
tv[0].tv_nsec = UTIME_NOW;
+
else if (valid & FUSE_SET_ATTR_ATIME)
+
tv[0] = attr->st_atim;
+
+
if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
tv[1].tv_nsec = UTIME_NOW;
+
else if (valid & FUSE_SET_ATTR_MTIME)
+
tv[1] = attr->st_mtim;
+
+
if (fi)
+
res = futimens(fi->fh, tv);
+
else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = utimensat(AT_FDCWD, procname, tv, 0);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
+
return lo_getattr(req, ino, fi);
+
+
out_err:
+
saverr = errno;
+
fuse_reply_err(req, saverr);
+
}
+
+
static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+
{
+
struct lo_inode *p;
+
struct lo_inode *ret = NULL;
+
+
pthread_mutex_lock(&lo->mutex);
+
for (p = lo->root.next; p != &lo->root; p = p->next) {
+
if (p->ino == st->st_ino && p->dev == st->st_dev) {
+
assert(p->refcount > 0);
+
ret = p;
+
ret->refcount++;
+
break;
+
}
+
}
+
pthread_mutex_unlock(&lo->mutex);
+
return ret;
+
}
+
+
+
static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
+
{
+
struct lo_inode *inode = NULL;
+
struct lo_inode *prev, *next;
+
+
inode = calloc(1, sizeof(struct lo_inode));
+
if (!inode)
+
return NULL;
+
+
inode->refcount = 1;
+
inode->fd = fd;
+
inode->ino = e->attr.st_ino;
+
inode->dev = e->attr.st_dev;
+
+
pthread_mutex_lock(&lo->mutex);
+
prev = &lo->root;
+
next = prev->next;
+
next->prev = inode;
+
inode->next = next;
+
inode->prev = prev;
+
prev->next = inode;
+
pthread_mutex_unlock(&lo->mutex);
+
return inode;
+
}
+
+
static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
+
{
+
int res;
+
struct lo_data *lo = lo_data(req);
+
+
memset(e, 0, sizeof(*e));
+
e->attr_timeout = lo->timeout;
+
e->entry_timeout = lo->timeout;
+
+
res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return errno;
+
+
e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%d -> %lli\n",
+
(unsigned long long) parent, fd, (unsigned long long) e->ino);
+
+
return 0;
+
+
}
+
+
static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
+
struct fuse_entry_param *e)
+
{
+
int newfd;
+
int res;
+
int saverr;
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode;
+
+
memset(e, 0, sizeof(*e));
+
e->attr_timeout = lo->timeout;
+
e->entry_timeout = lo->timeout;
+
+
newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
+
if (newfd == -1)
+
goto out_err;
+
+
res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
inode = lo_find(lo_data(req), &e->attr);
+
if (inode) {
+
close(newfd);
+
newfd = -1;
+
} else {
+
inode = create_new_inode(newfd, e, lo);
+
if (!inode)
+
goto out_err;
+
}
+
e->ino = (uintptr_t) inode;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name, (unsigned long long) e->ino);
+
+
return 0;
+
+
out_err:
+
saverr = errno;
+
if (newfd != -1)
+
close(newfd);
+
return saverr;
+
}
+
+
static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
+
parent, name);
+
+
err = lo_do_lookup(req, parent, name, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_entry(req, &e);
+
}
+
+
static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
+
const char *name, mode_t mode, dev_t rdev,
+
const char *link)
+
{
+
int res;
+
int saverr;
+
struct lo_inode *dir = lo_inode(req, parent);
+
struct fuse_entry_param e;
+
+
res = mknod_wrapper(dir->fd, name, link, mode, rdev);
+
+
saverr = errno;
+
if (res == -1)
+
goto out;
+
+
saverr = lo_do_lookup(req, parent, name, &e);
+
if (saverr)
+
goto out;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name, (unsigned long long) e.ino);
+
+
fuse_reply_entry(req, &e);
+
return;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
+
const char *name, mode_t mode, dev_t rdev)
+
{
+
lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
+
}
+
+
static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
mode_t mode)
+
{
+
lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
+
}
+
+
static void lo_symlink(fuse_req_t req, const char *link,
+
fuse_ino_t parent, const char *name)
+
{
+
lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
+
}
+
+
static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
+
const char *name)
+
{
+
int res;
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode = lo_inode(req, ino);
+
struct fuse_entry_param e;
+
char procname[64];
+
int saverr;
+
+
memset(&e, 0, sizeof(struct fuse_entry_param));
+
e.attr_timeout = lo->timeout;
+
e.entry_timeout = lo->timeout;
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
+
AT_SYMLINK_FOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
pthread_mutex_lock(&lo->mutex);
+
inode->refcount++;
+
pthread_mutex_unlock(&lo->mutex);
+
e.ino = (uintptr_t) inode;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name,
+
(unsigned long long) e.ino);
+
+
fuse_reply_entry(req, &e);
+
return;
+
+
out_err:
+
saverr = errno;
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
int res;
+
+
res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
+
fuse_ino_t newparent, const char *newname,
+
unsigned int flags)
+
{
+
int res;
+
+
if (flags) {
+
fuse_reply_err(req, EINVAL);
+
return;
+
}
+
+
res = renameat(lo_fd(req, parent), name,
+
lo_fd(req, newparent), newname);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
int res;
+
+
res = unlinkat(lo_fd(req, parent), name, 0);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
+
{
+
if (!inode)
+
return;
+
+
pthread_mutex_lock(&lo->mutex);
+
assert(inode->refcount >= n);
+
inode->refcount -= n;
+
if (!inode->refcount) {
+
struct lo_inode *prev, *next;
+
+
prev = inode->prev;
+
next = inode->next;
+
next->prev = prev;
+
prev->next = next;
+
+
pthread_mutex_unlock(&lo->mutex);
+
close(inode->fd);
+
free(inode);
+
+
} else {
+
pthread_mutex_unlock(&lo->mutex);
+
}
+
}
+
+
static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
{
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode = lo_inode(req, ino);
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
+
(unsigned long long) ino,
+
(unsigned long long) inode->refcount,
+
(unsigned long long) nlookup);
+
}
+
+
unref_inode(lo, inode, nlookup);
+
}
+
+
static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
{
+
lo_forget_one(req, ino, nlookup);
+ +
}
+
+
static void lo_forget_multi(fuse_req_t req, size_t count,
+
struct fuse_forget_data *forgets)
+
{
+
int i;
+
+
for (i = 0; i < count; i++)
+
lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
+ +
}
+
+
static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
+
{
+
char buf[PATH_MAX + 1];
+
int res;
+
+
res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
+
if (res == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
if (res == sizeof(buf))
+
return (void) fuse_reply_err(req, ENAMETOOLONG);
+
+
buf[res] = '\0';
+
+ +
}
+
+
struct lo_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
+
{
+
return (struct lo_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int error = ENOMEM;
+
struct lo_data *lo = lo_data(req);
+
struct lo_dirp *d;
+
int fd = -1;
+
+
d = calloc(1, sizeof(struct lo_dirp));
+
if (d == NULL)
+
goto out_err;
+
+
fd = openat(lo_fd(req, ino), ".", O_RDONLY);
+
if (fd == -1)
+
goto out_errno;
+
+
d->dp = fdopendir(fd);
+
if (d->dp == NULL)
+
goto out_errno;
+
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (uintptr_t) d;
+
if (lo->cache != CACHE_NEVER)
+
fi->cache_readdir = 1;
+
if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
fuse_reply_open(req, fi);
+
return;
+
+
out_errno:
+
error = errno;
+
out_err:
+
if (d) {
+
if (fd != -1)
+
close(fd);
+
free(d);
+
}
+
fuse_reply_err(req, error);
+
}
+
+
static int is_dot_or_dotdot(const char *name)
+
{
+
return name[0] == '.' && (name[1] == '\0' ||
+
(name[1] == '.' && name[2] == '\0'));
+
}
+
+
static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi, int plus)
+
{
+
struct lo_dirp *d = lo_dirp(fi);
+
char *buf;
+
char *p;
+
size_t rem = size;
+
int err;
+
+
(void) ino;
+
+
buf = calloc(1, size);
+
if (!buf) {
+
err = ENOMEM;
+
goto error;
+
}
+
p = buf;
+
+
if (offset != d->offset) {
+
seekdir(d->dp, offset);
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
size_t entsize;
+
off_t nextoff;
+
const char *name;
+
+
if (!d->entry) {
+
errno = 0;
+
d->entry = readdir(d->dp);
+
if (!d->entry) {
+
if (errno) { // Error
+
err = errno;
+
goto error;
+
} else { // End of stream
+
break;
+
}
+
}
+
}
+
nextoff = d->entry->d_off;
+
name = d->entry->d_name;
+
fuse_ino_t entry_ino = 0;
+
if (plus) {
+
struct fuse_entry_param e;
+
if (is_dot_or_dotdot(name)) {
+
e = (struct fuse_entry_param) {
+
.attr.st_ino = d->entry->d_ino,
+
.attr.st_mode = d->entry->d_type << 12,
+
};
+
} else {
+
err = lo_do_lookup(req, ino, name, &e);
+
if (err)
+
goto error;
+
entry_ino = e.ino;
+
}
+
+
entsize = fuse_add_direntry_plus(req, p, rem, name,
+
&e, nextoff);
+
} else {
+
struct stat st = {
+
.st_ino = d->entry->d_ino,
+
.st_mode = d->entry->d_type << 12,
+
};
+
entsize = fuse_add_direntry(req, p, rem, name,
+
&st, nextoff);
+
}
+
if (entsize > rem) {
+
if (entry_ino != 0)
+
lo_forget_one(req, entry_ino, 1);
+
break;
+
}
+
+
p += entsize;
+
rem -= entsize;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
err = 0;
+
error:
+
// If there's an error, we can only signal it if we haven't stored
+
// any entries yet - otherwise we'd end up with wrong lookup
+
// counts for the entries that are already in the buffer. So we
+
// return what we've collected until that point.
+
if (err && rem == size)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_buf(req, buf, size - rem);
+
free(buf);
+
}
+
+
static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
lo_do_readdir(req, ino, size, offset, fi, 0);
+
}
+
+
static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
lo_do_readdir(req, ino, size, offset, fi, 1);
+
}
+
+
static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
struct lo_dirp *d = lo_dirp(fi);
+
(void) ino;
+
closedir(d->dp);
+
free(d);
+
fuse_reply_err(req, 0);
+
}
+
+
static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
+
mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
struct lo_data *lo = lo_data(req);
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
+
parent);
+
+
fd = openat(lo_fd(req, parent), ".",
+
(fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
err = fill_entry_param_new_inode(req, parent, fd, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_create(req, &e, fi);
+
}
+
+
static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+
mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
struct lo_data *lo = lo_data(req);
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
+
parent, name);
+
+
fd = openat(lo_fd(req, parent), name,
+
(fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
err = lo_do_lookup(req, parent, name, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_create(req, &e, fi);
+
}
+
+
static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
int fd = dirfd(lo_dirp(fi)->dp);
+
(void) ino;
+
if (datasync)
+
res = fdatasync(fd);
+
else
+
res = fsync(fd);
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int fd;
+
char buf[64];
+
struct lo_data *lo = lo_data(req);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
+
ino, fi->flags);
+
+
/* With writeback cache, kernel may send read requests even
+
when userspace opened write-only */
+
if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
+
fi->flags &= ~O_ACCMODE;
+
fi->flags |= O_RDWR;
+
}
+
+
/* With writeback cache, O_APPEND is handled by the kernel.
+
This breaks atomicity (since the file may change in the
+
underlying filesystem, so that the kernel's idea of the
+
end of the file isn't accurate anymore). In this example,
+
we just accept that. A more rigorous filesystem may want
+
to return an error here */
+
if (lo->writeback && (fi->flags & O_APPEND))
+
fi->flags &= ~O_APPEND;
+
+
sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
+
fd = open(buf, fi->flags & ~O_NOFOLLOW);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file in the kernel). */
+
if (fi->flags & O_DIRECT)
+
fi->direct_io = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
fuse_reply_open(req, fi);
+
}
+
+
static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
(void) ino;
+
+
close(fi->fh);
+
fuse_reply_err(req, 0);
+
}
+
+
static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int res;
+
(void) ino;
+
res = close(dup(fi->fh));
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) ino;
+
if (datasync)
+
res = fdatasync(fi->fh);
+
else
+
res = fsync(fi->fh);
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
+
"off=%lu)\n", ino, size, (unsigned long) offset);
+
+ +
buf.buf[0].fd = fi->fh;
+
buf.buf[0].pos = offset;
+
+ +
}
+
+
static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_bufvec *in_buf, off_t off,
+
struct fuse_file_info *fi)
+
{
+
(void) ino;
+
ssize_t res;
+
struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
+
+ +
out_buf.buf[0].fd = fi->fh;
+
out_buf.buf[0].pos = off;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%jd)\n",
+
ino, out_buf.buf[0].size, (intmax_t) off);
+
+
res = fuse_buf_copy(&out_buf, in_buf, 0);
+
if(res < 0)
+
fuse_reply_err(req, -res);
+
else
+
fuse_reply_write(req, (size_t) res);
+
}
+
+
static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
+
{
+
int res;
+
struct statvfs stbuf;
+
+
res = fstatvfs(lo_fd(req, ino), &stbuf);
+
if (res == -1)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_statfs(req, &stbuf);
+
}
+
+
static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
int err;
+
(void) ino;
+
+
err = -do_fallocate(fi->fh, mode, offset, length);
+
+
fuse_reply_err(req, err);
+
}
+
+
static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
int op)
+
{
+
int res;
+
(void) ino;
+
+
res = flock(fi->fh, op);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
char *value = NULL;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
+
ino, name, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
if (size) {
+
value = malloc(size);
+
if (!value)
+
goto out_err;
+
+
ret = getxattr(procname, name, value, size);
+
if (ret == -1)
+
goto out_err;
+
saverr = 0;
+
if (ret == 0)
+
goto out;
+
+
fuse_reply_buf(req, value, ret);
+
} else {
+
ret = getxattr(procname, name, NULL, 0);
+
if (ret == -1)
+
goto out_err;
+
+
fuse_reply_xattr(req, ret);
+
}
+
out_free:
+
free(value);
+
return;
+
+
out_err:
+
saverr = errno;
+
out:
+
fuse_reply_err(req, saverr);
+
goto out_free;
+
}
+
+
static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
{
+
char *value = NULL;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
+
ino, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
if (size) {
+
value = malloc(size);
+
if (!value)
+
goto out_err;
+
+
ret = listxattr(procname, value, size);
+
if (ret == -1)
+
goto out_err;
+
saverr = 0;
+
if (ret == 0)
+
goto out;
+
+
fuse_reply_buf(req, value, ret);
+
} else {
+
ret = listxattr(procname, NULL, 0);
+
if (ret == -1)
+
goto out_err;
+
+
fuse_reply_xattr(req, ret);
+
}
+
out_free:
+
free(value);
+
return;
+
+
out_err:
+
saverr = errno;
+
out:
+
fuse_reply_err(req, saverr);
+
goto out_free;
+
}
+
+
static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
+
ino, name, value, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
ret = setxattr(procname, name, value, size, flags);
+
saverr = ret == -1 ? errno : 0;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
+
ino, name);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
ret = removexattr(procname, name);
+
saverr = ret == -1 ? errno : 0;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
+
struct fuse_file_info *fi_in,
+
fuse_ino_t ino_out, off_t off_out,
+
struct fuse_file_info *fi_out, size_t len,
+
int flags)
+
{
+
ssize_t res;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG,
+
"%s(ino=%lld fd=%lld off=%jd ino=%lld fd=%lld off=%jd, size=%zd, flags=0x%x)\n",
+
__func__, (unsigned long long)ino_in,
+
(unsigned long long)fi_in->fh,
+
(intmax_t) off_in, (unsigned long long)ino_out,
+
(unsigned long long)fi_out->fh, (intmax_t) off_out,
+
len, flags);
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res < 0)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_write(req, res);
+
}
+
#endif
+
+
static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
struct fuse_file_info *fi)
+
{
+
off_t res;
+
+
(void)ino;
+
res = lseek(fi->fh, off, whence);
+
if (res != -1)
+
fuse_reply_lseek(req, res);
+
else
+
fuse_reply_err(req, errno);
+
}
+
+
#ifdef HAVE_STATX
+
static void lo_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
+
struct fuse_file_info *fi)
+
{
+
struct lo_data *lo = lo_data(req);
+
struct statx buf;
+
int res;
+
int fd;
+
+
if (fi)
+
fd = fi->fh;
+
else
+
fd = lo_fd(req, ino);
+
+
res = statx(fd, "", flags | AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, mask, &buf);
+
if (res == -1)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_statx(req, 0, &buf, lo->timeout);
+
}
+
#endif
+
+
static const struct fuse_lowlevel_ops lo_oper = {
+
.init = lo_init,
+
.destroy = lo_destroy,
+
.lookup = lo_lookup,
+
.mkdir = lo_mkdir,
+
.mknod = lo_mknod,
+
.symlink = lo_symlink,
+
.link = lo_link,
+
.unlink = lo_unlink,
+
.rmdir = lo_rmdir,
+
.rename = lo_rename,
+
.forget = lo_forget,
+
.forget_multi = lo_forget_multi,
+
.getattr = lo_getattr,
+
.setattr = lo_setattr,
+
.readlink = lo_readlink,
+
.opendir = lo_opendir,
+
.readdir = lo_readdir,
+
.readdirplus = lo_readdirplus,
+
.releasedir = lo_releasedir,
+
.fsyncdir = lo_fsyncdir,
+
.create = lo_create,
+
.tmpfile = lo_tmpfile,
+
.open = lo_open,
+
.release = lo_release,
+
.flush = lo_flush,
+
.fsync = lo_fsync,
+
.read = lo_read,
+
.write_buf = lo_write_buf,
+
.statfs = lo_statfs,
+
.fallocate = lo_fallocate,
+
.flock = lo_flock,
+
.getxattr = lo_getxattr,
+
.listxattr = lo_listxattr,
+
.setxattr = lo_setxattr,
+
.removexattr = lo_removexattr,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = lo_copy_file_range,
+
#endif
+
.lseek = lo_lseek,
+
#ifdef HAVE_STATX
+
.statx = lo_statx,
+
#endif
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
struct lo_data lo = { .debug = 0,
+
.writeback = 0 };
+
int ret = -1;
+
+
/* Don't mask creation mode, kernel already did that */
+
umask(0);
+
+
pthread_mutex_init(&lo.mutex, NULL);
+
lo.root.next = lo.root.prev = &lo.root;
+
lo.root.fd = -1;
+
lo.cache = CACHE_NORMAL;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
passthrough_ll_help();
+
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
+
return 1;
+
+
lo.debug = opts.debug;
+
lo.root.refcount = 2;
+
if (lo.source) {
+
struct stat stat;
+
int res;
+
+
res = lstat(lo.source, &stat);
+
if (res == -1) {
+
fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
+
lo.source);
+
exit(1);
+
}
+
if (!S_ISDIR(stat.st_mode)) {
+
fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
+
exit(1);
+
}
+
+
} else {
+
lo.source = strdup("/");
+
if(!lo.source) {
+
fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
exit(1);
+
}
+
}
+
if (!lo.timeout_set) {
+
switch (lo.cache) {
+
case CACHE_NEVER:
+
lo.timeout = 0.0;
+
break;
+
+
case CACHE_NORMAL:
+
lo.timeout = 1.0;
+
break;
+
+
case CACHE_ALWAYS:
+
lo.timeout = 86400.0;
+
break;
+
}
+
} else if (lo.timeout < 0) {
+
fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
+
lo.timeout);
+
exit(1);
+
}
+
+
lo.root.fd = open(lo.source, O_PATH);
+
if (lo.root.fd == -1) {
+
fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
+
lo.source);
+
exit(1);
+
}
+
+
se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
if (lo.root.fd >= 0)
+
close(lo.root.fd);
+
+
free(lo.source);
+
return ret ? 1 : 0;
+
}
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
off_t pos
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
uint32_t capable
+
+
double entry_timeout
+
fuse_ino_t ino
+
double attr_timeout
+
struct stat attr
+ + +
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file passthrough_ll.c.

+
+ + + + diff --git a/doc/html/example_2passthrough__ll_8c_source.html b/doc/html/example_2passthrough__ll_8c_source.html new file mode 100644 index 0000000..5faede4 --- /dev/null +++ b/doc/html/example_2passthrough__ll_8c_source.html @@ -0,0 +1,1525 @@ + + + + + + + +libfuse: example/passthrough_ll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_ll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
33#define _GNU_SOURCE
+
34#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
35
+
36#include <fuse_lowlevel.h>
+
37#include <unistd.h>
+
38#include <stdlib.h>
+
39#include <stdio.h>
+
40#include <stddef.h>
+
41#include <stdbool.h>
+
42#include <string.h>
+
43#include <limits.h>
+
44#include <dirent.h>
+
45#include <assert.h>
+
46#include <errno.h>
+
47#include <inttypes.h>
+
48#include <pthread.h>
+
49#include <sys/file.h>
+
50#include <sys/xattr.h>
+
51
+
52#include "passthrough_helpers.h"
+
53
+
54/* We are re-using pointers to our `struct lo_inode` and `struct
+
55 lo_dirp` elements as inodes. This means that we must be able to
+
56 store uintptr_t values in a fuse_ino_t variable. The following
+
57 incantation checks this condition at compile time. */
+
58#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
+
59_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
+
60 "fuse_ino_t too small to hold uintptr_t values!");
+
61#else
+
62struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
+
63 { unsigned _uintptr_to_must_hold_fuse_ino_t:
+
64 ((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
+
65#endif
+
66
+
67struct lo_inode {
+
68 struct lo_inode *next; /* protected by lo->mutex */
+
69 struct lo_inode *prev; /* protected by lo->mutex */
+
70 int fd;
+
71 ino_t ino;
+
72 dev_t dev;
+
73 uint64_t refcount; /* protected by lo->mutex */
+
74};
+
75
+
76enum {
+
77 CACHE_NEVER,
+
78 CACHE_NORMAL,
+
79 CACHE_ALWAYS,
+
80};
+
81
+
82struct lo_data {
+
83 pthread_mutex_t mutex;
+
84 int debug;
+
85 int writeback;
+
86 int flock;
+
87 int xattr;
+
88 char *source;
+
89 double timeout;
+
90 int cache;
+
91 int timeout_set;
+
92 struct lo_inode root; /* protected by lo->mutex */
+
93};
+
94
+
95static const struct fuse_opt lo_opts[] = {
+
96 { "writeback",
+
97 offsetof(struct lo_data, writeback), 1 },
+
98 { "no_writeback",
+
99 offsetof(struct lo_data, writeback), 0 },
+
100 { "source=%s",
+
101 offsetof(struct lo_data, source), 0 },
+
102 { "flock",
+
103 offsetof(struct lo_data, flock), 1 },
+
104 { "no_flock",
+
105 offsetof(struct lo_data, flock), 0 },
+
106 { "xattr",
+
107 offsetof(struct lo_data, xattr), 1 },
+
108 { "no_xattr",
+
109 offsetof(struct lo_data, xattr), 0 },
+
110 { "timeout=%lf",
+
111 offsetof(struct lo_data, timeout), 0 },
+
112 { "timeout=",
+
113 offsetof(struct lo_data, timeout_set), 1 },
+
114 { "cache=never",
+
115 offsetof(struct lo_data, cache), CACHE_NEVER },
+
116 { "cache=auto",
+
117 offsetof(struct lo_data, cache), CACHE_NORMAL },
+
118 { "cache=always",
+
119 offsetof(struct lo_data, cache), CACHE_ALWAYS },
+
120
+ +
122};
+
123
+
124static void passthrough_ll_help(void)
+
125{
+
126 printf(
+
127" -o writeback Enable writeback\n"
+
128" -o no_writeback Disable write back\n"
+
129" -o source=/home/dir Source directory to be mounted\n"
+
130" -o flock Enable flock\n"
+
131" -o no_flock Disable flock\n"
+
132" -o xattr Enable xattr\n"
+
133" -o no_xattr Disable xattr\n"
+
134" -o timeout=1.0 Caching timeout\n"
+
135" -o timeout=0/1 Timeout is set\n"
+
136" -o cache=never Disable cache\n"
+
137" -o cache=auto Auto enable cache\n"
+
138" -o cache=always Cache always\n");
+
139}
+
140
+
141static struct lo_data *lo_data(fuse_req_t req)
+
142{
+
143 return (struct lo_data *) fuse_req_userdata(req);
+
144}
+
145
+
146static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
+
147{
+
148 if (ino == FUSE_ROOT_ID)
+
149 return &lo_data(req)->root;
+
150 else
+
151 return (struct lo_inode *) (uintptr_t) ino;
+
152}
+
153
+
154static int lo_fd(fuse_req_t req, fuse_ino_t ino)
+
155{
+
156 return lo_inode(req, ino)->fd;
+
157}
+
158
+
159static bool lo_debug(fuse_req_t req)
+
160{
+
161 return lo_data(req)->debug != 0;
+
162}
+
163
+
164static void lo_init(void *userdata,
+
165 struct fuse_conn_info *conn)
+
166{
+
167 struct lo_data *lo = (struct lo_data *)userdata;
+
168 bool has_flag;
+
169
+
170 if (lo->writeback) {
+ +
172 if (lo->debug && has_flag)
+
173 fuse_log(FUSE_LOG_DEBUG,
+
174 "lo_init: activating writeback\n");
+
175 }
+
176 if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
+ +
178 if (lo->debug && has_flag)
+
179 fuse_log(FUSE_LOG_DEBUG,
+
180 "lo_init: activating flock locks\n");
+
181 }
+
182
+
183 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
184 conn->no_interrupt = 1;
+
185}
+
186
+
187static void lo_destroy(void *userdata)
+
188{
+
189 struct lo_data *lo = (struct lo_data*) userdata;
+
190
+
191 while (lo->root.next != &lo->root) {
+
192 struct lo_inode* next = lo->root.next;
+
193 lo->root.next = next->next;
+
194 close(next->fd);
+
195 free(next);
+
196 }
+
197}
+
198
+
199static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
+
200 struct fuse_file_info *fi)
+
201{
+
202 int res;
+
203 struct stat buf;
+
204 struct lo_data *lo = lo_data(req);
+
205 int fd = fi ? fi->fh : lo_fd(req, ino);
+
206
+
207 (void) fi;
+
208
+
209 res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
210 if (res == -1)
+
211 return (void) fuse_reply_err(req, errno);
+
212
+
213 fuse_reply_attr(req, &buf, lo->timeout);
+
214}
+
215
+
216static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
217 int valid, struct fuse_file_info *fi)
+
218{
+
219 int saverr;
+
220 char procname[64];
+
221 struct lo_inode *inode = lo_inode(req, ino);
+
222 int ifd = inode->fd;
+
223 int res;
+
224
+
225 if (valid & FUSE_SET_ATTR_MODE) {
+
226 if (fi) {
+
227 res = fchmod(fi->fh, attr->st_mode);
+
228 } else {
+
229 sprintf(procname, "/proc/self/fd/%i", ifd);
+
230 res = chmod(procname, attr->st_mode);
+
231 }
+
232 if (res == -1)
+
233 goto out_err;
+
234 }
+
235 if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+
236 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
237 attr->st_uid : (uid_t) -1;
+
238 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
239 attr->st_gid : (gid_t) -1;
+
240
+
241 res = fchownat(ifd, "", uid, gid,
+
242 AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
243 if (res == -1)
+
244 goto out_err;
+
245 }
+
246 if (valid & FUSE_SET_ATTR_SIZE) {
+
247 if (fi) {
+
248 res = ftruncate(fi->fh, attr->st_size);
+
249 } else {
+
250 sprintf(procname, "/proc/self/fd/%i", ifd);
+
251 res = truncate(procname, attr->st_size);
+
252 }
+
253 if (res == -1)
+
254 goto out_err;
+
255 }
+
256 if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
257 struct timespec tv[2];
+
258
+
259 tv[0].tv_sec = 0;
+
260 tv[1].tv_sec = 0;
+
261 tv[0].tv_nsec = UTIME_OMIT;
+
262 tv[1].tv_nsec = UTIME_OMIT;
+
263
+
264 if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
265 tv[0].tv_nsec = UTIME_NOW;
+
266 else if (valid & FUSE_SET_ATTR_ATIME)
+
267 tv[0] = attr->st_atim;
+
268
+
269 if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
270 tv[1].tv_nsec = UTIME_NOW;
+
271 else if (valid & FUSE_SET_ATTR_MTIME)
+
272 tv[1] = attr->st_mtim;
+
273
+
274 if (fi)
+
275 res = futimens(fi->fh, tv);
+
276 else {
+
277 sprintf(procname, "/proc/self/fd/%i", ifd);
+
278 res = utimensat(AT_FDCWD, procname, tv, 0);
+
279 }
+
280 if (res == -1)
+
281 goto out_err;
+
282 }
+
283
+
284 return lo_getattr(req, ino, fi);
+
285
+
286out_err:
+
287 saverr = errno;
+
288 fuse_reply_err(req, saverr);
+
289}
+
290
+
291static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+
292{
+
293 struct lo_inode *p;
+
294 struct lo_inode *ret = NULL;
+
295
+
296 pthread_mutex_lock(&lo->mutex);
+
297 for (p = lo->root.next; p != &lo->root; p = p->next) {
+
298 if (p->ino == st->st_ino && p->dev == st->st_dev) {
+
299 assert(p->refcount > 0);
+
300 ret = p;
+
301 ret->refcount++;
+
302 break;
+
303 }
+
304 }
+
305 pthread_mutex_unlock(&lo->mutex);
+
306 return ret;
+
307}
+
308
+
309
+
310static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
+
311{
+
312 struct lo_inode *inode = NULL;
+
313 struct lo_inode *prev, *next;
+
314
+
315 inode = calloc(1, sizeof(struct lo_inode));
+
316 if (!inode)
+
317 return NULL;
+
318
+
319 inode->refcount = 1;
+
320 inode->fd = fd;
+
321 inode->ino = e->attr.st_ino;
+
322 inode->dev = e->attr.st_dev;
+
323
+
324 pthread_mutex_lock(&lo->mutex);
+
325 prev = &lo->root;
+
326 next = prev->next;
+
327 next->prev = inode;
+
328 inode->next = next;
+
329 inode->prev = prev;
+
330 prev->next = inode;
+
331 pthread_mutex_unlock(&lo->mutex);
+
332 return inode;
+
333}
+
334
+
335static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
+
336{
+
337 int res;
+
338 struct lo_data *lo = lo_data(req);
+
339
+
340 memset(e, 0, sizeof(*e));
+
341 e->attr_timeout = lo->timeout;
+
342 e->entry_timeout = lo->timeout;
+
343
+
344 res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
345 if (res == -1)
+
346 return errno;
+
347
+
348 e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
+
349
+
350 if (lo_debug(req))
+
351 fuse_log(FUSE_LOG_DEBUG, " %lli/%d -> %lli\n",
+
352 (unsigned long long) parent, fd, (unsigned long long) e->ino);
+
353
+
354 return 0;
+
355
+
356}
+
357
+
358static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
+
359 struct fuse_entry_param *e)
+
360{
+
361 int newfd;
+
362 int res;
+
363 int saverr;
+
364 struct lo_data *lo = lo_data(req);
+
365 struct lo_inode *inode;
+
366
+
367 memset(e, 0, sizeof(*e));
+
368 e->attr_timeout = lo->timeout;
+
369 e->entry_timeout = lo->timeout;
+
370
+
371 newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
+
372 if (newfd == -1)
+
373 goto out_err;
+
374
+
375 res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
376 if (res == -1)
+
377 goto out_err;
+
378
+
379 inode = lo_find(lo_data(req), &e->attr);
+
380 if (inode) {
+
381 close(newfd);
+
382 newfd = -1;
+
383 } else {
+
384 inode = create_new_inode(newfd, e, lo);
+
385 if (!inode)
+
386 goto out_err;
+
387 }
+
388 e->ino = (uintptr_t) inode;
+
389
+
390 if (lo_debug(req))
+
391 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
392 (unsigned long long) parent, name, (unsigned long long) e->ino);
+
393
+
394 return 0;
+
395
+
396out_err:
+
397 saverr = errno;
+
398 if (newfd != -1)
+
399 close(newfd);
+
400 return saverr;
+
401}
+
402
+
403static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
404{
+
405 struct fuse_entry_param e;
+
406 int err;
+
407
+
408 if (lo_debug(req))
+
409 fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
+
410 parent, name);
+
411
+
412 err = lo_do_lookup(req, parent, name, &e);
+
413 if (err)
+
414 fuse_reply_err(req, err);
+
415 else
+
416 fuse_reply_entry(req, &e);
+
417}
+
418
+
419static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
+
420 const char *name, mode_t mode, dev_t rdev,
+
421 const char *link)
+
422{
+
423 int res;
+
424 int saverr;
+
425 struct lo_inode *dir = lo_inode(req, parent);
+
426 struct fuse_entry_param e;
+
427
+
428 res = mknod_wrapper(dir->fd, name, link, mode, rdev);
+
429
+
430 saverr = errno;
+
431 if (res == -1)
+
432 goto out;
+
433
+
434 saverr = lo_do_lookup(req, parent, name, &e);
+
435 if (saverr)
+
436 goto out;
+
437
+
438 if (lo_debug(req))
+
439 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
440 (unsigned long long) parent, name, (unsigned long long) e.ino);
+
441
+
442 fuse_reply_entry(req, &e);
+
443 return;
+
444
+
445out:
+
446 fuse_reply_err(req, saverr);
+
447}
+
448
+
449static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
+
450 const char *name, mode_t mode, dev_t rdev)
+
451{
+
452 lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
+
453}
+
454
+
455static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
456 mode_t mode)
+
457{
+
458 lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
+
459}
+
460
+
461static void lo_symlink(fuse_req_t req, const char *link,
+
462 fuse_ino_t parent, const char *name)
+
463{
+
464 lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
+
465}
+
466
+
467static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
+
468 const char *name)
+
469{
+
470 int res;
+
471 struct lo_data *lo = lo_data(req);
+
472 struct lo_inode *inode = lo_inode(req, ino);
+
473 struct fuse_entry_param e;
+
474 char procname[64];
+
475 int saverr;
+
476
+
477 memset(&e, 0, sizeof(struct fuse_entry_param));
+
478 e.attr_timeout = lo->timeout;
+
479 e.entry_timeout = lo->timeout;
+
480
+
481 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
482 res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
+
483 AT_SYMLINK_FOLLOW);
+
484 if (res == -1)
+
485 goto out_err;
+
486
+
487 res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
488 if (res == -1)
+
489 goto out_err;
+
490
+
491 pthread_mutex_lock(&lo->mutex);
+
492 inode->refcount++;
+
493 pthread_mutex_unlock(&lo->mutex);
+
494 e.ino = (uintptr_t) inode;
+
495
+
496 if (lo_debug(req))
+
497 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
498 (unsigned long long) parent, name,
+
499 (unsigned long long) e.ino);
+
500
+
501 fuse_reply_entry(req, &e);
+
502 return;
+
503
+
504out_err:
+
505 saverr = errno;
+
506 fuse_reply_err(req, saverr);
+
507}
+
508
+
509static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
510{
+
511 int res;
+
512
+
513 res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
+
514
+
515 fuse_reply_err(req, res == -1 ? errno : 0);
+
516}
+
517
+
518static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
+
519 fuse_ino_t newparent, const char *newname,
+
520 unsigned int flags)
+
521{
+
522 int res;
+
523
+
524 if (flags) {
+
525 fuse_reply_err(req, EINVAL);
+
526 return;
+
527 }
+
528
+
529 res = renameat(lo_fd(req, parent), name,
+
530 lo_fd(req, newparent), newname);
+
531
+
532 fuse_reply_err(req, res == -1 ? errno : 0);
+
533}
+
534
+
535static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
+
536{
+
537 int res;
+
538
+
539 res = unlinkat(lo_fd(req, parent), name, 0);
+
540
+
541 fuse_reply_err(req, res == -1 ? errno : 0);
+
542}
+
543
+
544static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
+
545{
+
546 if (!inode)
+
547 return;
+
548
+
549 pthread_mutex_lock(&lo->mutex);
+
550 assert(inode->refcount >= n);
+
551 inode->refcount -= n;
+
552 if (!inode->refcount) {
+
553 struct lo_inode *prev, *next;
+
554
+
555 prev = inode->prev;
+
556 next = inode->next;
+
557 next->prev = prev;
+
558 prev->next = next;
+
559
+
560 pthread_mutex_unlock(&lo->mutex);
+
561 close(inode->fd);
+
562 free(inode);
+
563
+
564 } else {
+
565 pthread_mutex_unlock(&lo->mutex);
+
566 }
+
567}
+
568
+
569static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
570{
+
571 struct lo_data *lo = lo_data(req);
+
572 struct lo_inode *inode = lo_inode(req, ino);
+
573
+
574 if (lo_debug(req)) {
+
575 fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
+
576 (unsigned long long) ino,
+
577 (unsigned long long) inode->refcount,
+
578 (unsigned long long) nlookup);
+
579 }
+
580
+
581 unref_inode(lo, inode, nlookup);
+
582}
+
583
+
584static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
585{
+
586 lo_forget_one(req, ino, nlookup);
+
587 fuse_reply_none(req);
+
588}
+
589
+
590static void lo_forget_multi(fuse_req_t req, size_t count,
+
591 struct fuse_forget_data *forgets)
+
592{
+
593 int i;
+
594
+
595 for (i = 0; i < count; i++)
+
596 lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
+
597 fuse_reply_none(req);
+
598}
+
599
+
600static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
+
601{
+
602 char buf[PATH_MAX + 1];
+
603 int res;
+
604
+
605 res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
+
606 if (res == -1)
+
607 return (void) fuse_reply_err(req, errno);
+
608
+
609 if (res == sizeof(buf))
+
610 return (void) fuse_reply_err(req, ENAMETOOLONG);
+
611
+
612 buf[res] = '\0';
+
613
+
614 fuse_reply_readlink(req, buf);
+
615}
+
616
+
617struct lo_dirp {
+
618 DIR *dp;
+
619 struct dirent *entry;
+
620 off_t offset;
+
621};
+
622
+
623static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
+
624{
+
625 return (struct lo_dirp *) (uintptr_t) fi->fh;
+
626}
+
627
+
628static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
629{
+
630 int error = ENOMEM;
+
631 struct lo_data *lo = lo_data(req);
+
632 struct lo_dirp *d;
+
633 int fd = -1;
+
634
+
635 d = calloc(1, sizeof(struct lo_dirp));
+
636 if (d == NULL)
+
637 goto out_err;
+
638
+
639 fd = openat(lo_fd(req, ino), ".", O_RDONLY);
+
640 if (fd == -1)
+
641 goto out_errno;
+
642
+
643 d->dp = fdopendir(fd);
+
644 if (d->dp == NULL)
+
645 goto out_errno;
+
646
+
647 d->offset = 0;
+
648 d->entry = NULL;
+
649
+
650 fi->fh = (uintptr_t) d;
+
651 if (lo->cache != CACHE_NEVER)
+
652 fi->cache_readdir = 1;
+
653 if (lo->cache == CACHE_ALWAYS)
+
654 fi->keep_cache = 1;
+
655 fuse_reply_open(req, fi);
+
656 return;
+
657
+
658out_errno:
+
659 error = errno;
+
660out_err:
+
661 if (d) {
+
662 if (fd != -1)
+
663 close(fd);
+
664 free(d);
+
665 }
+
666 fuse_reply_err(req, error);
+
667}
+
668
+
669static int is_dot_or_dotdot(const char *name)
+
670{
+
671 return name[0] == '.' && (name[1] == '\0' ||
+
672 (name[1] == '.' && name[2] == '\0'));
+
673}
+
674
+
675static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
676 off_t offset, struct fuse_file_info *fi, int plus)
+
677{
+
678 struct lo_dirp *d = lo_dirp(fi);
+
679 char *buf;
+
680 char *p;
+
681 size_t rem = size;
+
682 int err;
+
683
+
684 (void) ino;
+
685
+
686 buf = calloc(1, size);
+
687 if (!buf) {
+
688 err = ENOMEM;
+
689 goto error;
+
690 }
+
691 p = buf;
+
692
+
693 if (offset != d->offset) {
+
694 seekdir(d->dp, offset);
+
695 d->entry = NULL;
+
696 d->offset = offset;
+
697 }
+
698 while (1) {
+
699 size_t entsize;
+
700 off_t nextoff;
+
701 const char *name;
+
702
+
703 if (!d->entry) {
+
704 errno = 0;
+
705 d->entry = readdir(d->dp);
+
706 if (!d->entry) {
+
707 if (errno) { // Error
+
708 err = errno;
+
709 goto error;
+
710 } else { // End of stream
+
711 break;
+
712 }
+
713 }
+
714 }
+
715 nextoff = d->entry->d_off;
+
716 name = d->entry->d_name;
+
717 fuse_ino_t entry_ino = 0;
+
718 if (plus) {
+
719 struct fuse_entry_param e;
+
720 if (is_dot_or_dotdot(name)) {
+
721 e = (struct fuse_entry_param) {
+
722 .attr.st_ino = d->entry->d_ino,
+
723 .attr.st_mode = d->entry->d_type << 12,
+
724 };
+
725 } else {
+
726 err = lo_do_lookup(req, ino, name, &e);
+
727 if (err)
+
728 goto error;
+
729 entry_ino = e.ino;
+
730 }
+
731
+
732 entsize = fuse_add_direntry_plus(req, p, rem, name,
+
733 &e, nextoff);
+
734 } else {
+
735 struct stat st = {
+
736 .st_ino = d->entry->d_ino,
+
737 .st_mode = d->entry->d_type << 12,
+
738 };
+
739 entsize = fuse_add_direntry(req, p, rem, name,
+
740 &st, nextoff);
+
741 }
+
742 if (entsize > rem) {
+
743 if (entry_ino != 0)
+
744 lo_forget_one(req, entry_ino, 1);
+
745 break;
+
746 }
+
747
+
748 p += entsize;
+
749 rem -= entsize;
+
750
+
751 d->entry = NULL;
+
752 d->offset = nextoff;
+
753 }
+
754
+
755 err = 0;
+
756error:
+
757 // If there's an error, we can only signal it if we haven't stored
+
758 // any entries yet - otherwise we'd end up with wrong lookup
+
759 // counts for the entries that are already in the buffer. So we
+
760 // return what we've collected until that point.
+
761 if (err && rem == size)
+
762 fuse_reply_err(req, err);
+
763 else
+
764 fuse_reply_buf(req, buf, size - rem);
+
765 free(buf);
+
766}
+
767
+
768static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
769 off_t offset, struct fuse_file_info *fi)
+
770{
+
771 lo_do_readdir(req, ino, size, offset, fi, 0);
+
772}
+
773
+
774static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
775 off_t offset, struct fuse_file_info *fi)
+
776{
+
777 lo_do_readdir(req, ino, size, offset, fi, 1);
+
778}
+
779
+
780static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
781{
+
782 struct lo_dirp *d = lo_dirp(fi);
+
783 (void) ino;
+
784 closedir(d->dp);
+
785 free(d);
+
786 fuse_reply_err(req, 0);
+
787}
+
788
+
789static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
+
790 mode_t mode, struct fuse_file_info *fi)
+
791{
+
792 int fd;
+
793 struct lo_data *lo = lo_data(req);
+
794 struct fuse_entry_param e;
+
795 int err;
+
796
+
797 if (lo_debug(req))
+
798 fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
+
799 parent);
+
800
+
801 fd = openat(lo_fd(req, parent), ".",
+
802 (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
+
803 if (fd == -1)
+
804 return (void) fuse_reply_err(req, errno);
+
805
+
806 fi->fh = fd;
+
807 if (lo->cache == CACHE_NEVER)
+
808 fi->direct_io = 1;
+
809 else if (lo->cache == CACHE_ALWAYS)
+
810 fi->keep_cache = 1;
+
811
+
812 /* parallel_direct_writes feature depends on direct_io features.
+
813 To make parallel_direct_writes valid, need set fi->direct_io
+
814 in current function. */
+ +
816
+
817 err = fill_entry_param_new_inode(req, parent, fd, &e);
+
818 if (err)
+
819 fuse_reply_err(req, err);
+
820 else
+
821 fuse_reply_create(req, &e, fi);
+
822}
+
823
+
824static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+
825 mode_t mode, struct fuse_file_info *fi)
+
826{
+
827 int fd;
+
828 struct lo_data *lo = lo_data(req);
+
829 struct fuse_entry_param e;
+
830 int err;
+
831
+
832 if (lo_debug(req))
+
833 fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
+
834 parent, name);
+
835
+
836 fd = openat(lo_fd(req, parent), name,
+
837 (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
+
838 if (fd == -1)
+
839 return (void) fuse_reply_err(req, errno);
+
840
+
841 fi->fh = fd;
+
842 if (lo->cache == CACHE_NEVER)
+
843 fi->direct_io = 1;
+
844 else if (lo->cache == CACHE_ALWAYS)
+
845 fi->keep_cache = 1;
+
846
+
847 /* parallel_direct_writes feature depends on direct_io features.
+
848 To make parallel_direct_writes valid, need set fi->direct_io
+
849 in current function. */
+ +
851
+
852 err = lo_do_lookup(req, parent, name, &e);
+
853 if (err)
+
854 fuse_reply_err(req, err);
+
855 else
+
856 fuse_reply_create(req, &e, fi);
+
857}
+
858
+
859static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
860 struct fuse_file_info *fi)
+
861{
+
862 int res;
+
863 int fd = dirfd(lo_dirp(fi)->dp);
+
864 (void) ino;
+
865 if (datasync)
+
866 res = fdatasync(fd);
+
867 else
+
868 res = fsync(fd);
+
869 fuse_reply_err(req, res == -1 ? errno : 0);
+
870}
+
871
+
872static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
873{
+
874 int fd;
+
875 char buf[64];
+
876 struct lo_data *lo = lo_data(req);
+
877
+
878 if (lo_debug(req))
+
879 fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
+
880 ino, fi->flags);
+
881
+
882 /* With writeback cache, kernel may send read requests even
+
883 when userspace opened write-only */
+
884 if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
+
885 fi->flags &= ~O_ACCMODE;
+
886 fi->flags |= O_RDWR;
+
887 }
+
888
+
889 /* With writeback cache, O_APPEND is handled by the kernel.
+
890 This breaks atomicity (since the file may change in the
+
891 underlying filesystem, so that the kernel's idea of the
+
892 end of the file isn't accurate anymore). In this example,
+
893 we just accept that. A more rigorous filesystem may want
+
894 to return an error here */
+
895 if (lo->writeback && (fi->flags & O_APPEND))
+
896 fi->flags &= ~O_APPEND;
+
897
+
898 sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
+
899 fd = open(buf, fi->flags & ~O_NOFOLLOW);
+
900 if (fd == -1)
+
901 return (void) fuse_reply_err(req, errno);
+
902
+
903 fi->fh = fd;
+
904 if (lo->cache == CACHE_NEVER)
+
905 fi->direct_io = 1;
+
906 else if (lo->cache == CACHE_ALWAYS)
+
907 fi->keep_cache = 1;
+
908
+
909 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
910 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
911 for writes to the same file in the kernel). */
+
912 if (fi->flags & O_DIRECT)
+
913 fi->direct_io = 1;
+
914
+
915 /* parallel_direct_writes feature depends on direct_io features.
+
916 To make parallel_direct_writes valid, need set fi->direct_io
+
917 in current function. */
+ +
919
+
920 fuse_reply_open(req, fi);
+
921}
+
922
+
923static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
924{
+
925 (void) ino;
+
926
+
927 close(fi->fh);
+
928 fuse_reply_err(req, 0);
+
929}
+
930
+
931static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
932{
+
933 int res;
+
934 (void) ino;
+
935 res = close(dup(fi->fh));
+
936 fuse_reply_err(req, res == -1 ? errno : 0);
+
937}
+
938
+
939static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
940 struct fuse_file_info *fi)
+
941{
+
942 int res;
+
943 (void) ino;
+
944 if (datasync)
+
945 res = fdatasync(fi->fh);
+
946 else
+
947 res = fsync(fi->fh);
+
948 fuse_reply_err(req, res == -1 ? errno : 0);
+
949}
+
950
+
951static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
952 off_t offset, struct fuse_file_info *fi)
+
953{
+
954 struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
+
955
+
956 if (lo_debug(req))
+
957 fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
+
958 "off=%lu)\n", ino, size, (unsigned long) offset);
+
959
+ +
961 buf.buf[0].fd = fi->fh;
+
962 buf.buf[0].pos = offset;
+
963
+ +
965}
+
966
+
967static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
+
968 struct fuse_bufvec *in_buf, off_t off,
+
969 struct fuse_file_info *fi)
+
970{
+
971 (void) ino;
+
972 ssize_t res;
+
973 struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
+
974
+ +
976 out_buf.buf[0].fd = fi->fh;
+
977 out_buf.buf[0].pos = off;
+
978
+
979 if (lo_debug(req))
+
980 fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%jd)\n",
+
981 ino, out_buf.buf[0].size, (intmax_t) off);
+
982
+
983 res = fuse_buf_copy(&out_buf, in_buf, 0);
+
984 if(res < 0)
+
985 fuse_reply_err(req, -res);
+
986 else
+
987 fuse_reply_write(req, (size_t) res);
+
988}
+
989
+
990static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
+
991{
+
992 int res;
+
993 struct statvfs stbuf;
+
994
+
995 res = fstatvfs(lo_fd(req, ino), &stbuf);
+
996 if (res == -1)
+
997 fuse_reply_err(req, errno);
+
998 else
+
999 fuse_reply_statfs(req, &stbuf);
+
1000}
+
1001
+
1002static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
1003 off_t offset, off_t length, struct fuse_file_info *fi)
+
1004{
+
1005 int err;
+
1006 (void) ino;
+
1007
+
1008 err = -do_fallocate(fi->fh, mode, offset, length);
+
1009
+
1010 fuse_reply_err(req, err);
+
1011}
+
1012
+
1013static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
1014 int op)
+
1015{
+
1016 int res;
+
1017 (void) ino;
+
1018
+
1019 res = flock(fi->fh, op);
+
1020
+
1021 fuse_reply_err(req, res == -1 ? errno : 0);
+
1022}
+
1023
+
1024static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
1025 size_t size)
+
1026{
+
1027 char *value = NULL;
+
1028 char procname[64];
+
1029 struct lo_inode *inode = lo_inode(req, ino);
+
1030 ssize_t ret;
+
1031 int saverr;
+
1032
+
1033 saverr = ENOSYS;
+
1034 if (!lo_data(req)->xattr)
+
1035 goto out;
+
1036
+
1037 if (lo_debug(req)) {
+
1038 fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
+
1039 ino, name, size);
+
1040 }
+
1041
+
1042 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1043
+
1044 if (size) {
+
1045 value = malloc(size);
+
1046 if (!value)
+
1047 goto out_err;
+
1048
+
1049 ret = getxattr(procname, name, value, size);
+
1050 if (ret == -1)
+
1051 goto out_err;
+
1052 saverr = 0;
+
1053 if (ret == 0)
+
1054 goto out;
+
1055
+
1056 fuse_reply_buf(req, value, ret);
+
1057 } else {
+
1058 ret = getxattr(procname, name, NULL, 0);
+
1059 if (ret == -1)
+
1060 goto out_err;
+
1061
+
1062 fuse_reply_xattr(req, ret);
+
1063 }
+
1064out_free:
+
1065 free(value);
+
1066 return;
+
1067
+
1068out_err:
+
1069 saverr = errno;
+
1070out:
+
1071 fuse_reply_err(req, saverr);
+
1072 goto out_free;
+
1073}
+
1074
+
1075static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
1076{
+
1077 char *value = NULL;
+
1078 char procname[64];
+
1079 struct lo_inode *inode = lo_inode(req, ino);
+
1080 ssize_t ret;
+
1081 int saverr;
+
1082
+
1083 saverr = ENOSYS;
+
1084 if (!lo_data(req)->xattr)
+
1085 goto out;
+
1086
+
1087 if (lo_debug(req)) {
+
1088 fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
+
1089 ino, size);
+
1090 }
+
1091
+
1092 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1093
+
1094 if (size) {
+
1095 value = malloc(size);
+
1096 if (!value)
+
1097 goto out_err;
+
1098
+
1099 ret = listxattr(procname, value, size);
+
1100 if (ret == -1)
+
1101 goto out_err;
+
1102 saverr = 0;
+
1103 if (ret == 0)
+
1104 goto out;
+
1105
+
1106 fuse_reply_buf(req, value, ret);
+
1107 } else {
+
1108 ret = listxattr(procname, NULL, 0);
+
1109 if (ret == -1)
+
1110 goto out_err;
+
1111
+
1112 fuse_reply_xattr(req, ret);
+
1113 }
+
1114out_free:
+
1115 free(value);
+
1116 return;
+
1117
+
1118out_err:
+
1119 saverr = errno;
+
1120out:
+
1121 fuse_reply_err(req, saverr);
+
1122 goto out_free;
+
1123}
+
1124
+
1125static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
1126 const char *value, size_t size, int flags)
+
1127{
+
1128 char procname[64];
+
1129 struct lo_inode *inode = lo_inode(req, ino);
+
1130 ssize_t ret;
+
1131 int saverr;
+
1132
+
1133 saverr = ENOSYS;
+
1134 if (!lo_data(req)->xattr)
+
1135 goto out;
+
1136
+
1137 if (lo_debug(req)) {
+
1138 fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
+
1139 ino, name, value, size);
+
1140 }
+
1141
+
1142 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1143
+
1144 ret = setxattr(procname, name, value, size, flags);
+
1145 saverr = ret == -1 ? errno : 0;
+
1146
+
1147out:
+
1148 fuse_reply_err(req, saverr);
+
1149}
+
1150
+
1151static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
1152{
+
1153 char procname[64];
+
1154 struct lo_inode *inode = lo_inode(req, ino);
+
1155 ssize_t ret;
+
1156 int saverr;
+
1157
+
1158 saverr = ENOSYS;
+
1159 if (!lo_data(req)->xattr)
+
1160 goto out;
+
1161
+
1162 if (lo_debug(req)) {
+
1163 fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
+
1164 ino, name);
+
1165 }
+
1166
+
1167 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1168
+
1169 ret = removexattr(procname, name);
+
1170 saverr = ret == -1 ? errno : 0;
+
1171
+
1172out:
+
1173 fuse_reply_err(req, saverr);
+
1174}
+
1175
+
1176#ifdef HAVE_COPY_FILE_RANGE
+
1177static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
+
1178 struct fuse_file_info *fi_in,
+
1179 fuse_ino_t ino_out, off_t off_out,
+
1180 struct fuse_file_info *fi_out, size_t len,
+
1181 int flags)
+
1182{
+
1183 ssize_t res;
+
1184
+
1185 if (lo_debug(req))
+
1186 fuse_log(FUSE_LOG_DEBUG,
+
1187 "%s(ino=%lld fd=%lld off=%jd ino=%lld fd=%lld off=%jd, size=%zd, flags=0x%x)\n",
+
1188 __func__, (unsigned long long)ino_in,
+
1189 (unsigned long long)fi_in->fh,
+
1190 (intmax_t) off_in, (unsigned long long)ino_out,
+
1191 (unsigned long long)fi_out->fh, (intmax_t) off_out,
+
1192 len, flags);
+
1193
+
1194 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
1195 flags);
+
1196 if (res < 0)
+
1197 fuse_reply_err(req, errno);
+
1198 else
+
1199 fuse_reply_write(req, res);
+
1200}
+
1201#endif
+
1202
+
1203static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
1204 struct fuse_file_info *fi)
+
1205{
+
1206 off_t res;
+
1207
+
1208 (void)ino;
+
1209 res = lseek(fi->fh, off, whence);
+
1210 if (res != -1)
+
1211 fuse_reply_lseek(req, res);
+
1212 else
+
1213 fuse_reply_err(req, errno);
+
1214}
+
1215
+
1216#ifdef HAVE_STATX
+
1217static void lo_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
+
1218 struct fuse_file_info *fi)
+
1219{
+
1220 struct lo_data *lo = lo_data(req);
+
1221 struct statx buf;
+
1222 int res;
+
1223 int fd;
+
1224
+
1225 if (fi)
+
1226 fd = fi->fh;
+
1227 else
+
1228 fd = lo_fd(req, ino);
+
1229
+
1230 res = statx(fd, "", flags | AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, mask, &buf);
+
1231 if (res == -1)
+
1232 fuse_reply_err(req, errno);
+
1233 else
+
1234 fuse_reply_statx(req, 0, &buf, lo->timeout);
+
1235}
+
1236#endif
+
1237
+
1238static const struct fuse_lowlevel_ops lo_oper = {
+
1239 .init = lo_init,
+
1240 .destroy = lo_destroy,
+
1241 .lookup = lo_lookup,
+
1242 .mkdir = lo_mkdir,
+
1243 .mknod = lo_mknod,
+
1244 .symlink = lo_symlink,
+
1245 .link = lo_link,
+
1246 .unlink = lo_unlink,
+
1247 .rmdir = lo_rmdir,
+
1248 .rename = lo_rename,
+
1249 .forget = lo_forget,
+
1250 .forget_multi = lo_forget_multi,
+
1251 .getattr = lo_getattr,
+
1252 .setattr = lo_setattr,
+
1253 .readlink = lo_readlink,
+
1254 .opendir = lo_opendir,
+
1255 .readdir = lo_readdir,
+
1256 .readdirplus = lo_readdirplus,
+
1257 .releasedir = lo_releasedir,
+
1258 .fsyncdir = lo_fsyncdir,
+
1259 .create = lo_create,
+
1260 .tmpfile = lo_tmpfile,
+
1261 .open = lo_open,
+
1262 .release = lo_release,
+
1263 .flush = lo_flush,
+
1264 .fsync = lo_fsync,
+
1265 .read = lo_read,
+
1266 .write_buf = lo_write_buf,
+
1267 .statfs = lo_statfs,
+
1268 .fallocate = lo_fallocate,
+
1269 .flock = lo_flock,
+
1270 .getxattr = lo_getxattr,
+
1271 .listxattr = lo_listxattr,
+
1272 .setxattr = lo_setxattr,
+
1273 .removexattr = lo_removexattr,
+
1274#ifdef HAVE_COPY_FILE_RANGE
+
1275 .copy_file_range = lo_copy_file_range,
+
1276#endif
+
1277 .lseek = lo_lseek,
+
1278#ifdef HAVE_STATX
+
1279 .statx = lo_statx,
+
1280#endif
+
1281};
+
1282
+
1283int main(int argc, char *argv[])
+
1284{
+
1285 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
1286 struct fuse_session *se;
+
1287 struct fuse_cmdline_opts opts;
+
1288 struct fuse_loop_config *config;
+
1289 struct lo_data lo = { .debug = 0,
+
1290 .writeback = 0 };
+
1291 int ret = -1;
+
1292
+
1293 /* Don't mask creation mode, kernel already did that */
+
1294 umask(0);
+
1295
+
1296 pthread_mutex_init(&lo.mutex, NULL);
+
1297 lo.root.next = lo.root.prev = &lo.root;
+
1298 lo.root.fd = -1;
+
1299 lo.cache = CACHE_NORMAL;
+
1300
+
1301 if (fuse_parse_cmdline(&args, &opts) != 0)
+
1302 return 1;
+
1303 if (opts.show_help) {
+
1304 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
1307 passthrough_ll_help();
+
1308 ret = 0;
+
1309 goto err_out1;
+
1310 } else if (opts.show_version) {
+
1311 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
1313 ret = 0;
+
1314 goto err_out1;
+
1315 }
+
1316
+
1317 if(opts.mountpoint == NULL) {
+
1318 printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
1319 printf(" %s --help\n", argv[0]);
+
1320 ret = 1;
+
1321 goto err_out1;
+
1322 }
+
1323
+
1324 if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
+
1325 return 1;
+
1326
+
1327 lo.debug = opts.debug;
+
1328 lo.root.refcount = 2;
+
1329 if (lo.source) {
+
1330 struct stat stat;
+
1331 int res;
+
1332
+
1333 res = lstat(lo.source, &stat);
+
1334 if (res == -1) {
+
1335 fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
+
1336 lo.source);
+
1337 exit(1);
+
1338 }
+
1339 if (!S_ISDIR(stat.st_mode)) {
+
1340 fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
+
1341 exit(1);
+
1342 }
+
1343
+
1344 } else {
+
1345 lo.source = strdup("/");
+
1346 if(!lo.source) {
+
1347 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
1348 exit(1);
+
1349 }
+
1350 }
+
1351 if (!lo.timeout_set) {
+
1352 switch (lo.cache) {
+
1353 case CACHE_NEVER:
+
1354 lo.timeout = 0.0;
+
1355 break;
+
1356
+
1357 case CACHE_NORMAL:
+
1358 lo.timeout = 1.0;
+
1359 break;
+
1360
+
1361 case CACHE_ALWAYS:
+
1362 lo.timeout = 86400.0;
+
1363 break;
+
1364 }
+
1365 } else if (lo.timeout < 0) {
+
1366 fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
+
1367 lo.timeout);
+
1368 exit(1);
+
1369 }
+
1370
+
1371 lo.root.fd = open(lo.source, O_PATH);
+
1372 if (lo.root.fd == -1) {
+
1373 fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
+
1374 lo.source);
+
1375 exit(1);
+
1376 }
+
1377
+
1378 se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
+
1379 if (se == NULL)
+
1380 goto err_out1;
+
1381
+
1382 if (fuse_set_signal_handlers(se) != 0)
+
1383 goto err_out2;
+
1384
+
1385 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
1386 goto err_out3;
+
1387
+
1388 fuse_daemonize(opts.foreground);
+
1389
+
1390 /* Block until ctrl+c or fusermount -u */
+
1391 if (opts.singlethread)
+
1392 ret = fuse_session_loop(se);
+
1393 else {
+
1394 config = fuse_loop_cfg_create();
+
1395 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
1396 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
1397 ret = fuse_session_loop_mt(se, config);
+
1398 fuse_loop_cfg_destroy(config);
+
1399 config = NULL;
+
1400 }
+
1401
+ +
1403err_out3:
+ +
1405err_out2:
+ +
1407err_out1:
+
1408 free(opts.mountpoint);
+
1409 fuse_opt_free_args(&args);
+
1410
+
1411 if (lo.root.fd >= 0)
+
1412 close(lo.root.fd);
+
1413
+
1414 free(lo.source);
+
1415 return ret ? 1 : 0;
+
1416}
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
off_t pos
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
uint32_t capable
+
+
double entry_timeout
+
fuse_ino_t ino
+
double attr_timeout
+
struct stat attr
+ + +
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/example_2poll_8c.html b/doc/html/example_2poll_8c.html new file mode 100644 index 0000000..5d7d1d9 --- /dev/null +++ b/doc/html/example_2poll_8c.html @@ -0,0 +1,379 @@ + + + + + + + +libfuse: example/poll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
poll.c File Reference
+
+
+
#include <fuse.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include <pthread.h>
+#include <poll.h>
+#include <stdbool.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example illustrates how to write a FUSE file system that supports polling for changes that don't come through the kernel. It can be tested with the poll_client.c program.

+

Compile with:

gcc -Wall poll.c `pkg-config fuse3 --cflags --libs` -o poll
+

+Source code

+
/*
+
FUSE fsel: FUSE select example
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <unistd.h>
+
#include <ctype.h>
+
#include <string.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <errno.h>
+
#include <time.h>
+
#include <pthread.h>
+
#include <poll.h>
+
#include <stdbool.h>
+
+
/*
+
* fsel_open_mask is used to limit the number of opens to 1 per file.
+
* This is to use file index (0-F) as fh as poll support requires
+
* unique fh per open file. Lifting this would require proper open
+
* file management.
+
*/
+
static unsigned fsel_open_mask;
+
static const char fsel_hex_map[] = "0123456789ABCDEF";
+
static struct fuse *fsel_fuse; /* needed for poll notification */
+
+
#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
+
#define FSEL_FILES 16
+
+
static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
+
static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
+
static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
+
static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
+
static _Atomic bool fsel_stop = false;
+
static pthread_t fsel_producer_thread;
+
+
+
static int fsel_path_index(const char *path)
+
{
+
char ch = path[1];
+
+
if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
+
return -1;
+
return ch <= '9' ? ch - '0' : ch - 'A' + 10;
+
}
+
+
static void fsel_destroy(void *private_data)
+
{
+
(void)private_data;
+
+
fsel_stop = true;
+
+
pthread_join(fsel_producer_thread, NULL);
+
}
+
+
static int fsel_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int idx;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0555;
+
stbuf->st_nlink = 2;
+
return 0;
+
}
+
+
idx = fsel_path_index(path);
+
if (idx < 0)
+
return -ENOENT;
+
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = fsel_cnt[idx];
+
return 0;
+
}
+
+
static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
char name[2] = { };
+
int i;
+
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
name[0] = fsel_hex_map[i];
+
filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
}
+
+
return 0;
+
}
+
+
static int fsel_open(const char *path, struct fuse_file_info *fi)
+
{
+
int idx = fsel_path_index(path);
+
+
if (idx < 0)
+
return -ENOENT;
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
if (fsel_open_mask & (1 << idx))
+
return -EBUSY;
+
fsel_open_mask |= (1 << idx);
+
+
/*
+
* fsel files are nonseekable somewhat pipe-like files which
+
* gets filled up periodically by producer thread and consumed
+
* on read. Tell FUSE as such.
+
*/
+
fi->fh = idx;
+
fi->direct_io = 1;
+
fi->nonseekable = 1;
+
+
return 0;
+
}
+
+
static int fsel_release(const char *path, struct fuse_file_info *fi)
+
{
+
int idx = fi->fh;
+
+
(void) path;
+
+
fsel_open_mask &= ~(1 << idx);
+
return 0;
+
}
+
+
static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int idx = fi->fh;
+
+
(void) path;
+
(void) offset;
+
+
pthread_mutex_lock(&fsel_mutex);
+
if (fsel_cnt[idx] < size)
+
size = fsel_cnt[idx];
+
printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
+
fsel_cnt[idx] -= size;
+
pthread_mutex_unlock(&fsel_mutex);
+
+
memset(buf, fsel_hex_map[idx], size);
+
return size;
+
}
+
+
static int fsel_poll(const char *path, struct fuse_file_info *fi,
+
struct fuse_pollhandle *ph, unsigned *reventsp)
+
{
+
static unsigned polled_zero;
+
int idx = fi->fh;
+
+
(void) path;
+
+
/*
+
* Poll notification requires pointer to struct fuse which
+
* can't be obtained when using fuse_main(). As notification
+
* happens only after poll is called, fill it here from
+
* fuse_context.
+
*/
+
if (!fsel_fuse) {
+
struct fuse_context *cxt = fuse_get_context();
+
if (cxt)
+
fsel_fuse = cxt->fuse;
+
}
+
+
pthread_mutex_lock(&fsel_mutex);
+
+
if (ph != NULL) {
+
struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
+
+
if (oldph)
+ +
+
fsel_poll_notify_mask |= (1 << idx);
+
fsel_poll_handle[idx] = ph;
+
}
+
+
if (fsel_cnt[idx]) {
+
*reventsp |= POLLIN;
+
printf("POLL %X cnt=%u polled_zero=%u\n",
+
idx, fsel_cnt[idx], polled_zero);
+
polled_zero = 0;
+
} else
+
polled_zero++;
+
+
pthread_mutex_unlock(&fsel_mutex);
+
return 0;
+
}
+
+
static const struct fuse_operations fsel_oper = {
+
.destroy = fsel_destroy,
+
.getattr = fsel_getattr,
+
.readdir = fsel_readdir,
+
.open = fsel_open,
+
.release = fsel_release,
+
.read = fsel_read,
+
.poll = fsel_poll,
+
};
+
+
static void *fsel_producer(void *data)
+
{
+
const struct timespec interval = { 0, 250000000 };
+
unsigned idx = 0, nr = 1;
+
+
(void) data;
+
+
while (!fsel_stop) {
+
int i, t;
+
+
pthread_mutex_lock(&fsel_mutex);
+
+
/*
+
* This is the main producer loop which is executed
+
* ever 500ms. On each iteration, it fills one byte
+
* to 1, 2 or 4 files and sends poll notification if
+
* requested.
+
*/
+
for (i = 0, t = idx; i < nr;
+
i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
+
if (fsel_cnt[t] == FSEL_CNT_MAX)
+
continue;
+
+
fsel_cnt[t]++;
+
if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
+
struct fuse_pollhandle *ph;
+
+
printf("NOTIFY %X\n", t);
+
ph = fsel_poll_handle[t];
+
fuse_notify_poll(ph);
+ +
fsel_poll_notify_mask &= ~(1 << t);
+
fsel_poll_handle[t] = NULL;
+
}
+
}
+
+
idx = (idx + 1) % FSEL_FILES;
+
if (idx == 0)
+
nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
+
+
pthread_mutex_unlock(&fsel_mutex);
+
+
nanosleep(&interval, NULL);
+
}
+
+
return NULL;
+
}
+
+
int main(int argc, char *argv[])
+
{
+
pthread_attr_t attr;
+
int ret;
+
+
errno = pthread_mutex_init(&fsel_mutex, NULL);
+
if (errno) {
+
perror("pthread_mutex_init");
+
return 1;
+
}
+
+
errno = pthread_attr_init(&attr);
+
if (errno) {
+
perror("pthread_attr_init");
+
return 1;
+
}
+
+
errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
+
if (errno) {
+
perror("pthread_create");
+
return 1;
+
}
+
+
ret = fuse_main(argc, argv, &fsel_oper, NULL);
+
+
return ret;
+
}
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+ +
struct fuse * fuse
Definition fuse.h:862
+ + +
uint32_t nonseekable
Definition fuse_common.h:78
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+
+

Definition in file poll.c.

+
+ + + + diff --git a/doc/html/example_2poll_8c_source.html b/doc/html/example_2poll_8c_source.html new file mode 100644 index 0000000..c743111 --- /dev/null +++ b/doc/html/example_2poll_8c_source.html @@ -0,0 +1,364 @@ + + + + + + + +libfuse: example/poll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
poll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fsel: FUSE select example
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
24#define FUSE_USE_VERSION 31
+
25
+
26#include <fuse.h>
+
27#include <unistd.h>
+
28#include <ctype.h>
+
29#include <string.h>
+
30#include <stdio.h>
+
31#include <stdlib.h>
+
32#include <errno.h>
+
33#include <time.h>
+
34#include <pthread.h>
+
35#include <poll.h>
+
36#include <stdbool.h>
+
37
+
38/*
+
39 * fsel_open_mask is used to limit the number of opens to 1 per file.
+
40 * This is to use file index (0-F) as fh as poll support requires
+
41 * unique fh per open file. Lifting this would require proper open
+
42 * file management.
+
43 */
+
44static unsigned fsel_open_mask;
+
45static const char fsel_hex_map[] = "0123456789ABCDEF";
+
46static struct fuse *fsel_fuse; /* needed for poll notification */
+
47
+
48#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
+
49#define FSEL_FILES 16
+
50
+
51static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
+
52static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
+
53static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
+
54static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
+
55static _Atomic bool fsel_stop = false;
+
56static pthread_t fsel_producer_thread;
+
57
+
58
+
59static int fsel_path_index(const char *path)
+
60{
+
61 char ch = path[1];
+
62
+
63 if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
+
64 return -1;
+
65 return ch <= '9' ? ch - '0' : ch - 'A' + 10;
+
66}
+
67
+
68static void fsel_destroy(void *private_data)
+
69{
+
70 (void)private_data;
+
71
+
72 fsel_stop = true;
+
73
+
74 pthread_join(fsel_producer_thread, NULL);
+
75}
+
76
+
77static int fsel_getattr(const char *path, struct stat *stbuf,
+
78 struct fuse_file_info *fi)
+
79{
+
80 (void) fi;
+
81 int idx;
+
82
+
83 memset(stbuf, 0, sizeof(struct stat));
+
84
+
85 if (strcmp(path, "/") == 0) {
+
86 stbuf->st_mode = S_IFDIR | 0555;
+
87 stbuf->st_nlink = 2;
+
88 return 0;
+
89 }
+
90
+
91 idx = fsel_path_index(path);
+
92 if (idx < 0)
+
93 return -ENOENT;
+
94
+
95 stbuf->st_mode = S_IFREG | 0444;
+
96 stbuf->st_nlink = 1;
+
97 stbuf->st_size = fsel_cnt[idx];
+
98 return 0;
+
99}
+
100
+
101static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
102 off_t offset, struct fuse_file_info *fi,
+
103 enum fuse_readdir_flags flags)
+
104{
+
105 char name[2] = { };
+
106 int i;
+
107
+
108 (void) offset;
+
109 (void) fi;
+
110 (void) flags;
+
111
+
112 if (strcmp(path, "/") != 0)
+
113 return -ENOENT;
+
114
+
115 for (i = 0; i < FSEL_FILES; i++) {
+
116 name[0] = fsel_hex_map[i];
+
117 filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
118 }
+
119
+
120 return 0;
+
121}
+
122
+
123static int fsel_open(const char *path, struct fuse_file_info *fi)
+
124{
+
125 int idx = fsel_path_index(path);
+
126
+
127 if (idx < 0)
+
128 return -ENOENT;
+
129 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
130 return -EACCES;
+
131 if (fsel_open_mask & (1 << idx))
+
132 return -EBUSY;
+
133 fsel_open_mask |= (1 << idx);
+
134
+
135 /*
+
136 * fsel files are nonseekable somewhat pipe-like files which
+
137 * gets filled up periodically by producer thread and consumed
+
138 * on read. Tell FUSE as such.
+
139 */
+
140 fi->fh = idx;
+
141 fi->direct_io = 1;
+
142 fi->nonseekable = 1;
+
143
+
144 return 0;
+
145}
+
146
+
147static int fsel_release(const char *path, struct fuse_file_info *fi)
+
148{
+
149 int idx = fi->fh;
+
150
+
151 (void) path;
+
152
+
153 fsel_open_mask &= ~(1 << idx);
+
154 return 0;
+
155}
+
156
+
157static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
+
158 struct fuse_file_info *fi)
+
159{
+
160 int idx = fi->fh;
+
161
+
162 (void) path;
+
163 (void) offset;
+
164
+
165 pthread_mutex_lock(&fsel_mutex);
+
166 if (fsel_cnt[idx] < size)
+
167 size = fsel_cnt[idx];
+
168 printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
+
169 fsel_cnt[idx] -= size;
+
170 pthread_mutex_unlock(&fsel_mutex);
+
171
+
172 memset(buf, fsel_hex_map[idx], size);
+
173 return size;
+
174}
+
175
+
176static int fsel_poll(const char *path, struct fuse_file_info *fi,
+
177 struct fuse_pollhandle *ph, unsigned *reventsp)
+
178{
+
179 static unsigned polled_zero;
+
180 int idx = fi->fh;
+
181
+
182 (void) path;
+
183
+
184 /*
+
185 * Poll notification requires pointer to struct fuse which
+
186 * can't be obtained when using fuse_main(). As notification
+
187 * happens only after poll is called, fill it here from
+
188 * fuse_context.
+
189 */
+
190 if (!fsel_fuse) {
+
191 struct fuse_context *cxt = fuse_get_context();
+
192 if (cxt)
+
193 fsel_fuse = cxt->fuse;
+
194 }
+
195
+
196 pthread_mutex_lock(&fsel_mutex);
+
197
+
198 if (ph != NULL) {
+
199 struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
+
200
+
201 if (oldph)
+ +
203
+
204 fsel_poll_notify_mask |= (1 << idx);
+
205 fsel_poll_handle[idx] = ph;
+
206 }
+
207
+
208 if (fsel_cnt[idx]) {
+
209 *reventsp |= POLLIN;
+
210 printf("POLL %X cnt=%u polled_zero=%u\n",
+
211 idx, fsel_cnt[idx], polled_zero);
+
212 polled_zero = 0;
+
213 } else
+
214 polled_zero++;
+
215
+
216 pthread_mutex_unlock(&fsel_mutex);
+
217 return 0;
+
218}
+
219
+
220static const struct fuse_operations fsel_oper = {
+
221 .destroy = fsel_destroy,
+
222 .getattr = fsel_getattr,
+
223 .readdir = fsel_readdir,
+
224 .open = fsel_open,
+
225 .release = fsel_release,
+
226 .read = fsel_read,
+
227 .poll = fsel_poll,
+
228};
+
229
+
230static void *fsel_producer(void *data)
+
231{
+
232 const struct timespec interval = { 0, 250000000 };
+
233 unsigned idx = 0, nr = 1;
+
234
+
235 (void) data;
+
236
+
237 while (!fsel_stop) {
+
238 int i, t;
+
239
+
240 pthread_mutex_lock(&fsel_mutex);
+
241
+
242 /*
+
243 * This is the main producer loop which is executed
+
244 * ever 500ms. On each iteration, it fills one byte
+
245 * to 1, 2 or 4 files and sends poll notification if
+
246 * requested.
+
247 */
+
248 for (i = 0, t = idx; i < nr;
+
249 i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
+
250 if (fsel_cnt[t] == FSEL_CNT_MAX)
+
251 continue;
+
252
+
253 fsel_cnt[t]++;
+
254 if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
+
255 struct fuse_pollhandle *ph;
+
256
+
257 printf("NOTIFY %X\n", t);
+
258 ph = fsel_poll_handle[t];
+
259 fuse_notify_poll(ph);
+ +
261 fsel_poll_notify_mask &= ~(1 << t);
+
262 fsel_poll_handle[t] = NULL;
+
263 }
+
264 }
+
265
+
266 idx = (idx + 1) % FSEL_FILES;
+
267 if (idx == 0)
+
268 nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
+
269
+
270 pthread_mutex_unlock(&fsel_mutex);
+
271
+
272 nanosleep(&interval, NULL);
+
273 }
+
274
+
275 return NULL;
+
276}
+
277
+
278int main(int argc, char *argv[])
+
279{
+
280 pthread_attr_t attr;
+
281 int ret;
+
282
+
283 errno = pthread_mutex_init(&fsel_mutex, NULL);
+
284 if (errno) {
+
285 perror("pthread_mutex_init");
+
286 return 1;
+
287 }
+
288
+
289 errno = pthread_attr_init(&attr);
+
290 if (errno) {
+
291 perror("pthread_attr_init");
+
292 return 1;
+
293 }
+
294
+
295 errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
+
296 if (errno) {
+
297 perror("pthread_create");
+
298 return 1;
+
299 }
+
300
+
301 ret = fuse_main(argc, argv, &fsel_oper, NULL);
+
302
+
303 return ret;
+
304}
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+ +
struct fuse * fuse
Definition fuse.h:862
+ + +
uint32_t nonseekable
Definition fuse_common.h:78
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+
+ + + + diff --git a/doc/html/example_2poll__client_8c.html b/doc/html/example_2poll__client_8c.html new file mode 100644 index 0000000..309ff26 --- /dev/null +++ b/doc/html/example_2poll__client_8c.html @@ -0,0 +1,145 @@ + + + + + + + +libfuse: example/poll_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
poll_client.c File Reference
+
+
+
#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the poll.c example file systsem.

+

Compile with:

 gcc -Wall poll_client.c -o poll_client
+

+Source code

+
/*
+
FUSE fselclient: FUSE select example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#include <sys/select.h>
+
#include <sys/time.h>
+
#include <sys/types.h>
+
#include <sys/stat.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <ctype.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <errno.h>
+
+
#define FSEL_FILES 16
+
+
int main(void)
+
{
+
static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
+
int fds[FSEL_FILES];
+
int i, nfds, tries;
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
char name[] = { hex_map[i], '\0' };
+
fds[i] = open(name, O_RDONLY);
+
if (fds[i] < 0) {
+
perror("open");
+
return 1;
+
}
+
}
+
nfds = fds[FSEL_FILES - 1] + 1;
+
+
for(tries=0; tries < 16; tries++) {
+
static char buf[4096];
+
fd_set rfds;
+
int rc;
+
+
FD_ZERO(&rfds);
+
for (i = 0; i < FSEL_FILES; i++)
+
FD_SET(fds[i], &rfds);
+
+
rc = select(nfds, &rfds, NULL, NULL, NULL);
+
+
if (rc < 0) {
+
perror("select");
+
return 1;
+
}
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
if (!FD_ISSET(fds[i], &rfds)) {
+
printf("_: ");
+
continue;
+
}
+
printf("%X:", i);
+
rc = read(fds[i], buf, sizeof(buf));
+
if (rc < 0) {
+
perror("read");
+
return 1;
+
}
+
printf("%02d ", rc);
+
}
+
printf("\n");
+
}
+
return 0;
+
}
+
+

Definition in file poll_client.c.

+
+ + + + diff --git a/doc/html/example_2poll__client_8c_source.html b/doc/html/example_2poll__client_8c_source.html new file mode 100644 index 0000000..6c13669 --- /dev/null +++ b/doc/html/example_2poll__client_8c_source.html @@ -0,0 +1,131 @@ + + + + + + + +libfuse: example/poll_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
poll_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fselclient: FUSE select example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
23#include <sys/select.h>
+
24#include <sys/time.h>
+
25#include <sys/types.h>
+
26#include <sys/stat.h>
+
27#include <fcntl.h>
+
28#include <unistd.h>
+
29#include <ctype.h>
+
30#include <stdio.h>
+
31#include <stdlib.h>
+
32#include <errno.h>
+
33
+
34#define FSEL_FILES 16
+
35
+
36int main(void)
+
37{
+
38 static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
+
39 int fds[FSEL_FILES];
+
40 int i, nfds, tries;
+
41
+
42 for (i = 0; i < FSEL_FILES; i++) {
+
43 char name[] = { hex_map[i], '\0' };
+
44 fds[i] = open(name, O_RDONLY);
+
45 if (fds[i] < 0) {
+
46 perror("open");
+
47 return 1;
+
48 }
+
49 }
+
50 nfds = fds[FSEL_FILES - 1] + 1;
+
51
+
52 for(tries=0; tries < 16; tries++) {
+
53 static char buf[4096];
+
54 fd_set rfds;
+
55 int rc;
+
56
+
57 FD_ZERO(&rfds);
+
58 for (i = 0; i < FSEL_FILES; i++)
+
59 FD_SET(fds[i], &rfds);
+
60
+
61 rc = select(nfds, &rfds, NULL, NULL, NULL);
+
62
+
63 if (rc < 0) {
+
64 perror("select");
+
65 return 1;
+
66 }
+
67
+
68 for (i = 0; i < FSEL_FILES; i++) {
+
69 if (!FD_ISSET(fds[i], &rfds)) {
+
70 printf("_: ");
+
71 continue;
+
72 }
+
73 printf("%X:", i);
+
74 rc = read(fds[i], buf, sizeof(buf));
+
75 if (rc < 0) {
+
76 perror("read");
+
77 return 1;
+
78 }
+
79 printf("%02d ", rc);
+
80 }
+
81 printf("\n");
+
82 }
+
83 return 0;
+
84}
+
+ + + + diff --git a/doc/html/example_2printcap_8c.html b/doc/html/example_2printcap_8c.html new file mode 100644 index 0000000..1ca99fc --- /dev/null +++ b/doc/html/example_2printcap_8c.html @@ -0,0 +1,240 @@ + + + + + + + +libfuse: example/printcap.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
printcap.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem that prints out all capabilities supported by the kernel and then exits.

+

Compile with:

gcc -Wall printcap.c `pkg-config fuse3 --cflags --libs` -o printcap
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <unistd.h>
+
#include <string.h>
+
#include <stdlib.h>
+
+
struct fuse_session *se;
+
+
// Define a structure to hold capability information
+
struct cap_info {
+
uint64_t flag;
+
const char *name;
+
};
+
+
// Define an array of all capabilities
+
static const struct cap_info capabilities[] = {
+
{FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
+
{FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
+
{FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
+
{FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
+
{FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
+
{FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
+
{FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
+
{FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
+
{FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
+
{FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
+
{FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
+
{FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
+
{FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
+
{FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
+
{FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
+
{FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
+
{FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
+
{FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
+
{FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
+
{FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
+
{FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
+
{FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
+
{FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
+
{FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
+
{FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
+
{FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
+
{FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
+
{FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
+
// Add any new capabilities here
+
{0, NULL} // Sentinel to mark the end of the array
+
};
+
+
static void print_capabilities(struct fuse_conn_info *conn)
+
{
+
printf("Capabilities:\n");
+
for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
+
if (fuse_get_feature_flag(conn, cap->flag)) {
+
printf("\t%s\n", cap->name);
+
}
+
}
+
}
+
+
static void pc_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void) userdata;
+
+
printf("Protocol version: %d.%d\n", conn->proto_major,
+
conn->proto_minor);
+
print_capabilities(conn);
+ +
}
+
+
+
static const struct fuse_lowlevel_ops pc_oper = {
+
.init = pc_init,
+
};
+
+
int main(int argc, char **argv)
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
char *mountpoint;
+
int ret = -1;
+
+
mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
+
if(mkdtemp(mountpoint) == NULL) {
+
perror("mkdtemp");
+
return 1;
+
}
+
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
+
se = fuse_session_new(&args, &pc_oper,
+
sizeof(pc_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, mountpoint) != 0)
+
goto err_out3;
+
+
ret = fuse_session_loop(se);
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
rmdir(mountpoint);
+
free(mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t proto_major
+
uint32_t proto_minor
+ +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file printcap.c.

+
+ + + + diff --git a/doc/html/example_2printcap_8c_source.html b/doc/html/example_2printcap_8c_source.html new file mode 100644 index 0000000..f7485aa --- /dev/null +++ b/doc/html/example_2printcap_8c_source.html @@ -0,0 +1,231 @@ + + + + + + + +libfuse: example/printcap.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
printcap.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
22#define FUSE_USE_VERSION 31
+
23
+
24#include <fuse_lowlevel.h>
+
25#include <stdio.h>
+
26#include <unistd.h>
+
27#include <string.h>
+
28#include <stdlib.h>
+
29
+
30struct fuse_session *se;
+
31
+
32// Define a structure to hold capability information
+
33struct cap_info {
+
34 uint64_t flag;
+
35 const char *name;
+
36};
+
37
+
38// Define an array of all capabilities
+
39static const struct cap_info capabilities[] = {
+
40 {FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
+
41 {FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
+
42 {FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
+
43 {FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
+
44 {FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
+
45 {FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
+
46 {FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
+
47 {FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
+
48 {FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
+
49 {FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
+
50 {FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
+
51 {FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
+
52 {FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
+
53 {FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
+
54 {FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
+
55 {FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
+
56 {FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
+
57 {FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
+
58 {FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
+
59 {FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
+
60 {FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
+
61 {FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
+
62 {FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
+
63 {FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
+
64 {FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
+
65 {FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
+
66 {FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
+
67 {FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
+
68 // Add any new capabilities here
+
69 {0, NULL} // Sentinel to mark the end of the array
+
70};
+
71
+
72static void print_capabilities(struct fuse_conn_info *conn)
+
73{
+
74 printf("Capabilities:\n");
+
75 for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
+
76 if (fuse_get_feature_flag(conn, cap->flag)) {
+
77 printf("\t%s\n", cap->name);
+
78 }
+
79 }
+
80}
+
81
+
82static void pc_init(void *userdata, struct fuse_conn_info *conn)
+
83{
+
84 (void) userdata;
+
85
+
86 printf("Protocol version: %d.%d\n", conn->proto_major,
+
87 conn->proto_minor);
+
88 print_capabilities(conn);
+ +
90}
+
91
+
92
+
93static const struct fuse_lowlevel_ops pc_oper = {
+
94 .init = pc_init,
+
95};
+
96
+
97int main(int argc, char **argv)
+
98{
+
99 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
100 char *mountpoint;
+
101 int ret = -1;
+
102
+
103 mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
+
104 if(mkdtemp(mountpoint) == NULL) {
+
105 perror("mkdtemp");
+
106 return 1;
+
107 }
+
108
+
109 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
111
+
112 se = fuse_session_new(&args, &pc_oper,
+
113 sizeof(pc_oper), NULL);
+
114 if (se == NULL)
+
115 goto err_out1;
+
116
+
117 if (fuse_set_signal_handlers(se) != 0)
+
118 goto err_out2;
+
119
+
120 if (fuse_session_mount(se, mountpoint) != 0)
+
121 goto err_out3;
+
122
+
123 ret = fuse_session_loop(se);
+
124
+ +
126err_out3:
+ +
128err_out2:
+ +
130err_out1:
+
131 rmdir(mountpoint);
+
132 free(mountpoint);
+
133 fuse_opt_free_args(&args);
+
134
+
135 return ret ? 1 : 0;
+
136}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t proto_major
+
uint32_t proto_minor
+ +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/fast17-vangoor.pdf b/doc/html/fast17-vangoor.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cef723762933f15b9b179794c7335574b96fa62f GIT binary patch literal 599157 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

fzRwn`Q%HHe<+Scq0Sk9mwO=g>hmrEO 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

spbq1Go#6A)= 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

A@uBNDhJn$S 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�$W;Igw!{^1IDv+^Yn!e9 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 z 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 HHl_R@i@NEa literal 0 HcmV?d00001 diff --git a/doc/html/files.html b/doc/html/files.html new file mode 100644 index 0000000..cda8a0b --- /dev/null +++ b/doc/html/files.html @@ -0,0 +1,255 @@ + + + + + + + +libfuse: File List + + + + + + +

+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
File List
+
+
+
Here is a list of all documented files with brief descriptions:
+
[detail level 1234]
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
  example
 cuse.c
 cuse_client.c
 hello.c
 hello_ll.c
 hello_ll_uds.c
 invalidate_path.c
 ioctl.c
 ioctl.h
 ioctl_client.c
 notify_inval_entry.c
 notify_inval_inode.c
 notify_store_retrieve.c
 null.c
 passthrough.c
 passthrough_fh.c
 passthrough_helpers.h
 passthrough_ll.c
 poll.c
 poll_client.c
 printcap.c
  fuse-3.17.4
  example
  include
  lib
  test
  util
  fuse-3.18.1
  example
  include
  lib
  test
  util
  include
 cuse_lowlevel.h
 fuse.h
 fuse_common.h
 fuse_kernel.h
 fuse_log.h
 fuse_lowlevel.h
 fuse_mount_compat.h
 fuse_opt.h
  lib
  modules
 buffer.c
 compat.c
 cuse_lowlevel.c
 fuse.c
 fuse_i.h
 fuse_log.c
 fuse_loop.c
 fuse_loop_mt.c
 fuse_lowlevel.c
 fuse_misc.h
 fuse_opt.c
 fuse_signals.c
 fuse_uring.c
 fuse_uring_i.h
 helper.c
 mount.c
 mount_bsd.c
 mount_util.c
 mount_util.h
 usdt.h
 util.c
 util.h
  test
 hello.c
 readdir_inode.c
 release_unlink_race.c
 stracedecode.c
 test_abi.c
 test_setattr.c
 test_signals.c
 test_syscalls.c
 test_want_conversion.c
 test_write_cache.c
 wrong_command.c
  util
 fusermount.c
 mount.fuse.c
+
+
+ +
+ + diff --git a/doc/html/folderclosed.svg b/doc/html/folderclosed.svg new file mode 100644 index 0000000..b04bed2 --- /dev/null +++ b/doc/html/folderclosed.svg @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/doc/html/folderclosedd.svg b/doc/html/folderclosedd.svg new file mode 100644 index 0000000..52f0166 --- /dev/null +++ b/doc/html/folderclosedd.svg @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/doc/html/folderopen.svg b/doc/html/folderopen.svg new file mode 100644 index 0000000..f6896dd --- /dev/null +++ b/doc/html/folderopen.svg @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/doc/html/folderopend.svg b/doc/html/folderopend.svg new file mode 100644 index 0000000..2d1f06e --- /dev/null +++ b/doc/html/folderopend.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/doc/html/functions.html b/doc/html/functions.html new file mode 100644 index 0000000..78b3302 --- /dev/null +++ b/doc/html/functions.html @@ -0,0 +1,258 @@ + + + + + + + +libfuse: Data Fields + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Here is a list of all documented struct and union fields with links to the struct/union documentation for each field:
+ +

- a -

+ + +

- b -

+ + +

- c -

+ + +

- d -

+ + +

- e -

+ + +

- f -

+ + +

- g -

+ + +

- h -

+ + +

- i -

+ + +

- k -

+ + +

- l -

+ + +

- m -

+ + +

- n -

+ + +

- o -

+ + +

- p -

+ + +

- r -

+ + +

- s -

+ + +

- t -

+ + +

- u -

+ + +

- v -

+ + +

- w -

+
+ + + + diff --git a/doc/html/functions_vars.html b/doc/html/functions_vars.html new file mode 100644 index 0000000..497893e --- /dev/null +++ b/doc/html/functions_vars.html @@ -0,0 +1,258 @@ + + + + + + + +libfuse: Data Fields - Variables + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Here is a list of all documented variables with links to the struct/union documentation for each field:
+ +

- a -

+ + +

- b -

+ + +

- c -

+ + +

- d -

+ + +

- e -

+ + +

- f -

+ + +

- g -

+ + +

- h -

+ + +

- i -

+ + +

- k -

+ + +

- l -

+ + +

- m -

+ + +

- n -

+ + +

- o -

+ + +

- p -

+ + +

- r -

+ + +

- s -

+ + +

- t -

+ + +

- u -

+ + +

- v -

+ + +

- w -

+
+ + + + diff --git a/doc/html/fuse-3_817_84_2example_2notify__store__retrieve_8c.html b/doc/html/fuse-3_817_84_2example_2notify__store__retrieve_8c.html new file mode 100644 index 0000000..27c0176 --- /dev/null +++ b/doc/html/fuse-3_817_84_2example_2notify__store__retrieve_8c.html @@ -0,0 +1,560 @@ + + + + + + + +libfuse: fuse-3.17.4/example/notify_store_retrieve.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_store_retrieve.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

+

While notify_inval_inode.c uses fuse_lowlevel_notify_inval_inode() to let the kernel know that it has to invalidate the cache, this example actively pushes the updated data into the kernel cache using fuse_lowlevel_notify_store().

+

To see the effect, first start the file system with the --no-notify option:

$ notify_store_retrieve --update-interval=1 --no-notify mnt/
+

Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+

If you instead enable the notification functions, the changes become visible:

$ notify_store_retrieve --update-interval=1 mnt/
+$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:40
+The current time is 15:58:41
+The current time is 15:58:42
+The current time is 15:58:43
+The current time is 15:58:44
+

+Compilation

+
gcc -Wall notify_store_retrieve.c `pkg-config fuse3 --cflags --libs` -o notify_store_retrieve
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
#include <stdbool.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define FILE_INO 2
+
#define FILE_NAME "current_time"
+
static char file_contents[MAX_STR_LEN];
+
static int lookup_cnt = 0;
+
static int open_cnt = 0;
+
static size_t file_size;
+
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
+
/* Keep track if we ever stored data (==1), and
+
received it back correctly (==2) */
+
static int retrieve_status = 0;
+
+
static bool is_umount = false;
+
+
/* updater thread tid */
+
static pthread_t updater;
+
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == FILE_INO) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = file_size;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, FILE_NAME) == 0) {
+
e.ino = FILE_INO;
+
} else
+
goto err_out;
+
+
e.attr_timeout = NO_TIMEOUT;
+
e.entry_timeout = NO_TIMEOUT;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
+
/*
+
* must only be set when the kernel knows about the entry,
+
* otherwise update_fs_loop() might see a positive count, but kernel
+
* would not have the entry yet
+
*/
+
if (e.ino == FILE_INO) {
+
pthread_mutex_lock(&lock);
+
lookup_cnt++;
+
pthread_mutex_unlock(&lock);
+
}
+
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == FILE_INO) {
+
pthread_mutex_lock(&lock);
+
lookup_cnt -= nlookup;
+
pthread_mutex_unlock(&lock);
+
} else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
+
if (ino == FUSE_ROOT_ID)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else if (ino == FILE_INO) {
+
fuse_reply_open(req, fi);
+
pthread_mutex_lock(&lock);
+
open_cnt++;
+
pthread_mutex_unlock(&lock);
+
} else {
+
// This should not happen
+
fprintf(stderr, "Got open for non-existing inode!\n");
+
fuse_reply_err(req, ENOENT);
+
}
+
}
+
+
static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
assert(ino == FILE_INO);
+
reply_buf_limited(req, file_contents, file_size, off, size);
+
}
+
+
static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
+
off_t offset, struct fuse_bufvec *data) {
+
struct fuse_bufvec bufv;
+
char buf[MAX_STR_LEN];
+
char *expected;
+
ssize_t ret;
+
+
assert(ino == FILE_INO);
+
assert(offset == 0);
+
expected = (char*) cookie;
+
+
bufv.count = 1;
+
bufv.idx = 0;
+
bufv.off = 0;
+
bufv.buf[0].size = MAX_STR_LEN;
+
bufv.buf[0].mem = buf;
+
bufv.buf[0].flags = 0;
+
+
ret = fuse_buf_copy(&bufv, data, 0);
+
assert(ret > 0);
+
assert(strncmp(buf, expected, ret) == 0);
+
free(expected);
+
retrieve_status = 2;
+ +
}
+
+
static void tfs_destroy(void *userdata)
+
{
+
(void)userdata;
+
+
is_umount = true;
+
+
pthread_join(updater, NULL);
+
}
+
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.open = tfs_open,
+
.read = tfs_read,
+
.forget = tfs_forget,
+
.retrieve_reply = tfs_retrieve_reply,
+
.destroy = tfs_destroy,
+
};
+
+
static void update_fs(void) {
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
file_size = strftime(file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(file_size != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
struct fuse_bufvec bufv;
+
int ret;
+
+
while(!is_umount) {
+
update_fs();
+
pthread_mutex_lock(&lock);
+
if (!options.no_notify && open_cnt && lookup_cnt) {
+
/* Only send notification if the kernel
+
is aware of the inode */
+
bufv.count = 1;
+
bufv.idx = 0;
+
bufv.off = 0;
+
bufv.buf[0].size = file_size;
+
bufv.buf[0].mem = file_contents;
+
bufv.buf[0].flags = 0;
+
+
/*
+
* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
* might come up during umount, when kernel side already releases
+
* all inodes, but does not send FUSE_DESTROY yet.
+
*/
+
+
ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
+
if ((ret != 0 && !is_umount) &&
+
ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
fprintf(stderr,
+
"ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
strerror(-ret), -ret);
+
abort();
+
}
+
+
/* To make sure that everything worked correctly, ask the
+
kernel to send us back the stored data */
+
ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
+
0, (void*) strdup(file_contents));
+
assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF ||
+
ret != -ENODEV);
+
if(retrieve_status == 0)
+
retrieve_status = 1;
+
}
+
pthread_mutex_unlock(&lock);
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+
assert(retrieve_status != 1);
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_store_retrieve.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_84_2example_2notify__store__retrieve_8c_source.html b/doc/html/fuse-3_817_84_2example_2notify__store__retrieve_8c_source.html new file mode 100644 index 0000000..570a2c3 --- /dev/null +++ b/doc/html/fuse-3_817_84_2example_2notify__store__retrieve_8c_source.html @@ -0,0 +1,522 @@ + + + + + + + +libfuse: fuse-3.17.4/example/notify_store_retrieve.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_store_retrieve.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
61#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
62
+
63#include <fuse_lowlevel.h>
+
64#include <stdio.h>
+
65#include <stdlib.h>
+
66#include <string.h>
+
67#include <errno.h>
+
68#include <fcntl.h>
+
69#include <assert.h>
+
70#include <stddef.h>
+
71#include <unistd.h>
+
72#include <pthread.h>
+
73#include <stdbool.h>
+
74
+
75/* We can't actually tell the kernel that there is no
+
76 timeout, so we just send a big value */
+
77#define NO_TIMEOUT 500000
+
78
+
79#define MAX_STR_LEN 128
+
80#define FILE_INO 2
+
81#define FILE_NAME "current_time"
+
82static char file_contents[MAX_STR_LEN];
+
83static int lookup_cnt = 0;
+
84static int open_cnt = 0;
+
85static size_t file_size;
+
86static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
87
+
88/* Keep track if we ever stored data (==1), and
+
89 received it back correctly (==2) */
+
90static int retrieve_status = 0;
+
91
+
92static bool is_umount = false;
+
93
+
94/* updater thread tid */
+
95static pthread_t updater;
+
96
+
97
+
98/* Command line parsing */
+
99struct options {
+
100 int no_notify;
+
101 int update_interval;
+
102};
+
103static struct options options = {
+
104 .no_notify = 0,
+
105 .update_interval = 1,
+
106};
+
107
+
108#define OPTION(t, p) \
+
109 { t, offsetof(struct options, p), 1 }
+
110static const struct fuse_opt option_spec[] = {
+
111 OPTION("--no-notify", no_notify),
+
112 OPTION("--update-interval=%d", update_interval),
+ +
114};
+
115
+
116static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
117 stbuf->st_ino = ino;
+
118 if (ino == FUSE_ROOT_ID) {
+
119 stbuf->st_mode = S_IFDIR | 0755;
+
120 stbuf->st_nlink = 1;
+
121 }
+
122
+
123 else if (ino == FILE_INO) {
+
124 stbuf->st_mode = S_IFREG | 0444;
+
125 stbuf->st_nlink = 1;
+
126 stbuf->st_size = file_size;
+
127 }
+
128
+
129 else
+
130 return -1;
+
131
+
132 return 0;
+
133}
+
134
+
135static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
136 (void)userdata;
+
137
+
138 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
139 conn->no_interrupt = 1;
+
140}
+
141
+
142static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
143 const char *name) {
+
144 struct fuse_entry_param e;
+
145 memset(&e, 0, sizeof(e));
+
146
+
147 if (parent != FUSE_ROOT_ID)
+
148 goto err_out;
+
149 else if (strcmp(name, FILE_NAME) == 0) {
+
150 e.ino = FILE_INO;
+
151 } else
+
152 goto err_out;
+
153
+
154 e.attr_timeout = NO_TIMEOUT;
+
155 e.entry_timeout = NO_TIMEOUT;
+
156 if (tfs_stat(e.ino, &e.attr) != 0)
+
157 goto err_out;
+
158 fuse_reply_entry(req, &e);
+
159
+
160 /*
+
161 * must only be set when the kernel knows about the entry,
+
162 * otherwise update_fs_loop() might see a positive count, but kernel
+
163 * would not have the entry yet
+
164 */
+
165 if (e.ino == FILE_INO) {
+
166 pthread_mutex_lock(&lock);
+
167 lookup_cnt++;
+
168 pthread_mutex_unlock(&lock);
+
169 }
+
170
+
171 return;
+
172
+
173err_out:
+
174 fuse_reply_err(req, ENOENT);
+
175}
+
176
+
177static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
178 uint64_t nlookup) {
+
179 (void) req;
+
180 if(ino == FILE_INO) {
+
181 pthread_mutex_lock(&lock);
+
182 lookup_cnt -= nlookup;
+
183 pthread_mutex_unlock(&lock);
+
184 } else
+
185 assert(ino == FUSE_ROOT_ID);
+
186 fuse_reply_none(req);
+
187}
+
188
+
189static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
190 struct fuse_file_info *fi) {
+
191 struct stat stbuf;
+
192
+
193 (void) fi;
+
194
+
195 memset(&stbuf, 0, sizeof(stbuf));
+
196 if (tfs_stat(ino, &stbuf) != 0)
+
197 fuse_reply_err(req, ENOENT);
+
198 else
+
199 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
200}
+
201
+
202struct dirbuf {
+
203 char *p;
+
204 size_t size;
+
205};
+
206
+
207static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
208 fuse_ino_t ino) {
+
209 struct stat stbuf;
+
210 size_t oldsize = b->size;
+
211 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
212 b->p = (char *) realloc(b->p, b->size);
+
213 memset(&stbuf, 0, sizeof(stbuf));
+
214 stbuf.st_ino = ino;
+
215 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
216 b->size);
+
217}
+
218
+
219#define min(x, y) ((x) < (y) ? (x) : (y))
+
220
+
221static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
222 off_t off, size_t maxsize) {
+
223 if (off < bufsize)
+
224 return fuse_reply_buf(req, buf + off,
+
225 min(bufsize - off, maxsize));
+
226 else
+
227 return fuse_reply_buf(req, NULL, 0);
+
228}
+
229
+
230static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
231 off_t off, struct fuse_file_info *fi) {
+
232 (void) fi;
+
233
+
234 if (ino != FUSE_ROOT_ID)
+
235 fuse_reply_err(req, ENOTDIR);
+
236 else {
+
237 struct dirbuf b;
+
238
+
239 memset(&b, 0, sizeof(b));
+
240 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
241 reply_buf_limited(req, b.p, b.size, off, size);
+
242 free(b.p);
+
243 }
+
244}
+
245
+
246static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
247 struct fuse_file_info *fi) {
+
248
+
249 /* Make cache persistent even if file is closed,
+
250 this makes it easier to see the effects */
+
251 fi->keep_cache = 1;
+
252
+
253 if (ino == FUSE_ROOT_ID)
+
254 fuse_reply_err(req, EISDIR);
+
255 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
256 fuse_reply_err(req, EACCES);
+
257 else if (ino == FILE_INO) {
+
258 fuse_reply_open(req, fi);
+
259 pthread_mutex_lock(&lock);
+
260 open_cnt++;
+
261 pthread_mutex_unlock(&lock);
+
262 } else {
+
263 // This should not happen
+
264 fprintf(stderr, "Got open for non-existing inode!\n");
+
265 fuse_reply_err(req, ENOENT);
+
266 }
+
267}
+
268
+
269static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
270 off_t off, struct fuse_file_info *fi) {
+
271 (void) fi;
+
272
+
273 assert(ino == FILE_INO);
+
274 reply_buf_limited(req, file_contents, file_size, off, size);
+
275}
+
276
+
277static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
+
278 off_t offset, struct fuse_bufvec *data) {
+
279 struct fuse_bufvec bufv;
+
280 char buf[MAX_STR_LEN];
+
281 char *expected;
+
282 ssize_t ret;
+
283
+
284 assert(ino == FILE_INO);
+
285 assert(offset == 0);
+
286 expected = (char*) cookie;
+
287
+
288 bufv.count = 1;
+
289 bufv.idx = 0;
+
290 bufv.off = 0;
+
291 bufv.buf[0].size = MAX_STR_LEN;
+
292 bufv.buf[0].mem = buf;
+
293 bufv.buf[0].flags = 0;
+
294
+
295 ret = fuse_buf_copy(&bufv, data, 0);
+
296 assert(ret > 0);
+
297 assert(strncmp(buf, expected, ret) == 0);
+
298 free(expected);
+
299 retrieve_status = 2;
+
300 fuse_reply_none(req);
+
301}
+
302
+
303static void tfs_destroy(void *userdata)
+
304{
+
305 (void)userdata;
+
306
+
307 is_umount = true;
+
308
+
309 pthread_join(updater, NULL);
+
310}
+
311
+
312
+
313static const struct fuse_lowlevel_ops tfs_oper = {
+
314 .init = tfs_init,
+
315 .lookup = tfs_lookup,
+
316 .getattr = tfs_getattr,
+
317 .readdir = tfs_readdir,
+
318 .open = tfs_open,
+
319 .read = tfs_read,
+
320 .forget = tfs_forget,
+
321 .retrieve_reply = tfs_retrieve_reply,
+
322 .destroy = tfs_destroy,
+
323};
+
324
+
325static void update_fs(void) {
+
326 struct tm *now;
+
327 time_t t;
+
328 t = time(NULL);
+
329 now = localtime(&t);
+
330 assert(now != NULL);
+
331
+
332 file_size = strftime(file_contents, MAX_STR_LEN,
+
333 "The current time is %H:%M:%S\n", now);
+
334 assert(file_size != 0);
+
335}
+
336
+
337static void* update_fs_loop(void *data) {
+
338 struct fuse_session *se = (struct fuse_session*) data;
+
339 struct fuse_bufvec bufv;
+
340 int ret;
+
341
+
342 while(!is_umount) {
+
343 update_fs();
+
344 pthread_mutex_lock(&lock);
+
345 if (!options.no_notify && open_cnt && lookup_cnt) {
+
346 /* Only send notification if the kernel
+
347 is aware of the inode */
+
348 bufv.count = 1;
+
349 bufv.idx = 0;
+
350 bufv.off = 0;
+
351 bufv.buf[0].size = file_size;
+
352 bufv.buf[0].mem = file_contents;
+
353 bufv.buf[0].flags = 0;
+
354
+
355 /*
+
356 * Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
357 * might come up during umount, when kernel side already releases
+
358 * all inodes, but does not send FUSE_DESTROY yet.
+
359 */
+
360
+
361 ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
+
362 if ((ret != 0 && !is_umount) &&
+
363 ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
364 fprintf(stderr,
+
365 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
366 strerror(-ret), -ret);
+
367 abort();
+
368 }
+
369
+
370 /* To make sure that everything worked correctly, ask the
+
371 kernel to send us back the stored data */
+
372 ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
+
373 0, (void*) strdup(file_contents));
+
374 assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF ||
+
375 ret != -ENODEV);
+
376 if(retrieve_status == 0)
+
377 retrieve_status = 1;
+
378 }
+
379 pthread_mutex_unlock(&lock);
+
380 sleep(options.update_interval);
+
381 }
+
382 return NULL;
+
383}
+
384
+
385static void show_help(const char *progname)
+
386{
+
387 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
388 printf("File-system specific options:\n"
+
389 " --update-interval=<secs> Update-rate of file system contents\n"
+
390 " --no-notify Disable kernel notifications\n"
+
391 "\n");
+
392}
+
393
+
394int main(int argc, char *argv[]) {
+
395 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
396 struct fuse_session *se;
+
397 struct fuse_cmdline_opts opts;
+
398 struct fuse_loop_config *config;
+
399 int ret = -1;
+
400
+
401 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
402 return 1;
+
403
+
404 if (fuse_parse_cmdline(&args, &opts) != 0)
+
405 return 1;
+
406 if (opts.show_help) {
+
407 show_help(argv[0]);
+ + +
410 ret = 0;
+
411 goto err_out1;
+
412 } else if (opts.show_version) {
+
413 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
415 ret = 0;
+
416 goto err_out1;
+
417 }
+
418
+
419 /* Initial contents */
+
420 update_fs();
+
421
+
422 se = fuse_session_new(&args, &tfs_oper,
+
423 sizeof(tfs_oper), NULL);
+
424 if (se == NULL)
+
425 goto err_out1;
+
426
+
427 if (fuse_set_signal_handlers(se) != 0)
+
428 goto err_out2;
+
429
+
430 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
431 goto err_out3;
+
432
+
433 fuse_daemonize(opts.foreground);
+
434
+
435 /* Start thread to update file contents */
+
436 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
437 if (ret != 0) {
+
438 fprintf(stderr, "pthread_create failed with %s\n",
+
439 strerror(ret));
+
440 goto err_out3;
+
441 }
+
442
+
443 /* Block until ctrl+c or fusermount -u */
+
444 if (opts.singlethread)
+
445 ret = fuse_session_loop(se);
+
446 else {
+
447 config = fuse_loop_cfg_create();
+
448 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
449 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
450 ret = fuse_session_loop_mt(se, config);
+
451 fuse_loop_cfg_destroy(config);
+
452 config = NULL;
+
453 }
+
454
+
455 assert(retrieve_status != 1);
+ +
457err_out3:
+ +
459err_out2:
+ +
461err_out1:
+
462 free(opts.mountpoint);
+
463 fuse_opt_free_args(&args);
+
464
+
465 return ret ? 1 : 0;
+
466}
+
467
+
468
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_84_2example_2null_8c.html b/doc/html/fuse-3_817_84_2example_2null_8c.html new file mode 100644 index 0000000..61464e7 --- /dev/null +++ b/doc/html/fuse-3_817_84_2example_2null_8c.html @@ -0,0 +1,779 @@ + + + + + + + +libfuse: fuse-3.17.4/example/null.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
null.c File Reference
+
+
+
#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This "filesystem" provides only a single file. The mountpoint needs to be a file rather than a directory. All writes to the file will be discarded, and reading the file always returns \0.

+

Compile with:

gcc -Wall null.c `pkg-config fuse3 --cflags --libs` -o null
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
+
#define _GNU_SOURCE
+
+
#include <fuse.h>
+
+
#ifdef HAVE_LIBULOCKMGR
+
#include <ulockmgr.h>
+
#endif
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
#include <sys/file.h> /* flock(2) */
+
+
#include "passthrough_helpers.h"
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
cfg->nullpath_ok = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
+
if(fi)
+
res = fstat(fi->fh, stbuf);
+
else
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
struct xmp_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
if (d == NULL)
+
return -ENOMEM;
+
+
d->dp = opendir(path);
+
if (d->dp == NULL) {
+
res = -errno;
+
free(d);
+
return res;
+
}
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (unsigned long) d;
+
return 0;
+
}
+
+
static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
{
+
return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
+
(void) path;
+
if (offset != d->offset) {
+
#ifndef __FreeBSD__
+
seekdir(d->dp, offset);
+
#else
+
/* Subtract the one that we add when calling
+
telldir() below */
+
seekdir(d->dp, offset-1);
+
#endif
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
struct stat st;
+
off_t nextoff;
+ +
+
if (!d->entry) {
+
d->entry = readdir(d->dp);
+
if (!d->entry)
+
break;
+
}
+
#ifdef HAVE_FSTATAT
+
if (flags & FUSE_READDIR_PLUS) {
+
int res;
+
+
res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
if (res != -1)
+
fill_flags |= FUSE_FILL_DIR_PLUS;
+
}
+
#endif
+
if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = d->entry->d_ino;
+
st.st_mode = d->entry->d_type << 12;
+
}
+
nextoff = telldir(d->dp);
+
#ifdef __FreeBSD__
+
/* Under FreeBSD, telldir() may return 0 the first time
+
it is called. But for libfuse, an offset of zero
+
means that offsets are not supported, so we shift
+
everything by one. */
+
nextoff++;
+
#endif
+
if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
break;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
return 0;
+
}
+
+
static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
(void) path;
+
closedir(d->dp);
+
free(d);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
if (S_ISFIFO(mode))
+
res = mkfifo(path, mode);
+
else
+
res = mknod(path, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
/* When we have renameat2() in libc, then we can implement flags */
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = fchmod(fi->fh, mode);
+
else
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi)
+
res = fchown(fi->fh, uid, gid);
+
else
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
if (fi)
+
res = futimens(fi->fh, ts);
+
else
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags, mode);
+
if (fd == -1)
+
return -errno;
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags);
+
if (fd == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pread(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
size_t size, off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec *src;
+
+
(void) path;
+
+
src = malloc(sizeof(struct fuse_bufvec));
+
if (src == NULL)
+
return -ENOMEM;
+
+
*src = FUSE_BUFVEC_INIT(size);
+
+ +
src->buf[0].fd = fi->fh;
+
src->buf[0].pos = offset;
+
+
*bufp = src;
+
+
return 0;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pwrite(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
+
(void) path;
+
+ +
dst.buf[0].fd = fi->fh;
+
dst.buf[0].pos = offset;
+
+ +
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
/* This is called from every close on an open file, so call the
+
close on the underlying filesystem. But since flush may be
+
called multiple times for an open file, this must not really
+
close the file. This is important if used on a network
+
filesystem like NFS which flush the data/metadata on close() */
+
res = close(dup(fi->fh));
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) path;
+
+
#ifndef HAVE_FDATASYNC
+
(void) isdatasync;
+
#else
+
if (isdatasync)
+
res = fdatasync(fi->fh);
+
else
+
#endif
+
res = fsync(fi->fh);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
(void) path;
+
+
return do_fallocate(fi->fh, mode, offset, length);
+
}
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_LIBULOCKMGR
+
static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
struct flock *lock)
+
{
+
(void) path;
+
+
return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
sizeof(fi->lock_owner));
+
}
+
#endif
+
+
static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
{
+
int res;
+
(void) path;
+
+
res = flock(fi->fh, op);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t off_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t off_out, size_t len, int flags)
+
{
+
ssize_t res;
+
(void) path_in;
+
(void) path_out;
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
off_t res;
+
(void) path;
+
+
res = lseek(fi->fh, off, whence);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
+
#ifdef HAVE_STATX
+
static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
struct fuse_file_info *fi)
+
{
+
int fd = -1;
+
int res;
+
+
if (fi)
+
fd = fi->fh;
+
+
res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.opendir = xmp_opendir,
+
.readdir = xmp_readdir,
+
.releasedir = xmp_releasedir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.create = xmp_create,
+
.open = xmp_open,
+
.read = xmp_read,
+
.read_buf = xmp_read_buf,
+
.write = xmp_write,
+
.write_buf = xmp_write_buf,
+
.statfs = xmp_statfs,
+
.flush = xmp_flush,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
.fallocate = xmp_fallocate,
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_LIBULOCKMGR
+
.lock = xmp_lock,
+
#endif
+
.flock = xmp_flock,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
#ifdef HAVE_STATX
+
.statx = xmp_statx,
+
#endif
+
};
+
+
int main(int argc, char *argv[])
+
{
+
umask(0);
+
return fuse_main(argc, argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file null.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_84_2example_2null_8c_source.html b/doc/html/fuse-3_817_84_2example_2null_8c_source.html new file mode 100644 index 0000000..c75fc73 --- /dev/null +++ b/doc/html/fuse-3_817_84_2example_2null_8c_source.html @@ -0,0 +1,196 @@ + + + + + + + +libfuse: fuse-3.17.4/example/null.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
null.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
25#define FUSE_USE_VERSION 31
+
26
+
27#include <fuse.h>
+
28#include <fuse_lowlevel.h>
+
29#include <stdio.h>
+
30#include <stdlib.h>
+
31#include <string.h>
+
32#include <unistd.h>
+
33#include <time.h>
+
34#include <errno.h>
+
35
+
36static int null_getattr(const char *path, struct stat *stbuf,
+
37 struct fuse_file_info *fi)
+
38{
+
39 (void) fi;
+
40
+
41 if(strcmp(path, "/") != 0)
+
42 return -ENOENT;
+
43
+
44 stbuf->st_mode = S_IFREG | 0644;
+
45 stbuf->st_nlink = 1;
+
46 stbuf->st_uid = getuid();
+
47 stbuf->st_gid = getgid();
+
48 stbuf->st_size = (1ULL << 32); /* 4G */
+
49 stbuf->st_blocks = 0;
+
50 stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = time(NULL);
+
51
+
52 return 0;
+
53}
+
54
+
55static int null_truncate(const char *path, off_t size,
+
56 struct fuse_file_info *fi)
+
57{
+
58 (void) size;
+
59 (void) fi;
+
60
+
61 if(strcmp(path, "/") != 0)
+
62 return -ENOENT;
+
63
+
64 return 0;
+
65}
+
66
+
67static int null_open(const char *path, struct fuse_file_info *fi)
+
68{
+
69 (void) fi;
+
70
+
71 if(strcmp(path, "/") != 0)
+
72 return -ENOENT;
+
73
+
74 return 0;
+
75}
+
76
+
77static int null_read(const char *path, char *buf, size_t size,
+
78 off_t offset, struct fuse_file_info *fi)
+
79{
+
80 (void) buf;
+
81 (void) offset;
+
82 (void) fi;
+
83
+
84 if(strcmp(path, "/") != 0)
+
85 return -ENOENT;
+
86
+
87 if (offset >= (1ULL << 32))
+
88 return 0;
+
89
+
90 memset(buf, 0, size);
+
91 return size;
+
92}
+
93
+
94static int null_write(const char *path, const char *buf, size_t size,
+
95 off_t offset, struct fuse_file_info *fi)
+
96{
+
97 (void) buf;
+
98 (void) offset;
+
99 (void) fi;
+
100
+
101 if(strcmp(path, "/") != 0)
+
102 return -ENOENT;
+
103
+
104 return size;
+
105}
+
106
+
107static const struct fuse_operations null_oper = {
+
108 .getattr = null_getattr,
+
109 .truncate = null_truncate,
+
110 .open = null_open,
+
111 .read = null_read,
+
112 .write = null_write,
+
113};
+
114
+
115int main(int argc, char *argv[])
+
116{
+
117 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
118 struct fuse_cmdline_opts opts;
+
119 struct stat stbuf;
+
120
+
121 if (fuse_parse_cmdline(&args, &opts) != 0)
+
122 return 1;
+
123 fuse_opt_free_args(&args);
+
124
+
125 if (!opts.mountpoint) {
+
126 fprintf(stderr, "missing mountpoint parameter\n");
+
127 return 1;
+
128 }
+
129
+
130 if (stat(opts.mountpoint, &stbuf) == -1) {
+
131 fprintf(stderr ,"failed to access mountpoint %s: %s\n",
+
132 opts.mountpoint, strerror(errno));
+
133 free(opts.mountpoint);
+
134 return 1;
+
135 }
+
136 free(opts.mountpoint);
+
137 if (!S_ISREG(stbuf.st_mode)) {
+
138 fprintf(stderr, "mountpoint is not a regular file\n");
+
139 return 1;
+
140 }
+
141
+
142 return fuse_main(argc, argv, &null_oper, NULL);
+
143}
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2example_2passthrough_8c.html b/doc/html/fuse-3_817_84_2example_2passthrough_8c.html new file mode 100644 index 0000000..435e6bd --- /dev/null +++ b/doc/html/fuse-3_817_84_2example_2passthrough_8c.html @@ -0,0 +1,679 @@ + + + + + + + +libfuse: fuse-3.17.4/example/passthrough.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. Its performance is terrible.

+

Compile with

gcc -Wall passthrough.c `pkg-config fuse3 --cflags --libs` -o passthrough
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
+
#define _GNU_SOURCE
+
+
#ifdef linux
+
/* For pread()/pwrite()/utimensat() */
+
#define _XOPEN_SOURCE 700
+
#endif
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
+
#include "passthrough_helpers.h"
+
+
static int fill_dir_plus = 0;
+
static int readdir_zero_ino;
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = !readdir_zero_ino;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
if (!cfg->auto_cache) {
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
}
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
DIR *dp;
+
struct dirent *de;
+
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
dp = opendir(path);
+
if (dp == NULL)
+
return -errno;
+
+
while ((de = readdir(dp)) != NULL) {
+
struct stat st;
+
if (fill_dir_plus) {
+
fstatat(dirfd(dp), de->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
} else {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = de->d_ino;
+
st.st_mode = de->d_type << 12;
+
}
+
if (readdir_zero_ino)
+
st.st_ino = 0;
+
if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
+
break;
+
}
+
+
closedir(dp);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi != NULL)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
res = open(path, fi->flags, mode);
+
if (res == -1)
+
return -errno;
+
+
fi->fh = res;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
res = open(path, fi->flags);
+
if (res == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = res;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
if(fi == NULL)
+
fd = open(path, O_RDONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = pread(fd, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
(void) fi;
+
if(fi == NULL)
+
fd = open(path, O_WRONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = pwrite(fd, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
/* Just a stub. This method is optional and can safely be left
+
unimplemented */
+
+
(void) path;
+
(void) isdatasync;
+
(void) fi;
+
return 0;
+
}
+
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
(void) fi;
+
+
if(fi == NULL)
+
fd = open(path, O_WRONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = do_fallocate(fd, mode, offset, length);
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t offset_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t offset_out, size_t len, int flags)
+
{
+
int fd_in, fd_out;
+
ssize_t res;
+
+
if(fi_in == NULL)
+
fd_in = open(path_in, O_RDONLY);
+
else
+
fd_in = fi_in->fh;
+
+
if (fd_in == -1)
+
return -errno;
+
+
if(fi_out == NULL)
+
fd_out = open(path_out, O_WRONLY);
+
else
+
fd_out = fi_out->fh;
+
+
if (fd_out == -1) {
+
close(fd_in);
+
return -errno;
+
}
+
+
res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
+
flags);
+
if (res == -1)
+
res = -errno;
+
+
if (fi_out == NULL)
+
close(fd_out);
+
if (fi_in == NULL)
+
close(fd_in);
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
int fd;
+
off_t res;
+
+
if (fi == NULL)
+
fd = open(path, O_RDONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = lseek(fd, off, whence);
+
if (res == -1)
+
res = -errno;
+
+
if (fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
#ifdef HAVE_STATX
+
static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
struct fuse_file_info *fi)
+
{
+
int fd = -1;
+
int res;
+
+
if (fi)
+
fd = fi->fh;
+
+
res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.readdir = xmp_readdir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.open = xmp_open,
+
.create = xmp_create,
+
.read = xmp_read,
+
.write = xmp_write,
+
.statfs = xmp_statfs,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
.fallocate = xmp_fallocate,
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
#ifdef HAVE_STATX
+
.statx = xmp_statx,
+
#endif
+
};
+
+
int main(int argc, char *argv[])
+
{
+
enum { MAX_ARGS = 10 };
+
int i,new_argc;
+
char *new_argv[MAX_ARGS];
+
+
umask(0);
+
/* Process the "--plus" option apart */
+
for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
+
if (!strcmp(argv[i], "--plus")) {
+
fill_dir_plus = FUSE_FILL_DIR_PLUS;
+
} else if (!strcmp(argv[i], "--readdir-zero-inodes")) {
+
// Return zero inodes from readdir
+
readdir_zero_ino = 1;
+
} else {
+
new_argv[new_argc++] = argv[i];
+
}
+
}
+
return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ +
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t auto_cache
Definition fuse.h:253
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file passthrough.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_84_2example_2passthrough_8c_source.html b/doc/html/fuse-3_817_84_2example_2passthrough_8c_source.html new file mode 100644 index 0000000..54cc23f --- /dev/null +++ b/doc/html/fuse-3_817_84_2example_2passthrough_8c_source.html @@ -0,0 +1,649 @@ + + + + + + + +libfuse: fuse-3.17.4/example/passthrough.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
26#define FUSE_USE_VERSION 31
+
27
+
28#define _GNU_SOURCE
+
29
+
30#ifdef linux
+
31/* For pread()/pwrite()/utimensat() */
+
32#define _XOPEN_SOURCE 700
+
33#endif
+
34
+
35#include <fuse.h>
+
36#include <stdio.h>
+
37#include <string.h>
+
38#include <unistd.h>
+
39#include <fcntl.h>
+
40#include <sys/stat.h>
+
41#include <dirent.h>
+
42#include <errno.h>
+
43#ifdef __FreeBSD__
+
44#include <sys/socket.h>
+
45#include <sys/un.h>
+
46#endif
+
47#include <sys/time.h>
+
48#ifdef HAVE_SETXATTR
+
49#include <sys/xattr.h>
+
50#endif
+
51
+
52#include "passthrough_helpers.h"
+
53
+
54static int fill_dir_plus = 0;
+
55
+
56static void *xmp_init(struct fuse_conn_info *conn,
+
57 struct fuse_config *cfg)
+
58{
+
59 (void) conn;
+
60 cfg->use_ino = 1;
+
61
+
62 /* parallel_direct_writes feature depends on direct_io features.
+
63 To make parallel_direct_writes valid, need either set cfg->direct_io
+
64 in current function (recommended in high level API) or set fi->direct_io
+
65 in xmp_create() or xmp_open(). */
+
66 // cfg->direct_io = 1;
+ +
68
+
69 /* Pick up changes from lower filesystem right away. This is
+
70 also necessary for better hardlink support. When the kernel
+
71 calls the unlink() handler, it does not know the inode of
+
72 the to-be-removed entry and can therefore not invalidate
+
73 the cache of the associated inode - resulting in an
+
74 incorrect st_nlink value being reported for any remaining
+
75 hardlinks to this inode. */
+
76 if (!cfg->auto_cache) {
+
77 cfg->entry_timeout = 0;
+
78 cfg->attr_timeout = 0;
+
79 cfg->negative_timeout = 0;
+
80 }
+
81
+
82 return NULL;
+
83}
+
84
+
85static int xmp_getattr(const char *path, struct stat *stbuf,
+
86 struct fuse_file_info *fi)
+
87{
+
88 (void) fi;
+
89 int res;
+
90
+
91 res = lstat(path, stbuf);
+
92 if (res == -1)
+
93 return -errno;
+
94
+
95 return 0;
+
96}
+
97
+
98static int xmp_access(const char *path, int mask)
+
99{
+
100 int res;
+
101
+
102 res = access(path, mask);
+
103 if (res == -1)
+
104 return -errno;
+
105
+
106 return 0;
+
107}
+
108
+
109static int xmp_readlink(const char *path, char *buf, size_t size)
+
110{
+
111 int res;
+
112
+
113 res = readlink(path, buf, size - 1);
+
114 if (res == -1)
+
115 return -errno;
+
116
+
117 buf[res] = '\0';
+
118 return 0;
+
119}
+
120
+
121
+
122static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
123 off_t offset, struct fuse_file_info *fi,
+
124 enum fuse_readdir_flags flags)
+
125{
+
126 DIR *dp;
+
127 struct dirent *de;
+
128
+
129 (void) offset;
+
130 (void) fi;
+
131 (void) flags;
+
132
+
133 dp = opendir(path);
+
134 if (dp == NULL)
+
135 return -errno;
+
136
+
137 while ((de = readdir(dp)) != NULL) {
+
138 struct stat st;
+
139 if (fill_dir_plus) {
+
140 fstatat(dirfd(dp), de->d_name, &st,
+
141 AT_SYMLINK_NOFOLLOW);
+
142 } else {
+
143 memset(&st, 0, sizeof(st));
+
144 st.st_ino = de->d_ino;
+
145 st.st_mode = de->d_type << 12;
+
146 }
+
147 if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
+
148 break;
+
149 }
+
150
+
151 closedir(dp);
+
152 return 0;
+
153}
+
154
+
155static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
156{
+
157 int res;
+
158
+
159 res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
+
160 if (res == -1)
+
161 return -errno;
+
162
+
163 return 0;
+
164}
+
165
+
166static int xmp_mkdir(const char *path, mode_t mode)
+
167{
+
168 int res;
+
169
+
170 res = mkdir(path, mode);
+
171 if (res == -1)
+
172 return -errno;
+
173
+
174 return 0;
+
175}
+
176
+
177static int xmp_unlink(const char *path)
+
178{
+
179 int res;
+
180
+
181 res = unlink(path);
+
182 if (res == -1)
+
183 return -errno;
+
184
+
185 return 0;
+
186}
+
187
+
188static int xmp_rmdir(const char *path)
+
189{
+
190 int res;
+
191
+
192 res = rmdir(path);
+
193 if (res == -1)
+
194 return -errno;
+
195
+
196 return 0;
+
197}
+
198
+
199static int xmp_symlink(const char *from, const char *to)
+
200{
+
201 int res;
+
202
+
203 res = symlink(from, to);
+
204 if (res == -1)
+
205 return -errno;
+
206
+
207 return 0;
+
208}
+
209
+
210static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
211{
+
212 int res;
+
213
+
214 if (flags)
+
215 return -EINVAL;
+
216
+
217 res = rename(from, to);
+
218 if (res == -1)
+
219 return -errno;
+
220
+
221 return 0;
+
222}
+
223
+
224static int xmp_link(const char *from, const char *to)
+
225{
+
226 int res;
+
227
+
228 res = link(from, to);
+
229 if (res == -1)
+
230 return -errno;
+
231
+
232 return 0;
+
233}
+
234
+
235static int xmp_chmod(const char *path, mode_t mode,
+
236 struct fuse_file_info *fi)
+
237{
+
238 (void) fi;
+
239 int res;
+
240
+
241 res = chmod(path, mode);
+
242 if (res == -1)
+
243 return -errno;
+
244
+
245 return 0;
+
246}
+
247
+
248static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
249 struct fuse_file_info *fi)
+
250{
+
251 (void) fi;
+
252 int res;
+
253
+
254 res = lchown(path, uid, gid);
+
255 if (res == -1)
+
256 return -errno;
+
257
+
258 return 0;
+
259}
+
260
+
261static int xmp_truncate(const char *path, off_t size,
+
262 struct fuse_file_info *fi)
+
263{
+
264 int res;
+
265
+
266 if (fi != NULL)
+
267 res = ftruncate(fi->fh, size);
+
268 else
+
269 res = truncate(path, size);
+
270 if (res == -1)
+
271 return -errno;
+
272
+
273 return 0;
+
274}
+
275
+
276#ifdef HAVE_UTIMENSAT
+
277static int xmp_utimens(const char *path, const struct timespec ts[2],
+
278 struct fuse_file_info *fi)
+
279{
+
280 (void) fi;
+
281 int res;
+
282
+
283 /* don't use utime/utimes since they follow symlinks */
+
284 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
285 if (res == -1)
+
286 return -errno;
+
287
+
288 return 0;
+
289}
+
290#endif
+
291
+
292static int xmp_create(const char *path, mode_t mode,
+
293 struct fuse_file_info *fi)
+
294{
+
295 int res;
+
296
+
297 res = open(path, fi->flags, mode);
+
298 if (res == -1)
+
299 return -errno;
+
300
+
301 fi->fh = res;
+
302 return 0;
+
303}
+
304
+
305static int xmp_open(const char *path, struct fuse_file_info *fi)
+
306{
+
307 int res;
+
308
+
309 res = open(path, fi->flags);
+
310 if (res == -1)
+
311 return -errno;
+
312
+
313 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
314 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
315 for writes to the same file). */
+
316 if (fi->flags & O_DIRECT) {
+
317 fi->direct_io = 1;
+ +
319 }
+
320
+
321 fi->fh = res;
+
322 return 0;
+
323}
+
324
+
325static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
326 struct fuse_file_info *fi)
+
327{
+
328 int fd;
+
329 int res;
+
330
+
331 if(fi == NULL)
+
332 fd = open(path, O_RDONLY);
+
333 else
+
334 fd = fi->fh;
+
335
+
336 if (fd == -1)
+
337 return -errno;
+
338
+
339 res = pread(fd, buf, size, offset);
+
340 if (res == -1)
+
341 res = -errno;
+
342
+
343 if(fi == NULL)
+
344 close(fd);
+
345 return res;
+
346}
+
347
+
348static int xmp_write(const char *path, const char *buf, size_t size,
+
349 off_t offset, struct fuse_file_info *fi)
+
350{
+
351 int fd;
+
352 int res;
+
353
+
354 (void) fi;
+
355 if(fi == NULL)
+
356 fd = open(path, O_WRONLY);
+
357 else
+
358 fd = fi->fh;
+
359
+
360 if (fd == -1)
+
361 return -errno;
+
362
+
363 res = pwrite(fd, buf, size, offset);
+
364 if (res == -1)
+
365 res = -errno;
+
366
+
367 if(fi == NULL)
+
368 close(fd);
+
369 return res;
+
370}
+
371
+
372static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
373{
+
374 int res;
+
375
+
376 res = statvfs(path, stbuf);
+
377 if (res == -1)
+
378 return -errno;
+
379
+
380 return 0;
+
381}
+
382
+
383static int xmp_release(const char *path, struct fuse_file_info *fi)
+
384{
+
385 (void) path;
+
386 close(fi->fh);
+
387 return 0;
+
388}
+
389
+
390static int xmp_fsync(const char *path, int isdatasync,
+
391 struct fuse_file_info *fi)
+
392{
+
393 /* Just a stub. This method is optional and can safely be left
+
394 unimplemented */
+
395
+
396 (void) path;
+
397 (void) isdatasync;
+
398 (void) fi;
+
399 return 0;
+
400}
+
401
+
402#ifdef HAVE_POSIX_FALLOCATE
+
403static int xmp_fallocate(const char *path, int mode,
+
404 off_t offset, off_t length, struct fuse_file_info *fi)
+
405{
+
406 int fd;
+
407 int res;
+
408
+
409 (void) fi;
+
410
+
411 if (mode)
+
412 return -EOPNOTSUPP;
+
413
+
414 if(fi == NULL)
+
415 fd = open(path, O_WRONLY);
+
416 else
+
417 fd = fi->fh;
+
418
+
419 if (fd == -1)
+
420 return -errno;
+
421
+
422 res = -posix_fallocate(fd, offset, length);
+
423
+
424 if(fi == NULL)
+
425 close(fd);
+
426 return res;
+
427}
+
428#endif
+
429
+
430#ifdef HAVE_SETXATTR
+
431/* xattr operations are optional and can safely be left unimplemented */
+
432static int xmp_setxattr(const char *path, const char *name, const char *value,
+
433 size_t size, int flags)
+
434{
+
435 int res = lsetxattr(path, name, value, size, flags);
+
436 if (res == -1)
+
437 return -errno;
+
438 return 0;
+
439}
+
440
+
441static int xmp_getxattr(const char *path, const char *name, char *value,
+
442 size_t size)
+
443{
+
444 int res = lgetxattr(path, name, value, size);
+
445 if (res == -1)
+
446 return -errno;
+
447 return res;
+
448}
+
449
+
450static int xmp_listxattr(const char *path, char *list, size_t size)
+
451{
+
452 int res = llistxattr(path, list, size);
+
453 if (res == -1)
+
454 return -errno;
+
455 return res;
+
456}
+
457
+
458static int xmp_removexattr(const char *path, const char *name)
+
459{
+
460 int res = lremovexattr(path, name);
+
461 if (res == -1)
+
462 return -errno;
+
463 return 0;
+
464}
+
465#endif /* HAVE_SETXATTR */
+
466
+
467#ifdef HAVE_COPY_FILE_RANGE
+
468static ssize_t xmp_copy_file_range(const char *path_in,
+
469 struct fuse_file_info *fi_in,
+
470 off_t offset_in, const char *path_out,
+
471 struct fuse_file_info *fi_out,
+
472 off_t offset_out, size_t len, int flags)
+
473{
+
474 int fd_in, fd_out;
+
475 ssize_t res;
+
476
+
477 if(fi_in == NULL)
+
478 fd_in = open(path_in, O_RDONLY);
+
479 else
+
480 fd_in = fi_in->fh;
+
481
+
482 if (fd_in == -1)
+
483 return -errno;
+
484
+
485 if(fi_out == NULL)
+
486 fd_out = open(path_out, O_WRONLY);
+
487 else
+
488 fd_out = fi_out->fh;
+
489
+
490 if (fd_out == -1) {
+
491 close(fd_in);
+
492 return -errno;
+
493 }
+
494
+
495 res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
+
496 flags);
+
497 if (res == -1)
+
498 res = -errno;
+
499
+
500 if (fi_out == NULL)
+
501 close(fd_out);
+
502 if (fi_in == NULL)
+
503 close(fd_in);
+
504
+
505 return res;
+
506}
+
507#endif
+
508
+
509static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
510{
+
511 int fd;
+
512 off_t res;
+
513
+
514 if (fi == NULL)
+
515 fd = open(path, O_RDONLY);
+
516 else
+
517 fd = fi->fh;
+
518
+
519 if (fd == -1)
+
520 return -errno;
+
521
+
522 res = lseek(fd, off, whence);
+
523 if (res == -1)
+
524 res = -errno;
+
525
+
526 if (fi == NULL)
+
527 close(fd);
+
528 return res;
+
529}
+
530
+
531static const struct fuse_operations xmp_oper = {
+
532 .init = xmp_init,
+
533 .getattr = xmp_getattr,
+
534 .access = xmp_access,
+
535 .readlink = xmp_readlink,
+
536 .readdir = xmp_readdir,
+
537 .mknod = xmp_mknod,
+
538 .mkdir = xmp_mkdir,
+
539 .symlink = xmp_symlink,
+
540 .unlink = xmp_unlink,
+
541 .rmdir = xmp_rmdir,
+
542 .rename = xmp_rename,
+
543 .link = xmp_link,
+
544 .chmod = xmp_chmod,
+
545 .chown = xmp_chown,
+
546 .truncate = xmp_truncate,
+
547#ifdef HAVE_UTIMENSAT
+
548 .utimens = xmp_utimens,
+
549#endif
+
550 .open = xmp_open,
+
551 .create = xmp_create,
+
552 .read = xmp_read,
+
553 .write = xmp_write,
+
554 .statfs = xmp_statfs,
+
555 .release = xmp_release,
+
556 .fsync = xmp_fsync,
+
557#ifdef HAVE_POSIX_FALLOCATE
+
558 .fallocate = xmp_fallocate,
+
559#endif
+
560#ifdef HAVE_SETXATTR
+
561 .setxattr = xmp_setxattr,
+
562 .getxattr = xmp_getxattr,
+
563 .listxattr = xmp_listxattr,
+
564 .removexattr = xmp_removexattr,
+
565#endif
+
566#ifdef HAVE_COPY_FILE_RANGE
+
567 .copy_file_range = xmp_copy_file_range,
+
568#endif
+
569 .lseek = xmp_lseek,
+
570};
+
571
+
572int main(int argc, char *argv[])
+
573{
+
574 enum { MAX_ARGS = 10 };
+
575 int i,new_argc;
+
576 char *new_argv[MAX_ARGS];
+
577
+
578 umask(0);
+
579 /* Process the "--plus" option apart */
+
580 for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
+
581 if (!strcmp(argv[i], "--plus")) {
+
582 fill_dir_plus = FUSE_FILL_DIR_PLUS;
+
583 } else {
+
584 new_argv[new_argc++] = argv[i];
+
585 }
+
586 }
+
587 return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
+
588}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ +
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t auto_cache
Definition fuse.h:253
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2example_2passthrough__fh_8c.html b/doc/html/fuse-3_817_84_2example_2passthrough__fh_8c.html new file mode 100644 index 0000000..202071e --- /dev/null +++ b/doc/html/fuse-3_817_84_2example_2passthrough__fh_8c.html @@ -0,0 +1,782 @@ + + + + + + + +libfuse: fuse-3.17.4/example/passthrough_fh.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough_fh.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/file.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. This implementation is a little more sophisticated than the one in passthrough.c, so performance is not quite as bad.

+

Compile with:

gcc -Wall passthrough_fh.c `pkg-config fuse3 --cflags --libs` -lulockmgr -o passthrough_fh
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
+
#define _GNU_SOURCE
+
+
#include <fuse.h>
+
+
#ifdef HAVE_LIBULOCKMGR
+
#include <ulockmgr.h>
+
#endif
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
#include <sys/file.h> /* flock(2) */
+
+
#include "passthrough_helpers.h"
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
cfg->nullpath_ok = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
+
if(fi)
+
res = fstat(fi->fh, stbuf);
+
else
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
struct xmp_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
if (d == NULL)
+
return -ENOMEM;
+
+
d->dp = opendir(path);
+
if (d->dp == NULL) {
+
res = -errno;
+
free(d);
+
return res;
+
}
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (unsigned long) d;
+
return 0;
+
}
+
+
static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
{
+
return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
+
(void) path;
+
if (offset != d->offset) {
+
#ifndef __FreeBSD__
+
seekdir(d->dp, offset);
+
#else
+
/* Subtract the one that we add when calling
+
telldir() below */
+
seekdir(d->dp, offset-1);
+
#endif
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
struct stat st;
+
off_t nextoff;
+ +
+
if (!d->entry) {
+
d->entry = readdir(d->dp);
+
if (!d->entry)
+
break;
+
}
+
#ifdef HAVE_FSTATAT
+
if (flags & FUSE_READDIR_PLUS) {
+
int res;
+
+
res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
if (res != -1)
+
fill_flags |= FUSE_FILL_DIR_PLUS;
+
}
+
#endif
+
if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = d->entry->d_ino;
+
st.st_mode = d->entry->d_type << 12;
+
}
+
nextoff = telldir(d->dp);
+
#ifdef __FreeBSD__
+
/* Under FreeBSD, telldir() may return 0 the first time
+
it is called. But for libfuse, an offset of zero
+
means that offsets are not supported, so we shift
+
everything by one. */
+
nextoff++;
+
#endif
+
if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
break;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
return 0;
+
}
+
+
static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
(void) path;
+
closedir(d->dp);
+
free(d);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
if (S_ISFIFO(mode))
+
res = mkfifo(path, mode);
+
else
+
res = mknod(path, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
/* When we have renameat2() in libc, then we can implement flags */
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = fchmod(fi->fh, mode);
+
else
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi)
+
res = fchown(fi->fh, uid, gid);
+
else
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
if (fi)
+
res = futimens(fi->fh, ts);
+
else
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags, mode);
+
if (fd == -1)
+
return -errno;
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags);
+
if (fd == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pread(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
size_t size, off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec *src;
+
+
(void) path;
+
+
src = malloc(sizeof(struct fuse_bufvec));
+
if (src == NULL)
+
return -ENOMEM;
+
+
*src = FUSE_BUFVEC_INIT(size);
+
+ +
src->buf[0].fd = fi->fh;
+
src->buf[0].pos = offset;
+
+
*bufp = src;
+
+
return 0;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pwrite(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
+
(void) path;
+
+ +
dst.buf[0].fd = fi->fh;
+
dst.buf[0].pos = offset;
+
+ +
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
/* This is called from every close on an open file, so call the
+
close on the underlying filesystem. But since flush may be
+
called multiple times for an open file, this must not really
+
close the file. This is important if used on a network
+
filesystem like NFS which flush the data/metadata on close() */
+
res = close(dup(fi->fh));
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) path;
+
+
#ifndef HAVE_FDATASYNC
+
(void) isdatasync;
+
#else
+
if (isdatasync)
+
res = fdatasync(fi->fh);
+
else
+
#endif
+
res = fsync(fi->fh);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
(void) path;
+
+
return do_fallocate(fi->fh, mode, offset, length);
+
}
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_LIBULOCKMGR
+
static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
struct flock *lock)
+
{
+
(void) path;
+
+
return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
sizeof(fi->lock_owner));
+
}
+
#endif
+
+
static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
{
+
int res;
+
(void) path;
+
+
res = flock(fi->fh, op);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t off_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t off_out, size_t len, int flags)
+
{
+
ssize_t res;
+
(void) path_in;
+
(void) path_out;
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
off_t res;
+
(void) path;
+
+
res = lseek(fi->fh, off, whence);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
+
#ifdef HAVE_STATX
+
static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
struct fuse_file_info *fi)
+
{
+
int fd = -1;
+
int res;
+
+
if (fi)
+
fd = fi->fh;
+
+
res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.opendir = xmp_opendir,
+
.readdir = xmp_readdir,
+
.releasedir = xmp_releasedir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.create = xmp_create,
+
.open = xmp_open,
+
.read = xmp_read,
+
.read_buf = xmp_read_buf,
+
.write = xmp_write,
+
.write_buf = xmp_write_buf,
+
.statfs = xmp_statfs,
+
.flush = xmp_flush,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
.fallocate = xmp_fallocate,
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_LIBULOCKMGR
+
.lock = xmp_lock,
+
#endif
+
.flock = xmp_flock,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
#ifdef HAVE_STATX
+
.statx = xmp_statx,
+
#endif
+
};
+
+
int main(int argc, char *argv[])
+
{
+
umask(0);
+
return fuse_main(argc, argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file passthrough_fh.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_84_2example_2passthrough__fh_8c_source.html b/doc/html/fuse-3_817_84_2example_2passthrough__fh_8c_source.html new file mode 100644 index 0000000..149d534 --- /dev/null +++ b/doc/html/fuse-3_817_84_2example_2passthrough__fh_8c_source.html @@ -0,0 +1,751 @@ + + + + + + + +libfuse: fuse-3.17.4/example/passthrough_fh.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_fh.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
26#define FUSE_USE_VERSION 31
+
27
+
28#define _GNU_SOURCE
+
29
+
30#include <fuse.h>
+
31
+
32#ifdef HAVE_LIBULOCKMGR
+
33#include <ulockmgr.h>
+
34#endif
+
35
+
36#include <stdio.h>
+
37#include <stdlib.h>
+
38#include <string.h>
+
39#include <unistd.h>
+
40#include <fcntl.h>
+
41#include <sys/stat.h>
+
42#include <dirent.h>
+
43#include <errno.h>
+
44#include <sys/time.h>
+
45#ifdef HAVE_SETXATTR
+
46#include <sys/xattr.h>
+
47#endif
+
48#include <sys/file.h> /* flock(2) */
+
49
+
50static void *xmp_init(struct fuse_conn_info *conn,
+
51 struct fuse_config *cfg)
+
52{
+
53 (void) conn;
+
54 cfg->use_ino = 1;
+
55 cfg->nullpath_ok = 1;
+
56
+
57 /* parallel_direct_writes feature depends on direct_io features.
+
58 To make parallel_direct_writes valid, need either set cfg->direct_io
+
59 in current function (recommended in high level API) or set fi->direct_io
+
60 in xmp_create() or xmp_open(). */
+
61 // cfg->direct_io = 1;
+ +
63
+
64 /* Pick up changes from lower filesystem right away. This is
+
65 also necessary for better hardlink support. When the kernel
+
66 calls the unlink() handler, it does not know the inode of
+
67 the to-be-removed entry and can therefore not invalidate
+
68 the cache of the associated inode - resulting in an
+
69 incorrect st_nlink value being reported for any remaining
+
70 hardlinks to this inode. */
+
71 cfg->entry_timeout = 0;
+
72 cfg->attr_timeout = 0;
+
73 cfg->negative_timeout = 0;
+
74
+
75 return NULL;
+
76}
+
77
+
78static int xmp_getattr(const char *path, struct stat *stbuf,
+
79 struct fuse_file_info *fi)
+
80{
+
81 int res;
+
82
+
83 (void) path;
+
84
+
85 if(fi)
+
86 res = fstat(fi->fh, stbuf);
+
87 else
+
88 res = lstat(path, stbuf);
+
89 if (res == -1)
+
90 return -errno;
+
91
+
92 return 0;
+
93}
+
94
+
95static int xmp_access(const char *path, int mask)
+
96{
+
97 int res;
+
98
+
99 res = access(path, mask);
+
100 if (res == -1)
+
101 return -errno;
+
102
+
103 return 0;
+
104}
+
105
+
106static int xmp_readlink(const char *path, char *buf, size_t size)
+
107{
+
108 int res;
+
109
+
110 res = readlink(path, buf, size - 1);
+
111 if (res == -1)
+
112 return -errno;
+
113
+
114 buf[res] = '\0';
+
115 return 0;
+
116}
+
117
+
118struct xmp_dirp {
+
119 DIR *dp;
+
120 struct dirent *entry;
+
121 off_t offset;
+
122};
+
123
+
124static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
125{
+
126 int res;
+
127 struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
128 if (d == NULL)
+
129 return -ENOMEM;
+
130
+
131 d->dp = opendir(path);
+
132 if (d->dp == NULL) {
+
133 res = -errno;
+
134 free(d);
+
135 return res;
+
136 }
+
137 d->offset = 0;
+
138 d->entry = NULL;
+
139
+
140 fi->fh = (unsigned long) d;
+
141 return 0;
+
142}
+
143
+
144static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
145{
+
146 return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
147}
+
148
+
149static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
150 off_t offset, struct fuse_file_info *fi,
+
151 enum fuse_readdir_flags flags)
+
152{
+
153 struct xmp_dirp *d = get_dirp(fi);
+
154
+
155 (void) path;
+
156 if (offset != d->offset) {
+
157#ifndef __FreeBSD__
+
158 seekdir(d->dp, offset);
+
159#else
+
160 /* Subtract the one that we add when calling
+
161 telldir() below */
+
162 seekdir(d->dp, offset-1);
+
163#endif
+
164 d->entry = NULL;
+
165 d->offset = offset;
+
166 }
+
167 while (1) {
+
168 struct stat st;
+
169 off_t nextoff;
+ +
171
+
172 if (!d->entry) {
+
173 d->entry = readdir(d->dp);
+
174 if (!d->entry)
+
175 break;
+
176 }
+
177#ifdef HAVE_FSTATAT
+
178 if (flags & FUSE_READDIR_PLUS) {
+
179 int res;
+
180
+
181 res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
182 AT_SYMLINK_NOFOLLOW);
+
183 if (res != -1)
+
184 fill_flags |= FUSE_FILL_DIR_PLUS;
+
185 }
+
186#endif
+
187 if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
188 memset(&st, 0, sizeof(st));
+
189 st.st_ino = d->entry->d_ino;
+
190 st.st_mode = d->entry->d_type << 12;
+
191 }
+
192 nextoff = telldir(d->dp);
+
193#ifdef __FreeBSD__
+
194 /* Under FreeBSD, telldir() may return 0 the first time
+
195 it is called. But for libfuse, an offset of zero
+
196 means that offsets are not supported, so we shift
+
197 everything by one. */
+
198 nextoff++;
+
199#endif
+
200 if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
201 break;
+
202
+
203 d->entry = NULL;
+
204 d->offset = nextoff;
+
205 }
+
206
+
207 return 0;
+
208}
+
209
+
210static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
211{
+
212 struct xmp_dirp *d = get_dirp(fi);
+
213 (void) path;
+
214 closedir(d->dp);
+
215 free(d);
+
216 return 0;
+
217}
+
218
+
219static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
220{
+
221 int res;
+
222
+
223 if (S_ISFIFO(mode))
+
224 res = mkfifo(path, mode);
+
225 else
+
226 res = mknod(path, mode, rdev);
+
227 if (res == -1)
+
228 return -errno;
+
229
+
230 return 0;
+
231}
+
232
+
233static int xmp_mkdir(const char *path, mode_t mode)
+
234{
+
235 int res;
+
236
+
237 res = mkdir(path, mode);
+
238 if (res == -1)
+
239 return -errno;
+
240
+
241 return 0;
+
242}
+
243
+
244static int xmp_unlink(const char *path)
+
245{
+
246 int res;
+
247
+
248 res = unlink(path);
+
249 if (res == -1)
+
250 return -errno;
+
251
+
252 return 0;
+
253}
+
254
+
255static int xmp_rmdir(const char *path)
+
256{
+
257 int res;
+
258
+
259 res = rmdir(path);
+
260 if (res == -1)
+
261 return -errno;
+
262
+
263 return 0;
+
264}
+
265
+
266static int xmp_symlink(const char *from, const char *to)
+
267{
+
268 int res;
+
269
+
270 res = symlink(from, to);
+
271 if (res == -1)
+
272 return -errno;
+
273
+
274 return 0;
+
275}
+
276
+
277static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
278{
+
279 int res;
+
280
+
281 /* When we have renameat2() in libc, then we can implement flags */
+
282 if (flags)
+
283 return -EINVAL;
+
284
+
285 res = rename(from, to);
+
286 if (res == -1)
+
287 return -errno;
+
288
+
289 return 0;
+
290}
+
291
+
292static int xmp_link(const char *from, const char *to)
+
293{
+
294 int res;
+
295
+
296 res = link(from, to);
+
297 if (res == -1)
+
298 return -errno;
+
299
+
300 return 0;
+
301}
+
302
+
303static int xmp_chmod(const char *path, mode_t mode,
+
304 struct fuse_file_info *fi)
+
305{
+
306 int res;
+
307
+
308 if(fi)
+
309 res = fchmod(fi->fh, mode);
+
310 else
+
311 res = chmod(path, mode);
+
312 if (res == -1)
+
313 return -errno;
+
314
+
315 return 0;
+
316}
+
317
+
318static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
319 struct fuse_file_info *fi)
+
320{
+
321 int res;
+
322
+
323 if (fi)
+
324 res = fchown(fi->fh, uid, gid);
+
325 else
+
326 res = lchown(path, uid, gid);
+
327 if (res == -1)
+
328 return -errno;
+
329
+
330 return 0;
+
331}
+
332
+
333static int xmp_truncate(const char *path, off_t size,
+
334 struct fuse_file_info *fi)
+
335{
+
336 int res;
+
337
+
338 if(fi)
+
339 res = ftruncate(fi->fh, size);
+
340 else
+
341 res = truncate(path, size);
+
342
+
343 if (res == -1)
+
344 return -errno;
+
345
+
346 return 0;
+
347}
+
348
+
349#ifdef HAVE_UTIMENSAT
+
350static int xmp_utimens(const char *path, const struct timespec ts[2],
+
351 struct fuse_file_info *fi)
+
352{
+
353 int res;
+
354
+
355 /* don't use utime/utimes since they follow symlinks */
+
356 if (fi)
+
357 res = futimens(fi->fh, ts);
+
358 else
+
359 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
360 if (res == -1)
+
361 return -errno;
+
362
+
363 return 0;
+
364}
+
365#endif
+
366
+
367static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
368{
+
369 int fd;
+
370
+
371 fd = open(path, fi->flags, mode);
+
372 if (fd == -1)
+
373 return -errno;
+
374
+
375 fi->fh = fd;
+
376 return 0;
+
377}
+
378
+
379static int xmp_open(const char *path, struct fuse_file_info *fi)
+
380{
+
381 int fd;
+
382
+
383 fd = open(path, fi->flags);
+
384 if (fd == -1)
+
385 return -errno;
+
386
+
387 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
388 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
389 for writes to the same file). */
+
390 if (fi->flags & O_DIRECT) {
+
391 fi->direct_io = 1;
+ +
393 }
+
394
+
395 fi->fh = fd;
+
396 return 0;
+
397}
+
398
+
399static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
400 struct fuse_file_info *fi)
+
401{
+
402 int res;
+
403
+
404 (void) path;
+
405 res = pread(fi->fh, buf, size, offset);
+
406 if (res == -1)
+
407 res = -errno;
+
408
+
409 return res;
+
410}
+
411
+
412static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
413 size_t size, off_t offset, struct fuse_file_info *fi)
+
414{
+
415 struct fuse_bufvec *src;
+
416
+
417 (void) path;
+
418
+
419 src = malloc(sizeof(struct fuse_bufvec));
+
420 if (src == NULL)
+
421 return -ENOMEM;
+
422
+
423 *src = FUSE_BUFVEC_INIT(size);
+
424
+ +
426 src->buf[0].fd = fi->fh;
+
427 src->buf[0].pos = offset;
+
428
+
429 *bufp = src;
+
430
+
431 return 0;
+
432}
+
433
+
434static int xmp_write(const char *path, const char *buf, size_t size,
+
435 off_t offset, struct fuse_file_info *fi)
+
436{
+
437 int res;
+
438
+
439 (void) path;
+
440 res = pwrite(fi->fh, buf, size, offset);
+
441 if (res == -1)
+
442 res = -errno;
+
443
+
444 return res;
+
445}
+
446
+
447static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
448 off_t offset, struct fuse_file_info *fi)
+
449{
+
450 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
451
+
452 (void) path;
+
453
+ +
455 dst.buf[0].fd = fi->fh;
+
456 dst.buf[0].pos = offset;
+
457
+ +
459}
+
460
+
461static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
462{
+
463 int res;
+
464
+
465 res = statvfs(path, stbuf);
+
466 if (res == -1)
+
467 return -errno;
+
468
+
469 return 0;
+
470}
+
471
+
472static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
473{
+
474 int res;
+
475
+
476 (void) path;
+
477 /* This is called from every close on an open file, so call the
+
478 close on the underlying filesystem. But since flush may be
+
479 called multiple times for an open file, this must not really
+
480 close the file. This is important if used on a network
+
481 filesystem like NFS which flush the data/metadata on close() */
+
482 res = close(dup(fi->fh));
+
483 if (res == -1)
+
484 return -errno;
+
485
+
486 return 0;
+
487}
+
488
+
489static int xmp_release(const char *path, struct fuse_file_info *fi)
+
490{
+
491 (void) path;
+
492 close(fi->fh);
+
493
+
494 return 0;
+
495}
+
496
+
497static int xmp_fsync(const char *path, int isdatasync,
+
498 struct fuse_file_info *fi)
+
499{
+
500 int res;
+
501 (void) path;
+
502
+
503#ifndef HAVE_FDATASYNC
+
504 (void) isdatasync;
+
505#else
+
506 if (isdatasync)
+
507 res = fdatasync(fi->fh);
+
508 else
+
509#endif
+
510 res = fsync(fi->fh);
+
511 if (res == -1)
+
512 return -errno;
+
513
+
514 return 0;
+
515}
+
516
+
517#ifdef HAVE_POSIX_FALLOCATE
+
518static int xmp_fallocate(const char *path, int mode,
+
519 off_t offset, off_t length, struct fuse_file_info *fi)
+
520{
+
521 (void) path;
+
522
+
523 if (mode)
+
524 return -EOPNOTSUPP;
+
525
+
526 return -posix_fallocate(fi->fh, offset, length);
+
527}
+
528#endif
+
529
+
530#ifdef HAVE_SETXATTR
+
531/* xattr operations are optional and can safely be left unimplemented */
+
532static int xmp_setxattr(const char *path, const char *name, const char *value,
+
533 size_t size, int flags)
+
534{
+
535 int res = lsetxattr(path, name, value, size, flags);
+
536 if (res == -1)
+
537 return -errno;
+
538 return 0;
+
539}
+
540
+
541static int xmp_getxattr(const char *path, const char *name, char *value,
+
542 size_t size)
+
543{
+
544 int res = lgetxattr(path, name, value, size);
+
545 if (res == -1)
+
546 return -errno;
+
547 return res;
+
548}
+
549
+
550static int xmp_listxattr(const char *path, char *list, size_t size)
+
551{
+
552 int res = llistxattr(path, list, size);
+
553 if (res == -1)
+
554 return -errno;
+
555 return res;
+
556}
+
557
+
558static int xmp_removexattr(const char *path, const char *name)
+
559{
+
560 int res = lremovexattr(path, name);
+
561 if (res == -1)
+
562 return -errno;
+
563 return 0;
+
564}
+
565#endif /* HAVE_SETXATTR */
+
566
+
567#ifdef HAVE_LIBULOCKMGR
+
568static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
569 struct flock *lock)
+
570{
+
571 (void) path;
+
572
+
573 return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
574 sizeof(fi->lock_owner));
+
575}
+
576#endif
+
577
+
578static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
579{
+
580 int res;
+
581 (void) path;
+
582
+
583 res = flock(fi->fh, op);
+
584 if (res == -1)
+
585 return -errno;
+
586
+
587 return 0;
+
588}
+
589
+
590#ifdef HAVE_COPY_FILE_RANGE
+
591static ssize_t xmp_copy_file_range(const char *path_in,
+
592 struct fuse_file_info *fi_in,
+
593 off_t off_in, const char *path_out,
+
594 struct fuse_file_info *fi_out,
+
595 off_t off_out, size_t len, int flags)
+
596{
+
597 ssize_t res;
+
598 (void) path_in;
+
599 (void) path_out;
+
600
+
601 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
602 flags);
+
603 if (res == -1)
+
604 return -errno;
+
605
+
606 return res;
+
607}
+
608#endif
+
609
+
610static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
611{
+
612 off_t res;
+
613 (void) path;
+
614
+
615 res = lseek(fi->fh, off, whence);
+
616 if (res == -1)
+
617 return -errno;
+
618
+
619 return res;
+
620}
+
621
+
622static const struct fuse_operations xmp_oper = {
+
623 .init = xmp_init,
+
624 .getattr = xmp_getattr,
+
625 .access = xmp_access,
+
626 .readlink = xmp_readlink,
+
627 .opendir = xmp_opendir,
+
628 .readdir = xmp_readdir,
+
629 .releasedir = xmp_releasedir,
+
630 .mknod = xmp_mknod,
+
631 .mkdir = xmp_mkdir,
+
632 .symlink = xmp_symlink,
+
633 .unlink = xmp_unlink,
+
634 .rmdir = xmp_rmdir,
+
635 .rename = xmp_rename,
+
636 .link = xmp_link,
+
637 .chmod = xmp_chmod,
+
638 .chown = xmp_chown,
+
639 .truncate = xmp_truncate,
+
640#ifdef HAVE_UTIMENSAT
+
641 .utimens = xmp_utimens,
+
642#endif
+
643 .create = xmp_create,
+
644 .open = xmp_open,
+
645 .read = xmp_read,
+
646 .read_buf = xmp_read_buf,
+
647 .write = xmp_write,
+
648 .write_buf = xmp_write_buf,
+
649 .statfs = xmp_statfs,
+
650 .flush = xmp_flush,
+
651 .release = xmp_release,
+
652 .fsync = xmp_fsync,
+
653#ifdef HAVE_POSIX_FALLOCATE
+
654 .fallocate = xmp_fallocate,
+
655#endif
+
656#ifdef HAVE_SETXATTR
+
657 .setxattr = xmp_setxattr,
+
658 .getxattr = xmp_getxattr,
+
659 .listxattr = xmp_listxattr,
+
660 .removexattr = xmp_removexattr,
+
661#endif
+
662#ifdef HAVE_LIBULOCKMGR
+
663 .lock = xmp_lock,
+
664#endif
+
665 .flock = xmp_flock,
+
666#ifdef HAVE_COPY_FILE_RANGE
+
667 .copy_file_range = xmp_copy_file_range,
+
668#endif
+
669 .lseek = xmp_lseek,
+
670};
+
671
+
672int main(int argc, char *argv[])
+
673{
+
674 umask(0);
+
675 return fuse_main(argc, argv, &xmp_oper, NULL);
+
676}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2example_2passthrough__helpers_8h_source.html b/doc/html/fuse-3_817_84_2example_2passthrough__helpers_8h_source.html new file mode 100644 index 0000000..4a24e5c --- /dev/null +++ b/doc/html/fuse-3_817_84_2example_2passthrough__helpers_8h_source.html @@ -0,0 +1,136 @@ + + + + + + + +libfuse: fuse-3.17.4/example/passthrough_helpers.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_helpers.h
+
+
+
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 *
+
4 * Redistribution and use in source and binary forms, with or without
+
5 * modification, are permitted provided that the following conditions
+
6 * are met:
+
7 * 1. Redistributions of source code must retain the above copyright
+
8 * notice, this list of conditions and the following disclaimer.
+
9 * 2. Redistributions in binary form must reproduce the above copyright
+
10 * notice, this list of conditions and the following disclaimer in the
+
11 * documentation and/or other materials provided with the distribution.
+
12 *
+
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
23 * SUCH DAMAGE
+
24 */
+
25
+
26/*
+
27 * Creates files on the underlying file system in response to a FUSE_MKNOD
+
28 * operation
+
29 */
+
30static int mknod_wrapper(int dirfd, const char *path, const char *link,
+
31 int mode, dev_t rdev)
+
32{
+
33 int res;
+
34
+
35 if (S_ISREG(mode)) {
+
36 res = openat(dirfd, path, O_CREAT | O_EXCL | O_WRONLY, mode);
+
37 if (res >= 0)
+
38 res = close(res);
+
39 } else if (S_ISDIR(mode)) {
+
40 res = mkdirat(dirfd, path, mode);
+
41 } else if (S_ISLNK(mode) && link != NULL) {
+
42 res = symlinkat(link, dirfd, path);
+
43 } else if (S_ISFIFO(mode)) {
+
44 res = mkfifoat(dirfd, path, mode);
+
45#ifdef __FreeBSD__
+
46 } else if (S_ISSOCK(mode)) {
+
47 struct sockaddr_un su;
+
48 int fd;
+
49
+
50 if (strlen(path) >= sizeof(su.sun_path)) {
+
51 errno = ENAMETOOLONG;
+
52 return -1;
+
53 }
+
54 fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
55 if (fd >= 0) {
+
56 /*
+
57 * We must bind the socket to the underlying file
+
58 * system to create the socket file, even though
+
59 * we'll never listen on this socket.
+
60 */
+
61 su.sun_family = AF_UNIX;
+
62 strncpy(su.sun_path, path, sizeof(su.sun_path));
+
63 res = bindat(dirfd, fd, (struct sockaddr*)&su,
+
64 sizeof(su));
+
65 if (res == 0)
+
66 close(fd);
+
67 } else {
+
68 res = -1;
+
69 }
+
70#endif
+
71 } else {
+
72 res = mknodat(dirfd, path, mode, rdev);
+
73 }
+
74
+
75 return res;
+
76}
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2example_2passthrough__ll_8c.html b/doc/html/fuse-3_817_84_2example_2passthrough__ll_8c.html new file mode 100644 index 0000000..a2528e9 --- /dev/null +++ b/doc/html/fuse-3_817_84_2example_2passthrough__ll_8c.html @@ -0,0 +1,1546 @@ + + + + + + + +libfuse: fuse-3.17.4/example/passthrough_ll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough_ll.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <sys/file.h>
+#include <sys/xattr.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. In contrast to passthrough.c and passthrough_fh.c, this implementation uses the low-level API. Its performance should be the least bad among the three, but many operations are not implemented. In particular, it is not possible to remove files (or directories) because the code necessary to defer actual removal until the file is not opened anymore would make the example much more complicated.

+

When writeback caching is enabled (-o writeback mount option), it is only possible to write to files for which the mounting user has read permissions. This is because the writeback cache requires the kernel to be able to issue read requests for all files (which the passthrough filesystem cannot satisfy if it can't read the file in the underlying filesystem).

+

Compile with:

gcc -Wall passthrough_ll.c `pkg-config fuse3 --cflags --libs` -o passthrough_ll
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define _GNU_SOURCE
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
+
#include <fuse_lowlevel.h>
+
#include <unistd.h>
+
#include <stdlib.h>
+
#include <stdio.h>
+
#include <stddef.h>
+
#include <stdbool.h>
+
#include <string.h>
+
#include <limits.h>
+
#include <dirent.h>
+
#include <assert.h>
+
#include <errno.h>
+
#include <inttypes.h>
+
#include <pthread.h>
+
#include <sys/file.h>
+
#include <sys/xattr.h>
+
+
#include "passthrough_helpers.h"
+
+
/* We are re-using pointers to our `struct lo_inode` and `struct
+
lo_dirp` elements as inodes. This means that we must be able to
+
store uintptr_t values in a fuse_ino_t variable. The following
+
incantation checks this condition at compile time. */
+
#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
+
_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
+
"fuse_ino_t too small to hold uintptr_t values!");
+
#else
+
struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
+
{ unsigned _uintptr_to_must_hold_fuse_ino_t:
+
((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
+
#endif
+
+
struct lo_inode {
+
struct lo_inode *next; /* protected by lo->mutex */
+
struct lo_inode *prev; /* protected by lo->mutex */
+
int fd;
+
ino_t ino;
+
dev_t dev;
+
uint64_t refcount; /* protected by lo->mutex */
+
};
+
+
enum {
+
CACHE_NEVER,
+
CACHE_NORMAL,
+
CACHE_ALWAYS,
+
};
+
+
struct lo_data {
+
pthread_mutex_t mutex;
+
int debug;
+
int writeback;
+
int flock;
+
int xattr;
+
char *source;
+
double timeout;
+
int cache;
+
int timeout_set;
+
struct lo_inode root; /* protected by lo->mutex */
+
};
+
+
static const struct fuse_opt lo_opts[] = {
+
{ "writeback",
+
offsetof(struct lo_data, writeback), 1 },
+
{ "no_writeback",
+
offsetof(struct lo_data, writeback), 0 },
+
{ "source=%s",
+
offsetof(struct lo_data, source), 0 },
+
{ "flock",
+
offsetof(struct lo_data, flock), 1 },
+
{ "no_flock",
+
offsetof(struct lo_data, flock), 0 },
+
{ "xattr",
+
offsetof(struct lo_data, xattr), 1 },
+
{ "no_xattr",
+
offsetof(struct lo_data, xattr), 0 },
+
{ "timeout=%lf",
+
offsetof(struct lo_data, timeout), 0 },
+
{ "timeout=",
+
offsetof(struct lo_data, timeout_set), 1 },
+
{ "cache=never",
+
offsetof(struct lo_data, cache), CACHE_NEVER },
+
{ "cache=auto",
+
offsetof(struct lo_data, cache), CACHE_NORMAL },
+
{ "cache=always",
+
offsetof(struct lo_data, cache), CACHE_ALWAYS },
+
+ +
};
+
+
static void passthrough_ll_help(void)
+
{
+
printf(
+
" -o writeback Enable writeback\n"
+
" -o no_writeback Disable write back\n"
+
" -o source=/home/dir Source directory to be mounted\n"
+
" -o flock Enable flock\n"
+
" -o no_flock Disable flock\n"
+
" -o xattr Enable xattr\n"
+
" -o no_xattr Disable xattr\n"
+
" -o timeout=1.0 Caching timeout\n"
+
" -o timeout=0/1 Timeout is set\n"
+
" -o cache=never Disable cache\n"
+
" -o cache=auto Auto enable cache\n"
+
" -o cache=always Cache always\n");
+
}
+
+
static struct lo_data *lo_data(fuse_req_t req)
+
{
+
return (struct lo_data *) fuse_req_userdata(req);
+
}
+
+
static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
+
{
+
if (ino == FUSE_ROOT_ID)
+
return &lo_data(req)->root;
+
else
+
return (struct lo_inode *) (uintptr_t) ino;
+
}
+
+
static int lo_fd(fuse_req_t req, fuse_ino_t ino)
+
{
+
return lo_inode(req, ino)->fd;
+
}
+
+
static bool lo_debug(fuse_req_t req)
+
{
+
return lo_data(req)->debug != 0;
+
}
+
+
static void lo_init(void *userdata,
+
struct fuse_conn_info *conn)
+
{
+
struct lo_data *lo = (struct lo_data *)userdata;
+
bool has_flag;
+
+
if (lo->writeback) {
+ +
if (lo->debug && has_flag)
+
fuse_log(FUSE_LOG_DEBUG,
+
"lo_init: activating writeback\n");
+
}
+
if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
+ +
if (lo->debug && has_flag)
+
fuse_log(FUSE_LOG_DEBUG,
+
"lo_init: activating flock locks\n");
+
}
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void lo_destroy(void *userdata)
+
{
+
struct lo_data *lo = (struct lo_data*) userdata;
+
+
while (lo->root.next != &lo->root) {
+
struct lo_inode* next = lo->root.next;
+
lo->root.next = next->next;
+
close(next->fd);
+
free(next);
+
}
+
}
+
+
static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
struct stat buf;
+
struct lo_data *lo = lo_data(req);
+
int fd = fi ? fi->fh : lo_fd(req, ino);
+
+
(void) fi;
+
+
res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fuse_reply_attr(req, &buf, lo->timeout);
+
}
+
+
static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
int valid, struct fuse_file_info *fi)
+
{
+
int saverr;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
int ifd = inode->fd;
+
int res;
+
+
if (valid & FUSE_SET_ATTR_MODE) {
+
if (fi) {
+
res = fchmod(fi->fh, attr->st_mode);
+
} else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = chmod(procname, attr->st_mode);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+
uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
attr->st_uid : (uid_t) -1;
+
gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
attr->st_gid : (gid_t) -1;
+
+
res = fchownat(ifd, "", uid, gid,
+
AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & FUSE_SET_ATTR_SIZE) {
+
if (fi) {
+
res = ftruncate(fi->fh, attr->st_size);
+
} else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = truncate(procname, attr->st_size);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
struct timespec tv[2];
+
+
tv[0].tv_sec = 0;
+
tv[1].tv_sec = 0;
+
tv[0].tv_nsec = UTIME_OMIT;
+
tv[1].tv_nsec = UTIME_OMIT;
+
+
if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
tv[0].tv_nsec = UTIME_NOW;
+
else if (valid & FUSE_SET_ATTR_ATIME)
+
tv[0] = attr->st_atim;
+
+
if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
tv[1].tv_nsec = UTIME_NOW;
+
else if (valid & FUSE_SET_ATTR_MTIME)
+
tv[1] = attr->st_mtim;
+
+
if (fi)
+
res = futimens(fi->fh, tv);
+
else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = utimensat(AT_FDCWD, procname, tv, 0);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
+
return lo_getattr(req, ino, fi);
+
+
out_err:
+
saverr = errno;
+
fuse_reply_err(req, saverr);
+
}
+
+
static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+
{
+
struct lo_inode *p;
+
struct lo_inode *ret = NULL;
+
+
pthread_mutex_lock(&lo->mutex);
+
for (p = lo->root.next; p != &lo->root; p = p->next) {
+
if (p->ino == st->st_ino && p->dev == st->st_dev) {
+
assert(p->refcount > 0);
+
ret = p;
+
ret->refcount++;
+
break;
+
}
+
}
+
pthread_mutex_unlock(&lo->mutex);
+
return ret;
+
}
+
+
+
static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
+
{
+
struct lo_inode *inode = NULL;
+
struct lo_inode *prev, *next;
+
+
inode = calloc(1, sizeof(struct lo_inode));
+
if (!inode)
+
return NULL;
+
+
inode->refcount = 1;
+
inode->fd = fd;
+
inode->ino = e->attr.st_ino;
+
inode->dev = e->attr.st_dev;
+
+
pthread_mutex_lock(&lo->mutex);
+
prev = &lo->root;
+
next = prev->next;
+
next->prev = inode;
+
inode->next = next;
+
inode->prev = prev;
+
prev->next = inode;
+
pthread_mutex_unlock(&lo->mutex);
+
return inode;
+
}
+
+
static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
+
{
+
int res;
+
struct lo_data *lo = lo_data(req);
+
+
memset(e, 0, sizeof(*e));
+
e->attr_timeout = lo->timeout;
+
e->entry_timeout = lo->timeout;
+
+
res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return errno;
+
+
e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%d -> %lli\n",
+
(unsigned long long) parent, fd, (unsigned long long) e->ino);
+
+
return 0;
+
+
}
+
+
static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
+
struct fuse_entry_param *e)
+
{
+
int newfd;
+
int res;
+
int saverr;
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode;
+
+
memset(e, 0, sizeof(*e));
+
e->attr_timeout = lo->timeout;
+
e->entry_timeout = lo->timeout;
+
+
newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
+
if (newfd == -1)
+
goto out_err;
+
+
res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
inode = lo_find(lo_data(req), &e->attr);
+
if (inode) {
+
close(newfd);
+
newfd = -1;
+
} else {
+
inode = create_new_inode(newfd, e, lo);
+
if (!inode)
+
goto out_err;
+
}
+
e->ino = (uintptr_t) inode;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name, (unsigned long long) e->ino);
+
+
return 0;
+
+
out_err:
+
saverr = errno;
+
if (newfd != -1)
+
close(newfd);
+
return saverr;
+
}
+
+
static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
+
parent, name);
+
+
err = lo_do_lookup(req, parent, name, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_entry(req, &e);
+
}
+
+
static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
+
const char *name, mode_t mode, dev_t rdev,
+
const char *link)
+
{
+
int res;
+
int saverr;
+
struct lo_inode *dir = lo_inode(req, parent);
+
struct fuse_entry_param e;
+
+
res = mknod_wrapper(dir->fd, name, link, mode, rdev);
+
+
saverr = errno;
+
if (res == -1)
+
goto out;
+
+
saverr = lo_do_lookup(req, parent, name, &e);
+
if (saverr)
+
goto out;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name, (unsigned long long) e.ino);
+
+
fuse_reply_entry(req, &e);
+
return;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
+
const char *name, mode_t mode, dev_t rdev)
+
{
+
lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
+
}
+
+
static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
mode_t mode)
+
{
+
lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
+
}
+
+
static void lo_symlink(fuse_req_t req, const char *link,
+
fuse_ino_t parent, const char *name)
+
{
+
lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
+
}
+
+
static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
+
const char *name)
+
{
+
int res;
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode = lo_inode(req, ino);
+
struct fuse_entry_param e;
+
char procname[64];
+
int saverr;
+
+
memset(&e, 0, sizeof(struct fuse_entry_param));
+
e.attr_timeout = lo->timeout;
+
e.entry_timeout = lo->timeout;
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
+
AT_SYMLINK_FOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
pthread_mutex_lock(&lo->mutex);
+
inode->refcount++;
+
pthread_mutex_unlock(&lo->mutex);
+
e.ino = (uintptr_t) inode;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name,
+
(unsigned long long) e.ino);
+
+
fuse_reply_entry(req, &e);
+
return;
+
+
out_err:
+
saverr = errno;
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
int res;
+
+
res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
+
fuse_ino_t newparent, const char *newname,
+
unsigned int flags)
+
{
+
int res;
+
+
if (flags) {
+
fuse_reply_err(req, EINVAL);
+
return;
+
}
+
+
res = renameat(lo_fd(req, parent), name,
+
lo_fd(req, newparent), newname);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
int res;
+
+
res = unlinkat(lo_fd(req, parent), name, 0);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
+
{
+
if (!inode)
+
return;
+
+
pthread_mutex_lock(&lo->mutex);
+
assert(inode->refcount >= n);
+
inode->refcount -= n;
+
if (!inode->refcount) {
+
struct lo_inode *prev, *next;
+
+
prev = inode->prev;
+
next = inode->next;
+
next->prev = prev;
+
prev->next = next;
+
+
pthread_mutex_unlock(&lo->mutex);
+
close(inode->fd);
+
free(inode);
+
+
} else {
+
pthread_mutex_unlock(&lo->mutex);
+
}
+
}
+
+
static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
{
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode = lo_inode(req, ino);
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
+
(unsigned long long) ino,
+
(unsigned long long) inode->refcount,
+
(unsigned long long) nlookup);
+
}
+
+
unref_inode(lo, inode, nlookup);
+
}
+
+
static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
{
+
lo_forget_one(req, ino, nlookup);
+ +
}
+
+
static void lo_forget_multi(fuse_req_t req, size_t count,
+
struct fuse_forget_data *forgets)
+
{
+
int i;
+
+
for (i = 0; i < count; i++)
+
lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
+ +
}
+
+
static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
+
{
+
char buf[PATH_MAX + 1];
+
int res;
+
+
res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
+
if (res == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
if (res == sizeof(buf))
+
return (void) fuse_reply_err(req, ENAMETOOLONG);
+
+
buf[res] = '\0';
+
+ +
}
+
+
struct lo_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
+
{
+
return (struct lo_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int error = ENOMEM;
+
struct lo_data *lo = lo_data(req);
+
struct lo_dirp *d;
+
int fd = -1;
+
+
d = calloc(1, sizeof(struct lo_dirp));
+
if (d == NULL)
+
goto out_err;
+
+
fd = openat(lo_fd(req, ino), ".", O_RDONLY);
+
if (fd == -1)
+
goto out_errno;
+
+
d->dp = fdopendir(fd);
+
if (d->dp == NULL)
+
goto out_errno;
+
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (uintptr_t) d;
+
if (lo->cache != CACHE_NEVER)
+
fi->cache_readdir = 1;
+
if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
fuse_reply_open(req, fi);
+
return;
+
+
out_errno:
+
error = errno;
+
out_err:
+
if (d) {
+
if (fd != -1)
+
close(fd);
+
free(d);
+
}
+
fuse_reply_err(req, error);
+
}
+
+
static int is_dot_or_dotdot(const char *name)
+
{
+
return name[0] == '.' && (name[1] == '\0' ||
+
(name[1] == '.' && name[2] == '\0'));
+
}
+
+
static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi, int plus)
+
{
+
struct lo_dirp *d = lo_dirp(fi);
+
char *buf;
+
char *p;
+
size_t rem = size;
+
int err;
+
+
(void) ino;
+
+
buf = calloc(1, size);
+
if (!buf) {
+
err = ENOMEM;
+
goto error;
+
}
+
p = buf;
+
+
if (offset != d->offset) {
+
seekdir(d->dp, offset);
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
size_t entsize;
+
off_t nextoff;
+
const char *name;
+
+
if (!d->entry) {
+
errno = 0;
+
d->entry = readdir(d->dp);
+
if (!d->entry) {
+
if (errno) { // Error
+
err = errno;
+
goto error;
+
} else { // End of stream
+
break;
+
}
+
}
+
}
+
nextoff = d->entry->d_off;
+
name = d->entry->d_name;
+
fuse_ino_t entry_ino = 0;
+
if (plus) {
+
struct fuse_entry_param e;
+
if (is_dot_or_dotdot(name)) {
+
e = (struct fuse_entry_param) {
+
.attr.st_ino = d->entry->d_ino,
+
.attr.st_mode = d->entry->d_type << 12,
+
};
+
} else {
+
err = lo_do_lookup(req, ino, name, &e);
+
if (err)
+
goto error;
+
entry_ino = e.ino;
+
}
+
+
entsize = fuse_add_direntry_plus(req, p, rem, name,
+
&e, nextoff);
+
} else {
+
struct stat st = {
+
.st_ino = d->entry->d_ino,
+
.st_mode = d->entry->d_type << 12,
+
};
+
entsize = fuse_add_direntry(req, p, rem, name,
+
&st, nextoff);
+
}
+
if (entsize > rem) {
+
if (entry_ino != 0)
+
lo_forget_one(req, entry_ino, 1);
+
break;
+
}
+
+
p += entsize;
+
rem -= entsize;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
err = 0;
+
error:
+
// If there's an error, we can only signal it if we haven't stored
+
// any entries yet - otherwise we'd end up with wrong lookup
+
// counts for the entries that are already in the buffer. So we
+
// return what we've collected until that point.
+
if (err && rem == size)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_buf(req, buf, size - rem);
+
free(buf);
+
}
+
+
static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
lo_do_readdir(req, ino, size, offset, fi, 0);
+
}
+
+
static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
lo_do_readdir(req, ino, size, offset, fi, 1);
+
}
+
+
static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
struct lo_dirp *d = lo_dirp(fi);
+
(void) ino;
+
closedir(d->dp);
+
free(d);
+
fuse_reply_err(req, 0);
+
}
+
+
static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
+
mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
struct lo_data *lo = lo_data(req);
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
+
parent);
+
+
fd = openat(lo_fd(req, parent), ".",
+
(fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
err = fill_entry_param_new_inode(req, parent, fd, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_create(req, &e, fi);
+
}
+
+
static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+
mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
struct lo_data *lo = lo_data(req);
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
+
parent, name);
+
+
fd = openat(lo_fd(req, parent), name,
+
(fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
err = lo_do_lookup(req, parent, name, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_create(req, &e, fi);
+
}
+
+
static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
int fd = dirfd(lo_dirp(fi)->dp);
+
(void) ino;
+
if (datasync)
+
res = fdatasync(fd);
+
else
+
res = fsync(fd);
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int fd;
+
char buf[64];
+
struct lo_data *lo = lo_data(req);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
+
ino, fi->flags);
+
+
/* With writeback cache, kernel may send read requests even
+
when userspace opened write-only */
+
if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
+
fi->flags &= ~O_ACCMODE;
+
fi->flags |= O_RDWR;
+
}
+
+
/* With writeback cache, O_APPEND is handled by the kernel.
+
This breaks atomicity (since the file may change in the
+
underlying filesystem, so that the kernel's idea of the
+
end of the file isn't accurate anymore). In this example,
+
we just accept that. A more rigorous filesystem may want
+
to return an error here */
+
if (lo->writeback && (fi->flags & O_APPEND))
+
fi->flags &= ~O_APPEND;
+
+
sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
+
fd = open(buf, fi->flags & ~O_NOFOLLOW);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file in the kernel). */
+
if (fi->flags & O_DIRECT)
+
fi->direct_io = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
fuse_reply_open(req, fi);
+
}
+
+
static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
(void) ino;
+
+
close(fi->fh);
+
fuse_reply_err(req, 0);
+
}
+
+
static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int res;
+
(void) ino;
+
res = close(dup(fi->fh));
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) ino;
+
if (datasync)
+
res = fdatasync(fi->fh);
+
else
+
res = fsync(fi->fh);
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
+
"off=%lu)\n", ino, size, (unsigned long) offset);
+
+ +
buf.buf[0].fd = fi->fh;
+
buf.buf[0].pos = offset;
+
+ +
}
+
+
static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_bufvec *in_buf, off_t off,
+
struct fuse_file_info *fi)
+
{
+
(void) ino;
+
ssize_t res;
+
struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
+
+ +
out_buf.buf[0].fd = fi->fh;
+
out_buf.buf[0].pos = off;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%jd)\n",
+
ino, out_buf.buf[0].size, (intmax_t) off);
+
+
res = fuse_buf_copy(&out_buf, in_buf, 0);
+
if(res < 0)
+
fuse_reply_err(req, -res);
+
else
+
fuse_reply_write(req, (size_t) res);
+
}
+
+
static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
+
{
+
int res;
+
struct statvfs stbuf;
+
+
res = fstatvfs(lo_fd(req, ino), &stbuf);
+
if (res == -1)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_statfs(req, &stbuf);
+
}
+
+
static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
int err;
+
(void) ino;
+
+
err = -do_fallocate(fi->fh, mode, offset, length);
+
+
fuse_reply_err(req, err);
+
}
+
+
static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
int op)
+
{
+
int res;
+
(void) ino;
+
+
res = flock(fi->fh, op);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
char *value = NULL;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
+
ino, name, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
if (size) {
+
value = malloc(size);
+
if (!value)
+
goto out_err;
+
+
ret = getxattr(procname, name, value, size);
+
if (ret == -1)
+
goto out_err;
+
saverr = 0;
+
if (ret == 0)
+
goto out;
+
+
fuse_reply_buf(req, value, ret);
+
} else {
+
ret = getxattr(procname, name, NULL, 0);
+
if (ret == -1)
+
goto out_err;
+
+
fuse_reply_xattr(req, ret);
+
}
+
out_free:
+
free(value);
+
return;
+
+
out_err:
+
saverr = errno;
+
out:
+
fuse_reply_err(req, saverr);
+
goto out_free;
+
}
+
+
static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
{
+
char *value = NULL;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
+
ino, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
if (size) {
+
value = malloc(size);
+
if (!value)
+
goto out_err;
+
+
ret = listxattr(procname, value, size);
+
if (ret == -1)
+
goto out_err;
+
saverr = 0;
+
if (ret == 0)
+
goto out;
+
+
fuse_reply_buf(req, value, ret);
+
} else {
+
ret = listxattr(procname, NULL, 0);
+
if (ret == -1)
+
goto out_err;
+
+
fuse_reply_xattr(req, ret);
+
}
+
out_free:
+
free(value);
+
return;
+
+
out_err:
+
saverr = errno;
+
out:
+
fuse_reply_err(req, saverr);
+
goto out_free;
+
}
+
+
static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
+
ino, name, value, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
ret = setxattr(procname, name, value, size, flags);
+
saverr = ret == -1 ? errno : 0;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
+
ino, name);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
ret = removexattr(procname, name);
+
saverr = ret == -1 ? errno : 0;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
+
struct fuse_file_info *fi_in,
+
fuse_ino_t ino_out, off_t off_out,
+
struct fuse_file_info *fi_out, size_t len,
+
int flags)
+
{
+
ssize_t res;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG,
+
"%s(ino=%lld fd=%lld off=%jd ino=%lld fd=%lld off=%jd, size=%zd, flags=0x%x)\n",
+
__func__, (unsigned long long)ino_in,
+
(unsigned long long)fi_in->fh,
+
(intmax_t) off_in, (unsigned long long)ino_out,
+
(unsigned long long)fi_out->fh, (intmax_t) off_out,
+
len, flags);
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res < 0)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_write(req, res);
+
}
+
#endif
+
+
static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
struct fuse_file_info *fi)
+
{
+
off_t res;
+
+
(void)ino;
+
res = lseek(fi->fh, off, whence);
+
if (res != -1)
+
fuse_reply_lseek(req, res);
+
else
+
fuse_reply_err(req, errno);
+
}
+
+
#ifdef HAVE_STATX
+
static void lo_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
+
struct fuse_file_info *fi)
+
{
+
struct lo_data *lo = lo_data(req);
+
struct statx buf;
+
int res;
+
int fd;
+
+
if (fi)
+
fd = fi->fh;
+
else
+
fd = lo_fd(req, ino);
+
+
res = statx(fd, "", flags | AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, mask, &buf);
+
if (res == -1)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_statx(req, 0, &buf, lo->timeout);
+
}
+
#endif
+
+
static const struct fuse_lowlevel_ops lo_oper = {
+
.init = lo_init,
+
.destroy = lo_destroy,
+
.lookup = lo_lookup,
+
.mkdir = lo_mkdir,
+
.mknod = lo_mknod,
+
.symlink = lo_symlink,
+
.link = lo_link,
+
.unlink = lo_unlink,
+
.rmdir = lo_rmdir,
+
.rename = lo_rename,
+
.forget = lo_forget,
+
.forget_multi = lo_forget_multi,
+
.getattr = lo_getattr,
+
.setattr = lo_setattr,
+
.readlink = lo_readlink,
+
.opendir = lo_opendir,
+
.readdir = lo_readdir,
+
.readdirplus = lo_readdirplus,
+
.releasedir = lo_releasedir,
+
.fsyncdir = lo_fsyncdir,
+
.create = lo_create,
+
.tmpfile = lo_tmpfile,
+
.open = lo_open,
+
.release = lo_release,
+
.flush = lo_flush,
+
.fsync = lo_fsync,
+
.read = lo_read,
+
.write_buf = lo_write_buf,
+
.statfs = lo_statfs,
+
.fallocate = lo_fallocate,
+
.flock = lo_flock,
+
.getxattr = lo_getxattr,
+
.listxattr = lo_listxattr,
+
.setxattr = lo_setxattr,
+
.removexattr = lo_removexattr,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = lo_copy_file_range,
+
#endif
+
.lseek = lo_lseek,
+
#ifdef HAVE_STATX
+
.statx = lo_statx,
+
#endif
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
struct lo_data lo = { .debug = 0,
+
.writeback = 0 };
+
int ret = -1;
+
+
/* Don't mask creation mode, kernel already did that */
+
umask(0);
+
+
pthread_mutex_init(&lo.mutex, NULL);
+
lo.root.next = lo.root.prev = &lo.root;
+
lo.root.fd = -1;
+
lo.cache = CACHE_NORMAL;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
passthrough_ll_help();
+
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
+
return 1;
+
+
lo.debug = opts.debug;
+
lo.root.refcount = 2;
+
if (lo.source) {
+
struct stat stat;
+
int res;
+
+
res = lstat(lo.source, &stat);
+
if (res == -1) {
+
fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
+
lo.source);
+
exit(1);
+
}
+
if (!S_ISDIR(stat.st_mode)) {
+
fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
+
exit(1);
+
}
+
+
} else {
+
lo.source = strdup("/");
+
if(!lo.source) {
+
fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
exit(1);
+
}
+
}
+
if (!lo.timeout_set) {
+
switch (lo.cache) {
+
case CACHE_NEVER:
+
lo.timeout = 0.0;
+
break;
+
+
case CACHE_NORMAL:
+
lo.timeout = 1.0;
+
break;
+
+
case CACHE_ALWAYS:
+
lo.timeout = 86400.0;
+
break;
+
}
+
} else if (lo.timeout < 0) {
+
fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
+
lo.timeout);
+
exit(1);
+
}
+
+
lo.root.fd = open(lo.source, O_PATH);
+
if (lo.root.fd == -1) {
+
fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
+
lo.source);
+
exit(1);
+
}
+
+
se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
if (lo.root.fd >= 0)
+
close(lo.root.fd);
+
+
free(lo.source);
+
return ret ? 1 : 0;
+
}
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
off_t pos
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
uint32_t capable
+
+
double entry_timeout
+
fuse_ino_t ino
+
double attr_timeout
+
struct stat attr
+ + +
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file passthrough_ll.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_84_2example_2passthrough__ll_8c_source.html b/doc/html/fuse-3_817_84_2example_2passthrough__ll_8c_source.html new file mode 100644 index 0000000..b3c27c1 --- /dev/null +++ b/doc/html/fuse-3_817_84_2example_2passthrough__ll_8c_source.html @@ -0,0 +1,1509 @@ + + + + + + + +libfuse: fuse-3.17.4/example/passthrough_ll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_ll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
37#define _GNU_SOURCE
+
38#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
39
+
40#include <fuse_lowlevel.h>
+
41#include <unistd.h>
+
42#include <stdlib.h>
+
43#include <stdio.h>
+
44#include <stddef.h>
+
45#include <stdbool.h>
+
46#include <string.h>
+
47#include <limits.h>
+
48#include <dirent.h>
+
49#include <assert.h>
+
50#include <errno.h>
+
51#include <inttypes.h>
+
52#include <pthread.h>
+
53#include <sys/file.h>
+
54#include <sys/xattr.h>
+
55
+
56#include "passthrough_helpers.h"
+
57
+
58/* We are re-using pointers to our `struct lo_inode` and `struct
+
59 lo_dirp` elements as inodes. This means that we must be able to
+
60 store uintptr_t values in a fuse_ino_t variable. The following
+
61 incantation checks this condition at compile time. */
+
62#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
+
63_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
+
64 "fuse_ino_t too small to hold uintptr_t values!");
+
65#else
+
66struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
+
67 { unsigned _uintptr_to_must_hold_fuse_ino_t:
+
68 ((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
+
69#endif
+
70
+
71struct lo_inode {
+
72 struct lo_inode *next; /* protected by lo->mutex */
+
73 struct lo_inode *prev; /* protected by lo->mutex */
+
74 int fd;
+
75 ino_t ino;
+
76 dev_t dev;
+
77 uint64_t refcount; /* protected by lo->mutex */
+
78};
+
79
+
80enum {
+
81 CACHE_NEVER,
+
82 CACHE_NORMAL,
+
83 CACHE_ALWAYS,
+
84};
+
85
+
86struct lo_data {
+
87 pthread_mutex_t mutex;
+
88 int debug;
+
89 int writeback;
+
90 int flock;
+
91 int xattr;
+
92 char *source;
+
93 double timeout;
+
94 int cache;
+
95 int timeout_set;
+
96 struct lo_inode root; /* protected by lo->mutex */
+
97};
+
98
+
99static const struct fuse_opt lo_opts[] = {
+
100 { "writeback",
+
101 offsetof(struct lo_data, writeback), 1 },
+
102 { "no_writeback",
+
103 offsetof(struct lo_data, writeback), 0 },
+
104 { "source=%s",
+
105 offsetof(struct lo_data, source), 0 },
+
106 { "flock",
+
107 offsetof(struct lo_data, flock), 1 },
+
108 { "no_flock",
+
109 offsetof(struct lo_data, flock), 0 },
+
110 { "xattr",
+
111 offsetof(struct lo_data, xattr), 1 },
+
112 { "no_xattr",
+
113 offsetof(struct lo_data, xattr), 0 },
+
114 { "timeout=%lf",
+
115 offsetof(struct lo_data, timeout), 0 },
+
116 { "timeout=",
+
117 offsetof(struct lo_data, timeout_set), 1 },
+
118 { "cache=never",
+
119 offsetof(struct lo_data, cache), CACHE_NEVER },
+
120 { "cache=auto",
+
121 offsetof(struct lo_data, cache), CACHE_NORMAL },
+
122 { "cache=always",
+
123 offsetof(struct lo_data, cache), CACHE_ALWAYS },
+
124
+ +
126};
+
127
+
128static void passthrough_ll_help(void)
+
129{
+
130 printf(
+
131" -o writeback Enable writeback\n"
+
132" -o no_writeback Disable write back\n"
+
133" -o source=/home/dir Source directory to be mounted\n"
+
134" -o flock Enable flock\n"
+
135" -o no_flock Disable flock\n"
+
136" -o xattr Enable xattr\n"
+
137" -o no_xattr Disable xattr\n"
+
138" -o timeout=1.0 Caching timeout\n"
+
139" -o timeout=0/1 Timeout is set\n"
+
140" -o cache=never Disable cache\n"
+
141" -o cache=auto Auto enable cache\n"
+
142" -o cache=always Cache always\n");
+
143}
+
144
+
145static struct lo_data *lo_data(fuse_req_t req)
+
146{
+
147 return (struct lo_data *) fuse_req_userdata(req);
+
148}
+
149
+
150static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
+
151{
+
152 if (ino == FUSE_ROOT_ID)
+
153 return &lo_data(req)->root;
+
154 else
+
155 return (struct lo_inode *) (uintptr_t) ino;
+
156}
+
157
+
158static int lo_fd(fuse_req_t req, fuse_ino_t ino)
+
159{
+
160 return lo_inode(req, ino)->fd;
+
161}
+
162
+
163static bool lo_debug(fuse_req_t req)
+
164{
+
165 return lo_data(req)->debug != 0;
+
166}
+
167
+
168static void lo_init(void *userdata,
+
169 struct fuse_conn_info *conn)
+
170{
+
171 struct lo_data *lo = (struct lo_data *)userdata;
+
172 bool has_flag;
+
173
+
174 if (lo->writeback) {
+ +
176 if (lo->debug && has_flag)
+
177 fuse_log(FUSE_LOG_DEBUG,
+
178 "lo_init: activating writeback\n");
+
179 }
+
180 if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
+ +
182 if (lo->debug && has_flag)
+
183 fuse_log(FUSE_LOG_DEBUG,
+
184 "lo_init: activating flock locks\n");
+
185 }
+
186
+
187 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
188 conn->no_interrupt = 1;
+
189}
+
190
+
191static void lo_destroy(void *userdata)
+
192{
+
193 struct lo_data *lo = (struct lo_data*) userdata;
+
194
+
195 while (lo->root.next != &lo->root) {
+
196 struct lo_inode* next = lo->root.next;
+
197 lo->root.next = next->next;
+
198 close(next->fd);
+
199 free(next);
+
200 }
+
201}
+
202
+
203static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
+
204 struct fuse_file_info *fi)
+
205{
+
206 int res;
+
207 struct stat buf;
+
208 struct lo_data *lo = lo_data(req);
+
209 int fd = fi ? fi->fh : lo_fd(req, ino);
+
210
+
211 (void) fi;
+
212
+
213 res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
214 if (res == -1)
+
215 return (void) fuse_reply_err(req, errno);
+
216
+
217 fuse_reply_attr(req, &buf, lo->timeout);
+
218}
+
219
+
220static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
221 int valid, struct fuse_file_info *fi)
+
222{
+
223 int saverr;
+
224 char procname[64];
+
225 struct lo_inode *inode = lo_inode(req, ino);
+
226 int ifd = inode->fd;
+
227 int res;
+
228
+
229 if (valid & FUSE_SET_ATTR_MODE) {
+
230 if (fi) {
+
231 res = fchmod(fi->fh, attr->st_mode);
+
232 } else {
+
233 sprintf(procname, "/proc/self/fd/%i", ifd);
+
234 res = chmod(procname, attr->st_mode);
+
235 }
+
236 if (res == -1)
+
237 goto out_err;
+
238 }
+
239 if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+
240 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
241 attr->st_uid : (uid_t) -1;
+
242 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
243 attr->st_gid : (gid_t) -1;
+
244
+
245 res = fchownat(ifd, "", uid, gid,
+
246 AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
247 if (res == -1)
+
248 goto out_err;
+
249 }
+
250 if (valid & FUSE_SET_ATTR_SIZE) {
+
251 if (fi) {
+
252 res = ftruncate(fi->fh, attr->st_size);
+
253 } else {
+
254 sprintf(procname, "/proc/self/fd/%i", ifd);
+
255 res = truncate(procname, attr->st_size);
+
256 }
+
257 if (res == -1)
+
258 goto out_err;
+
259 }
+
260 if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
261 struct timespec tv[2];
+
262
+
263 tv[0].tv_sec = 0;
+
264 tv[1].tv_sec = 0;
+
265 tv[0].tv_nsec = UTIME_OMIT;
+
266 tv[1].tv_nsec = UTIME_OMIT;
+
267
+
268 if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
269 tv[0].tv_nsec = UTIME_NOW;
+
270 else if (valid & FUSE_SET_ATTR_ATIME)
+
271 tv[0] = attr->st_atim;
+
272
+
273 if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
274 tv[1].tv_nsec = UTIME_NOW;
+
275 else if (valid & FUSE_SET_ATTR_MTIME)
+
276 tv[1] = attr->st_mtim;
+
277
+
278 if (fi)
+
279 res = futimens(fi->fh, tv);
+
280 else {
+
281 sprintf(procname, "/proc/self/fd/%i", ifd);
+
282 res = utimensat(AT_FDCWD, procname, tv, 0);
+
283 }
+
284 if (res == -1)
+
285 goto out_err;
+
286 }
+
287
+
288 return lo_getattr(req, ino, fi);
+
289
+
290out_err:
+
291 saverr = errno;
+
292 fuse_reply_err(req, saverr);
+
293}
+
294
+
295static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+
296{
+
297 struct lo_inode *p;
+
298 struct lo_inode *ret = NULL;
+
299
+
300 pthread_mutex_lock(&lo->mutex);
+
301 for (p = lo->root.next; p != &lo->root; p = p->next) {
+
302 if (p->ino == st->st_ino && p->dev == st->st_dev) {
+
303 assert(p->refcount > 0);
+
304 ret = p;
+
305 ret->refcount++;
+
306 break;
+
307 }
+
308 }
+
309 pthread_mutex_unlock(&lo->mutex);
+
310 return ret;
+
311}
+
312
+
313
+
314static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
+
315{
+
316 struct lo_inode *inode = NULL;
+
317 struct lo_inode *prev, *next;
+
318
+
319 inode = calloc(1, sizeof(struct lo_inode));
+
320 if (!inode)
+
321 return NULL;
+
322
+
323 inode->refcount = 1;
+
324 inode->fd = fd;
+
325 inode->ino = e->attr.st_ino;
+
326 inode->dev = e->attr.st_dev;
+
327
+
328 pthread_mutex_lock(&lo->mutex);
+
329 prev = &lo->root;
+
330 next = prev->next;
+
331 next->prev = inode;
+
332 inode->next = next;
+
333 inode->prev = prev;
+
334 prev->next = inode;
+
335 pthread_mutex_unlock(&lo->mutex);
+
336 return inode;
+
337}
+
338
+
339static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
+
340{
+
341 int res;
+
342 struct lo_data *lo = lo_data(req);
+
343
+
344 memset(e, 0, sizeof(*e));
+
345 e->attr_timeout = lo->timeout;
+
346 e->entry_timeout = lo->timeout;
+
347
+
348 res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
349 if (res == -1)
+
350 return errno;
+
351
+
352 e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
+
353
+
354 if (lo_debug(req))
+
355 fuse_log(FUSE_LOG_DEBUG, " %lli/%lli -> %lli\n",
+
356 (unsigned long long) parent, fd, (unsigned long long) e->ino);
+
357
+
358 return 0;
+
359
+
360}
+
361
+
362static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
+
363 struct fuse_entry_param *e)
+
364{
+
365 int newfd;
+
366 int res;
+
367 int saverr;
+
368 struct lo_data *lo = lo_data(req);
+
369 struct lo_inode *inode;
+
370
+
371 memset(e, 0, sizeof(*e));
+
372 e->attr_timeout = lo->timeout;
+
373 e->entry_timeout = lo->timeout;
+
374
+
375 newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
+
376 if (newfd == -1)
+
377 goto out_err;
+
378
+
379 res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
380 if (res == -1)
+
381 goto out_err;
+
382
+
383 inode = lo_find(lo_data(req), &e->attr);
+
384 if (inode) {
+
385 close(newfd);
+
386 newfd = -1;
+
387 } else {
+
388 inode = create_new_inode(newfd, e, lo);
+
389 if (!inode)
+
390 goto out_err;
+
391 }
+
392 e->ino = (uintptr_t) inode;
+
393
+
394 if (lo_debug(req))
+
395 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
396 (unsigned long long) parent, name, (unsigned long long) e->ino);
+
397
+
398 return 0;
+
399
+
400out_err:
+
401 saverr = errno;
+
402 if (newfd != -1)
+
403 close(newfd);
+
404 return saverr;
+
405}
+
406
+
407static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
408{
+
409 struct fuse_entry_param e;
+
410 int err;
+
411
+
412 if (lo_debug(req))
+
413 fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
+
414 parent, name);
+
415
+
416 err = lo_do_lookup(req, parent, name, &e);
+
417 if (err)
+
418 fuse_reply_err(req, err);
+
419 else
+
420 fuse_reply_entry(req, &e);
+
421}
+
422
+
423static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
+
424 const char *name, mode_t mode, dev_t rdev,
+
425 const char *link)
+
426{
+
427 int res;
+
428 int saverr;
+
429 struct lo_inode *dir = lo_inode(req, parent);
+
430 struct fuse_entry_param e;
+
431
+
432 res = mknod_wrapper(dir->fd, name, link, mode, rdev);
+
433
+
434 saverr = errno;
+
435 if (res == -1)
+
436 goto out;
+
437
+
438 saverr = lo_do_lookup(req, parent, name, &e);
+
439 if (saverr)
+
440 goto out;
+
441
+
442 if (lo_debug(req))
+
443 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
444 (unsigned long long) parent, name, (unsigned long long) e.ino);
+
445
+
446 fuse_reply_entry(req, &e);
+
447 return;
+
448
+
449out:
+
450 fuse_reply_err(req, saverr);
+
451}
+
452
+
453static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
+
454 const char *name, mode_t mode, dev_t rdev)
+
455{
+
456 lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
+
457}
+
458
+
459static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
460 mode_t mode)
+
461{
+
462 lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
+
463}
+
464
+
465static void lo_symlink(fuse_req_t req, const char *link,
+
466 fuse_ino_t parent, const char *name)
+
467{
+
468 lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
+
469}
+
470
+
471static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
+
472 const char *name)
+
473{
+
474 int res;
+
475 struct lo_data *lo = lo_data(req);
+
476 struct lo_inode *inode = lo_inode(req, ino);
+
477 struct fuse_entry_param e;
+
478 char procname[64];
+
479 int saverr;
+
480
+
481 memset(&e, 0, sizeof(struct fuse_entry_param));
+
482 e.attr_timeout = lo->timeout;
+
483 e.entry_timeout = lo->timeout;
+
484
+
485 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
486 res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
+
487 AT_SYMLINK_FOLLOW);
+
488 if (res == -1)
+
489 goto out_err;
+
490
+
491 res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
492 if (res == -1)
+
493 goto out_err;
+
494
+
495 pthread_mutex_lock(&lo->mutex);
+
496 inode->refcount++;
+
497 pthread_mutex_unlock(&lo->mutex);
+
498 e.ino = (uintptr_t) inode;
+
499
+
500 if (lo_debug(req))
+
501 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
502 (unsigned long long) parent, name,
+
503 (unsigned long long) e.ino);
+
504
+
505 fuse_reply_entry(req, &e);
+
506 return;
+
507
+
508out_err:
+
509 saverr = errno;
+
510 fuse_reply_err(req, saverr);
+
511}
+
512
+
513static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
514{
+
515 int res;
+
516
+
517 res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
+
518
+
519 fuse_reply_err(req, res == -1 ? errno : 0);
+
520}
+
521
+
522static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
+
523 fuse_ino_t newparent, const char *newname,
+
524 unsigned int flags)
+
525{
+
526 int res;
+
527
+
528 if (flags) {
+
529 fuse_reply_err(req, EINVAL);
+
530 return;
+
531 }
+
532
+
533 res = renameat(lo_fd(req, parent), name,
+
534 lo_fd(req, newparent), newname);
+
535
+
536 fuse_reply_err(req, res == -1 ? errno : 0);
+
537}
+
538
+
539static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
+
540{
+
541 int res;
+
542
+
543 res = unlinkat(lo_fd(req, parent), name, 0);
+
544
+
545 fuse_reply_err(req, res == -1 ? errno : 0);
+
546}
+
547
+
548static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
+
549{
+
550 if (!inode)
+
551 return;
+
552
+
553 pthread_mutex_lock(&lo->mutex);
+
554 assert(inode->refcount >= n);
+
555 inode->refcount -= n;
+
556 if (!inode->refcount) {
+
557 struct lo_inode *prev, *next;
+
558
+
559 prev = inode->prev;
+
560 next = inode->next;
+
561 next->prev = prev;
+
562 prev->next = next;
+
563
+
564 pthread_mutex_unlock(&lo->mutex);
+
565 close(inode->fd);
+
566 free(inode);
+
567
+
568 } else {
+
569 pthread_mutex_unlock(&lo->mutex);
+
570 }
+
571}
+
572
+
573static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
574{
+
575 struct lo_data *lo = lo_data(req);
+
576 struct lo_inode *inode = lo_inode(req, ino);
+
577
+
578 if (lo_debug(req)) {
+
579 fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
+
580 (unsigned long long) ino,
+
581 (unsigned long long) inode->refcount,
+
582 (unsigned long long) nlookup);
+
583 }
+
584
+
585 unref_inode(lo, inode, nlookup);
+
586}
+
587
+
588static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
589{
+
590 lo_forget_one(req, ino, nlookup);
+
591 fuse_reply_none(req);
+
592}
+
593
+
594static void lo_forget_multi(fuse_req_t req, size_t count,
+
595 struct fuse_forget_data *forgets)
+
596{
+
597 int i;
+
598
+
599 for (i = 0; i < count; i++)
+
600 lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
+
601 fuse_reply_none(req);
+
602}
+
603
+
604static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
+
605{
+
606 char buf[PATH_MAX + 1];
+
607 int res;
+
608
+
609 res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
+
610 if (res == -1)
+
611 return (void) fuse_reply_err(req, errno);
+
612
+
613 if (res == sizeof(buf))
+
614 return (void) fuse_reply_err(req, ENAMETOOLONG);
+
615
+
616 buf[res] = '\0';
+
617
+
618 fuse_reply_readlink(req, buf);
+
619}
+
620
+
621struct lo_dirp {
+
622 DIR *dp;
+
623 struct dirent *entry;
+
624 off_t offset;
+
625};
+
626
+
627static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
+
628{
+
629 return (struct lo_dirp *) (uintptr_t) fi->fh;
+
630}
+
631
+
632static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
633{
+
634 int error = ENOMEM;
+
635 struct lo_data *lo = lo_data(req);
+
636 struct lo_dirp *d;
+
637 int fd = -1;
+
638
+
639 d = calloc(1, sizeof(struct lo_dirp));
+
640 if (d == NULL)
+
641 goto out_err;
+
642
+
643 fd = openat(lo_fd(req, ino), ".", O_RDONLY);
+
644 if (fd == -1)
+
645 goto out_errno;
+
646
+
647 d->dp = fdopendir(fd);
+
648 if (d->dp == NULL)
+
649 goto out_errno;
+
650
+
651 d->offset = 0;
+
652 d->entry = NULL;
+
653
+
654 fi->fh = (uintptr_t) d;
+
655 if (lo->cache != CACHE_NEVER)
+
656 fi->cache_readdir = 1;
+
657 if (lo->cache == CACHE_ALWAYS)
+
658 fi->keep_cache = 1;
+
659 fuse_reply_open(req, fi);
+
660 return;
+
661
+
662out_errno:
+
663 error = errno;
+
664out_err:
+
665 if (d) {
+
666 if (fd != -1)
+
667 close(fd);
+
668 free(d);
+
669 }
+
670 fuse_reply_err(req, error);
+
671}
+
672
+
673static int is_dot_or_dotdot(const char *name)
+
674{
+
675 return name[0] == '.' && (name[1] == '\0' ||
+
676 (name[1] == '.' && name[2] == '\0'));
+
677}
+
678
+
679static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
680 off_t offset, struct fuse_file_info *fi, int plus)
+
681{
+
682 struct lo_dirp *d = lo_dirp(fi);
+
683 char *buf;
+
684 char *p;
+
685 size_t rem = size;
+
686 int err;
+
687
+
688 (void) ino;
+
689
+
690 buf = calloc(1, size);
+
691 if (!buf) {
+
692 err = ENOMEM;
+
693 goto error;
+
694 }
+
695 p = buf;
+
696
+
697 if (offset != d->offset) {
+
698 seekdir(d->dp, offset);
+
699 d->entry = NULL;
+
700 d->offset = offset;
+
701 }
+
702 while (1) {
+
703 size_t entsize;
+
704 off_t nextoff;
+
705 const char *name;
+
706
+
707 if (!d->entry) {
+
708 errno = 0;
+
709 d->entry = readdir(d->dp);
+
710 if (!d->entry) {
+
711 if (errno) { // Error
+
712 err = errno;
+
713 goto error;
+
714 } else { // End of stream
+
715 break;
+
716 }
+
717 }
+
718 }
+
719 nextoff = d->entry->d_off;
+
720 name = d->entry->d_name;
+
721 fuse_ino_t entry_ino = 0;
+
722 if (plus) {
+
723 struct fuse_entry_param e;
+
724 if (is_dot_or_dotdot(name)) {
+
725 e = (struct fuse_entry_param) {
+
726 .attr.st_ino = d->entry->d_ino,
+
727 .attr.st_mode = d->entry->d_type << 12,
+
728 };
+
729 } else {
+
730 err = lo_do_lookup(req, ino, name, &e);
+
731 if (err)
+
732 goto error;
+
733 entry_ino = e.ino;
+
734 }
+
735
+
736 entsize = fuse_add_direntry_plus(req, p, rem, name,
+
737 &e, nextoff);
+
738 } else {
+
739 struct stat st = {
+
740 .st_ino = d->entry->d_ino,
+
741 .st_mode = d->entry->d_type << 12,
+
742 };
+
743 entsize = fuse_add_direntry(req, p, rem, name,
+
744 &st, nextoff);
+
745 }
+
746 if (entsize > rem) {
+
747 if (entry_ino != 0)
+
748 lo_forget_one(req, entry_ino, 1);
+
749 break;
+
750 }
+
751
+
752 p += entsize;
+
753 rem -= entsize;
+
754
+
755 d->entry = NULL;
+
756 d->offset = nextoff;
+
757 }
+
758
+
759 err = 0;
+
760error:
+
761 // If there's an error, we can only signal it if we haven't stored
+
762 // any entries yet - otherwise we'd end up with wrong lookup
+
763 // counts for the entries that are already in the buffer. So we
+
764 // return what we've collected until that point.
+
765 if (err && rem == size)
+
766 fuse_reply_err(req, err);
+
767 else
+
768 fuse_reply_buf(req, buf, size - rem);
+
769 free(buf);
+
770}
+
771
+
772static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
773 off_t offset, struct fuse_file_info *fi)
+
774{
+
775 lo_do_readdir(req, ino, size, offset, fi, 0);
+
776}
+
777
+
778static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
779 off_t offset, struct fuse_file_info *fi)
+
780{
+
781 lo_do_readdir(req, ino, size, offset, fi, 1);
+
782}
+
783
+
784static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
785{
+
786 struct lo_dirp *d = lo_dirp(fi);
+
787 (void) ino;
+
788 closedir(d->dp);
+
789 free(d);
+
790 fuse_reply_err(req, 0);
+
791}
+
792
+
793static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
+
794 mode_t mode, struct fuse_file_info *fi)
+
795{
+
796 int fd;
+
797 struct lo_data *lo = lo_data(req);
+
798 struct fuse_entry_param e;
+
799 int err;
+
800
+
801 if (lo_debug(req))
+
802 fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
+
803 parent);
+
804
+
805 fd = openat(lo_fd(req, parent), ".",
+
806 (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
+
807 if (fd == -1)
+
808 return (void) fuse_reply_err(req, errno);
+
809
+
810 fi->fh = fd;
+
811 if (lo->cache == CACHE_NEVER)
+
812 fi->direct_io = 1;
+
813 else if (lo->cache == CACHE_ALWAYS)
+
814 fi->keep_cache = 1;
+
815
+
816 /* parallel_direct_writes feature depends on direct_io features.
+
817 To make parallel_direct_writes valid, need set fi->direct_io
+
818 in current function. */
+
819 fi->parallel_direct_writes = 1;
+
820
+
821 err = fill_entry_param_new_inode(req, parent, fd, &e);
+
822 if (err)
+
823 fuse_reply_err(req, err);
+
824 else
+
825 fuse_reply_create(req, &e, fi);
+
826}
+
827
+
828static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+
829 mode_t mode, struct fuse_file_info *fi)
+
830{
+
831 int fd;
+
832 struct lo_data *lo = lo_data(req);
+
833 struct fuse_entry_param e;
+
834 int err;
+
835
+
836 if (lo_debug(req))
+
837 fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
+
838 parent, name);
+
839
+
840 fd = openat(lo_fd(req, parent), name,
+
841 (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
+
842 if (fd == -1)
+
843 return (void) fuse_reply_err(req, errno);
+
844
+
845 fi->fh = fd;
+
846 if (lo->cache == CACHE_NEVER)
+
847 fi->direct_io = 1;
+
848 else if (lo->cache == CACHE_ALWAYS)
+
849 fi->keep_cache = 1;
+
850
+
851 /* parallel_direct_writes feature depends on direct_io features.
+
852 To make parallel_direct_writes valid, need set fi->direct_io
+
853 in current function. */
+ +
855
+
856 err = lo_do_lookup(req, parent, name, &e);
+
857 if (err)
+
858 fuse_reply_err(req, err);
+
859 else
+
860 fuse_reply_create(req, &e, fi);
+
861}
+
862
+
863static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
864 struct fuse_file_info *fi)
+
865{
+
866 int res;
+
867 int fd = dirfd(lo_dirp(fi)->dp);
+
868 (void) ino;
+
869 if (datasync)
+
870 res = fdatasync(fd);
+
871 else
+
872 res = fsync(fd);
+
873 fuse_reply_err(req, res == -1 ? errno : 0);
+
874}
+
875
+
876static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
877{
+
878 int fd;
+
879 char buf[64];
+
880 struct lo_data *lo = lo_data(req);
+
881
+
882 if (lo_debug(req))
+
883 fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
+
884 ino, fi->flags);
+
885
+
886 /* With writeback cache, kernel may send read requests even
+
887 when userspace opened write-only */
+
888 if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
+
889 fi->flags &= ~O_ACCMODE;
+
890 fi->flags |= O_RDWR;
+
891 }
+
892
+
893 /* With writeback cache, O_APPEND is handled by the kernel.
+
894 This breaks atomicity (since the file may change in the
+
895 underlying filesystem, so that the kernel's idea of the
+
896 end of the file isn't accurate anymore). In this example,
+
897 we just accept that. A more rigorous filesystem may want
+
898 to return an error here */
+
899 if (lo->writeback && (fi->flags & O_APPEND))
+
900 fi->flags &= ~O_APPEND;
+
901
+
902 sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
+
903 fd = open(buf, fi->flags & ~O_NOFOLLOW);
+
904 if (fd == -1)
+
905 return (void) fuse_reply_err(req, errno);
+
906
+
907 fi->fh = fd;
+
908 if (lo->cache == CACHE_NEVER)
+
909 fi->direct_io = 1;
+
910 else if (lo->cache == CACHE_ALWAYS)
+
911 fi->keep_cache = 1;
+
912
+
913 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
914 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
915 for writes to the same file in the kernel). */
+
916 if (fi->flags & O_DIRECT)
+
917 fi->direct_io = 1;
+
918
+
919 /* parallel_direct_writes feature depends on direct_io features.
+
920 To make parallel_direct_writes valid, need set fi->direct_io
+
921 in current function. */
+ +
923
+
924 fuse_reply_open(req, fi);
+
925}
+
926
+
927static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
928{
+
929 (void) ino;
+
930
+
931 close(fi->fh);
+
932 fuse_reply_err(req, 0);
+
933}
+
934
+
935static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
936{
+
937 int res;
+
938 (void) ino;
+
939 res = close(dup(fi->fh));
+
940 fuse_reply_err(req, res == -1 ? errno : 0);
+
941}
+
942
+
943static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
944 struct fuse_file_info *fi)
+
945{
+
946 int res;
+
947 (void) ino;
+
948 if (datasync)
+
949 res = fdatasync(fi->fh);
+
950 else
+
951 res = fsync(fi->fh);
+
952 fuse_reply_err(req, res == -1 ? errno : 0);
+
953}
+
954
+
955static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
956 off_t offset, struct fuse_file_info *fi)
+
957{
+
958 struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
+
959
+
960 if (lo_debug(req))
+
961 fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
+
962 "off=%lu)\n", ino, size, (unsigned long) offset);
+
963
+ +
965 buf.buf[0].fd = fi->fh;
+
966 buf.buf[0].pos = offset;
+
967
+ +
969}
+
970
+
971static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
+
972 struct fuse_bufvec *in_buf, off_t off,
+
973 struct fuse_file_info *fi)
+
974{
+
975 (void) ino;
+
976 ssize_t res;
+
977 struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
+
978
+ +
980 out_buf.buf[0].fd = fi->fh;
+
981 out_buf.buf[0].pos = off;
+
982
+
983 if (lo_debug(req))
+
984 fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%lu)\n",
+
985 ino, out_buf.buf[0].size, (unsigned long) off);
+
986
+
987 res = fuse_buf_copy(&out_buf, in_buf, 0);
+
988 if(res < 0)
+
989 fuse_reply_err(req, -res);
+
990 else
+
991 fuse_reply_write(req, (size_t) res);
+
992}
+
993
+
994static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
+
995{
+
996 int res;
+
997 struct statvfs stbuf;
+
998
+
999 res = fstatvfs(lo_fd(req, ino), &stbuf);
+
1000 if (res == -1)
+
1001 fuse_reply_err(req, errno);
+
1002 else
+
1003 fuse_reply_statfs(req, &stbuf);
+
1004}
+
1005
+
1006static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
1007 off_t offset, off_t length, struct fuse_file_info *fi)
+
1008{
+
1009 int err = EOPNOTSUPP;
+
1010 (void) ino;
+
1011
+
1012#ifdef HAVE_FALLOCATE
+
1013 err = fallocate(fi->fh, mode, offset, length);
+
1014 if (err < 0)
+
1015 err = errno;
+
1016
+
1017#elif defined(HAVE_POSIX_FALLOCATE)
+
1018 if (mode) {
+
1019 fuse_reply_err(req, EOPNOTSUPP);
+
1020 return;
+
1021 }
+
1022
+
1023 err = posix_fallocate(fi->fh, offset, length);
+
1024#endif
+
1025
+
1026 fuse_reply_err(req, err);
+
1027}
+
1028
+
1029static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
1030 int op)
+
1031{
+
1032 int res;
+
1033 (void) ino;
+
1034
+
1035 res = flock(fi->fh, op);
+
1036
+
1037 fuse_reply_err(req, res == -1 ? errno : 0);
+
1038}
+
1039
+
1040static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
1041 size_t size)
+
1042{
+
1043 char *value = NULL;
+
1044 char procname[64];
+
1045 struct lo_inode *inode = lo_inode(req, ino);
+
1046 ssize_t ret;
+
1047 int saverr;
+
1048
+
1049 saverr = ENOSYS;
+
1050 if (!lo_data(req)->xattr)
+
1051 goto out;
+
1052
+
1053 if (lo_debug(req)) {
+
1054 fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
+
1055 ino, name, size);
+
1056 }
+
1057
+
1058 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1059
+
1060 if (size) {
+
1061 value = malloc(size);
+
1062 if (!value)
+
1063 goto out_err;
+
1064
+
1065 ret = getxattr(procname, name, value, size);
+
1066 if (ret == -1)
+
1067 goto out_err;
+
1068 saverr = 0;
+
1069 if (ret == 0)
+
1070 goto out;
+
1071
+
1072 fuse_reply_buf(req, value, ret);
+
1073 } else {
+
1074 ret = getxattr(procname, name, NULL, 0);
+
1075 if (ret == -1)
+
1076 goto out_err;
+
1077
+
1078 fuse_reply_xattr(req, ret);
+
1079 }
+
1080out_free:
+
1081 free(value);
+
1082 return;
+
1083
+
1084out_err:
+
1085 saverr = errno;
+
1086out:
+
1087 fuse_reply_err(req, saverr);
+
1088 goto out_free;
+
1089}
+
1090
+
1091static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
1092{
+
1093 char *value = NULL;
+
1094 char procname[64];
+
1095 struct lo_inode *inode = lo_inode(req, ino);
+
1096 ssize_t ret;
+
1097 int saverr;
+
1098
+
1099 saverr = ENOSYS;
+
1100 if (!lo_data(req)->xattr)
+
1101 goto out;
+
1102
+
1103 if (lo_debug(req)) {
+
1104 fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
+
1105 ino, size);
+
1106 }
+
1107
+
1108 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1109
+
1110 if (size) {
+
1111 value = malloc(size);
+
1112 if (!value)
+
1113 goto out_err;
+
1114
+
1115 ret = listxattr(procname, value, size);
+
1116 if (ret == -1)
+
1117 goto out_err;
+
1118 saverr = 0;
+
1119 if (ret == 0)
+
1120 goto out;
+
1121
+
1122 fuse_reply_buf(req, value, ret);
+
1123 } else {
+
1124 ret = listxattr(procname, NULL, 0);
+
1125 if (ret == -1)
+
1126 goto out_err;
+
1127
+
1128 fuse_reply_xattr(req, ret);
+
1129 }
+
1130out_free:
+
1131 free(value);
+
1132 return;
+
1133
+
1134out_err:
+
1135 saverr = errno;
+
1136out:
+
1137 fuse_reply_err(req, saverr);
+
1138 goto out_free;
+
1139}
+
1140
+
1141static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
1142 const char *value, size_t size, int flags)
+
1143{
+
1144 char procname[64];
+
1145 struct lo_inode *inode = lo_inode(req, ino);
+
1146 ssize_t ret;
+
1147 int saverr;
+
1148
+
1149 saverr = ENOSYS;
+
1150 if (!lo_data(req)->xattr)
+
1151 goto out;
+
1152
+
1153 if (lo_debug(req)) {
+
1154 fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
+
1155 ino, name, value, size);
+
1156 }
+
1157
+
1158 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1159
+
1160 ret = setxattr(procname, name, value, size, flags);
+
1161 saverr = ret == -1 ? errno : 0;
+
1162
+
1163out:
+
1164 fuse_reply_err(req, saverr);
+
1165}
+
1166
+
1167static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
1168{
+
1169 char procname[64];
+
1170 struct lo_inode *inode = lo_inode(req, ino);
+
1171 ssize_t ret;
+
1172 int saverr;
+
1173
+
1174 saverr = ENOSYS;
+
1175 if (!lo_data(req)->xattr)
+
1176 goto out;
+
1177
+
1178 if (lo_debug(req)) {
+
1179 fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
+
1180 ino, name);
+
1181 }
+
1182
+
1183 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1184
+
1185 ret = removexattr(procname, name);
+
1186 saverr = ret == -1 ? errno : 0;
+
1187
+
1188out:
+
1189 fuse_reply_err(req, saverr);
+
1190}
+
1191
+
1192#ifdef HAVE_COPY_FILE_RANGE
+
1193static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
+
1194 struct fuse_file_info *fi_in,
+
1195 fuse_ino_t ino_out, off_t off_out,
+
1196 struct fuse_file_info *fi_out, size_t len,
+
1197 int flags)
+
1198{
+
1199 ssize_t res;
+
1200
+
1201 if (lo_debug(req))
+
1202 fuse_log(FUSE_LOG_DEBUG, "lo_copy_file_range(ino=%" PRIu64 "/fd=%lu, "
+
1203 "off=%lu, ino=%" PRIu64 "/fd=%lu, "
+
1204 "off=%lu, size=%zd, flags=0x%x)\n",
+
1205 ino_in, fi_in->fh, off_in, ino_out, fi_out->fh, off_out,
+
1206 len, flags);
+
1207
+
1208 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
1209 flags);
+
1210 if (res < 0)
+
1211 fuse_reply_err(req, errno);
+
1212 else
+
1213 fuse_reply_write(req, res);
+
1214}
+
1215#endif
+
1216
+
1217static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
1218 struct fuse_file_info *fi)
+
1219{
+
1220 off_t res;
+
1221
+
1222 (void)ino;
+
1223 res = lseek(fi->fh, off, whence);
+
1224 if (res != -1)
+
1225 fuse_reply_lseek(req, res);
+
1226 else
+
1227 fuse_reply_err(req, errno);
+
1228}
+
1229
+
1230static const struct fuse_lowlevel_ops lo_oper = {
+
1231 .init = lo_init,
+
1232 .destroy = lo_destroy,
+
1233 .lookup = lo_lookup,
+
1234 .mkdir = lo_mkdir,
+
1235 .mknod = lo_mknod,
+
1236 .symlink = lo_symlink,
+
1237 .link = lo_link,
+
1238 .unlink = lo_unlink,
+
1239 .rmdir = lo_rmdir,
+
1240 .rename = lo_rename,
+
1241 .forget = lo_forget,
+
1242 .forget_multi = lo_forget_multi,
+
1243 .getattr = lo_getattr,
+
1244 .setattr = lo_setattr,
+
1245 .readlink = lo_readlink,
+
1246 .opendir = lo_opendir,
+
1247 .readdir = lo_readdir,
+
1248 .readdirplus = lo_readdirplus,
+
1249 .releasedir = lo_releasedir,
+
1250 .fsyncdir = lo_fsyncdir,
+
1251 .create = lo_create,
+
1252 .tmpfile = lo_tmpfile,
+
1253 .open = lo_open,
+
1254 .release = lo_release,
+
1255 .flush = lo_flush,
+
1256 .fsync = lo_fsync,
+
1257 .read = lo_read,
+
1258 .write_buf = lo_write_buf,
+
1259 .statfs = lo_statfs,
+
1260 .fallocate = lo_fallocate,
+
1261 .flock = lo_flock,
+
1262 .getxattr = lo_getxattr,
+
1263 .listxattr = lo_listxattr,
+
1264 .setxattr = lo_setxattr,
+
1265 .removexattr = lo_removexattr,
+
1266#ifdef HAVE_COPY_FILE_RANGE
+
1267 .copy_file_range = lo_copy_file_range,
+
1268#endif
+
1269 .lseek = lo_lseek,
+
1270};
+
1271
+
1272int main(int argc, char *argv[])
+
1273{
+
1274 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
1275 struct fuse_session *se;
+
1276 struct fuse_cmdline_opts opts;
+
1277 struct fuse_loop_config *config;
+
1278 struct lo_data lo = { .debug = 0,
+
1279 .writeback = 0 };
+
1280 int ret = -1;
+
1281
+
1282 /* Don't mask creation mode, kernel already did that */
+
1283 umask(0);
+
1284
+
1285 pthread_mutex_init(&lo.mutex, NULL);
+
1286 lo.root.next = lo.root.prev = &lo.root;
+
1287 lo.root.fd = -1;
+
1288 lo.cache = CACHE_NORMAL;
+
1289
+
1290 if (fuse_parse_cmdline(&args, &opts) != 0)
+
1291 return 1;
+
1292 if (opts.show_help) {
+
1293 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
1296 passthrough_ll_help();
+
1297 ret = 0;
+
1298 goto err_out1;
+
1299 } else if (opts.show_version) {
+
1300 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
1302 ret = 0;
+
1303 goto err_out1;
+
1304 }
+
1305
+
1306 if(opts.mountpoint == NULL) {
+
1307 printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
1308 printf(" %s --help\n", argv[0]);
+
1309 ret = 1;
+
1310 goto err_out1;
+
1311 }
+
1312
+
1313 if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
+
1314 return 1;
+
1315
+
1316 lo.debug = opts.debug;
+
1317 lo.root.refcount = 2;
+
1318 if (lo.source) {
+
1319 struct stat stat;
+
1320 int res;
+
1321
+
1322 res = lstat(lo.source, &stat);
+
1323 if (res == -1) {
+
1324 fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
+
1325 lo.source);
+
1326 exit(1);
+
1327 }
+
1328 if (!S_ISDIR(stat.st_mode)) {
+
1329 fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
+
1330 exit(1);
+
1331 }
+
1332
+
1333 } else {
+
1334 lo.source = strdup("/");
+
1335 if(!lo.source) {
+
1336 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
1337 exit(1);
+
1338 }
+
1339 }
+
1340 if (!lo.timeout_set) {
+
1341 switch (lo.cache) {
+
1342 case CACHE_NEVER:
+
1343 lo.timeout = 0.0;
+
1344 break;
+
1345
+
1346 case CACHE_NORMAL:
+
1347 lo.timeout = 1.0;
+
1348 break;
+
1349
+
1350 case CACHE_ALWAYS:
+
1351 lo.timeout = 86400.0;
+
1352 break;
+
1353 }
+
1354 } else if (lo.timeout < 0) {
+
1355 fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
+
1356 lo.timeout);
+
1357 exit(1);
+
1358 }
+
1359
+
1360 lo.root.fd = open(lo.source, O_PATH);
+
1361 if (lo.root.fd == -1) {
+
1362 fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
+
1363 lo.source);
+
1364 exit(1);
+
1365 }
+
1366
+
1367 se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
+
1368 if (se == NULL)
+
1369 goto err_out1;
+
1370
+
1371 if (fuse_set_signal_handlers(se) != 0)
+
1372 goto err_out2;
+
1373
+
1374 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
1375 goto err_out3;
+
1376
+
1377 fuse_daemonize(opts.foreground);
+
1378
+
1379 /* Block until ctrl+c or fusermount -u */
+
1380 if (opts.singlethread)
+
1381 ret = fuse_session_loop(se);
+
1382 else {
+
1383 config = fuse_loop_cfg_create();
+
1384 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
1385 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
1386 ret = fuse_session_loop_mt(se, config);
+
1387 fuse_loop_cfg_destroy(config);
+
1388 config = NULL;
+
1389 }
+
1390
+ +
1392err_out3:
+ +
1394err_out2:
+ +
1396err_out1:
+
1397 free(opts.mountpoint);
+
1398 fuse_opt_free_args(&args);
+
1399
+
1400 if (lo.root.fd >= 0)
+
1401 close(lo.root.fd);
+
1402
+
1403 free(lo.source);
+
1404 return ret ? 1 : 0;
+
1405}
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
off_t pos
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
uint32_t capable
+
+
double entry_timeout
+
fuse_ino_t ino
+
double attr_timeout
+
struct stat attr
+ + +
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_84_2example_2poll_8c.html b/doc/html/fuse-3_817_84_2example_2poll_8c.html new file mode 100644 index 0000000..a2f364e --- /dev/null +++ b/doc/html/fuse-3_817_84_2example_2poll_8c.html @@ -0,0 +1,379 @@ + + + + + + + +libfuse: fuse-3.17.4/example/poll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
poll.c File Reference
+
+
+
#include <fuse.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include <pthread.h>
+#include <poll.h>
+#include <stdbool.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example illustrates how to write a FUSE file system that supports polling for changes that don't come through the kernel. It can be tested with the poll_client.c program.

+

Compile with:

gcc -Wall poll.c `pkg-config fuse3 --cflags --libs` -o poll
+

+Source code

+
/*
+
FUSE fsel: FUSE select example
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <unistd.h>
+
#include <ctype.h>
+
#include <string.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <errno.h>
+
#include <time.h>
+
#include <pthread.h>
+
#include <poll.h>
+
#include <stdbool.h>
+
+
/*
+
* fsel_open_mask is used to limit the number of opens to 1 per file.
+
* This is to use file index (0-F) as fh as poll support requires
+
* unique fh per open file. Lifting this would require proper open
+
* file management.
+
*/
+
static unsigned fsel_open_mask;
+
static const char fsel_hex_map[] = "0123456789ABCDEF";
+
static struct fuse *fsel_fuse; /* needed for poll notification */
+
+
#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
+
#define FSEL_FILES 16
+
+
static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
+
static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
+
static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
+
static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
+
static _Atomic bool fsel_stop = false;
+
static pthread_t fsel_producer_thread;
+
+
+
static int fsel_path_index(const char *path)
+
{
+
char ch = path[1];
+
+
if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
+
return -1;
+
return ch <= '9' ? ch - '0' : ch - 'A' + 10;
+
}
+
+
static void fsel_destroy(void *private_data)
+
{
+
(void)private_data;
+
+
fsel_stop = true;
+
+
pthread_join(fsel_producer_thread, NULL);
+
}
+
+
static int fsel_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int idx;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0555;
+
stbuf->st_nlink = 2;
+
return 0;
+
}
+
+
idx = fsel_path_index(path);
+
if (idx < 0)
+
return -ENOENT;
+
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = fsel_cnt[idx];
+
return 0;
+
}
+
+
static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
char name[2] = { };
+
int i;
+
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
name[0] = fsel_hex_map[i];
+
filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
}
+
+
return 0;
+
}
+
+
static int fsel_open(const char *path, struct fuse_file_info *fi)
+
{
+
int idx = fsel_path_index(path);
+
+
if (idx < 0)
+
return -ENOENT;
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
if (fsel_open_mask & (1 << idx))
+
return -EBUSY;
+
fsel_open_mask |= (1 << idx);
+
+
/*
+
* fsel files are nonseekable somewhat pipe-like files which
+
* gets filled up periodically by producer thread and consumed
+
* on read. Tell FUSE as such.
+
*/
+
fi->fh = idx;
+
fi->direct_io = 1;
+
fi->nonseekable = 1;
+
+
return 0;
+
}
+
+
static int fsel_release(const char *path, struct fuse_file_info *fi)
+
{
+
int idx = fi->fh;
+
+
(void) path;
+
+
fsel_open_mask &= ~(1 << idx);
+
return 0;
+
}
+
+
static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int idx = fi->fh;
+
+
(void) path;
+
(void) offset;
+
+
pthread_mutex_lock(&fsel_mutex);
+
if (fsel_cnt[idx] < size)
+
size = fsel_cnt[idx];
+
printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
+
fsel_cnt[idx] -= size;
+
pthread_mutex_unlock(&fsel_mutex);
+
+
memset(buf, fsel_hex_map[idx], size);
+
return size;
+
}
+
+
static int fsel_poll(const char *path, struct fuse_file_info *fi,
+
struct fuse_pollhandle *ph, unsigned *reventsp)
+
{
+
static unsigned polled_zero;
+
int idx = fi->fh;
+
+
(void) path;
+
+
/*
+
* Poll notification requires pointer to struct fuse which
+
* can't be obtained when using fuse_main(). As notification
+
* happens only after poll is called, fill it here from
+
* fuse_context.
+
*/
+
if (!fsel_fuse) {
+
struct fuse_context *cxt = fuse_get_context();
+
if (cxt)
+
fsel_fuse = cxt->fuse;
+
}
+
+
pthread_mutex_lock(&fsel_mutex);
+
+
if (ph != NULL) {
+
struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
+
+
if (oldph)
+ +
+
fsel_poll_notify_mask |= (1 << idx);
+
fsel_poll_handle[idx] = ph;
+
}
+
+
if (fsel_cnt[idx]) {
+
*reventsp |= POLLIN;
+
printf("POLL %X cnt=%u polled_zero=%u\n",
+
idx, fsel_cnt[idx], polled_zero);
+
polled_zero = 0;
+
} else
+
polled_zero++;
+
+
pthread_mutex_unlock(&fsel_mutex);
+
return 0;
+
}
+
+
static const struct fuse_operations fsel_oper = {
+
.destroy = fsel_destroy,
+
.getattr = fsel_getattr,
+
.readdir = fsel_readdir,
+
.open = fsel_open,
+
.release = fsel_release,
+
.read = fsel_read,
+
.poll = fsel_poll,
+
};
+
+
static void *fsel_producer(void *data)
+
{
+
const struct timespec interval = { 0, 250000000 };
+
unsigned idx = 0, nr = 1;
+
+
(void) data;
+
+
while (!fsel_stop) {
+
int i, t;
+
+
pthread_mutex_lock(&fsel_mutex);
+
+
/*
+
* This is the main producer loop which is executed
+
* ever 500ms. On each iteration, it fills one byte
+
* to 1, 2 or 4 files and sends poll notification if
+
* requested.
+
*/
+
for (i = 0, t = idx; i < nr;
+
i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
+
if (fsel_cnt[t] == FSEL_CNT_MAX)
+
continue;
+
+
fsel_cnt[t]++;
+
if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
+
struct fuse_pollhandle *ph;
+
+
printf("NOTIFY %X\n", t);
+
ph = fsel_poll_handle[t];
+
fuse_notify_poll(ph);
+ +
fsel_poll_notify_mask &= ~(1 << t);
+
fsel_poll_handle[t] = NULL;
+
}
+
}
+
+
idx = (idx + 1) % FSEL_FILES;
+
if (idx == 0)
+
nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
+
+
pthread_mutex_unlock(&fsel_mutex);
+
+
nanosleep(&interval, NULL);
+
}
+
+
return NULL;
+
}
+
+
int main(int argc, char *argv[])
+
{
+
pthread_attr_t attr;
+
int ret;
+
+
errno = pthread_mutex_init(&fsel_mutex, NULL);
+
if (errno) {
+
perror("pthread_mutex_init");
+
return 1;
+
}
+
+
errno = pthread_attr_init(&attr);
+
if (errno) {
+
perror("pthread_attr_init");
+
return 1;
+
}
+
+
errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
+
if (errno) {
+
perror("pthread_create");
+
return 1;
+
}
+
+
ret = fuse_main(argc, argv, &fsel_oper, NULL);
+
+
return ret;
+
}
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+ +
struct fuse * fuse
Definition fuse.h:862
+ + +
uint32_t nonseekable
Definition fuse_common.h:78
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+
+

Definition in file poll.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_84_2example_2poll_8c_source.html b/doc/html/fuse-3_817_84_2example_2poll_8c_source.html new file mode 100644 index 0000000..cc2ba2a --- /dev/null +++ b/doc/html/fuse-3_817_84_2example_2poll_8c_source.html @@ -0,0 +1,364 @@ + + + + + + + +libfuse: fuse-3.17.4/example/poll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
poll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fsel: FUSE select example
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
24#define FUSE_USE_VERSION 31
+
25
+
26#include <fuse.h>
+
27#include <unistd.h>
+
28#include <ctype.h>
+
29#include <string.h>
+
30#include <stdio.h>
+
31#include <stdlib.h>
+
32#include <errno.h>
+
33#include <time.h>
+
34#include <pthread.h>
+
35#include <poll.h>
+
36#include <stdbool.h>
+
37
+
38/*
+
39 * fsel_open_mask is used to limit the number of opens to 1 per file.
+
40 * This is to use file index (0-F) as fh as poll support requires
+
41 * unique fh per open file. Lifting this would require proper open
+
42 * file management.
+
43 */
+
44static unsigned fsel_open_mask;
+
45static const char fsel_hex_map[] = "0123456789ABCDEF";
+
46static struct fuse *fsel_fuse; /* needed for poll notification */
+
47
+
48#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
+
49#define FSEL_FILES 16
+
50
+
51static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
+
52static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
+
53static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
+
54static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
+
55static _Atomic bool fsel_stop = false;
+
56static pthread_t fsel_producer_thread;
+
57
+
58
+
59static int fsel_path_index(const char *path)
+
60{
+
61 char ch = path[1];
+
62
+
63 if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
+
64 return -1;
+
65 return ch <= '9' ? ch - '0' : ch - 'A' + 10;
+
66}
+
67
+
68static void fsel_destroy(void *private_data)
+
69{
+
70 (void)private_data;
+
71
+
72 fsel_stop = true;
+
73
+
74 pthread_join(fsel_producer_thread, NULL);
+
75}
+
76
+
77static int fsel_getattr(const char *path, struct stat *stbuf,
+
78 struct fuse_file_info *fi)
+
79{
+
80 (void) fi;
+
81 int idx;
+
82
+
83 memset(stbuf, 0, sizeof(struct stat));
+
84
+
85 if (strcmp(path, "/") == 0) {
+
86 stbuf->st_mode = S_IFDIR | 0555;
+
87 stbuf->st_nlink = 2;
+
88 return 0;
+
89 }
+
90
+
91 idx = fsel_path_index(path);
+
92 if (idx < 0)
+
93 return -ENOENT;
+
94
+
95 stbuf->st_mode = S_IFREG | 0444;
+
96 stbuf->st_nlink = 1;
+
97 stbuf->st_size = fsel_cnt[idx];
+
98 return 0;
+
99}
+
100
+
101static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
102 off_t offset, struct fuse_file_info *fi,
+
103 enum fuse_readdir_flags flags)
+
104{
+
105 char name[2] = { };
+
106 int i;
+
107
+
108 (void) offset;
+
109 (void) fi;
+
110 (void) flags;
+
111
+
112 if (strcmp(path, "/") != 0)
+
113 return -ENOENT;
+
114
+
115 for (i = 0; i < FSEL_FILES; i++) {
+
116 name[0] = fsel_hex_map[i];
+
117 filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
118 }
+
119
+
120 return 0;
+
121}
+
122
+
123static int fsel_open(const char *path, struct fuse_file_info *fi)
+
124{
+
125 int idx = fsel_path_index(path);
+
126
+
127 if (idx < 0)
+
128 return -ENOENT;
+
129 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
130 return -EACCES;
+
131 if (fsel_open_mask & (1 << idx))
+
132 return -EBUSY;
+
133 fsel_open_mask |= (1 << idx);
+
134
+
135 /*
+
136 * fsel files are nonseekable somewhat pipe-like files which
+
137 * gets filled up periodically by producer thread and consumed
+
138 * on read. Tell FUSE as such.
+
139 */
+
140 fi->fh = idx;
+
141 fi->direct_io = 1;
+
142 fi->nonseekable = 1;
+
143
+
144 return 0;
+
145}
+
146
+
147static int fsel_release(const char *path, struct fuse_file_info *fi)
+
148{
+
149 int idx = fi->fh;
+
150
+
151 (void) path;
+
152
+
153 fsel_open_mask &= ~(1 << idx);
+
154 return 0;
+
155}
+
156
+
157static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
+
158 struct fuse_file_info *fi)
+
159{
+
160 int idx = fi->fh;
+
161
+
162 (void) path;
+
163 (void) offset;
+
164
+
165 pthread_mutex_lock(&fsel_mutex);
+
166 if (fsel_cnt[idx] < size)
+
167 size = fsel_cnt[idx];
+
168 printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
+
169 fsel_cnt[idx] -= size;
+
170 pthread_mutex_unlock(&fsel_mutex);
+
171
+
172 memset(buf, fsel_hex_map[idx], size);
+
173 return size;
+
174}
+
175
+
176static int fsel_poll(const char *path, struct fuse_file_info *fi,
+
177 struct fuse_pollhandle *ph, unsigned *reventsp)
+
178{
+
179 static unsigned polled_zero;
+
180 int idx = fi->fh;
+
181
+
182 (void) path;
+
183
+
184 /*
+
185 * Poll notification requires pointer to struct fuse which
+
186 * can't be obtained when using fuse_main(). As notification
+
187 * happens only after poll is called, fill it here from
+
188 * fuse_context.
+
189 */
+
190 if (!fsel_fuse) {
+
191 struct fuse_context *cxt = fuse_get_context();
+
192 if (cxt)
+
193 fsel_fuse = cxt->fuse;
+
194 }
+
195
+
196 pthread_mutex_lock(&fsel_mutex);
+
197
+
198 if (ph != NULL) {
+
199 struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
+
200
+
201 if (oldph)
+ +
203
+
204 fsel_poll_notify_mask |= (1 << idx);
+
205 fsel_poll_handle[idx] = ph;
+
206 }
+
207
+
208 if (fsel_cnt[idx]) {
+
209 *reventsp |= POLLIN;
+
210 printf("POLL %X cnt=%u polled_zero=%u\n",
+
211 idx, fsel_cnt[idx], polled_zero);
+
212 polled_zero = 0;
+
213 } else
+
214 polled_zero++;
+
215
+
216 pthread_mutex_unlock(&fsel_mutex);
+
217 return 0;
+
218}
+
219
+
220static const struct fuse_operations fsel_oper = {
+
221 .destroy = fsel_destroy,
+
222 .getattr = fsel_getattr,
+
223 .readdir = fsel_readdir,
+
224 .open = fsel_open,
+
225 .release = fsel_release,
+
226 .read = fsel_read,
+
227 .poll = fsel_poll,
+
228};
+
229
+
230static void *fsel_producer(void *data)
+
231{
+
232 const struct timespec interval = { 0, 250000000 };
+
233 unsigned idx = 0, nr = 1;
+
234
+
235 (void) data;
+
236
+
237 while (!fsel_stop) {
+
238 int i, t;
+
239
+
240 pthread_mutex_lock(&fsel_mutex);
+
241
+
242 /*
+
243 * This is the main producer loop which is executed
+
244 * ever 500ms. On each iteration, it fills one byte
+
245 * to 1, 2 or 4 files and sends poll notification if
+
246 * requested.
+
247 */
+
248 for (i = 0, t = idx; i < nr;
+
249 i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
+
250 if (fsel_cnt[t] == FSEL_CNT_MAX)
+
251 continue;
+
252
+
253 fsel_cnt[t]++;
+
254 if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
+
255 struct fuse_pollhandle *ph;
+
256
+
257 printf("NOTIFY %X\n", t);
+
258 ph = fsel_poll_handle[t];
+
259 fuse_notify_poll(ph);
+ +
261 fsel_poll_notify_mask &= ~(1 << t);
+
262 fsel_poll_handle[t] = NULL;
+
263 }
+
264 }
+
265
+
266 idx = (idx + 1) % FSEL_FILES;
+
267 if (idx == 0)
+
268 nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
+
269
+
270 pthread_mutex_unlock(&fsel_mutex);
+
271
+
272 nanosleep(&interval, NULL);
+
273 }
+
274
+
275 return NULL;
+
276}
+
277
+
278int main(int argc, char *argv[])
+
279{
+
280 pthread_attr_t attr;
+
281 int ret;
+
282
+
283 errno = pthread_mutex_init(&fsel_mutex, NULL);
+
284 if (errno) {
+
285 perror("pthread_mutex_init");
+
286 return 1;
+
287 }
+
288
+
289 errno = pthread_attr_init(&attr);
+
290 if (errno) {
+
291 perror("pthread_attr_init");
+
292 return 1;
+
293 }
+
294
+
295 errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
+
296 if (errno) {
+
297 perror("pthread_create");
+
298 return 1;
+
299 }
+
300
+
301 ret = fuse_main(argc, argv, &fsel_oper, NULL);
+
302
+
303 return ret;
+
304}
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+ +
struct fuse * fuse
Definition fuse.h:862
+ + +
uint32_t nonseekable
Definition fuse_common.h:78
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2example_2poll__client_8c.html b/doc/html/fuse-3_817_84_2example_2poll__client_8c.html new file mode 100644 index 0000000..6a02abd --- /dev/null +++ b/doc/html/fuse-3_817_84_2example_2poll__client_8c.html @@ -0,0 +1,145 @@ + + + + + + + +libfuse: fuse-3.17.4/example/poll_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
poll_client.c File Reference
+
+
+
#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the poll.c example file systsem.

+

Compile with:

 gcc -Wall poll_client.c -o poll_client
+

+Source code

+
/*
+
FUSE fselclient: FUSE select example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#include <sys/select.h>
+
#include <sys/time.h>
+
#include <sys/types.h>
+
#include <sys/stat.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <ctype.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <errno.h>
+
+
#define FSEL_FILES 16
+
+
int main(void)
+
{
+
static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
+
int fds[FSEL_FILES];
+
int i, nfds, tries;
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
char name[] = { hex_map[i], '\0' };
+
fds[i] = open(name, O_RDONLY);
+
if (fds[i] < 0) {
+
perror("open");
+
return 1;
+
}
+
}
+
nfds = fds[FSEL_FILES - 1] + 1;
+
+
for(tries=0; tries < 16; tries++) {
+
static char buf[4096];
+
fd_set rfds;
+
int rc;
+
+
FD_ZERO(&rfds);
+
for (i = 0; i < FSEL_FILES; i++)
+
FD_SET(fds[i], &rfds);
+
+
rc = select(nfds, &rfds, NULL, NULL, NULL);
+
+
if (rc < 0) {
+
perror("select");
+
return 1;
+
}
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
if (!FD_ISSET(fds[i], &rfds)) {
+
printf("_: ");
+
continue;
+
}
+
printf("%X:", i);
+
rc = read(fds[i], buf, sizeof(buf));
+
if (rc < 0) {
+
perror("read");
+
return 1;
+
}
+
printf("%02d ", rc);
+
}
+
printf("\n");
+
}
+
return 0;
+
}
+
+

Definition in file poll_client.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_84_2example_2poll__client_8c_source.html b/doc/html/fuse-3_817_84_2example_2poll__client_8c_source.html new file mode 100644 index 0000000..e4dc4c8 --- /dev/null +++ b/doc/html/fuse-3_817_84_2example_2poll__client_8c_source.html @@ -0,0 +1,131 @@ + + + + + + + +libfuse: fuse-3.17.4/example/poll_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
poll_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fselclient: FUSE select example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
23#include <sys/select.h>
+
24#include <sys/time.h>
+
25#include <sys/types.h>
+
26#include <sys/stat.h>
+
27#include <fcntl.h>
+
28#include <unistd.h>
+
29#include <ctype.h>
+
30#include <stdio.h>
+
31#include <stdlib.h>
+
32#include <errno.h>
+
33
+
34#define FSEL_FILES 16
+
35
+
36int main(void)
+
37{
+
38 static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
+
39 int fds[FSEL_FILES];
+
40 int i, nfds, tries;
+
41
+
42 for (i = 0; i < FSEL_FILES; i++) {
+
43 char name[] = { hex_map[i], '\0' };
+
44 fds[i] = open(name, O_RDONLY);
+
45 if (fds[i] < 0) {
+
46 perror("open");
+
47 return 1;
+
48 }
+
49 }
+
50 nfds = fds[FSEL_FILES - 1] + 1;
+
51
+
52 for(tries=0; tries < 16; tries++) {
+
53 static char buf[4096];
+
54 fd_set rfds;
+
55 int rc;
+
56
+
57 FD_ZERO(&rfds);
+
58 for (i = 0; i < FSEL_FILES; i++)
+
59 FD_SET(fds[i], &rfds);
+
60
+
61 rc = select(nfds, &rfds, NULL, NULL, NULL);
+
62
+
63 if (rc < 0) {
+
64 perror("select");
+
65 return 1;
+
66 }
+
67
+
68 for (i = 0; i < FSEL_FILES; i++) {
+
69 if (!FD_ISSET(fds[i], &rfds)) {
+
70 printf("_: ");
+
71 continue;
+
72 }
+
73 printf("%X:", i);
+
74 rc = read(fds[i], buf, sizeof(buf));
+
75 if (rc < 0) {
+
76 perror("read");
+
77 return 1;
+
78 }
+
79 printf("%02d ", rc);
+
80 }
+
81 printf("\n");
+
82 }
+
83 return 0;
+
84}
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2example_2printcap_8c.html b/doc/html/fuse-3_817_84_2example_2printcap_8c.html new file mode 100644 index 0000000..b8a9a19 --- /dev/null +++ b/doc/html/fuse-3_817_84_2example_2printcap_8c.html @@ -0,0 +1,240 @@ + + + + + + + +libfuse: fuse-3.17.4/example/printcap.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
printcap.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem that prints out all capabilities supported by the kernel and then exits.

+

Compile with:

gcc -Wall printcap.c `pkg-config fuse3 --cflags --libs` -o printcap
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <unistd.h>
+
#include <string.h>
+
#include <stdlib.h>
+
+
struct fuse_session *se;
+
+
// Define a structure to hold capability information
+
struct cap_info {
+
uint64_t flag;
+
const char *name;
+
};
+
+
// Define an array of all capabilities
+
static const struct cap_info capabilities[] = {
+
{FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
+
{FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
+
{FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
+
{FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
+
{FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
+
{FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
+
{FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
+
{FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
+
{FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
+
{FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
+
{FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
+
{FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
+
{FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
+
{FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
+
{FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
+
{FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
+
{FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
+
{FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
+
{FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
+
{FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
+
{FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
+
{FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
+
{FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
+
{FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
+
{FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
+
{FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
+
{FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
+
{FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
+
// Add any new capabilities here
+
{0, NULL} // Sentinel to mark the end of the array
+
};
+
+
static void print_capabilities(struct fuse_conn_info *conn)
+
{
+
printf("Capabilities:\n");
+
for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
+
if (fuse_get_feature_flag(conn, cap->flag)) {
+
printf("\t%s\n", cap->name);
+
}
+
}
+
}
+
+
static void pc_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void) userdata;
+
+
printf("Protocol version: %d.%d\n", conn->proto_major,
+
conn->proto_minor);
+
print_capabilities(conn);
+ +
}
+
+
+
static const struct fuse_lowlevel_ops pc_oper = {
+
.init = pc_init,
+
};
+
+
int main(int argc, char **argv)
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
char *mountpoint;
+
int ret = -1;
+
+
mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
+
if(mkdtemp(mountpoint) == NULL) {
+
perror("mkdtemp");
+
return 1;
+
}
+
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
+
se = fuse_session_new(&args, &pc_oper,
+
sizeof(pc_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, mountpoint) != 0)
+
goto err_out3;
+
+
ret = fuse_session_loop(se);
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
rmdir(mountpoint);
+
free(mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t proto_major
+
uint32_t proto_minor
+ +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file printcap.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_84_2example_2printcap_8c_source.html b/doc/html/fuse-3_817_84_2example_2printcap_8c_source.html new file mode 100644 index 0000000..00c23dd --- /dev/null +++ b/doc/html/fuse-3_817_84_2example_2printcap_8c_source.html @@ -0,0 +1,231 @@ + + + + + + + +libfuse: fuse-3.17.4/example/printcap.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
printcap.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
22#define FUSE_USE_VERSION 31
+
23
+
24#include <fuse_lowlevel.h>
+
25#include <stdio.h>
+
26#include <unistd.h>
+
27#include <string.h>
+
28#include <stdlib.h>
+
29
+
30struct fuse_session *se;
+
31
+
32// Define a structure to hold capability information
+
33struct cap_info {
+
34 uint64_t flag;
+
35 const char *name;
+
36};
+
37
+
38// Define an array of all capabilities
+
39static const struct cap_info capabilities[] = {
+
40 {FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
+
41 {FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
+
42 {FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
+
43 {FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
+
44 {FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
+
45 {FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
+
46 {FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
+
47 {FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
+
48 {FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
+
49 {FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
+
50 {FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
+
51 {FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
+
52 {FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
+
53 {FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
+
54 {FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
+
55 {FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
+
56 {FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
+
57 {FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
+
58 {FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
+
59 {FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
+
60 {FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
+
61 {FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
+
62 {FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
+
63 {FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
+
64 {FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
+
65 {FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
+
66 {FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
+
67 {FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
+
68 // Add any new capabilities here
+
69 {0, NULL} // Sentinel to mark the end of the array
+
70};
+
71
+
72static void print_capabilities(struct fuse_conn_info *conn)
+
73{
+
74 printf("Capabilities:\n");
+
75 for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
+
76 if (fuse_get_feature_flag(conn, cap->flag)) {
+
77 printf("\t%s\n", cap->name);
+
78 }
+
79 }
+
80}
+
81
+
82static void pc_init(void *userdata, struct fuse_conn_info *conn)
+
83{
+
84 (void) userdata;
+
85
+
86 printf("Protocol version: %d.%d\n", conn->proto_major,
+
87 conn->proto_minor);
+
88 print_capabilities(conn);
+ +
90}
+
91
+
92
+
93static const struct fuse_lowlevel_ops pc_oper = {
+
94 .init = pc_init,
+
95};
+
96
+
97int main(int argc, char **argv)
+
98{
+
99 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
100 char *mountpoint;
+
101 int ret = -1;
+
102
+
103 mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
+
104 if(mkdtemp(mountpoint) == NULL) {
+
105 perror("mkdtemp");
+
106 return 1;
+
107 }
+
108
+
109 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
111
+
112 se = fuse_session_new(&args, &pc_oper,
+
113 sizeof(pc_oper), NULL);
+
114 if (se == NULL)
+
115 goto err_out1;
+
116
+
117 if (fuse_set_signal_handlers(se) != 0)
+
118 goto err_out2;
+
119
+
120 if (fuse_session_mount(se, mountpoint) != 0)
+
121 goto err_out3;
+
122
+
123 ret = fuse_session_loop(se);
+
124
+ +
126err_out3:
+ +
128err_out2:
+ +
130err_out1:
+
131 rmdir(mountpoint);
+
132 free(mountpoint);
+
133 fuse_opt_free_args(&args);
+
134
+
135 return ret ? 1 : 0;
+
136}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t proto_major
+
uint32_t proto_minor
+ +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2include_2cuse__lowlevel_8h_source.html b/doc/html/fuse-3_817_84_2include_2cuse__lowlevel_8h_source.html new file mode 100644 index 0000000..9db339e --- /dev/null +++ b/doc/html/fuse-3_817_84_2include_2cuse__lowlevel_8h_source.html @@ -0,0 +1,152 @@ + + + + + + + +libfuse: fuse-3.17.4/include/cuse_lowlevel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_lowlevel.h
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file COPYING.LIB.
+
8
+
9 Read example/cusexmp.c for usages.
+
10*/
+
11
+
12#ifndef CUSE_LOWLEVEL_H_
+
13#define CUSE_LOWLEVEL_H_
+
14
+
15#ifndef FUSE_USE_VERSION
+
16#define FUSE_USE_VERSION 29
+
17#endif
+
18
+
19#include "fuse_lowlevel.h"
+
20
+
21#include <fcntl.h>
+
22#include <sys/types.h>
+
23#include <sys/uio.h>
+
24
+
25#ifdef __cplusplus
+
26extern "C" {
+
27#endif
+
28
+
29#define CUSE_UNRESTRICTED_IOCTL (1 << 0) /* use unrestricted ioctl */
+
30
+
31struct fuse_session;
+
32
+
33struct cuse_info {
+
34 unsigned dev_major;
+
35 unsigned dev_minor;
+
36 unsigned dev_info_argc;
+
37 const char **dev_info_argv;
+
38 unsigned flags;
+
39};
+
40
+
41/*
+
42 * Most ops behave almost identically to the matching fuse_lowlevel
+
43 * ops except that they don't take @ino.
+
44 *
+
45 * init_done : called after initialization is complete
+
46 * read/write : always direct IO, simultaneous operations allowed
+
47 * ioctl : might be in unrestricted mode depending on ci->flags
+
48 */
+
49struct cuse_lowlevel_ops {
+
50 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
51 void (*init_done) (void *userdata);
+
52 void (*destroy) (void *userdata);
+
53 void (*open) (fuse_req_t req, struct fuse_file_info *fi);
+
54 void (*read) (fuse_req_t req, size_t size, off_t off,
+
55 struct fuse_file_info *fi);
+
56 void (*write) (fuse_req_t req, const char *buf, size_t size, off_t off,
+
57 struct fuse_file_info *fi);
+
58 void (*flush) (fuse_req_t req, struct fuse_file_info *fi);
+
59 void (*release) (fuse_req_t req, struct fuse_file_info *fi);
+
60 void (*fsync) (fuse_req_t req, int datasync, struct fuse_file_info *fi);
+
61 void (*ioctl) (fuse_req_t req, int cmd, void *arg,
+
62 struct fuse_file_info *fi, unsigned int flags,
+
63 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
64 void (*poll) (fuse_req_t req, struct fuse_file_info *fi,
+
65 struct fuse_pollhandle *ph);
+
66};
+
67
+
68struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
69 const struct cuse_info *ci,
+
70 const struct cuse_lowlevel_ops *clop,
+
71 void *userdata);
+
72
+
73struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
74 const struct cuse_info *ci,
+
75 const struct cuse_lowlevel_ops *clop,
+
76 int *multithreaded, void *userdata);
+
77
+
78void cuse_lowlevel_teardown(struct fuse_session *se);
+
79
+
80int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
81 const struct cuse_lowlevel_ops *clop, void *userdata);
+
82
+
83#ifdef __cplusplus
+
84}
+
85#endif
+
86
+
87#endif /* CUSE_LOWLEVEL_H_ */
+
struct fuse_req * fuse_req_t
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_84_2include_2fuse_8h.html b/doc/html/fuse-3_817_84_2include_2fuse_8h.html new file mode 100644 index 0000000..5298bd3 --- /dev/null +++ b/doc/html/fuse-3_817_84_2include_2fuse_8h.html @@ -0,0 +1,838 @@ + + + + + + + +libfuse: fuse-3.17.4/include/fuse.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse.h File Reference
+
+
+
#include "fuse_common.h"
+#include <fcntl.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + + + +

+Data Structures

struct  fuse_config
 
struct  fuse_operations
 
struct  fuse_context
 
+ + + +

+Macros

#define FUSE_REGISTER_MODULE(name_, factory_)    fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
 
+ + + + + +

+Typedefs

typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
 
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
 
+ + + + + +

+Enumerations

enum  fuse_readdir_flags { FUSE_READDIR_DEFAULTS = 0 +, FUSE_READDIR_PLUS = (1 << 0) + }
 
enum  fuse_fill_dir_flags { FUSE_FILL_DIR_DEFAULTS = 0 +, FUSE_FILL_DIR_PLUS = (1 << 1) + }
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

int fuse_main_real_versioned (int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
 
void fuse_lib_help (struct fuse_args *args)
 
int fuse_mount (struct fuse *f, const char *mountpoint)
 
void fuse_unmount (struct fuse *f)
 
void fuse_destroy (struct fuse *f)
 
int fuse_loop (struct fuse *f)
 
void fuse_exit (struct fuse *f)
 
struct fuse_contextfuse_get_context (void)
 
int fuse_getgroups (int size, gid_t list[])
 
int fuse_interrupted (void)
 
int fuse_invalidate_path (struct fuse *f, const char *path)
 
int fuse_start_cleanup_thread (struct fuse *fuse)
 
void fuse_stop_cleanup_thread (struct fuse *fuse)
 
int fuse_clean_cache (struct fuse *fuse)
 
struct fuse_fs * fuse_fs_new (const struct fuse_operations *op, size_t op_size, void *private_data)
 
struct fuse_session * fuse_get_session (struct fuse *f)
 
int fuse_open_channel (const char *mountpoint, const char *options)
 
+

Detailed Description

+

This file defines the library interface of FUSE

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header.

+ +

Definition in file fuse.h.

+

Macro Definition Documentation

+ +

◆ FUSE_REGISTER_MODULE

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_REGISTER_MODULE( name_,
 factory_ 
)    fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+

Register filesystem module

+

If the "-omodules=*name*_:..." option is present, filesystem objects are created and pushed onto the stack with the factory_ function.

+
Parameters
+ + + +
name_the name of this filesystem module
factory_the factory function for this filesystem module
+
+
+ +

Definition at line 1395 of file fuse.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_fill_dir_t

+ +
+
+ + + + +
typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
+
+

Function to add an entry in a readdir() operation

+

The off parameter can be any non-zero value that enables the filesystem to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to indicate that seeking in directories is not supported.

+
Parameters
+ + + + + + +
bufthe buffer passed to the readdir() operation
namethe file name of the directory entry
stbuffile attributes, can be NULL
offoffset of the next entry or zero
flagsfill flags
+
+
+
Returns
1 if buffer is full, zero otherwise
+ +

Definition at line 87 of file fuse.h.

+ +
+
+ +

◆ fuse_module_factory_t

+ +
+
+ + + + +
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
+
+

Factory for creating filesystem objects

+

The function may use and remove options from 'args' that belong to this module.

+

For now the 'fs' vector always contains exactly one filesystem. This is the filesystem which will be below the newly created filesystem in the stack.

+
Parameters
+ + + +
argsthe command line arguments
fsNULL terminated filesystem object vector
+
+
+
Returns
the new filesystem object
+ +

Definition at line 1366 of file fuse.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_fill_dir_flags

+ +
+
+ + + + +
enum fuse_fill_dir_flags
+
+

Readdir flags, passed to fuse_fill_dir_t callback.

+ + +
Enumerator
FUSE_FILL_DIR_DEFAULTS 

"Plus" mode: all file attributes are valid

+

The attributes are used by the kernel to prefill the inode cache during a readdir.

+

It is okay to set FUSE_FILL_DIR_PLUS if FUSE_READDIR_PLUS is not set and vice versa.

+
+ +

Definition at line 58 of file fuse.h.

+ +
+
+ +

◆ fuse_readdir_flags

+ +
+
+ + + + +
enum fuse_readdir_flags
+
+

Readdir flags, passed to ->readdir()

+ + +
Enumerator
FUSE_READDIR_DEFAULTS 

"Plus" mode.

+

The kernel wants to prefill the inode cache during readdir. The filesystem may honour this by filling in the attributes and setting FUSE_FILL_DIR_FLAGS for the filler function. The filesystem may also just ignore this flag completely.

+
+ +

Definition at line 42 of file fuse.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_clean_cache()

+ +
+
+ + + + + + + + +
int fuse_clean_cache (struct fuse * fuse)
+
+

Iterate over cache removing stale entries use in conjunction with "-oremember"

+

NOTE: This is already done for the standard sessions

+
Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
the number of seconds until the next cleanup
+ +

Definition at line 4433 of file fuse.c.

+ +
+
+ +

◆ fuse_destroy()

+ +
+
+ + + + + + + + +
void fuse_destroy (struct fuse * f)
+
+

Destroy the FUSE handle.

+

NOTE: This function does not unmount the filesystem. If this is needed, call fuse_unmount() before calling this function.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5146 of file fuse.c.

+ +
+
+ +

◆ fuse_exit()

+ +
+
+ + + + + + + + +
void fuse_exit (struct fuse * f)
+
+

Flag session as terminated

+

This function will cause any running event loops to exit on the next opportunity.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 4639 of file fuse.c.

+ +
+
+ +

◆ fuse_fs_new()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
struct fuse_fs * fuse_fs_new (const struct fuse_operationsop,
size_t op_size,
void * private_data 
)
+
+

Create a new fuse filesystem object

+

This is usually called from the factory of a fuse module to create a new instance of a filesystem.

+
Parameters
+ + + + +
opthe filesystem operations
op_sizethe size of the fuse_operations structure
private_dataInitial value for the private_data field of struct fuse_context. May be overridden by the struct fuse_operations.init handler.
+
+
+
Returns
a new filesystem object
+ +

Definition at line 4854 of file fuse.c.

+ +
+
+ +

◆ fuse_get_context()

+ +
+
+ + + + + + + + +
struct fuse_context * fuse_get_context (void )
+
+

Get the current context

+

The context is only valid for the duration of a filesystem operation, and thus must not be stored and used later.

+
Returns
the context
+ +

Definition at line 4644 of file fuse.c.

+ +
+
+ +

◆ fuse_get_session()

+ +
+
+ + + + + + + + +
struct fuse_session * fuse_get_session (struct fuse * f)
+
+

Get session from fuse object

+ +

Definition at line 4520 of file fuse.c.

+ +
+
+ +

◆ fuse_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_getgroups (int size,
gid_t list[] 
)
+
+

Get the current supplementary group IDs for the current request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + +
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 4654 of file fuse.c.

+ +
+
+ +

◆ fuse_interrupted()

+ +
+
+ + + + + + + + +
int fuse_interrupted (void )
+
+

Check if the current request has already been interrupted

+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 4663 of file fuse.c.

+ +
+
+ +

◆ fuse_invalidate_path()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_invalidate_path (struct fuse * f,
const char * path 
)
+
+

Invalidates cache for the given path.

+

This calls fuse_lowlevel_notify_inval_inode internally.

+
Returns
0 on successful invalidation, negative error value otherwise. This routine may return -ENOENT to indicate that there was no entry to be invalidated, e.g., because the path has not been seen before or has been forgotten; this should not be considered to be an error.
+ +

Definition at line 4673 of file fuse.c.

+ +
+
+ +

◆ fuse_lib_help()

+ +
+
+ + + + + + + + +
void fuse_lib_help (struct fuse_argsargs)
+
+

Print available options (high- and low-level) to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+

The function looks at the argument vector only to determine if there are additional modules to be loaded (module=foo option), and attempts to call their help functions as well.

+
Parameters
+ + +
argsthe argument vector.
+
+
+ +

Definition at line 4744 of file fuse.c.

+ +
+
+ +

◆ fuse_loop()

+ +
+
+ + + + + + + + +
int fuse_loop (struct fuse * f)
+
+

FUSE event loop.

+

Requests from the kernel are processed, and the appropriate operations are called.

+

For a description of the return value and the conditions when the event loop exits, refer to the documentation of fuse_session_loop().

+
Parameters
+ + +
fthe FUSE handle
+
+
+
Returns
see fuse_session_loop()
+

See also: fuse_loop_mt()

+ +

Definition at line 4577 of file fuse.c.

+ +
+
+ +

◆ fuse_main_real_versioned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_main_real_versioned (int argc,
char * argv[],
const struct fuse_operationsop,
size_t op_size,
struct libfuse_versionversion,
void * user_data 
)
+
+

The real main function

+

Do not call this directly, use fuse_main()

+ +

Definition at line 307 of file helper.c.

+ +
+
+ +

◆ fuse_mount()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_mount (struct fuse * f,
const char * mountpoint 
)
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
fthe FUSE handle
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 5197 of file fuse.c.

+ +
+
+ +

◆ fuse_open_channel()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_open_channel (const char * mountpoint,
const char * options 
)
+
+

Open a FUSE file descriptor and set up the mount for the given mountpoint and flags.

+
Parameters
+ + + +
mountpointreference to the mount in the file system
optionsmount options
+
+
+
Returns
the FUSE file descriptor or -1 upon error
+ +

Definition at line 482 of file helper.c.

+ +
+
+ +

◆ fuse_start_cleanup_thread()

+ +
+
+ + + + + + + + +
int fuse_start_cleanup_thread (struct fuse * fuse)
+
+

Start the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
0 on success and -1 on error
+ +

Definition at line 4906 of file fuse.c.

+ +
+
+ +

◆ fuse_stop_cleanup_thread()

+ +
+
+ + + + + + + + +
void fuse_stop_cleanup_thread (struct fuse * fuse)
+
+

Stop the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+ +

Definition at line 4914 of file fuse.c.

+ +
+
+ +

◆ fuse_unmount()

+ +
+
+ + + + + + + + +
void fuse_unmount (struct fuse * f)
+
+

Unmount a FUSE file system.

+

See fuse_session_unmount() for additional information.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5202 of file fuse.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2include_2fuse_8h_source.html b/doc/html/fuse-3_817_84_2include_2fuse_8h_source.html new file mode 100644 index 0000000..2163590 --- /dev/null +++ b/doc/html/fuse-3_817_84_2include_2fuse_8h_source.html @@ -0,0 +1,647 @@ + + + + + + + +libfuse: fuse-3.17.4/include/fuse.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_H_
+
10#define FUSE_H_
+
11
+
19#include "fuse_common.h"
+
20
+
21#include <fcntl.h>
+
22#include <time.h>
+
23#include <sys/types.h>
+
24#include <sys/stat.h>
+
25#include <sys/statvfs.h>
+
26#include <sys/uio.h>
+
27
+
28#ifdef __cplusplus
+
29extern "C" {
+
30#endif
+
31
+
32/* ----------------------------------------------------------- *
+
33 * Basic FUSE API *
+
34 * ----------------------------------------------------------- */
+
35
+
37struct fuse;
+
38
+
+ + +
52 FUSE_READDIR_PLUS = (1 << 0)
+
53};
+
+
54
+
+ + +
69 FUSE_FILL_DIR_PLUS = (1 << 1)
+
70};
+
+
71
+
87typedef int (*fuse_fill_dir_t) (void *buf, const char *name,
+
88 const struct stat *stbuf, off_t off,
+
89 enum fuse_fill_dir_flags flags);
+
+ +
106 int32_t set_gid;
+
107 uint32_t gid;
+
108
+
113 int32_t set_uid;
+
114 uint32_t uid;
+
115
+
120 int32_t set_mode;
+
121 uint32_t umask;
+
122
+ +
128
+ +
138
+ +
144
+
148 int32_t intr;
+
149
+
155 int32_t intr_signal;
+
156
+
167 int32_t remember;
+
168
+
185 int32_t hard_remove;
+
186
+
198 int32_t use_ino;
+
199
+
207 int32_t readdir_ino;
+
208
+
226 int32_t direct_io;
+
227
+ +
246
+
253 int32_t auto_cache;
+
254
+
255 /*
+
256 * The timeout in seconds for which file attributes are cached
+
257 * for the purpose of checking if auto_cache should flush the
+
258 * file data on open.
+
259 */
+
260 int32_t ac_attr_timeout_set;
+
261 double ac_attr_timeout;
+
262
+
273 int32_t nullpath_ok;
+
274
+
279 int32_t show_help;
+
280 char *modules;
+
281 int32_t debug;
+
282
+
288 uint32_t fmask;
+
289 uint32_t dmask;
+
290
+ +
298
+ +
313
+
314
+
318 uint32_t flags;
+
319
+
323 uint64_t reserved[48];
+
324};
+
+
325
+
326
+
+ +
361 int (*getattr) (const char *, struct stat *, struct fuse_file_info *fi);
+
362
+
371 int (*readlink) (const char *, char *, size_t);
+
372
+
379 int (*mknod) (const char *, mode_t, dev_t);
+
380
+
387 int (*mkdir) (const char *, mode_t);
+
388
+
390 int (*unlink) (const char *);
+
391
+
393 int (*rmdir) (const char *);
+
394
+
396 int (*symlink) (const char *, const char *);
+
397
+
407 int (*rename) (const char *, const char *, unsigned int flags);
+
408
+
410 int (*link) (const char *, const char *);
+
411
+
417 int (*chmod) (const char *, mode_t, struct fuse_file_info *fi);
+
418
+
427 int (*chown) (const char *, uid_t, gid_t, struct fuse_file_info *fi);
+
428
+
437 int (*truncate) (const char *, off_t, struct fuse_file_info *fi);
+
438
+
486 int (*open) (const char *, struct fuse_file_info *);
+
487
+
497 int (*read) (const char *, char *, size_t, off_t,
+
498 struct fuse_file_info *);
+
499
+
509 int (*write) (const char *, const char *, size_t, off_t,
+
510 struct fuse_file_info *);
+
511
+
516 int (*statfs) (const char *, struct statvfs *);
+
517
+
546 int (*flush) (const char *, struct fuse_file_info *);
+
547
+
560 int (*release) (const char *, struct fuse_file_info *);
+
561
+
567 int (*fsync) (const char *, int, struct fuse_file_info *);
+
568
+
570 int (*setxattr) (const char *, const char *, const char *, size_t, int);
+
571
+
573 int (*getxattr) (const char *, const char *, char *, size_t);
+
574
+
576 int (*listxattr) (const char *, char *, size_t);
+
577
+
579 int (*removexattr) (const char *, const char *);
+
580
+
589 int (*opendir) (const char *, struct fuse_file_info *);
+
590
+
613 int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t,
+
614 struct fuse_file_info *, enum fuse_readdir_flags);
+
615
+
621 int (*releasedir) (const char *, struct fuse_file_info *);
+
622
+
631 int (*fsyncdir) (const char *, int, struct fuse_file_info *);
+
632
+
641 void *(*init) (struct fuse_conn_info *conn,
+
642 struct fuse_config *cfg);
+
643
+
649 void (*destroy) (void *private_data);
+
650
+
660 int (*access) (const char *, int);
+
661
+
672 int (*create) (const char *, mode_t, struct fuse_file_info *);
+
673
+
704 int (*lock) (const char *, struct fuse_file_info *, int cmd,
+
705 struct flock *);
+
706
+
719 int (*utimens) (const char *, const struct timespec tv[2],
+
720 struct fuse_file_info *fi);
+
721
+
728 int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
+
729
+
730#if FUSE_USE_VERSION < 35
+
731 int (*ioctl) (const char *, int cmd, void *arg,
+
732 struct fuse_file_info *, unsigned int flags, void *data);
+
733#else
+
750 int (*ioctl) (const char *, unsigned int cmd, void *arg,
+
751 struct fuse_file_info *, unsigned int flags, void *data);
+
752#endif
+
753
+
769 int (*poll) (const char *, struct fuse_file_info *,
+
770 struct fuse_pollhandle *ph, unsigned *reventsp);
+
771
+
781 int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off,
+
782 struct fuse_file_info *);
+
783
+
798 int (*read_buf) (const char *, struct fuse_bufvec **bufp,
+
799 size_t size, off_t off, struct fuse_file_info *);
+
818 int (*flock) (const char *, struct fuse_file_info *, int op);
+
819
+
828 int (*fallocate) (const char *, int, off_t, off_t,
+
829 struct fuse_file_info *);
+
830
+
843 ssize_t (*copy_file_range) (const char *path_in,
+
844 struct fuse_file_info *fi_in,
+
845 off_t offset_in, const char *path_out,
+
846 struct fuse_file_info *fi_out,
+
847 off_t offset_out, size_t size, int flags);
+
848
+
852 off_t (*lseek) (const char *, off_t off, int whence, struct fuse_file_info *);
+
853};
+
+
854
+
+ +
862 struct fuse *fuse;
+
863
+
865 uid_t uid;
+
866
+
868 gid_t gid;
+
869
+
871 pid_t pid;
+
872
+ +
875
+
877 mode_t umask;
+
878};
+
+
879
+
885int fuse_main_real_versioned(int argc, char *argv[],
+
886 const struct fuse_operations *op, size_t op_size,
+
887 struct libfuse_version *version, void *user_data);
+
888static inline int fuse_main_real(int argc, char *argv[],
+
889 const struct fuse_operations *op,
+
890 size_t op_size, void *user_data)
+
891{
+
892 struct libfuse_version version = { .major = FUSE_MAJOR_VERSION,
+
893 .minor = FUSE_MINOR_VERSION,
+
894 .hotfix = FUSE_HOTFIX_VERSION,
+
895 .padding = 0 };
+
896
+
897 fuse_log(FUSE_LOG_ERR,
+
898 "%s is a libfuse internal function, please use fuse_main()\n",
+
899 __func__);
+
900
+
901 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
902 user_data);
+
903}
+
904
+
959static inline int fuse_main_fn(int argc, char *argv[],
+
960 const struct fuse_operations *op,
+
961 void *user_data)
+
962{
+
963 struct libfuse_version version = {
+
964 .major = FUSE_MAJOR_VERSION,
+
965 .minor = FUSE_MINOR_VERSION,
+
966 .hotfix = FUSE_HOTFIX_VERSION,
+
967 .padding = 0
+
968 };
+
969
+
970 return fuse_main_real_versioned(argc, argv, op, sizeof(*(op)), &version,
+
971 user_data);
+
972}
+
973#define fuse_main(argc, argv, op, user_data) \
+
974 fuse_main_fn(argc, argv, op, user_data)
+
975
+
976/* ----------------------------------------------------------- *
+
977 * More detailed API *
+
978 * ----------------------------------------------------------- */
+
979
+
991void fuse_lib_help(struct fuse_args *args);
+
992
+
993/* Do not call this directly, use fuse_new() instead */
+
994struct fuse *_fuse_new_30(struct fuse_args *args,
+
995 const struct fuse_operations *op, size_t op_size,
+
996 struct libfuse_version *version, void *user_data);
+
997struct fuse *_fuse_new_31(struct fuse_args *args,
+
998 const struct fuse_operations *op, size_t op_size,
+
999 struct libfuse_version *version, void *user_data);
+
1000
+
1028#if FUSE_USE_VERSION == 30
+
1029static inline struct fuse *fuse_new_fn(struct fuse_args *args,
+
1030 const struct fuse_operations *op,
+
1031 size_t op_size, void *user_data)
+
1032{
+
1033 struct libfuse_version version = {
+
1034 .major = FUSE_MAJOR_VERSION,
+
1035 .minor = FUSE_MINOR_VERSION,
+
1036 .hotfix = FUSE_HOTFIX_VERSION,
+
1037 .padding = 0
+
1038 };
+
1039
+
1040 return _fuse_new_30(args, op, op_size, &version, user_data);
+
1041}
+
1042#else /* FUSE_USE_VERSION */
+
1043static inline struct fuse *fuse_new_fn(struct fuse_args *args,
+
1044 const struct fuse_operations *op,
+
1045 size_t op_size, void *user_data)
+
1046{
+
1047 struct libfuse_version version = {
+
1048 .major = FUSE_MAJOR_VERSION,
+
1049 .minor = FUSE_MINOR_VERSION,
+
1050 .hotfix = FUSE_HOTFIX_VERSION,
+
1051 .padding = 0
+
1052 };
+
1053
+
1054 return _fuse_new_31(args, op, op_size, &version, user_data);
+
1055}
+
1056#endif
+
1057#define fuse_new(args, op, size, data) fuse_new_fn(args, op, size, data)
+
1058
+
1067int fuse_mount(struct fuse *f, const char *mountpoint);
+
1068
+
1076void fuse_unmount(struct fuse *f);
+
1077
+
1086void fuse_destroy(struct fuse *f);
+
1087
+
1103int fuse_loop(struct fuse *f);
+
1104
+
1113void fuse_exit(struct fuse *f);
+
1114
+
1115#if FUSE_USE_VERSION < 32
+
1116int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
1117#define fuse_loop_mt(f, clone_fd) fuse_loop_mt_31(f, clone_fd)
+
1118#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
1119int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config *config);
+
1120#define fuse_loop_mt(f, config) fuse_loop_mt_32(f, config)
+
1121#else
+
1153#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
1154int fuse_loop_mt(struct fuse *f, struct fuse_loop_config *config);
+
1155#else
+
1156#define fuse_loop_mt(f, config) fuse_loop_mt_312(f, config)
+
1157#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
1158#endif
+
1159
+
1160
+
1169struct fuse_context *fuse_get_context(void);
+
1170
+
1189int fuse_getgroups(int size, gid_t list[]);
+
1190
+
1196int fuse_interrupted(void);
+
1197
+
1209int fuse_invalidate_path(struct fuse *f, const char *path);
+
1210
+ +
1219
+
1226void fuse_stop_cleanup_thread(struct fuse *fuse);
+
1227
+
1237int fuse_clean_cache(struct fuse *fuse);
+
1238
+
1239/*
+
1240 * Stacking API
+
1241 */
+
1242
+
1248struct fuse_fs;
+
1249
+
1250/*
+
1251 * These functions call the relevant filesystem operation, and return
+
1252 * the result.
+
1253 *
+
1254 * If the operation is not defined, they return -ENOSYS, with the
+
1255 * exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir,
+
1256 * fuse_fs_releasedir and fuse_fs_statfs, which return 0.
+
1257 */
+
1258
+
1259int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1260 struct fuse_file_info *fi);
+
1261int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1262 const char *newpath, unsigned int flags);
+
1263int fuse_fs_unlink(struct fuse_fs *fs, const char *path);
+
1264int fuse_fs_rmdir(struct fuse_fs *fs, const char *path);
+
1265int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname,
+
1266 const char *path);
+
1267int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath);
+
1268int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1269 struct fuse_file_info *fi);
+
1270int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1271 struct fuse_file_info *fi);
+
1272int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size,
+
1273 off_t off, struct fuse_file_info *fi);
+
1274int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1275 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1276 struct fuse_file_info *fi);
+
1277int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf,
+
1278 size_t size, off_t off, struct fuse_file_info *fi);
+
1279int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1280 struct fuse_bufvec *buf, off_t off,
+
1281 struct fuse_file_info *fi);
+
1282int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1283 struct fuse_file_info *fi);
+
1284int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1285 struct fuse_file_info *fi);
+
1286int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf);
+
1287int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1288 struct fuse_file_info *fi);
+
1289int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
1290 fuse_fill_dir_t filler, off_t off,
+
1291 struct fuse_file_info *fi, enum fuse_readdir_flags flags);
+
1292int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1293 struct fuse_file_info *fi);
+
1294int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1295 struct fuse_file_info *fi);
+
1296int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
1297 struct fuse_file_info *fi);
+
1298int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
1299 struct fuse_file_info *fi, int cmd, struct flock *lock);
+
1300int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
1301 struct fuse_file_info *fi, int op);
+
1302int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1303 struct fuse_file_info *fi);
+
1304int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid,
+
1305 struct fuse_file_info *fi);
+
1306int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
1307 struct fuse_file_info *fi);
+
1308int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
1309 const struct timespec tv[2], struct fuse_file_info *fi);
+
1310int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask);
+
1311int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
1312 size_t len);
+
1313int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1314 dev_t rdev);
+
1315int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode);
+
1316int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1317 const char *value, size_t size, int flags);
+
1318int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1319 char *value, size_t size);
+
1320int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
1321 size_t size);
+
1322int fuse_fs_removexattr(struct fuse_fs *fs, const char *path,
+
1323 const char *name);
+
1324int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
1325 uint64_t *idx);
+
1326#if FUSE_USE_VERSION < 35
+
1327int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd,
+
1328 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1329 void *data);
+
1330#else
+
1331int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
1332 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1333 void *data);
+
1334#endif
+
1335int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
1336 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
1337 unsigned *reventsp);
+
1338int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
1339 off_t offset, off_t length, struct fuse_file_info *fi);
+
1340ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
1341 struct fuse_file_info *fi_in, off_t off_in,
+
1342 const char *path_out,
+
1343 struct fuse_file_info *fi_out, off_t off_out,
+
1344 size_t len, int flags);
+
1345off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
1346 struct fuse_file_info *fi);
+
1347void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
1348 struct fuse_config *cfg);
+
1349void fuse_fs_destroy(struct fuse_fs *fs);
+
1350
+
1351int fuse_notify_poll(struct fuse_pollhandle *ph);
+
1352
+
1366struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
1367 void *private_data);
+
1368
+
1383typedef struct fuse_fs *(*fuse_module_factory_t)(struct fuse_args *args,
+
1384 struct fuse_fs *fs[]);
+
+
1395#define FUSE_REGISTER_MODULE(name_, factory_) \
+
1396 fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+
1397
+
1399struct fuse_session *fuse_get_session(struct fuse *f);
+
1400
+
1409int fuse_open_channel(const char *mountpoint, const char *options);
+
1410
+
1411#ifdef __cplusplus
+
1412}
+
1413#endif
+
1414
+
1415#endif /* FUSE_H_ */
+
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4654
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5197
+
int fuse_interrupted(void)
Definition fuse.c:4663
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5146
+
int fuse_main_real_versioned(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
Definition helper.c:307
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4906
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4639
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4433
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:482
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5202
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4914
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
@ FUSE_READDIR_DEFAULTS
Definition fuse.h:51
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:93
+
fuse_readdir_flags
Definition fuse.h:45
+ + + + +
uint32_t fmask
Definition fuse.h:288
+
int32_t show_help
Definition fuse.h:279
+
uint32_t flags
Definition fuse.h:318
+
int32_t direct_io
Definition fuse.h:226
+
int32_t no_rofd_flush
Definition fuse.h:297
+
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t intr_signal
Definition fuse.h:155
+
int32_t remember
Definition fuse.h:167
+
int32_t kernel_cache
Definition fuse.h:245
+
int32_t readdir_ino
Definition fuse.h:207
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t set_mode
Definition fuse.h:120
+
int32_t auto_cache
Definition fuse.h:253
+
uint64_t reserved[48]
Definition fuse.h:323
+
int32_t intr
Definition fuse.h:148
+
double negative_timeout
Definition fuse.h:137
+
int32_t set_gid
Definition fuse.h:106
+
int32_t set_uid
Definition fuse.h:113
+
int32_t hard_remove
Definition fuse.h:185
+
double attr_timeout
Definition fuse.h:143
+ + +
void * private_data
Definition fuse.h:874
+
uid_t uid
Definition fuse.h:865
+
pid_t pid
Definition fuse.h:871
+
struct fuse * fuse
Definition fuse.h:862
+
gid_t gid
Definition fuse.h:868
+
mode_t umask
Definition fuse.h:877
+ + + +
int(* mkdir)(const char *, mode_t)
Definition fuse.h:387
+
int(* truncate)(const char *, off_t, struct fuse_file_info *fi)
Definition fuse.h:437
+
int(* mknod)(const char *, mode_t, dev_t)
Definition fuse.h:379
+
int(* utimens)(const char *, const struct timespec tv[2], struct fuse_file_info *fi)
Definition fuse.h:719
+
int(* open)(const char *, struct fuse_file_info *)
Definition fuse.h:486
+
int(* opendir)(const char *, struct fuse_file_info *)
Definition fuse.h:589
+
int(* link)(const char *, const char *)
Definition fuse.h:410
+
int(* lock)(const char *, struct fuse_file_info *, int cmd, struct flock *)
Definition fuse.h:704
+
int(* read_buf)(const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *)
Definition fuse.h:798
+
int(* access)(const char *, int)
Definition fuse.h:660
+
int(* read)(const char *, char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:497
+
int(* poll)(const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp)
Definition fuse.h:769
+
void(* destroy)(void *private_data)
Definition fuse.h:649
+
int(* statfs)(const char *, struct statvfs *)
Definition fuse.h:516
+
int(* fallocate)(const char *, int, off_t, off_t, struct fuse_file_info *)
Definition fuse.h:828
+
int(* removexattr)(const char *, const char *)
Definition fuse.h:579
+
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
int(* releasedir)(const char *, struct fuse_file_info *)
Definition fuse.h:621
+
int(* rename)(const char *, const char *, unsigned int flags)
Definition fuse.h:407
+
off_t(* lseek)(const char *, off_t off, int whence, struct fuse_file_info *)
Definition fuse.h:852
+
int(* write)(const char *, const char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:509
+
int(* write_buf)(const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *)
Definition fuse.h:781
+
int(* unlink)(const char *)
Definition fuse.h:390
+
ssize_t(* copy_file_range)(const char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags)
Definition fuse.h:843
+
int(* fsync)(const char *, int, struct fuse_file_info *)
Definition fuse.h:567
+
int(* create)(const char *, mode_t, struct fuse_file_info *)
Definition fuse.h:672
+
int(* setxattr)(const char *, const char *, const char *, size_t, int)
Definition fuse.h:570
+
int(* listxattr)(const char *, char *, size_t)
Definition fuse.h:576
+
int(* readlink)(const char *, char *, size_t)
Definition fuse.h:371
+
int(* symlink)(const char *, const char *)
Definition fuse.h:396
+
int(* fsyncdir)(const char *, int, struct fuse_file_info *)
Definition fuse.h:631
+
int(* release)(const char *, struct fuse_file_info *)
Definition fuse.h:560
+
int(* chown)(const char *, uid_t, gid_t, struct fuse_file_info *fi)
Definition fuse.h:427
+
int(* chmod)(const char *, mode_t, struct fuse_file_info *fi)
Definition fuse.h:417
+
int(* rmdir)(const char *)
Definition fuse.h:393
+
int(* flush)(const char *, struct fuse_file_info *)
Definition fuse.h:546
+
int(* flock)(const char *, struct fuse_file_info *, int op)
Definition fuse.h:818
+
int(* ioctl)(const char *, unsigned int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data)
Definition fuse.h:750
+
int(* readdir)(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags)
Definition fuse.h:613
+
int(* getxattr)(const char *, const char *, char *, size_t)
Definition fuse.h:573
+
int(* bmap)(const char *, size_t blocksize, uint64_t *idx)
Definition fuse.h:728
+ +
+ + + + diff --git a/doc/html/fuse-3_817_84_2include_2fuse__common_8h.html b/doc/html/fuse-3_817_84_2include_2fuse__common_8h.html new file mode 100644 index 0000000..9e322ff --- /dev/null +++ b/doc/html/fuse-3_817_84_2include_2fuse__common_8h.html @@ -0,0 +1,1284 @@ + + + + + + + +libfuse: fuse-3.17.4/include/fuse_common.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_common.h File Reference
+
+
+
#include <stdbool.h>
+#include "libfuse_config.h"
+#include "fuse_opt.h"
+#include "fuse_log.h"
+#include <stdint.h>
+#include <sys/types.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+ + + + + + + + + + + + + + +

+Data Structures

struct  fuse_file_info
 
struct  fuse_loop_config
 
struct  fuse_conn_info
 
struct  fuse_buf
 
struct  fuse_bufvec
 
struct  libfuse_version
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Macros

#define FUSE_CAP_ASYNC_READ   (1 << 0)
 
#define FUSE_CAP_POSIX_LOCKS   (1 << 1)
 
#define FUSE_CAP_ATOMIC_O_TRUNC   (1 << 3)
 
#define FUSE_CAP_EXPORT_SUPPORT   (1 << 4)
 
#define FUSE_CAP_DONT_MASK   (1 << 6)
 
#define FUSE_CAP_SPLICE_WRITE   (1 << 7)
 
#define FUSE_CAP_SPLICE_MOVE   (1 << 8)
 
#define FUSE_CAP_SPLICE_READ   (1 << 9)
 
#define FUSE_CAP_FLOCK_LOCKS   (1 << 10)
 
#define FUSE_CAP_IOCTL_DIR   (1 << 11)
 
#define FUSE_CAP_AUTO_INVAL_DATA   (1 << 12)
 
#define FUSE_CAP_READDIRPLUS   (1 << 13)
 
#define FUSE_CAP_READDIRPLUS_AUTO   (1 << 14)
 
#define FUSE_CAP_ASYNC_DIO   (1 << 15)
 
#define FUSE_CAP_WRITEBACK_CACHE   (1 << 16)
 
#define FUSE_CAP_NO_OPEN_SUPPORT   (1 << 17)
 
#define FUSE_CAP_PARALLEL_DIROPS   (1 << 18)
 
#define FUSE_CAP_POSIX_ACL   (1 << 19)
 
#define FUSE_CAP_HANDLE_KILLPRIV   (1 << 20)
 
#define FUSE_CAP_HANDLE_KILLPRIV_V2   (1 << 21)
 
#define FUSE_CAP_CACHE_SYMLINKS   (1 << 23)
 
#define FUSE_CAP_NO_OPENDIR_SUPPORT   (1 << 24)
 
#define FUSE_CAP_EXPLICIT_INVAL_DATA   (1 << 25)
 
#define FUSE_CAP_EXPIRE_ONLY   (1 << 26)
 
#define FUSE_CAP_SETXATTR_EXT   (1 << 27)
 
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP   (1 << 28)
 
#define FUSE_CAP_PASSTHROUGH   (1 << 29)
 
#define FUSE_CAP_NO_EXPORT_SUPPORT   (1 << 30)
 
#define FUSE_IOCTL_COMPAT   (1 << 0)
 
#define FUSE_BACKING_STACKED_UNDER   (0)
 
+ + + + + +

+Enumerations

enum  fuse_buf_flags { FUSE_BUF_IS_FD = (1 << 1) +, FUSE_BUF_FD_SEEK = (1 << 2) +, FUSE_BUF_FD_RETRY = (1 << 3) + }
 
enum  fuse_buf_copy_flags { FUSE_BUF_NO_SPLICE = (1 << 1) +, FUSE_BUF_FORCE_SPLICE = (1 << 2) +, FUSE_BUF_SPLICE_MOVE = (1 << 3) +, FUSE_BUF_SPLICE_NONBLOCK = (1 << 4) + }
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_args *args)
 
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
 
int fuse_daemonize (int foreground)
 
int fuse_version (void)
 
const char * fuse_pkgversion (void)
 
void fuse_pollhandle_destroy (struct fuse_pollhandle *ph)
 
size_t fuse_buf_size (const struct fuse_bufvec *bufv)
 
ssize_t fuse_buf_copy (struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
 
int fuse_set_signal_handlers (struct fuse_session *se)
 
int fuse_set_fail_signal_handlers (struct fuse_session *se)
 
void fuse_remove_signal_handlers (struct fuse_session *se)
 
bool fuse_set_feature_flag (struct fuse_conn_info *conn, uint64_t flag)
 
void fuse_unset_feature_flag (struct fuse_conn_info *conn, uint64_t flag)
 
bool fuse_get_feature_flag (struct fuse_conn_info *conn, uint64_t flag)
 
int fuse_convert_to_conn_want_ext (struct fuse_conn_info *conn)
 
+

Macro Definition Documentation

+ +

◆ FUSE_BACKING_STACKED_UNDER

+ +
+
+ + + + +
#define FUSE_BACKING_STACKED_UNDER   (0)
+
+

When FUSE_CAP_PASSTHROUGH is enabled, this is the maximum allowed stacking depth of the backing files. In current kernel, the maximum allowed stack depth if FILESYSTEM_MAX_STACK_DEPTH (2), which includes the FUSE passthrough layer, so the maximum stacking depth for backing files is 1.

+

The default is FUSE_BACKING_STACKED_UNDER (0), meaning that the backing files cannot be on a stacked filesystem, but another stacked filesystem can be stacked over this FUSE passthrough filesystem.

+

Set this to FUSE_BACKING_STACKED_OVER (1) if backing files may be on a stacked filesystem, such as overlayfs or another FUSE passthrough. In this configuration, another stacked filesystem cannot be stacked over this FUSE passthrough filesystem.

+ +

Definition at line 665 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ASYNC_DIO

+ +
+
+ + + + +
#define FUSE_CAP_ASYNC_DIO   (1 << 15)
+
+

Indicates that the filesystem supports asynchronous direct I/O submission.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read and one pending write request per direct I/O file-handle at any time.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 328 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ASYNC_READ

+ +
+
+ + + + +
#define FUSE_CAP_ASYNC_READ   (1 << 0)
+
+

Indicates that the filesystem supports asynchronous read requests.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read request per file-handle at any time, and will attempt to order read requests by increasing offset.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 177 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ATOMIC_O_TRUNC

+ +
+
+ + + + +
#define FUSE_CAP_ATOMIC_O_TRUNC   (1 << 3)
+
+

Indicates that the filesystem supports the O_TRUNC open flag. If disabled, and an application specifies O_TRUNC, fuse first calls truncate() and then open() with O_TRUNC filtered out.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 194 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_AUTO_INVAL_DATA

+ +
+
+ + + + +
#define FUSE_CAP_AUTO_INVAL_DATA   (1 << 12)
+
+

Traditionally, while a file is open the FUSE kernel module only asks the filesystem for an update of the file's attributes when a client attempts to read beyond EOF. This is unsuitable for e.g. network filesystems, where the file contents may change without the kernel knowing about it.

+

If this flag is set, FUSE will check the validity of the attributes on every read. If the attributes are no longer valid (i.e., if the attr_timeout passed to fuse_reply_attr() or set in struct fuse_entry_param has passed), it will first issue a getattr request. If the new mtime differs from the previous value, any cached file contents will be invalidated as well.

+

This flag should always be set when available. If all file changes go through the kernel, attr_timeout should be set to a very large number to avoid unnecessary getattr() calls.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 281 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_CACHE_SYMLINKS

+ +
+
+ + + + +
#define FUSE_CAP_CACHE_SYMLINKS   (1 << 23)
+
+

Indicates that the kernel supports caching symlinks in its page cache.

+

When this feature is enabled, symlink targets are saved in the page cache. You can invalidate a cached link by calling: fuse_lowlevel_notify_inval_inode(se, ino, 0, 0);

+

This feature is disabled by default. If the kernel supports it (>= 4.20), you can enable this feature by setting this flag in the want field of the fuse_conn_info structure.

+ +

Definition at line 418 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_DIRECT_IO_ALLOW_MMAP

+ +
+
+ + + + +
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP   (1 << 28)
+
+

Files opened with FUSE_DIRECT_IO do not support MAP_SHARED mmap. This restriction is relaxed through FUSE_CAP_DIRECT_IO_RELAX (kernel flag: FUSE_DIRECT_IO_RELAX). MAP_SHARED is disabled by default for FUSE_DIRECT_IO, as this flag can be used to ensure coherency between mount points (or network clients) and with kernel page cache as enforced by mmap that cannot be guaranteed anymore.

+ +

Definition at line 488 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_DONT_MASK

+ +
+
+ + + + +
#define FUSE_CAP_DONT_MASK   (1 << 6)
+
+

Indicates that the kernel should not apply the umask to the file mode on create operations.

+

This feature is disabled by default.

+ +

Definition at line 214 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPIRE_ONLY

+ +
+
+ + + + +
#define FUSE_CAP_EXPIRE_ONLY   (1 << 26)
+
+

Indicates support that dentries can be expired.

+

Expiring dentries, instead of invalidating them, makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+ +

Definition at line 472 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPLICIT_INVAL_DATA

+ +
+
+ + + + +
#define FUSE_CAP_EXPLICIT_INVAL_DATA   (1 << 25)
+
+

Indicates support for invalidating cached pages only on explicit request.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports invalidating cached pages only on explicit request by the filesystem through fuse_lowlevel_notify_inval_inode() or fuse_invalidate_path().

+

By setting this flag in the want field of the fuse_conn_info structure, the filesystem is responsible for invalidating cached pages through explicit requests to the kernel.

+

Note that setting this flag does not prevent the cached pages from being flushed by OS itself and/or through user actions.

+

Note that if both FUSE_CAP_EXPLICIT_INVAL_DATA and FUSE_CAP_AUTO_INVAL_DATA are set in the capable field of the fuse_conn_info structure then FUSE_CAP_AUTO_INVAL_DATA takes precedence.

+

This feature is disabled by default.

+ +

Definition at line 456 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPORT_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_EXPORT_SUPPORT   (1 << 4)
+
+

Indicates that the filesystem supports lookups of "." and "..".

+

When this flag is set, the filesystem must be prepared to receive requests for invalid inodes (i.e., for which a FORGET request was received or which have been used in a previous instance of the filesystem daemon) and must not reuse node-ids (even when setting generation numbers).

+

This feature is disabled by default.

+ +

Definition at line 206 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_FLOCK_LOCKS

+ +
+
+ + + + +
#define FUSE_CAP_FLOCK_LOCKS   (1 << 10)
+
+

If set, the calls to flock(2) will be emulated using POSIX locks and must then be handled by the filesystem's setlock() handler.

+

If not set, flock(2) calls will be handled by the FUSE kernel module internally (so any access that does not go through the kernel cannot be taken into account).

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a flock() handler.

+ +

Definition at line 252 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_HANDLE_KILLPRIV

+ +
+
+ + + + +
#define FUSE_CAP_HANDLE_KILLPRIV   (1 << 20)
+
+

Indicates that the filesystem is responsible for unsetting setuid and setgid bits when a file is written, truncated, or its owner is changed.

+

This feature is disabled by default.

+ +

Definition at line 388 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_HANDLE_KILLPRIV_V2

+ +
+
+ + + + +
#define FUSE_CAP_HANDLE_KILLPRIV_V2   (1 << 21)
+
+

Indicates that the filesystem is responsible for unsetting setuid and setgid bit and additionally cap (stored as xattr) when a file is written, truncated, or its owner is changed. Upon write/truncate suid/sgid is only killed if caller does not have CAP_FSETID. Additionally upon write/truncate sgid is killed only if file has group execute permission. (Same as Linux VFS behavior). KILLPRIV_V2 requires handling of

    +
  • FUSE_OPEN_KILL_SUIDGID (set in struct fuse_create_in::open_flags)
  • +
  • FATTR_KILL_SUIDGID (set in struct fuse_setattr_in::valid)
  • +
  • FUSE_WRITE_KILL_SUIDGID (set in struct fuse_write_in::write_flags)
  • +
+

This feature is disabled by default.

+ +

Definition at line 405 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_IOCTL_DIR

+ +
+
+ + + + +
#define FUSE_CAP_IOCTL_DIR   (1 << 11)
+
+

Indicates that the filesystem supports ioctl's on directories.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 259 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_EXPORT_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_EXPORT_SUPPORT   (1 << 30)
+
+

Indicates that the file system cannot handle NFS export

+

If this flag is set NFS export and name_to_handle_at is not going to work at all and will fail with EOPNOTSUPP.

+ +

Definition at line 508 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_OPEN_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_OPEN_SUPPORT   (1 << 17)
+
+

Indicates support for zero-message opens. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the open() handler to indicate success. Further attempts to open files will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signaled to the caller).

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users wish to have this behavior you must return ENOSYS from the open() handler on supporting kernels.

+ +

Definition at line 352 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_OPENDIR_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_OPENDIR_SUPPORT   (1 << 24)
+
+

Indicates support for zero-message opendirs. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the opendir() handler to indicate success. Further opendir and releasedir messages will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signalled to the caller.)

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users with to have this behavior you must return ENOSYS from the opendir() handler on supporting kernels.

+ +

Definition at line 433 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_PARALLEL_DIROPS

+ +
+
+ + + + +
#define FUSE_CAP_PARALLEL_DIROPS   (1 << 18)
+
+

Indicates support for parallel directory operations. If this flag is unset, the FUSE kernel module will ensure that lookup() and readdir() requests are never issued concurrently for the same directory.

+ +

Definition at line 360 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_PASSTHROUGH

+ +
+
+ + + + +
#define FUSE_CAP_PASSTHROUGH   (1 << 29)
+
+

Indicates support for passthrough mode access for read/write operations.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports redirecting read/write operations to the backing file instead of letting them to be handled by the FUSE daemon.

+

This feature is disabled by default.

+ +

Definition at line 500 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_POSIX_ACL

+ +
+
+ + + + +
#define FUSE_CAP_POSIX_ACL   (1 << 19)
+
+

Indicates support for POSIX ACLs.

+

If this feature is enabled, the kernel will cache and have responsibility for enforcing ACLs. ACL will be stored as xattrs and passed to userspace, which is responsible for updating the ACLs in the filesystem, keeping the file mode in sync with the ACL, and ensuring inheritance of default ACLs when new filesystem nodes are created. Note that this requires that the file system is able to parse and interpret the xattr representation of ACLs.

+

Enabling this feature implicitly turns on the default_permissions mount option (even if it was not passed to mount(2)).

+

This feature is disabled by default.

+ +

Definition at line 379 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_POSIX_LOCKS

+ +
+
+ + + + +
#define FUSE_CAP_POSIX_LOCKS   (1 << 1)
+
+

Indicates that the filesystem supports "remote" locking.

+

This feature is enabled by default when supported by the kernel, and if getlk() and setlk() handlers are implemented.

+ +

Definition at line 185 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_READDIRPLUS

+ +
+
+ + + + +
#define FUSE_CAP_READDIRPLUS   (1 << 13)
+
+

Indicates that the filesystem supports readdirplus.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a readdirplus() handler.

+ +

Definition at line 289 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_READDIRPLUS_AUTO

+ +
+
+ + + + +
#define FUSE_CAP_READDIRPLUS_AUTO   (1 << 14)
+
+

Indicates that the filesystem supports adaptive readdirplus.

+

If FUSE_CAP_READDIRPLUS is not set, this flag has no effect.

+

If FUSE_CAP_READDIRPLUS is set and this flag is not set, the kernel will always issue readdirplus() requests to retrieve directory contents.

+

If FUSE_CAP_READDIRPLUS is set and this flag is set, the kernel will issue both readdir() and readdirplus() requests, depending on how much information is expected to be required.

+

As of Linux 4.20, the algorithm is as follows: when userspace starts to read directory entries, issue a READDIRPLUS request to the filesystem. If any entry attributes have been looked up by the time userspace requests the next batch of entries continue with READDIRPLUS, otherwise switch to plain READDIR. This will reasult in eg plain "ls" triggering READDIRPLUS first then READDIR after that because it doesn't do lookups. "ls -l" should result in all READDIRPLUS, except if dentries are already cached.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements both a readdirplus() and a readdir() handler.

+ +

Definition at line 317 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SETXATTR_EXT

+ +
+
+ + + + +
#define FUSE_CAP_SETXATTR_EXT   (1 << 27)
+
+

Indicates that an extended 'struct fuse_setxattr' is used by the kernel side - extra_flags are passed, which are used (as of now by acl) processing. For example FUSE_SETXATTR_ACL_KILL_SGID might be set.

+ +

Definition at line 479 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_MOVE

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_MOVE   (1 << 8)
+
+

Indicates that libfuse should try to move pages instead of copying when writing to / reading from the fuse device. This may improve performance.

+

This feature is disabled by default.

+ +

Definition at line 230 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_READ

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_READ   (1 << 9)
+
+

Indicates that libfuse should try to use splice() when reading from the fuse device. This may improve performance.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a write_buf() handler.

+ +

Definition at line 239 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_WRITE

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_WRITE   (1 << 7)
+
+

Indicates that libfuse should try to use splice() when writing to the fuse device. This may improve performance.

+

This feature is disabled by default.

+ +

Definition at line 222 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_WRITEBACK_CACHE

+ +
+
+ + + + +
#define FUSE_CAP_WRITEBACK_CACHE   (1 << 16)
+
+

Indicates that writeback caching should be enabled. This means that individual write request may be buffered and merged in the kernel before they are send to the filesystem.

+

This feature is disabled by default.

+ +

Definition at line 337 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_IOCTL_COMPAT

+ +
+
+ + + + +
#define FUSE_IOCTL_COMPAT   (1 << 0)
+
+

Ioctl flags

+

FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed FUSE_IOCTL_RETRY: retry with new iovecs FUSE_IOCTL_DIR: is a directory

+

FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs

+ +

Definition at line 520 of file fuse_common.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_buf_copy_flags

+ +
+
+ + + + +
enum fuse_buf_copy_flags
+
+

Buffer copy flags

+ + + + + +
Enumerator
FUSE_BUF_NO_SPLICE 

Don't use splice(2)

+

Always fall back to using read and write instead of splice(2) to copy data from one file descriptor to another.

+

If this flag is not set, then only fall back if splice is unavailable.

+
FUSE_BUF_FORCE_SPLICE 

Force splice

+

Always use splice(2) to copy data from one file descriptor to another. If splice is not available, return -EINVAL.

+
FUSE_BUF_SPLICE_MOVE 

Try to move data with splice.

+

If splice is used, try to move pages from the source to the destination instead of copying. See documentation of SPLICE_F_MOVE in splice(2) man page.

+
FUSE_BUF_SPLICE_NONBLOCK 

Don't block on the pipe when copying data with splice

+

Makes the operations on the pipe non-blocking (if the pipe is full or empty). See SPLICE_F_NONBLOCK in the splice(2) man page.

+
+ +

Definition at line 829 of file fuse_common.h.

+ +
+
+ +

◆ fuse_buf_flags

+ +
+
+ + + + +
enum fuse_buf_flags
+
+

Buffer flags

+ + + + +
Enumerator
FUSE_BUF_IS_FD 

Buffer contains a file descriptor

+

If this flag is set, the .fd field is valid, otherwise the .mem fields is valid.

+
FUSE_BUF_FD_SEEK 

Seek on the file descriptor

+

If this flag is set then the .pos field is valid and is used to seek to the given offset before performing operation on file descriptor.

+
FUSE_BUF_FD_RETRY 

Retry operation on file descriptor

+

If this flag is set then retry operation on file descriptor until .size bytes have been copied or an error or EOF is detected.

+
+ +

Definition at line 798 of file fuse_common.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_apply_conn_info_opts()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts * opts,
struct fuse_conn_infoconn 
)
+
+

This function applies the (parsed) parameters in opts to the conn pointer. It may modify the following fields: wants, max_write, max_readahead, congestion_threshold, max_background, time_gran. A field is only set (or unset) if the corresponding option has been explicitly set.

+ +

Definition at line 412 of file helper.c.

+ +
+
+ +

◆ fuse_buf_copy()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
ssize_t fuse_buf_copy (struct fuse_bufvecdst,
struct fuse_bufvecsrc,
enum fuse_buf_copy_flags flags 
)
+
+

Copy data from one buffer vector to another

+
Parameters
+ + + + +
dstdestination buffer vector
srcsource buffer vector
flagsflags controlling the copy
+
+
+
Returns
actual number of bytes copied or -errno on error
+ +

Definition at line 284 of file buffer.c.

+ +
+
+ +

◆ fuse_buf_size()

+ +
+
+ + + + + + + + +
size_t fuse_buf_size (const struct fuse_bufvecbufv)
+
+

Get total size of data in a fuse buffer vector

+
Parameters
+ + +
bufvbuffer vector
+
+
+
Returns
size of data
+ +

Definition at line 22 of file buffer.c.

+ +
+
+ +

◆ fuse_convert_to_conn_want_ext()

+ +
+
+ + + + + + + + +
int fuse_convert_to_conn_want_ext (struct fuse_conn_infoconn)
+
+

Get the wanted capability flags, converting from old format if necessary

+ +

Definition at line 2000 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_daemonize()

+ +
+
+ + + + + + + + +
int fuse_daemonize (int foreground)
+
+

Go into the background

+
Parameters
+ + +
foregroundif true, stay in the foreground
+
+
+
Returns
0 on success, -1 on failure
+ +

Definition at line 253 of file helper.c.

+ +
+
+ +

◆ fuse_get_feature_flag()

+ +
+
+ + + + + + + + + + + + + + + + + + +
bool fuse_get_feature_flag (struct fuse_conn_infoconn,
uint64_t flag 
)
+
+

Get the value of a feature flag in the want_ext field of fuse_conn_info.

+
Parameters
+ + + +
connconnection information
flagfeature flag to be checked
+
+
+
Returns
true if the flag is set, false otherwise
+ +

Definition at line 2061 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_parse_conn_info_opts()

+ +
+
+ + + + + + + + +
struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_argsargs)
+
+

This function parses several command-line options that can be used to override elements of struct fuse_conn_info. The pointer returned by this function should be passed to the fuse_apply_conn_info_opts() method by the file system's init() handler.

+

Before using this function, think twice if you really want these parameters to be adjustable from the command line. In most cases, they should be determined by the file system internally.

+

The following options are recognized:

+

-o max_write=N sets conn->max_write -o max_readahead=N sets conn->max_readahead -o max_background=N sets conn->max_background -o congestion_threshold=N sets conn->congestion_threshold -o async_read sets FUSE_CAP_ASYNC_READ in conn->want -o sync_read unsets FUSE_CAP_ASYNC_READ in conn->want -o atomic_o_trunc sets FUSE_CAP_ATOMIC_O_TRUNC in conn->want -o no_remote_lock Equivalent to -o no_remote_flock,no_remote_posix_lock -o no_remote_flock Unsets FUSE_CAP_FLOCK_LOCKS in conn->want -o no_remote_posix_lock Unsets FUSE_CAP_POSIX_LOCKS in conn->want -o [no_]splice_write (un-)sets FUSE_CAP_SPLICE_WRITE in conn->want -o [no_]splice_move (un-)sets FUSE_CAP_SPLICE_MOVE in conn->want -o [no_]splice_read (un-)sets FUSE_CAP_SPLICE_READ in conn->want -o [no_]auto_inval_data (un-)sets FUSE_CAP_AUTO_INVAL_DATA in conn->want -o readdirplus=no unsets FUSE_CAP_READDIRPLUS in conn->want -o readdirplus=yes sets FUSE_CAP_READDIRPLUS and unsets FUSE_CAP_READDIRPLUS_AUTO in conn->want -o readdirplus=auto sets FUSE_CAP_READDIRPLUS and FUSE_CAP_READDIRPLUS_AUTO in conn->want -o [no_]async_dio (un-)sets FUSE_CAP_ASYNC_DIO in conn->want -o [no_]writeback_cache (un-)sets FUSE_CAP_WRITEBACK_CACHE in conn->want -o time_gran=N sets conn->time_gran

+

Known options will be removed from args, unknown options will be passed through unchanged.

+
Parameters
+ + +
argsargument vector (input+output)
+
+
+
Returns
parsed options
+ +

Definition at line 466 of file helper.c.

+ +
+
+ +

◆ fuse_pkgversion()

+ +
+
+ + + + + + + + +
const char * fuse_pkgversion (void )
+
+

Get the full package version string of the library

+
Returns
the package version
+ +

Definition at line 5211 of file fuse.c.

+ +
+
+ +

◆ fuse_pollhandle_destroy()

+ +
+
+ + + + + + + + +
void fuse_pollhandle_destroy (struct fuse_pollhandle * ph)
+
+

Destroy poll handle

+
Parameters
+ + +
phthe poll handle
+
+
+ +

Definition at line 1903 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_remove_signal_handlers()

+ +
+
+ + + + + + + + +
void fuse_remove_signal_handlers (struct fuse_session * se)
+
+

Restore default signal handlers

+

Resets global session. After this fuse_set_signal_handlers() may be called again.

+
Parameters
+ + +
sethe same session as given in fuse_set_signal_handlers()
+
+
+

See also: fuse_set_signal_handlers()

+ +

Definition at line 187 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_fail_signal_handlers()

+ +
+
+ + + + + + + + +
int fuse_set_fail_signal_handlers (struct fuse_session * se)
+
+

Print a stack backtrace diagnostic on critical signals ()

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 165 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_feature_flag()

+ +
+
+ + + + + + + + + + + + + + + + + + +
bool fuse_set_feature_flag (struct fuse_conn_infoconn,
uint64_t flag 
)
+
+

Config operations. These APIs are introduced in version 312 (FUSE_MAKE_VERSION(3, 12)). Using them in earlier versions will result in errors. Set a feature flag in the want_ext field of fuse_conn_info.

+
Parameters
+ + + +
connconnection information
flagfeature flag to be set
+
+
+
Returns
true if the flag was set, false if the flag is not supported
+ +

Definition at line 2035 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_set_signal_handlers()

+ +
+
+ + + + + + + + +
int fuse_set_signal_handlers (struct fuse_session * se)
+
+

Exit session on HUP, TERM and INT signals and ignore PIPE signal

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 140 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_unset_feature_flag()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_unset_feature_flag (struct fuse_conn_infoconn,
uint64_t flag 
)
+
+

Unset a feature flag in the want_ext field of fuse_conn_info.

+
Parameters
+ + + +
connconnection information
flagfeature flag to be unset
+
+
+ +

Definition at line 2050 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_version()

+ +
+
+ + + + + + + + +
int fuse_version (void )
+
+

Get the version of the library

+
Returns
the version
+ +

Definition at line 5206 of file fuse.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2include_2fuse__common_8h_source.html b/doc/html/fuse-3_817_84_2include_2fuse__common_8h_source.html new file mode 100644 index 0000000..b85546b --- /dev/null +++ b/doc/html/fuse-3_817_84_2include_2fuse__common_8h_source.html @@ -0,0 +1,502 @@ + + + + + + + +libfuse: fuse-3.17.4/include/fuse_common.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_common.h
+
+
+Go to the documentation of this file.
1/* FUSE: Filesystem in Userspace
+
2 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
3
+
4 This program can be distributed under the terms of the GNU LGPLv2.
+
5 See the file COPYING.LIB.
+
6*/
+
7
+
10#include <stdbool.h>
+
11#if !defined(FUSE_H_) && !defined(FUSE_LOWLEVEL_H_)
+
12#error "Never include <fuse_common.h> directly; use <fuse.h> or <fuse_lowlevel.h> instead."
+
13#endif
+
14
+
15#ifndef FUSE_COMMON_H_
+
16#define FUSE_COMMON_H_
+
17
+
18#ifdef HAVE_LIBFUSE_PRIVATE_CONFIG_H
+
19#include "fuse_config.h"
+
20#endif
+
21
+
22#include "libfuse_config.h"
+
23
+
24#include "fuse_opt.h"
+
25#include "fuse_log.h"
+
26#include <stdint.h>
+
27#include <sys/types.h>
+
28#include <assert.h>
+
29
+
30#define FUSE_MAKE_VERSION(maj, min) ((maj) * 100 + (min))
+
31#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION)
+
32
+
33#ifdef __cplusplus
+
34extern "C" {
+
35#endif
+
36
+
+ +
52 int32_t flags;
+
53
+
60 uint32_t writepage : 1;
+
61
+
63 uint32_t direct_io : 1;
+
64
+
69 uint32_t keep_cache : 1;
+
70
+
74 uint32_t flush : 1;
+
75
+
78 uint32_t nonseekable : 1;
+
79
+
80 /* Indicates that flock locks for this file should be
+
81 released. If set, lock_owner shall contain a valid value.
+
82 May only be set in ->release(). */
+
83 uint32_t flock_release : 1;
+
84
+
89 uint32_t cache_readdir : 1;
+
90
+
93 uint32_t noflush : 1;
+
94
+ +
98
+
100 uint32_t padding : 23;
+
101 uint32_t padding2 : 32;
+
102 uint32_t padding3 : 32;
+
103
+
107 uint64_t fh;
+
108
+
110 uint64_t lock_owner;
+
111
+
114 uint32_t poll_events;
+
115
+
119 int32_t backing_id;
+
120
+
122 uint64_t compat_flags;
+
123
+
124 uint64_t reserved[2];
+
125};
+
+
126
+
137#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
138struct fuse_loop_config_v1; /* forward declaration */
+
+ +
140#else
+
141struct fuse_loop_config_v1 {
+
142#endif
+ +
148
+
159 unsigned int max_idle_threads;
+
160};
+
+
161
+
162
+
163/**************************************************************************
+
164 * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want' *
+
165 **************************************************************************/
+
166
+
177#define FUSE_CAP_ASYNC_READ (1 << 0)
+
178
+
185#define FUSE_CAP_POSIX_LOCKS (1 << 1)
+
186
+
194#define FUSE_CAP_ATOMIC_O_TRUNC (1 << 3)
+
195
+
206#define FUSE_CAP_EXPORT_SUPPORT (1 << 4)
+
207
+
214#define FUSE_CAP_DONT_MASK (1 << 6)
+
215
+
222#define FUSE_CAP_SPLICE_WRITE (1 << 7)
+
223
+
230#define FUSE_CAP_SPLICE_MOVE (1 << 8)
+
231
+
239#define FUSE_CAP_SPLICE_READ (1 << 9)
+
240
+
252#define FUSE_CAP_FLOCK_LOCKS (1 << 10)
+
253
+
259#define FUSE_CAP_IOCTL_DIR (1 << 11)
+
260
+
281#define FUSE_CAP_AUTO_INVAL_DATA (1 << 12)
+
282
+
289#define FUSE_CAP_READDIRPLUS (1 << 13)
+
290
+
317#define FUSE_CAP_READDIRPLUS_AUTO (1 << 14)
+
318
+
328#define FUSE_CAP_ASYNC_DIO (1 << 15)
+
329
+
337#define FUSE_CAP_WRITEBACK_CACHE (1 << 16)
+
338
+
352#define FUSE_CAP_NO_OPEN_SUPPORT (1 << 17)
+
353
+
360#define FUSE_CAP_PARALLEL_DIROPS (1 << 18)
+
361
+
379#define FUSE_CAP_POSIX_ACL (1 << 19)
+
380
+
388#define FUSE_CAP_HANDLE_KILLPRIV (1 << 20)
+
389
+
405#define FUSE_CAP_HANDLE_KILLPRIV_V2 (1 << 21)
+
406
+
418#define FUSE_CAP_CACHE_SYMLINKS (1 << 23)
+
419
+
433#define FUSE_CAP_NO_OPENDIR_SUPPORT (1 << 24)
+
434
+
456#define FUSE_CAP_EXPLICIT_INVAL_DATA (1 << 25)
+
457
+
472#define FUSE_CAP_EXPIRE_ONLY (1 << 26)
+
473
+
479#define FUSE_CAP_SETXATTR_EXT (1 << 27)
+
480
+
488#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP (1 << 28)
+
489
+
500#define FUSE_CAP_PASSTHROUGH (1 << 29)
+
501
+
508#define FUSE_CAP_NO_EXPORT_SUPPORT (1 << 30)
+
509
+
520#define FUSE_IOCTL_COMPAT (1 << 0)
+
521#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
522#define FUSE_IOCTL_RETRY (1 << 2)
+
523#define FUSE_IOCTL_DIR (1 << 4)
+
524
+
525#define FUSE_IOCTL_MAX_IOV 256
+
526
+
+ +
542 uint32_t proto_major;
+
543
+
547 uint32_t proto_minor;
+
548
+
552 uint32_t max_write;
+
553
+
566 uint32_t max_read;
+
567
+ +
572
+
578 uint32_t capable;
+
579
+
590 uint32_t want;
+
591
+ +
621
+ +
631
+
647 uint32_t time_gran;
+
648
+
665#define FUSE_BACKING_STACKED_UNDER (0)
+
666#define FUSE_BACKING_STACKED_OVER (1)
+
667 uint32_t max_backing_stack_depth;
+
668
+
677 uint32_t no_interrupt : 1;
+
678
+
679 /* reserved bits for future use */
+
680 uint32_t padding : 31;
+
681
+
686 uint64_t capable_ext;
+
687
+
696 uint64_t want_ext;
+
697
+
701 uint32_t reserved[16];
+
702};
+
+
703
+
704struct fuse_session;
+
705struct fuse_pollhandle;
+
706struct fuse_conn_info_opts;
+
707
+
750struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args);
+
751
+
759void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
760 struct fuse_conn_info *conn);
+
761
+
768int fuse_daemonize(int foreground);
+
769
+
775int fuse_version(void);
+
776
+
782const char *fuse_pkgversion(void);
+
783
+
789void fuse_pollhandle_destroy(struct fuse_pollhandle *ph);
+
790
+
791/* ----------------------------------------------------------- *
+
792 * Data buffer *
+
793 * ----------------------------------------------------------- */
+
794
+
+ +
805 FUSE_BUF_IS_FD = (1 << 1),
+
806
+ +
815
+
823 FUSE_BUF_FD_RETRY = (1 << 3)
+ +
+
825
+
+ + +
840
+ +
848
+ +
857
+ + +
+
867
+
+
874struct fuse_buf {
+
878 size_t size;
+
879
+ +
884
+
890 void *mem;
+
891
+
897 int fd;
+
898
+
904 off_t pos;
+
905
+
912 size_t mem_size;
+
913};
+
+
914
+
+ +
927 size_t count;
+
928
+
932 size_t idx;
+
933
+
937 size_t off;
+
938
+
942 struct fuse_buf buf[1];
+
943};
+
+
944
+
+ +
950{
+
951 uint32_t major;
+
952 uint32_t minor;
+
953 uint32_t hotfix;
+
954 uint32_t padding;
+
955};
+
+
956
+
957/* Initialize bufvec with a single buffer of given size */
+
958#define FUSE_BUFVEC_INIT(size__) \
+
959 ((struct fuse_bufvec) { \
+
960 /* .count= */ 1, \
+
961 /* .idx = */ 0, \
+
962 /* .off = */ 0, \
+
963 /* .buf = */ { /* [0] = */ { \
+
964 /* .size = */ (size__), \
+
965 /* .flags = */ (enum fuse_buf_flags) 0, \
+
966 /* .mem = */ NULL, \
+
967 /* .fd = */ -1, \
+
968 /* .pos = */ 0, \
+
969 /* .mem_size = */ 0, \
+
970 } } \
+
971 } )
+
972
+
979size_t fuse_buf_size(const struct fuse_bufvec *bufv);
+
980
+
989ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src,
+
990 enum fuse_buf_copy_flags flags);
+
991
+
992/* ----------------------------------------------------------- *
+
993 * Signal handling *
+
994 * ----------------------------------------------------------- */
+
995
+
1011int fuse_set_signal_handlers(struct fuse_session *se);
+
1012
+
1028int fuse_set_fail_signal_handlers(struct fuse_session *se);
+
1029
+
1041void fuse_remove_signal_handlers(struct fuse_session *se);
+
1042
+
1048#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
1054struct fuse_loop_config *fuse_loop_cfg_create(void);
+
1055
+
1059void fuse_loop_cfg_destroy(struct fuse_loop_config *config);
+
1060
+
1064void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
1065 unsigned int value);
+
1066
+
1070void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
1071 unsigned int value);
+
1072
+
1076void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
1077 unsigned int value);
+
1078
+
1085void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
1086 struct fuse_loop_config_v1 *v1_conf);
+
1087#endif
+
1088
+
1096bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag);
+
1097
+
1104void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag);
+
1105
+
1113bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag);
+
1114
+
1115/*
+
1116 * DO NOT USE: Not part of public API, for internal test use only.
+
1117 * The function signature or any use of it is not guaranteeed to
+
1118 * remain stable. And neither are results of what this function does.
+
1119 */
+ +
1121
+
1122
+
1123
+
1124/* ----------------------------------------------------------- *
+
1125 * Compatibility stuff *
+
1126 * ----------------------------------------------------------- */
+
1127
+
1128#if !defined(FUSE_USE_VERSION) || FUSE_USE_VERSION < 30
+
1129# error only API version 30 or greater is supported
+
1130#endif
+
1131
+
1132#ifdef __cplusplus
+
1133}
+
1134#endif
+
1135
+
1136
+
1137/*
+
1138 * This interface uses 64 bit off_t.
+
1139 *
+
1140 * On 32bit systems please add -D_FILE_OFFSET_BITS=64 to your compile flags!
+
1141 */
+
1142
+
1143#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
+
1144_Static_assert(sizeof(off_t) == 8, "fuse: off_t must be 64bit");
+
1145#else
+
1146struct _fuse_off_t_must_be_64bit_dummy_struct \
+
1147 { unsigned _fuse_off_t_must_be_64bit:((sizeof(off_t) == 8) ? 1 : -1); };
+
1148#endif
+
1149
+
1150#endif /* FUSE_COMMON_H_ */
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:412
+
fuse_buf_flags
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:466
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
int fuse_version(void)
Definition fuse.c:5206
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
fuse_buf_flags
+ + + + +
enum fuse_buf_flags flags
+
size_t mem_size
+ +
off_t pos
+
void * mem
+
size_t size
+ + + + + +
uint32_t time_gran
+
uint32_t proto_major
+ +
uint32_t congestion_threshold
+
uint32_t proto_minor
+
uint32_t max_write
+
uint64_t capable_ext
+
uint32_t max_readahead
+
uint32_t no_interrupt
+
uint32_t max_read
+
uint32_t max_background
+
uint32_t capable
+
uint64_t want_ext
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:60
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t nonseekable
Definition fuse_common.h:78
+
int32_t backing_id
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t padding
+
uint32_t noflush
Definition fuse_common.h:93
+
uint64_t compat_flags
+
uint32_t flush
Definition fuse_common.h:74
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
unsigned int max_idle_threads
+ +
+ + + + diff --git a/doc/html/fuse-3_817_84_2include_2fuse__kernel_8h_source.html b/doc/html/fuse-3_817_84_2include_2fuse__kernel_8h_source.html new file mode 100644 index 0000000..fb04034 --- /dev/null +++ b/doc/html/fuse-3_817_84_2include_2fuse__kernel_8h_source.html @@ -0,0 +1,1097 @@ + + + + + + + +libfuse: fuse-3.17.4/include/fuse_kernel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_kernel.h
+
+
+
1/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */
+
2/*
+
3 This file defines the kernel interface of FUSE
+
4 Copyright (C) 2001-2008 Miklos Szeredi <miklos@szeredi.hu>
+
5
+
6 This program can be distributed under the terms of the GNU GPL.
+
7 See the file COPYING.
+
8
+
9 This -- and only this -- header file may also be distributed under
+
10 the terms of the BSD Licence as follows:
+
11
+
12 Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
+
13
+
14 Redistribution and use in source and binary forms, with or without
+
15 modification, are permitted provided that the following conditions
+
16 are met:
+
17 1. Redistributions of source code must retain the above copyright
+
18 notice, this list of conditions and the following disclaimer.
+
19 2. Redistributions in binary form must reproduce the above copyright
+
20 notice, this list of conditions and the following disclaimer in the
+
21 documentation and/or other materials provided with the distribution.
+
22
+
23 THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+
24 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
25 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
26 ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+
27 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
28 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
29 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
30 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
32 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
33 SUCH DAMAGE.
+
34*/
+
35
+
36/*
+
37 * This file defines the kernel interface of FUSE
+
38 *
+
39 * Protocol changelog:
+
40 *
+
41 * 7.1:
+
42 * - add the following messages:
+
43 * FUSE_SETATTR, FUSE_SYMLINK, FUSE_MKNOD, FUSE_MKDIR, FUSE_UNLINK,
+
44 * FUSE_RMDIR, FUSE_RENAME, FUSE_LINK, FUSE_OPEN, FUSE_READ, FUSE_WRITE,
+
45 * FUSE_RELEASE, FUSE_FSYNC, FUSE_FLUSH, FUSE_SETXATTR, FUSE_GETXATTR,
+
46 * FUSE_LISTXATTR, FUSE_REMOVEXATTR, FUSE_OPENDIR, FUSE_READDIR,
+
47 * FUSE_RELEASEDIR
+
48 * - add padding to messages to accommodate 32-bit servers on 64-bit kernels
+
49 *
+
50 * 7.2:
+
51 * - add FOPEN_DIRECT_IO and FOPEN_KEEP_CACHE flags
+
52 * - add FUSE_FSYNCDIR message
+
53 *
+
54 * 7.3:
+
55 * - add FUSE_ACCESS message
+
56 * - add FUSE_CREATE message
+
57 * - add filehandle to fuse_setattr_in
+
58 *
+
59 * 7.4:
+
60 * - add frsize to fuse_kstatfs
+
61 * - clean up request size limit checking
+
62 *
+
63 * 7.5:
+
64 * - add flags and max_write to fuse_init_out
+
65 *
+
66 * 7.6:
+
67 * - add max_readahead to fuse_init_in and fuse_init_out
+
68 *
+
69 * 7.7:
+
70 * - add FUSE_INTERRUPT message
+
71 * - add POSIX file lock support
+
72 *
+
73 * 7.8:
+
74 * - add lock_owner and flags fields to fuse_release_in
+
75 * - add FUSE_BMAP message
+
76 * - add FUSE_DESTROY message
+
77 *
+
78 * 7.9:
+
79 * - new fuse_getattr_in input argument of GETATTR
+
80 * - add lk_flags in fuse_lk_in
+
81 * - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in
+
82 * - add blksize field to fuse_attr
+
83 * - add file flags field to fuse_read_in and fuse_write_in
+
84 * - Add ATIME_NOW and MTIME_NOW flags to fuse_setattr_in
+
85 *
+
86 * 7.10
+
87 * - add nonseekable open flag
+
88 *
+
89 * 7.11
+
90 * - add IOCTL message
+
91 * - add unsolicited notification support
+
92 * - add POLL message and NOTIFY_POLL notification
+
93 *
+
94 * 7.12
+
95 * - add umask flag to input argument of create, mknod and mkdir
+
96 * - add notification messages for invalidation of inodes and
+
97 * directory entries
+
98 *
+
99 * 7.13
+
100 * - make max number of background requests and congestion threshold
+
101 * tunables
+
102 *
+
103 * 7.14
+
104 * - add splice support to fuse device
+
105 *
+
106 * 7.15
+
107 * - add store notify
+
108 * - add retrieve notify
+
109 *
+
110 * 7.16
+
111 * - add BATCH_FORGET request
+
112 * - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct
+
113 * fuse_ioctl_iovec' instead of ambiguous 'struct iovec'
+
114 * - add FUSE_IOCTL_32BIT flag
+
115 *
+
116 * 7.17
+
117 * - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK
+
118 *
+
119 * 7.18
+
120 * - add FUSE_IOCTL_DIR flag
+
121 * - add FUSE_NOTIFY_DELETE
+
122 *
+
123 * 7.19
+
124 * - add FUSE_FALLOCATE
+
125 *
+
126 * 7.20
+
127 * - add FUSE_AUTO_INVAL_DATA
+
128 *
+
129 * 7.21
+
130 * - add FUSE_READDIRPLUS
+
131 * - send the requested events in POLL request
+
132 *
+
133 * 7.22
+
134 * - add FUSE_ASYNC_DIO
+
135 *
+
136 * 7.23
+
137 * - add FUSE_WRITEBACK_CACHE
+
138 * - add time_gran to fuse_init_out
+
139 * - add reserved space to fuse_init_out
+
140 * - add FATTR_CTIME
+
141 * - add ctime and ctimensec to fuse_setattr_in
+
142 * - add FUSE_RENAME2 request
+
143 * - add FUSE_NO_OPEN_SUPPORT flag
+
144 *
+
145 * 7.24
+
146 * - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support
+
147 *
+
148 * 7.25
+
149 * - add FUSE_PARALLEL_DIROPS
+
150 *
+
151 * 7.26
+
152 * - add FUSE_HANDLE_KILLPRIV
+
153 * - add FUSE_POSIX_ACL
+
154 *
+
155 * 7.27
+
156 * - add FUSE_ABORT_ERROR
+
157 *
+
158 * 7.28
+
159 * - add FUSE_COPY_FILE_RANGE
+
160 * - add FOPEN_CACHE_DIR
+
161 * - add FUSE_MAX_PAGES, add max_pages to init_out
+
162 * - add FUSE_CACHE_SYMLINKS
+
163 *
+
164 * 7.29
+
165 * - add FUSE_NO_OPENDIR_SUPPORT flag
+
166 *
+
167 * 7.30
+
168 * - add FUSE_EXPLICIT_INVAL_DATA
+
169 * - add FUSE_IOCTL_COMPAT_X32
+
170 *
+
171 * 7.31
+
172 * - add FUSE_WRITE_KILL_PRIV flag
+
173 * - add FUSE_SETUPMAPPING and FUSE_REMOVEMAPPING
+
174 * - add map_alignment to fuse_init_out, add FUSE_MAP_ALIGNMENT flag
+
175 *
+
176 * 7.32
+
177 * - add flags to fuse_attr, add FUSE_ATTR_SUBMOUNT, add FUSE_SUBMOUNTS
+
178 *
+
179 * 7.33
+
180 * - add FUSE_HANDLE_KILLPRIV_V2, FUSE_WRITE_KILL_SUIDGID, FATTR_KILL_SUIDGID
+
181 * - add FUSE_OPEN_KILL_SUIDGID
+
182 * - extend fuse_setxattr_in, add FUSE_SETXATTR_EXT
+
183 * - add FUSE_SETXATTR_ACL_KILL_SGID
+
184 *
+
185 * 7.34
+
186 * - add FUSE_SYNCFS
+
187 *
+
188 * 7.35
+
189 * - add FOPEN_NOFLUSH
+
190 *
+
191 * 7.36
+
192 * - extend fuse_init_in with reserved fields, add FUSE_INIT_EXT init flag
+
193 * - add flags2 to fuse_init_in and fuse_init_out
+
194 * - add FUSE_SECURITY_CTX init flag
+
195 * - add security context to create, mkdir, symlink, and mknod requests
+
196 * - add FUSE_HAS_INODE_DAX, FUSE_ATTR_DAX
+
197 *
+
198 * 7.37
+
199 * - add FUSE_TMPFILE
+
200 *
+
201 * 7.38
+
202 * - add FUSE_EXPIRE_ONLY flag to fuse_notify_inval_entry
+
203 * - add FOPEN_PARALLEL_DIRECT_WRITES
+
204 * - add total_extlen to fuse_in_header
+
205 * - add FUSE_MAX_NR_SECCTX
+
206 * - add extension header
+
207 * - add FUSE_EXT_GROUPS
+
208 * - add FUSE_CREATE_SUPP_GROUP
+
209 * - add FUSE_HAS_EXPIRE_ONLY
+
210 *
+
211 * 7.39
+
212 * - add FUSE_DIRECT_IO_ALLOW_MMAP
+
213 * - add FUSE_STATX and related structures
+
214 *
+
215 * 7.40
+
216 * - add max_stack_depth to fuse_init_out, add FUSE_PASSTHROUGH init flag
+
217 * - add backing_id to fuse_open_out, add FOPEN_PASSTHROUGH open flag
+
218 * - add FUSE_NO_EXPORT_SUPPORT init flag
+
219 * - add FUSE_NOTIFY_RESEND, add FUSE_HAS_RESEND init flag
+
220 */
+
221
+
222#ifndef _LINUX_FUSE_H
+
223#define _LINUX_FUSE_H
+
224
+
225#ifdef __KERNEL__
+
226#include <linux/types.h>
+
227#else
+
228#include <stdint.h>
+
229#endif
+
230
+
231/*
+
232 * Version negotiation:
+
233 *
+
234 * Both the kernel and userspace send the version they support in the
+
235 * INIT request and reply respectively.
+
236 *
+
237 * If the major versions match then both shall use the smallest
+
238 * of the two minor versions for communication.
+
239 *
+
240 * If the kernel supports a larger major version, then userspace shall
+
241 * reply with the major version it supports, ignore the rest of the
+
242 * INIT message and expect a new INIT message from the kernel with a
+
243 * matching major version.
+
244 *
+
245 * If the library supports a larger major version, then it shall fall
+
246 * back to the major protocol version sent by the kernel for
+
247 * communication and reply with that major version (and an arbitrary
+
248 * supported minor version).
+
249 */
+
250
+
252#define FUSE_KERNEL_VERSION 7
+
253
+
255#define FUSE_KERNEL_MINOR_VERSION 40
+
256
+
258#define FUSE_ROOT_ID 1
+
259
+
260/* Make sure all structures are padded to 64bit boundary, so 32bit
+
261 userspace works under 64bit kernels */
+
262
+
263struct fuse_attr {
+
264 uint64_t ino;
+
265 uint64_t size;
+
266 uint64_t blocks;
+
267 uint64_t atime;
+
268 uint64_t mtime;
+
269 uint64_t ctime;
+
270 uint32_t atimensec;
+
271 uint32_t mtimensec;
+
272 uint32_t ctimensec;
+
273 uint32_t mode;
+
274 uint32_t nlink;
+
275 uint32_t uid;
+
276 uint32_t gid;
+
277 uint32_t rdev;
+
278 uint32_t blksize;
+
279 uint32_t flags;
+
280};
+
281
+
282/*
+
283 * The following structures are bit-for-bit compatible with the statx(2) ABI in
+
284 * Linux.
+
285 */
+
286struct fuse_sx_time {
+
287 int64_t tv_sec;
+
288 uint32_t tv_nsec;
+
289 int32_t __reserved;
+
290};
+
291
+
292struct fuse_statx {
+
293 uint32_t mask;
+
294 uint32_t blksize;
+
295 uint64_t attributes;
+
296 uint32_t nlink;
+
297 uint32_t uid;
+
298 uint32_t gid;
+
299 uint16_t mode;
+
300 uint16_t __spare0[1];
+
301 uint64_t ino;
+
302 uint64_t size;
+
303 uint64_t blocks;
+
304 uint64_t attributes_mask;
+
305 struct fuse_sx_time atime;
+
306 struct fuse_sx_time btime;
+
307 struct fuse_sx_time ctime;
+
308 struct fuse_sx_time mtime;
+
309 uint32_t rdev_major;
+
310 uint32_t rdev_minor;
+
311 uint32_t dev_major;
+
312 uint32_t dev_minor;
+
313 uint64_t __spare2[14];
+
314};
+
315
+
316struct fuse_kstatfs {
+
317 uint64_t blocks;
+
318 uint64_t bfree;
+
319 uint64_t bavail;
+
320 uint64_t files;
+
321 uint64_t ffree;
+
322 uint32_t bsize;
+
323 uint32_t namelen;
+
324 uint32_t frsize;
+
325 uint32_t padding;
+
326 uint32_t spare[6];
+
327};
+
328
+
329struct fuse_file_lock {
+
330 uint64_t start;
+
331 uint64_t end;
+
332 uint32_t type;
+
333 uint32_t pid; /* tgid */
+
334};
+
335
+
339#define FATTR_MODE (1 << 0)
+
340#define FATTR_UID (1 << 1)
+
341#define FATTR_GID (1 << 2)
+
342#define FATTR_SIZE (1 << 3)
+
343#define FATTR_ATIME (1 << 4)
+
344#define FATTR_MTIME (1 << 5)
+
345#define FATTR_FH (1 << 6)
+
346#define FATTR_ATIME_NOW (1 << 7)
+
347#define FATTR_MTIME_NOW (1 << 8)
+
348#define FATTR_LOCKOWNER (1 << 9)
+
349#define FATTR_CTIME (1 << 10)
+
350#define FATTR_KILL_SUIDGID (1 << 11)
+
351
+
364#define FOPEN_DIRECT_IO (1 << 0)
+
365#define FOPEN_KEEP_CACHE (1 << 1)
+
366#define FOPEN_NONSEEKABLE (1 << 2)
+
367#define FOPEN_CACHE_DIR (1 << 3)
+
368#define FOPEN_STREAM (1 << 4)
+
369#define FOPEN_NOFLUSH (1 << 5)
+
370#define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6)
+
371#define FOPEN_PASSTHROUGH (1 << 7)
+
372
+
425#define FUSE_ASYNC_READ (1 << 0)
+
426#define FUSE_POSIX_LOCKS (1 << 1)
+
427#define FUSE_FILE_OPS (1 << 2)
+
428#define FUSE_ATOMIC_O_TRUNC (1 << 3)
+
429#define FUSE_EXPORT_SUPPORT (1 << 4)
+
430#define FUSE_BIG_WRITES (1 << 5)
+
431#define FUSE_DONT_MASK (1 << 6)
+
432#define FUSE_SPLICE_WRITE (1 << 7)
+
433#define FUSE_SPLICE_MOVE (1 << 8)
+
434#define FUSE_SPLICE_READ (1 << 9)
+
435#define FUSE_FLOCK_LOCKS (1 << 10)
+
436#define FUSE_HAS_IOCTL_DIR (1 << 11)
+
437#define FUSE_AUTO_INVAL_DATA (1 << 12)
+
438#define FUSE_DO_READDIRPLUS (1 << 13)
+
439#define FUSE_READDIRPLUS_AUTO (1 << 14)
+
440#define FUSE_ASYNC_DIO (1 << 15)
+
441#define FUSE_WRITEBACK_CACHE (1 << 16)
+
442#define FUSE_NO_OPEN_SUPPORT (1 << 17)
+
443#define FUSE_PARALLEL_DIROPS (1 << 18)
+
444#define FUSE_HANDLE_KILLPRIV (1 << 19)
+
445#define FUSE_POSIX_ACL (1 << 20)
+
446#define FUSE_ABORT_ERROR (1 << 21)
+
447#define FUSE_MAX_PAGES (1 << 22)
+
448#define FUSE_CACHE_SYMLINKS (1 << 23)
+
449#define FUSE_NO_OPENDIR_SUPPORT (1 << 24)
+
450#define FUSE_EXPLICIT_INVAL_DATA (1 << 25)
+
451#define FUSE_MAP_ALIGNMENT (1 << 26)
+
452#define FUSE_SUBMOUNTS (1 << 27)
+
453#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28)
+
454#define FUSE_SETXATTR_EXT (1 << 29)
+
455#define FUSE_INIT_EXT (1 << 30)
+
456#define FUSE_INIT_RESERVED (1 << 31)
+
457/* bits 32..63 get shifted down 32 bits into the flags2 field */
+
458#define FUSE_SECURITY_CTX (1ULL << 32)
+
459#define FUSE_HAS_INODE_DAX (1ULL << 33)
+
460#define FUSE_CREATE_SUPP_GROUP (1ULL << 34)
+
461#define FUSE_HAS_EXPIRE_ONLY (1ULL << 35)
+
462#define FUSE_DIRECT_IO_ALLOW_MMAP (1ULL << 36)
+
463#define FUSE_PASSTHROUGH (1ULL << 37)
+
464#define FUSE_NO_EXPORT_SUPPORT (1ULL << 38)
+
465#define FUSE_HAS_RESEND (1ULL << 39)
+
466
+
467/* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */
+
468#define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP
+
469
+
475#define CUSE_UNRESTRICTED_IOCTL (1 << 0)
+
476
+
480#define FUSE_RELEASE_FLUSH (1 << 0)
+
481#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1)
+
482
+
486#define FUSE_GETATTR_FH (1 << 0)
+
487
+
491#define FUSE_LK_FLOCK (1 << 0)
+
492
+
500#define FUSE_WRITE_CACHE (1 << 0)
+
501#define FUSE_WRITE_LOCKOWNER (1 << 1)
+
502#define FUSE_WRITE_KILL_SUIDGID (1 << 2)
+
503
+
504/* Obsolete alias; this flag implies killing suid/sgid only. */
+
505#define FUSE_WRITE_KILL_PRIV FUSE_WRITE_KILL_SUIDGID
+
506
+
510#define FUSE_READ_LOCKOWNER (1 << 1)
+
511
+
524#define FUSE_IOCTL_COMPAT (1 << 0)
+
525#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
526#define FUSE_IOCTL_RETRY (1 << 2)
+
527#define FUSE_IOCTL_32BIT (1 << 3)
+
528#define FUSE_IOCTL_DIR (1 << 4)
+
529#define FUSE_IOCTL_COMPAT_X32 (1 << 5)
+
530
+
531#define FUSE_IOCTL_MAX_IOV 256
+
532
+
538#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
+
539
+
545#define FUSE_FSYNC_FDATASYNC (1 << 0)
+
546
+
553#define FUSE_ATTR_SUBMOUNT (1 << 0)
+
554#define FUSE_ATTR_DAX (1 << 1)
+
555
+
560#define FUSE_OPEN_KILL_SUIDGID (1 << 0)
+
561
+
566#define FUSE_SETXATTR_ACL_KILL_SGID (1 << 0)
+
567
+
572#define FUSE_EXPIRE_ONLY (1 << 0)
+
573
+
579enum fuse_ext_type {
+
580 /* Types 0..31 are reserved for fuse_secctx_header */
+
581 FUSE_MAX_NR_SECCTX = 31,
+
582 FUSE_EXT_GROUPS = 32,
+
583};
+
584
+
585enum fuse_opcode {
+
586 FUSE_LOOKUP = 1,
+
587 FUSE_FORGET = 2, /* no reply */
+
588 FUSE_GETATTR = 3,
+
589 FUSE_SETATTR = 4,
+
590 FUSE_READLINK = 5,
+
591 FUSE_SYMLINK = 6,
+
592 FUSE_MKNOD = 8,
+
593 FUSE_MKDIR = 9,
+
594 FUSE_UNLINK = 10,
+
595 FUSE_RMDIR = 11,
+
596 FUSE_RENAME = 12,
+
597 FUSE_LINK = 13,
+
598 FUSE_OPEN = 14,
+
599 FUSE_READ = 15,
+
600 FUSE_WRITE = 16,
+
601 FUSE_STATFS = 17,
+
602 FUSE_RELEASE = 18,
+
603 FUSE_FSYNC = 20,
+
604 FUSE_SETXATTR = 21,
+
605 FUSE_GETXATTR = 22,
+
606 FUSE_LISTXATTR = 23,
+
607 FUSE_REMOVEXATTR = 24,
+
608 FUSE_FLUSH = 25,
+
609 FUSE_INIT = 26,
+
610 FUSE_OPENDIR = 27,
+
611 FUSE_READDIR = 28,
+
612 FUSE_RELEASEDIR = 29,
+
613 FUSE_FSYNCDIR = 30,
+
614 FUSE_GETLK = 31,
+
615 FUSE_SETLK = 32,
+
616 FUSE_SETLKW = 33,
+
617 FUSE_ACCESS = 34,
+
618 FUSE_CREATE = 35,
+
619 FUSE_INTERRUPT = 36,
+
620 FUSE_BMAP = 37,
+
621 FUSE_DESTROY = 38,
+
622 FUSE_IOCTL = 39,
+
623 FUSE_POLL = 40,
+
624 FUSE_NOTIFY_REPLY = 41,
+
625 FUSE_BATCH_FORGET = 42,
+
626 FUSE_FALLOCATE = 43,
+
627 FUSE_READDIRPLUS = 44,
+
628 FUSE_RENAME2 = 45,
+
629 FUSE_LSEEK = 46,
+
630 FUSE_COPY_FILE_RANGE = 47,
+
631 FUSE_SETUPMAPPING = 48,
+
632 FUSE_REMOVEMAPPING = 49,
+
633 FUSE_SYNCFS = 50,
+
634 FUSE_TMPFILE = 51,
+
635 FUSE_STATX = 52,
+
636
+
637 /* CUSE specific operations */
+
638 CUSE_INIT = 4096,
+
639
+
640 /* Reserved opcodes: helpful to detect structure endian-ness */
+
641 CUSE_INIT_BSWAP_RESERVED = 1048576, /* CUSE_INIT << 8 */
+
642 FUSE_INIT_BSWAP_RESERVED = 436207616, /* FUSE_INIT << 24 */
+
643};
+
644
+
645enum fuse_notify_code {
+
646 FUSE_NOTIFY_POLL = 1,
+
647 FUSE_NOTIFY_INVAL_INODE = 2,
+
648 FUSE_NOTIFY_INVAL_ENTRY = 3,
+
649 FUSE_NOTIFY_STORE = 4,
+
650 FUSE_NOTIFY_RETRIEVE = 5,
+
651 FUSE_NOTIFY_DELETE = 6,
+
652 FUSE_NOTIFY_RESEND = 7,
+
653 FUSE_NOTIFY_CODE_MAX,
+
654};
+
655
+
656/* The read buffer is required to be at least 8k, but may be much larger */
+
657#define FUSE_MIN_READ_BUFFER 8192
+
658
+
659#define FUSE_COMPAT_ENTRY_OUT_SIZE 120
+
660
+
661struct fuse_entry_out {
+
662 uint64_t nodeid; /* Inode ID */
+
663 uint64_t generation; /* Inode generation: nodeid:gen must
+
664 be unique for the fs's lifetime */
+
665 uint64_t entry_valid; /* Cache timeout for the name */
+
666 uint64_t attr_valid; /* Cache timeout for the attributes */
+
667 uint32_t entry_valid_nsec;
+
668 uint32_t attr_valid_nsec;
+
669 struct fuse_attr attr;
+
670};
+
671
+
672struct fuse_forget_in {
+
673 uint64_t nlookup;
+
674};
+
675
+
676struct fuse_forget_one {
+
677 uint64_t nodeid;
+
678 uint64_t nlookup;
+
679};
+
680
+
681struct fuse_batch_forget_in {
+
682 uint32_t count;
+
683 uint32_t dummy;
+
684};
+
685
+
686struct fuse_getattr_in {
+
687 uint32_t getattr_flags;
+
688 uint32_t dummy;
+
689 uint64_t fh;
+
690};
+
691
+
692#define FUSE_COMPAT_ATTR_OUT_SIZE 96
+
693
+
694struct fuse_attr_out {
+
695 uint64_t attr_valid; /* Cache timeout for the attributes */
+
696 uint32_t attr_valid_nsec;
+
697 uint32_t dummy;
+
698 struct fuse_attr attr;
+
699};
+
700
+
701struct fuse_statx_in {
+
702 uint32_t getattr_flags;
+
703 uint32_t reserved;
+
704 uint64_t fh;
+
705 uint32_t sx_flags;
+
706 uint32_t sx_mask;
+
707};
+
708
+
709struct fuse_statx_out {
+
710 uint64_t attr_valid; /* Cache timeout for the attributes */
+
711 uint32_t attr_valid_nsec;
+
712 uint32_t flags;
+
713 uint64_t spare[2];
+
714 struct fuse_statx stat;
+
715};
+
716
+
717#define FUSE_COMPAT_MKNOD_IN_SIZE 8
+
718
+
719struct fuse_mknod_in {
+
720 uint32_t mode;
+
721 uint32_t rdev;
+
722 uint32_t umask;
+
723 uint32_t padding;
+
724};
+
725
+
726struct fuse_mkdir_in {
+
727 uint32_t mode;
+
728 uint32_t umask;
+
729};
+
730
+
731struct fuse_rename_in {
+
732 uint64_t newdir;
+
733};
+
734
+
735struct fuse_rename2_in {
+
736 uint64_t newdir;
+
737 uint32_t flags;
+
738 uint32_t padding;
+
739};
+
740
+
741struct fuse_link_in {
+
742 uint64_t oldnodeid;
+
743};
+
744
+
745struct fuse_setattr_in {
+
746 uint32_t valid;
+
747 uint32_t padding;
+
748 uint64_t fh;
+
749 uint64_t size;
+
750 uint64_t lock_owner;
+
751 uint64_t atime;
+
752 uint64_t mtime;
+
753 uint64_t ctime;
+
754 uint32_t atimensec;
+
755 uint32_t mtimensec;
+
756 uint32_t ctimensec;
+
757 uint32_t mode;
+
758 uint32_t unused4;
+
759 uint32_t uid;
+
760 uint32_t gid;
+
761 uint32_t unused5;
+
762};
+
763
+
764struct fuse_open_in {
+
765 uint32_t flags;
+
766 uint32_t open_flags; /* FUSE_OPEN_... */
+
767};
+
768
+
769struct fuse_create_in {
+
770 uint32_t flags;
+
771 uint32_t mode;
+
772 uint32_t umask;
+
773 uint32_t open_flags; /* FUSE_OPEN_... */
+
774};
+
775
+
776struct fuse_open_out {
+
777 uint64_t fh;
+
778 uint32_t open_flags;
+
779 int32_t backing_id;
+
780};
+
781
+
782struct fuse_release_in {
+
783 uint64_t fh;
+
784 uint32_t flags;
+
785 uint32_t release_flags;
+
786 uint64_t lock_owner;
+
787};
+
788
+
789struct fuse_flush_in {
+
790 uint64_t fh;
+
791 uint32_t unused;
+
792 uint32_t padding;
+
793 uint64_t lock_owner;
+
794};
+
795
+
796struct fuse_read_in {
+
797 uint64_t fh;
+
798 uint64_t offset;
+
799 uint32_t size;
+
800 uint32_t read_flags;
+
801 uint64_t lock_owner;
+
802 uint32_t flags;
+
803 uint32_t padding;
+
804};
+
805
+
806#define FUSE_COMPAT_WRITE_IN_SIZE 24
+
807
+
808struct fuse_write_in {
+
809 uint64_t fh;
+
810 uint64_t offset;
+
811 uint32_t size;
+
812 uint32_t write_flags;
+
813 uint64_t lock_owner;
+
814 uint32_t flags;
+
815 uint32_t padding;
+
816};
+
817
+
818struct fuse_write_out {
+
819 uint32_t size;
+
820 uint32_t padding;
+
821};
+
822
+
823#define FUSE_COMPAT_STATFS_SIZE 48
+
824
+
825struct fuse_statfs_out {
+
826 struct fuse_kstatfs st;
+
827};
+
828
+
829struct fuse_fsync_in {
+
830 uint64_t fh;
+
831 uint32_t fsync_flags;
+
832 uint32_t padding;
+
833};
+
834
+
835#define FUSE_COMPAT_SETXATTR_IN_SIZE 8
+
836
+
837struct fuse_setxattr_in {
+
838 uint32_t size;
+
839 uint32_t flags;
+
840 uint32_t setxattr_flags;
+
841 uint32_t padding;
+
842};
+
843
+
844struct fuse_getxattr_in {
+
845 uint32_t size;
+
846 uint32_t padding;
+
847};
+
848
+
849struct fuse_getxattr_out {
+
850 uint32_t size;
+
851 uint32_t padding;
+
852};
+
853
+
854struct fuse_lk_in {
+
855 uint64_t fh;
+
856 uint64_t owner;
+
857 struct fuse_file_lock lk;
+
858 uint32_t lk_flags;
+
859 uint32_t padding;
+
860};
+
861
+
862struct fuse_lk_out {
+
863 struct fuse_file_lock lk;
+
864};
+
865
+
866struct fuse_access_in {
+
867 uint32_t mask;
+
868 uint32_t padding;
+
869};
+
870
+
871struct fuse_init_in {
+
872 uint32_t major;
+
873 uint32_t minor;
+
874 uint32_t max_readahead;
+
875 uint32_t flags;
+
876 uint32_t flags2;
+
877 uint32_t unused[11];
+
878};
+
879
+
880#define FUSE_COMPAT_INIT_OUT_SIZE 8
+
881#define FUSE_COMPAT_22_INIT_OUT_SIZE 24
+
882
+
883struct fuse_init_out {
+
884 uint32_t major;
+
885 uint32_t minor;
+
886 uint32_t max_readahead;
+
887 uint32_t flags;
+
888 uint16_t max_background;
+
889 uint16_t congestion_threshold;
+
890 uint32_t max_write;
+
891 uint32_t time_gran;
+
892 uint16_t max_pages;
+
893 uint16_t map_alignment;
+
894 uint32_t flags2;
+
895 uint32_t max_stack_depth;
+
896 uint32_t unused[6];
+
897};
+
898
+
899#define CUSE_INIT_INFO_MAX 4096
+
900
+
901struct cuse_init_in {
+
902 uint32_t major;
+
903 uint32_t minor;
+
904 uint32_t unused;
+
905 uint32_t flags;
+
906};
+
907
+
908struct cuse_init_out {
+
909 uint32_t major;
+
910 uint32_t minor;
+
911 uint32_t unused;
+
912 uint32_t flags;
+
913 uint32_t max_read;
+
914 uint32_t max_write;
+
915 uint32_t dev_major; /* chardev major */
+
916 uint32_t dev_minor; /* chardev minor */
+
917 uint32_t spare[10];
+
918};
+
919
+
920struct fuse_interrupt_in {
+
921 uint64_t unique;
+
922};
+
923
+
924struct fuse_bmap_in {
+
925 uint64_t block;
+
926 uint32_t blocksize;
+
927 uint32_t padding;
+
928};
+
929
+
930struct fuse_bmap_out {
+
931 uint64_t block;
+
932};
+
933
+
934struct fuse_ioctl_in {
+
935 uint64_t fh;
+
936 uint32_t flags;
+
937 uint32_t cmd;
+
938 uint64_t arg;
+
939 uint32_t in_size;
+
940 uint32_t out_size;
+
941};
+
942
+
943struct fuse_ioctl_iovec {
+
944 uint64_t base;
+
945 uint64_t len;
+
946};
+
947
+
948struct fuse_ioctl_out {
+
949 int32_t result;
+
950 uint32_t flags;
+
951 uint32_t in_iovs;
+
952 uint32_t out_iovs;
+
953};
+
954
+
955struct fuse_poll_in {
+
956 uint64_t fh;
+
957 uint64_t kh;
+
958 uint32_t flags;
+
959 uint32_t events;
+
960};
+
961
+
962struct fuse_poll_out {
+
963 uint32_t revents;
+
964 uint32_t padding;
+
965};
+
966
+
967struct fuse_notify_poll_wakeup_out {
+
968 uint64_t kh;
+
969};
+
970
+
971struct fuse_fallocate_in {
+
972 uint64_t fh;
+
973 uint64_t offset;
+
974 uint64_t length;
+
975 uint32_t mode;
+
976 uint32_t padding;
+
977};
+
978
+
985#define FUSE_UNIQUE_RESEND (1ULL << 63)
+
986
+
987struct fuse_in_header {
+
988 uint32_t len;
+
989 uint32_t opcode;
+
990 uint64_t unique;
+
991 uint64_t nodeid;
+
992 uint32_t uid;
+
993 uint32_t gid;
+
994 uint32_t pid;
+
995 uint16_t total_extlen; /* length of extensions in 8byte units */
+
996 uint16_t padding;
+
997};
+
998
+
999struct fuse_out_header {
+
1000 uint32_t len;
+
1001 int32_t error;
+
1002 uint64_t unique;
+
1003};
+
1004
+
1005struct fuse_dirent {
+
1006 uint64_t ino;
+
1007 uint64_t off;
+
1008 uint32_t namelen;
+
1009 uint32_t type;
+
1010 char name[];
+
1011};
+
1012
+
1013/* Align variable length records to 64bit boundary */
+
1014#define FUSE_REC_ALIGN(x) \
+
1015 (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1))
+
1016
+
1017#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name)
+
1018#define FUSE_DIRENT_ALIGN(x) FUSE_REC_ALIGN(x)
+
1019#define FUSE_DIRENT_SIZE(d) \
+
1020 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen)
+
1021
+
1022struct fuse_direntplus {
+
1023 struct fuse_entry_out entry_out;
+
1024 struct fuse_dirent dirent;
+
1025};
+
1026
+
1027#define FUSE_NAME_OFFSET_DIRENTPLUS \
+
1028 offsetof(struct fuse_direntplus, dirent.name)
+
1029#define FUSE_DIRENTPLUS_SIZE(d) \
+
1030 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen)
+
1031
+
1032struct fuse_notify_inval_inode_out {
+
1033 uint64_t ino;
+
1034 int64_t off;
+
1035 int64_t len;
+
1036};
+
1037
+
1038struct fuse_notify_inval_entry_out {
+
1039 uint64_t parent;
+
1040 uint32_t namelen;
+
1041 uint32_t flags;
+
1042};
+
1043
+
1044struct fuse_notify_delete_out {
+
1045 uint64_t parent;
+
1046 uint64_t child;
+
1047 uint32_t namelen;
+
1048 uint32_t padding;
+
1049};
+
1050
+
1051struct fuse_notify_store_out {
+
1052 uint64_t nodeid;
+
1053 uint64_t offset;
+
1054 uint32_t size;
+
1055 uint32_t padding;
+
1056};
+
1057
+
1058struct fuse_notify_retrieve_out {
+
1059 uint64_t notify_unique;
+
1060 uint64_t nodeid;
+
1061 uint64_t offset;
+
1062 uint32_t size;
+
1063 uint32_t padding;
+
1064};
+
1065
+
1066/* Matches the size of fuse_write_in */
+
1067struct fuse_notify_retrieve_in {
+
1068 uint64_t dummy1;
+
1069 uint64_t offset;
+
1070 uint32_t size;
+
1071 uint32_t dummy2;
+
1072 uint64_t dummy3;
+
1073 uint64_t dummy4;
+
1074};
+
1075
+
1076struct fuse_backing_map {
+
1077 int32_t fd;
+
1078 uint32_t flags;
+
1079 uint64_t padding;
+
1080};
+
1081
+
1082/* Device ioctls: */
+
1083#define FUSE_DEV_IOC_MAGIC 229
+
1084#define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t)
+
1085#define FUSE_DEV_IOC_BACKING_OPEN _IOW(FUSE_DEV_IOC_MAGIC, 1, \
+
1086 struct fuse_backing_map)
+
1087#define FUSE_DEV_IOC_BACKING_CLOSE _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t)
+
1088
+
1089struct fuse_lseek_in {
+
1090 uint64_t fh;
+
1091 uint64_t offset;
+
1092 uint32_t whence;
+
1093 uint32_t padding;
+
1094};
+
1095
+
1096struct fuse_lseek_out {
+
1097 uint64_t offset;
+
1098};
+
1099
+
1100struct fuse_copy_file_range_in {
+
1101 uint64_t fh_in;
+
1102 uint64_t off_in;
+
1103 uint64_t nodeid_out;
+
1104 uint64_t fh_out;
+
1105 uint64_t off_out;
+
1106 uint64_t len;
+
1107 uint64_t flags;
+
1108};
+
1109
+
1110#define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0)
+
1111#define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1)
+
1112struct fuse_setupmapping_in {
+
1113 /* An already open handle */
+
1114 uint64_t fh;
+
1115 /* Offset into the file to start the mapping */
+
1116 uint64_t foffset;
+
1117 /* Length of mapping required */
+
1118 uint64_t len;
+
1119 /* Flags, FUSE_SETUPMAPPING_FLAG_* */
+
1120 uint64_t flags;
+
1121 /* Offset in Memory Window */
+
1122 uint64_t moffset;
+
1123};
+
1124
+
1125struct fuse_removemapping_in {
+
1126 /* number of fuse_removemapping_one follows */
+
1127 uint32_t count;
+
1128};
+
1129
+
1130struct fuse_removemapping_one {
+
1131 /* Offset into the dax window start the unmapping */
+
1132 uint64_t moffset;
+
1133 /* Length of mapping required */
+
1134 uint64_t len;
+
1135};
+
1136
+
1137#define FUSE_REMOVEMAPPING_MAX_ENTRY \
+
1138 (PAGE_SIZE / sizeof(struct fuse_removemapping_one))
+
1139
+
1140struct fuse_syncfs_in {
+
1141 uint64_t padding;
+
1142};
+
1143
+
1144/*
+
1145 * For each security context, send fuse_secctx with size of security context
+
1146 * fuse_secctx will be followed by security context name and this in turn
+
1147 * will be followed by actual context label.
+
1148 * fuse_secctx, name, context
+
1149 */
+
1150struct fuse_secctx {
+
1151 uint32_t size;
+
1152 uint32_t padding;
+
1153};
+
1154
+
1155/*
+
1156 * Contains the information about how many fuse_secctx structures are being
+
1157 * sent and what's the total size of all security contexts (including
+
1158 * size of fuse_secctx_header).
+
1159 *
+
1160 */
+
1161struct fuse_secctx_header {
+
1162 uint32_t size;
+
1163 uint32_t nr_secctx;
+
1164};
+
1165
+
+ +
1175 uint32_t size;
+
1176 uint32_t type;
+
1177};
+
+
1178
+
+ +
1185 uint32_t nr_groups;
+
1186 uint32_t groups[];
+
1187};
+
+
1188
+
1189#endif /* _LINUX_FUSE_H */
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_84_2include_2fuse__log_8h.html b/doc/html/fuse-3_817_84_2include_2fuse__log_8h.html new file mode 100644 index 0000000..ecd7b4d --- /dev/null +++ b/doc/html/fuse-3_817_84_2include_2fuse__log_8h.html @@ -0,0 +1,268 @@ + + + + + + + +libfuse: fuse-3.17.4/include/fuse_log.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_log.h File Reference
+
+
+
#include <stdarg.h>
+
+

Go to the source code of this file.

+ + + + +

+Typedefs

typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
 
+ + + +

+Enumerations

enum  fuse_log_level
 
+ + + + + + + + + +

+Functions

void fuse_set_log_func (fuse_log_func_t func)
 
void fuse_log (enum fuse_log_level level, const char *fmt,...)
 
void fuse_log_enable_syslog (const char *ident, int option, int facility)
 
void fuse_log_close_syslog (void)
 
+

Detailed Description

+

This file defines the logging interface of FUSE

+ +

Definition in file fuse_log.h.

+

Typedef Documentation

+ +

◆ fuse_log_func_t

+ +
+
+ + + + +
typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
+
+

Log message handler function.

+

This function must be thread-safe. It may be called from any libfuse function, including fuse_parse_cmdline() and other functions invoked before a FUSE filesystem is created.

+

Install a custom log message handler function using fuse_set_log_func().

+
Parameters
+ + + + +
levellog severity level
fmtsprintf-style format string including newline
apformat string arguments
+
+
+ +

Definition at line 52 of file fuse_log.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_log_level

+ +
+
+ + + + +
enum fuse_log_level
+
+

Log severity level

+

These levels correspond to syslog(2) log levels since they are widely used.

+ +

Definition at line 28 of file fuse_log.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_log()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_log (enum fuse_log_level level,
const char * fmt,
 ... 
)
+
+

Emit a log message

+
Parameters
+ + + +
levelseverity level (FUSE_LOG_ERR, FUSE_LOG_DEBUG, etc)
fmtsprintf-style format string including newline
+
+
+ +

Definition at line 77 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_close_syslog()

+ +
+
+ + + + + + + + +
void fuse_log_close_syslog (void )
+
+

To be called at teardown to close syslog.

+ +

Definition at line 93 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_enable_syslog()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_log_enable_syslog (const char * ident,
int option,
int facility 
)
+
+

Switch default log handler from stderr to syslog

+

Passed options are according to 'man 3 openlog'

+ +

Definition at line 86 of file fuse_log.c.

+ +
+
+ +

◆ fuse_set_log_func()

+ +
+
+ + + + + + + + +
void fuse_set_log_func (fuse_log_func_t func)
+
+

Install a custom log handler function.

+

Log messages are emitted by libfuse functions to report errors and debug information. Messages are printed to stderr by default but this can be overridden by installing a custom log message handler function.

+

The log message handler function is global and affects all FUSE filesystems created within this process.

+
Parameters
+ + +
funca custom log message handler function or NULL to revert to the default
+
+
+ +

Definition at line 69 of file fuse_log.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2include_2fuse__log_8h_source.html b/doc/html/fuse-3_817_84_2include_2fuse__log_8h_source.html new file mode 100644 index 0000000..4b25e19 --- /dev/null +++ b/doc/html/fuse-3_817_84_2include_2fuse__log_8h_source.html @@ -0,0 +1,112 @@ + + + + + + + +libfuse: fuse-3.17.4/include/fuse_log.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_log.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_LOG_H_
+
10#define FUSE_LOG_H_
+
11
+
17#include <stdarg.h>
+
18
+
19#ifdef __cplusplus
+
20extern "C" {
+
21#endif
+
22
+
+ +
29 FUSE_LOG_EMERG,
+
30 FUSE_LOG_ALERT,
+
31 FUSE_LOG_CRIT,
+
32 FUSE_LOG_ERR,
+
33 FUSE_LOG_WARNING,
+
34 FUSE_LOG_NOTICE,
+
35 FUSE_LOG_INFO,
+
36 FUSE_LOG_DEBUG
+
37};
+
+
38
+
52typedef void (*fuse_log_func_t)(enum fuse_log_level level,
+
53 const char *fmt, va_list ap);
+
54
+ +
69
+
76void fuse_log(enum fuse_log_level level, const char *fmt, ...);
+
77
+
83void fuse_log_enable_syslog(const char *ident, int option, int facility);
+
84
+
88void fuse_log_close_syslog(void);
+
89
+
90#ifdef __cplusplus
+
91}
+
92#endif
+
93
+
94#endif /* FUSE_LOG_H_ */
+
void fuse_log_close_syslog(void)
Definition fuse_log.c:93
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:86
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:69
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2include_2fuse__lowlevel_8h.html b/doc/html/fuse-3_817_84_2include_2fuse__lowlevel_8h.html new file mode 100644 index 0000000..bd2503f --- /dev/null +++ b/doc/html/fuse-3_817_84_2include_2fuse__lowlevel_8h.html @@ -0,0 +1,2363 @@ + + + + + + + +libfuse: fuse-3.17.4/include/fuse_lowlevel.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_lowlevel.h File Reference
+
+
+
#include "fuse_common.h"
+#include <stddef.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + + + + + +

+Data Structures

struct  fuse_entry_param
 
struct  fuse_ctx
 
struct  fuse_lowlevel_ops
 
struct  fuse_cmdline_opts
 
+ + + +

+Macros

#define FUSE_ROOT_ID   1
 
+ + + + + + + +

+Typedefs

typedef uint64_t fuse_ino_t
 
typedef struct fuse_req * fuse_req_t
 
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
 
+ + + +

+Enumerations

enum  fuse_notify_entry_flags
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

int fuse_reply_err (fuse_req_t req, int err)
 
void fuse_reply_none (fuse_req_t req)
 
int fuse_reply_entry (fuse_req_t req, const struct fuse_entry_param *e)
 
int fuse_reply_create (fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
 
int fuse_reply_attr (fuse_req_t req, const struct stat *attr, double attr_timeout)
 
int fuse_reply_readlink (fuse_req_t req, const char *link)
 
int fuse_passthrough_open (fuse_req_t req, int fd)
 
int fuse_reply_open (fuse_req_t req, const struct fuse_file_info *fi)
 
int fuse_reply_write (fuse_req_t req, size_t count)
 
int fuse_reply_buf (fuse_req_t req, const char *buf, size_t size)
 
int fuse_reply_data (fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
 
int fuse_reply_iov (fuse_req_t req, const struct iovec *iov, int count)
 
int fuse_reply_statfs (fuse_req_t req, const struct statvfs *stbuf)
 
int fuse_reply_xattr (fuse_req_t req, size_t count)
 
int fuse_reply_lock (fuse_req_t req, const struct flock *lock)
 
int fuse_reply_bmap (fuse_req_t req, uint64_t idx)
 
size_t fuse_add_direntry (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
 
size_t fuse_add_direntry_plus (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
 
int fuse_reply_ioctl_retry (fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
 
int fuse_reply_ioctl (fuse_req_t req, int result, const void *buf, size_t size)
 
int fuse_reply_ioctl_iov (fuse_req_t req, int result, const struct iovec *iov, int count)
 
int fuse_reply_poll (fuse_req_t req, unsigned revents)
 
int fuse_reply_lseek (fuse_req_t req, off_t off)
 
int fuse_lowlevel_notify_poll (struct fuse_pollhandle *ph)
 
int fuse_lowlevel_notify_inval_inode (struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
 
int fuse_lowlevel_notify_inval_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_expire_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_delete (struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_store (struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
 
int fuse_lowlevel_notify_retrieve (struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
 
void * fuse_req_userdata (fuse_req_t req)
 
const struct fuse_ctxfuse_req_ctx (fuse_req_t req)
 
int fuse_req_getgroups (fuse_req_t req, int size, gid_t list[])
 
void fuse_req_interrupt_func (fuse_req_t req, fuse_interrupt_func_t func, void *data)
 
int fuse_req_interrupted (fuse_req_t req)
 
void fuse_lowlevel_version (void)
 
void fuse_lowlevel_help (void)
 
void fuse_cmdline_help (void)
 
int fuse_parse_cmdline_30 (struct fuse_args *args, struct fuse_cmdline_opts *opts)
 
int fuse_session_mount (struct fuse_session *se, const char *mountpoint)
 
int fuse_session_loop (struct fuse_session *se)
 
void fuse_session_exit (struct fuse_session *se)
 
void fuse_session_reset (struct fuse_session *se)
 
int fuse_session_exited (struct fuse_session *se)
 
void fuse_session_unmount (struct fuse_session *se)
 
void fuse_session_destroy (struct fuse_session *se)
 
int fuse_session_fd (struct fuse_session *se)
 
void fuse_session_process_buf (struct fuse_session *se, const struct fuse_buf *buf)
 
int fuse_session_receive_buf (struct fuse_session *se, struct fuse_buf *buf)
 
+

Detailed Description

+

Low level API

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header. To use the newest API define it to 35 (recommended for any new application).

+ +

Definition in file fuse_lowlevel.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ROOT_ID

+ +
+
+ + + + +
#define FUSE_ROOT_ID   1
+
+

The node ID of the root inode

+ +

Definition at line 44 of file fuse_lowlevel.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_ino_t

+ +
+
+ + + + +
typedef uint64_t fuse_ino_t
+
+

Inode number type

+ +

Definition at line 47 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_interrupt_func_t

+ +
+
+ + + + +
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
+
+

Callback function for an interrupt

+
Parameters
+ + + +
reqinterrupted request
datauser data
+
+
+ +

Definition at line 1948 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_req_t

+ +
+
+ + + + +
typedef struct fuse_req* fuse_req_t
+
+

Request pointer type

+ +

Definition at line 50 of file fuse_lowlevel.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_notify_entry_flags

+ +
+
+ + + + +
enum fuse_notify_entry_flags
+
+

Flags for fuse_lowlevel_notify_entry() 0 = invalidate entry FUSE_LL_EXPIRE_ONLY = expire entry

+ +

Definition at line 148 of file fuse_lowlevel.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_add_direntry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct stat * stbuf,
off_t off 
)
+
+

Add a directory entry to the buffer

+

Buffer needs to be large enough to hold the entry. If it's not, then the entry is not filled in but the size of the entry is still returned. The caller can check this by comparing the bufsize parameter with the returned entry size. If the entry size is larger than the buffer size, the operation failed.

+

From the 'stbuf' argument the st_ino field and bits 12-15 of the st_mode field are used. The other fields are ignored.

+

off should be any non-zero value that the filesystem can use to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to mean "from the beginning", and should therefore never be used (the first call to fuse_add_direntry should be passed the offset of the second directory entry).

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
stbufthe file attributes
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 286 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_add_direntry_plus()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry_plus (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct fuse_entry_parame,
off_t off 
)
+
+

Add a directory entry to the buffer with the attributes

+

See documentation of fuse_add_direntry() for more details.

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
ethe directory entry
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 376 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_cmdline_help()

+ +
+
+ + + + + + + + +
void fuse_cmdline_help (void )
+
+

Print available options for fuse_parse_cmdline().

+ +

Definition at line 130 of file helper.c.

+ +
+
+ +

◆ fuse_lowlevel_help()

+ +
+
+ + + + + + + + +
void fuse_lowlevel_help (void )
+
+

Print available low-level options to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+ +

Definition at line 3000 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_delete()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_delete (struct fuse_session * se,
fuse_ino_t parent,
fuse_ino_t child,
const char * name,
size_t namelen 
)
+
+

This function behaves like fuse_lowlevel_notify_inval_entry() with the following additional effect (at least as of Linux kernel 4.8):

+

If the provided child inode matches the inode that is currently associated with the cached dentry, and if there are any inotify watches registered for the dentry, then the watchers are informed that the dentry has been deleted.

+

To avoid a deadlock this function must not be called while executing a related filesystem operation or while holding a lock that could be needed to execute such an operation (see the description of fuse_lowlevel_notify_inval_entry() for more details).

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.18. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
parentinode number
childinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2562 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_expire_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_expire_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen 
)
+
+

Notify to expire parent attributes and the dentry matching parent/name

+

Same restrictions apply as for fuse_lowlevel_notify_inval_entry()

+

Compared to invalidating an entry, expiring the entry results not in a forceful removal of that entry from kernel cache but instead the next access to it forces a lookup from the filesystem.

+

This makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+

Added in FUSE protocol version 7.38. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure, -enosys if no kernel support
+ +

Definition at line 2549 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen 
)
+
+

Notify to invalidate parent attributes and the dentry matching parent/name

+

To avoid a deadlock this function must not be called in the execution path of a related filesystem operation or within any code that could hold a lock that could be needed to execute such an operation. As of kernel 4.18, a "related operation" is a lookup(), symlink(), mknod(), mkdir(), unlink(), rename(), link() or create() request for the parent, and a setattr(), unlink(), rmdir(), rename(), setxattr(), removexattr(), readdir() or readdirplus() request for the inode itself.

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2543 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_inode()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_inode (struct fuse_session * se,
fuse_ino_t ino,
off_t off,
off_t len 
)
+
+

Notify to invalidate cache for an inode.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+

If the filesystem has writeback caching enabled, invalidating an inode will first trigger a writeback of all dirty pages. The call will block until all writeback requests have completed and the inode has been invalidated. It will, however, not wait for completion of pending writeback requests that have been issued before.

+

If there are no dirty pages, this function will never block.

+
Parameters
+ + + + + +
sethe session object
inothe inode number
offthe offset in the inode where to start invalidating or negative to invalidate attributes only
lenthe amount of cache to invalidate or 0 for all
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2475 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_poll()

+ +
+
+ + + + + + + + +
int fuse_lowlevel_notify_poll (struct fuse_pollhandle * ph)
+
+

Notify IO readiness event

+

For more information, please read comment for poll operation.

+
Parameters
+ + +
phpoll handle to notify IO readiness event for
+
+
+ +

Definition at line 2458 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_retrieve()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_retrieve (struct fuse_session * se,
fuse_ino_t ino,
size_t size,
off_t offset,
void * cookie 
)
+
+

Retrieve data from the kernel buffers

+

Retrieve data in the kernel buffers belonging to the given inode. If successful then the retrieve_reply() method will be called with the returned data.

+

Only present pages are returned in the retrieve reply. Retrieving stops when it finds a non-present page and only data prior to that is returned.

+

If this function returns an error, then the retrieve will not be completed and no reply will be sent.

+

This function doesn't change the dirty state of pages in the kernel buffer. For dirty pages the write() method will be called regardless of having been retrieved previously.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
sizethe number of bytes to retrieve
offsetthe starting offset into the file to retrieve from
cookieuser data to supply to the reply callback
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2668 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_store()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_store (struct fuse_session * se,
fuse_ino_t ino,
off_t offset,
struct fuse_bufvecbufv,
enum fuse_buf_copy_flags flags 
)
+
+

Store data to the kernel buffers

+

Synchronously store data in the kernel buffers belonging to the given inode. The stored data is marked up-to-date (no read will be performed against it, unless it's invalidated or evicted from the cache).

+

If the stored data overflows the current file size, then the size is extended, similarly to a write(2) on the filesystem.

+

If this function returns an error, then the store wasn't fully completed, but it may have been partially completed.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
offsetthe starting offset into the file to store to
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2588 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_version()

+ +
+
+ + + + + + + + +
void fuse_lowlevel_version (void )
+
+

Print low-level version information to stdout.

+ +

Definition at line 2993 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_parse_cmdline_30()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_parse_cmdline_30 (struct fuse_argsargs,
struct fuse_cmdline_optsopts 
)
+
+

Utility function to parse common options for simple file systems using the low-level API. A help text that describes the available options can be printed with fuse_cmdline_help. A single non-option argument is treated as the mountpoint. Multiple non-option arguments will result in an error.

+

If neither -o subtype= or -o fsname= options are given, a new subtype option will be added and set to the basename of the program (the fsname will remain unset, and then defaults to "fuse").

+

Known options will be removed from args, unknown options will remain.

+
Parameters
+ + + +
argsargument vector (input+output)
optsoutput argument for parsed options
+
+
+
Returns
0 on success, -1 on failure
+

struct fuse_cmdline_opts got extended in libfuse-3.12

+ +

Definition at line 237 of file helper.c.

+ +
+
+ +

◆ fuse_passthrough_open()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_passthrough_open (fuse_req_t req,
int fd 
)
+
+

Setup passthrough backing file for open reply

+

Currently there should be only one backing id per node / backing file.

+

Possible requests: open, opendir, create

+
Parameters
+ + + +
reqrequest handle
fdbacking file descriptor
+
+
+
Returns
positive backing id for success, 0 for failure
+ +

Definition at line 480 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_attr()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_attr (fuse_req_t req,
const struct stat * attr,
double attr_timeout 
)
+
+

Reply with attributes

+

Possible requests: getattr, setattr

+
Parameters
+ + + + +
reqrequest handle
attrthe attributes
attr_timeoutvalidity timeout (in seconds) for the attributes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 460 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_bmap()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_bmap (fuse_req_t req,
uint64_t idx 
)
+
+

Reply with block index

+

Possible requests: bmap

+
Parameters
+ + + +
reqrequest handle
idxblock index within device
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 973 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_buf (fuse_req_t req,
const char * buf,
size_t size 
)
+
+

Reply with data

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
bufbuffer containing data
sizethe size of data in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 524 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_create()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_create (fuse_req_t req,
const struct fuse_entry_parame,
const struct fuse_file_infofi 
)
+
+

Reply with a directory entry and open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes

+

Possible requests: create

+

Side effects: increments the lookup count on success

+
Parameters
+ + + + +
reqrequest handle
ethe entry parameters
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 444 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_data()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_data (fuse_req_t req,
struct fuse_bufvecbufv,
enum fuse_buf_copy_flags flags 
)
+
+

Reply with data copied/moved from buffer(s)

+

Zero copy data transfer ("splicing") will be used under the following circumstances:

+
    +
  1. FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports splicing from the fuse device (FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.capable), and
  4. +
  5. flags does not contain FUSE_BUF_NO_SPLICE
  6. +
  7. The amount of data that is provided in file-descriptor backed buffers (i.e., buffers for which bufv[n].flags == FUSE_BUF_FD) is at least twice the page size.
  8. +
+

In order for SPLICE_F_MOVE to be used, the following additional conditions have to be fulfilled:

+
    +
  1. FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports it (i.e, FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.capable), and
  4. +
  5. flags contains FUSE_BUF_SPLICE_MOVE
  6. +
+

Note that, if splice is used, the data is actually spliced twice: once into a temporary pipe (to prepend header data), and then again into the kernel. If some of the provided buffers are memory-backed, the data in them is copied in step one and spliced in step two.

+

The FUSE_BUF_SPLICE_FORCE_SPLICE and FUSE_BUF_SPLICE_NONBLOCK flags are silently ignored.

+

Possible requests: read, readdir, getxattr, listxattr

+

Side effects: when used to return data from a readdirplus() (but not readdir()) call, increments the lookup count of each returned entry by one on success.

+
Parameters
+ + + + +
reqrequest handle
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 912 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_entry (fuse_req_t req,
const struct fuse_entry_parame 
)
+
+

Reply with a directory entry

+

Possible requests: lookup, mknod, mkdir, symlink, link

+

Side effects: increments the lookup count on success

+
Parameters
+ + + +
reqrequest handle
ethe entry parameters
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 428 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_err()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_err (fuse_req_t req,
int err 
)
+
+

Reply with an error code or success.

+

Possible requests: all except forget, forget_multi, retrieve_reply

+

Wherever possible, error codes should be chosen from the list of documented error conditions in the corresponding system calls manpage.

+

An error code of ENOSYS is sometimes treated specially. This is indicated in the documentation of the affected handler functions.

+

The following requests may be answered with a zero error code: unlink, rmdir, rename, flush, release, fsync, fsyncdir, setxattr, removexattr, setlk.

+
Parameters
+ + + +
reqrequest handle
errthe positive error value, or zero for success
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 331 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl (fuse_req_t req,
int result,
const void * buf,
size_t size 
)
+
+

Reply to finish ioctl

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
bufbuffer containing output data
sizelength of output data
+
+
+ +

Definition at line 1071 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_iov (fuse_req_t req,
int result,
const struct iovec * iov,
int count 
)
+
+

Reply to finish ioctl with iov buffer

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
iovthe vector containing the data
countthe size of vector
+
+
+ +

Definition at line 1092 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_retry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_retry (fuse_req_t req,
const struct iovec * in_iov,
size_t in_count,
const struct iovec * out_iov,
size_t out_count 
)
+
+

Reply to ask for data fetch and output buffer preparation. ioctl will be retried with the specified input data fetched and output buffer prepared.

+

Possible requests: ioctl

+
Parameters
+ + + + + + +
reqrequest handle
in_ioviovec specifying data to fetch from the caller
in_countnumber of entries in in_iov
out_ioviovec specifying addresses to write output to
out_countnumber of entries in out_iov
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1001 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_iov (fuse_req_t req,
const struct iovec * iov,
int count 
)
+
+

Reply with data vector

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
iovthe vector containing the data
countthe size of vector
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 265 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lock()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_lock (fuse_req_t req,
const struct flock * lock 
)
+
+

Reply with file lock information

+

Possible requests: getlk

+
Parameters
+ + + +
reqrequest handle
lockthe lock information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 956 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lseek()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_lseek (fuse_req_t req,
off_t off 
)
+
+

Reply with offset

+

Possible requests: lseek

+
Parameters
+ + + +
reqrequest handle
offoffset of next data or hole
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1126 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_none()

+ +
+
+ + + + + + + + +
void fuse_reply_none (fuse_req_t req)
+
+

Don't send reply

+

Possible requests: forget forget_multi retrieve_reply

+
Parameters
+ + +
reqrequest handle
+
+
+ +

Definition at line 336 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_open()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_open (fuse_req_t req,
const struct fuse_file_infofi 
)
+
+

Reply with open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes,

+

Possible requests: open, opendir

+
Parameters
+ + + +
reqrequest handle
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 505 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_poll()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_poll (fuse_req_t req,
unsigned revents 
)
+
+

Reply with poll result event mask

+
Parameters
+ + + +
reqrequest handle
reventspoll result event mask
+
+
+ +

Definition at line 1116 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_readlink()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_readlink (fuse_req_t req,
const char * link 
)
+
+

Reply with the contents of a symbolic link

+

Possible requests: readlink

+
Parameters
+ + + +
reqrequest handle
linksymbolic link contents
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 475 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_statfs()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_statfs (fuse_req_t req,
const struct statvfs * stbuf 
)
+
+

Reply with filesystem statistics

+

Possible requests: statfs

+
Parameters
+ + + +
reqrequest handle
stbuffilesystem statistics
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 934 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_write()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_write (fuse_req_t req,
size_t count 
)
+
+

Reply with number of bytes written

+

Possible requests: write

+
Parameters
+ + + +
reqrequest handle
countthe number of bytes written
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 514 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_xattr()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_xattr (fuse_req_t req,
size_t count 
)
+
+

Reply with needed buffer size

+

Possible requests: getxattr, listxattr

+
Parameters
+ + + +
reqrequest handle
countthe buffer size needed in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 946 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_ctx()

+ +
+
+ + + + + + + + +
const struct fuse_ctx * fuse_req_ctx (fuse_req_t req)
+
+

Get the context from the request

+

The pointer returned by this function will only be valid for the request's lifetime

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the context structure
+ +

Definition at line 2718 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_req_getgroups (fuse_req_t req,
int size,
gid_t list[] 
)
+
+

Get the current supplementary group IDs for the specified request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + + +
reqrequest handle
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 3614 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupt_func()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_req_interrupt_func (fuse_req_t req,
fuse_interrupt_func_t func,
void * data 
)
+
+

Register/unregister callback for an interrupt

+

If an interrupt has already happened, then the callback function is called from within this function, hence it's not possible for interrupts to be lost.

+
Parameters
+ + + + +
reqrequest handle
functhe callback function or NULL for unregister
datauser data passed to the callback function
+
+
+ +

Definition at line 2723 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupted()

+ +
+
+ + + + + + + + +
int fuse_req_interrupted (fuse_req_t req)
+
+

Check if a request has already been interrupted

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 2736 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_userdata()

+ +
+
+ + + + + + + + +
void * fuse_req_userdata (fuse_req_t req)
+
+

Get the userdata from the request

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the user data passed to fuse_session_new()
+ +

Definition at line 2713 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_destroy()

+ +
+
+ + + + + + + + +
void fuse_session_destroy (struct fuse_session * se)
+
+

Destroy a session

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 3010 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_exit()

+ +
+
+ + + + + + + + +
void fuse_session_exit (struct fuse_session * se)
+
+

Flag a session as terminated.

+

This will cause any running event loops to terminate on the next opportunity. If this function is called by a thread that is not a FUSE worker thread, the next opportunity will be when FUSE a request is received (which may be far in the future if the filesystem is not currently being used by any clients). One way to avoid this delay is to afterwards sent a signal to the main thread (if fuse_set_signal_handlers() is used, SIGPIPE will cause the main thread to wake-up but otherwise be ignored).

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_exited()

+ +
+
+ + + + + + + + +
int fuse_session_exited (struct fuse_session * se)
+
+

Query the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+
Returns
1 if exited, 0 if not exited
+ +
+
+ +

◆ fuse_session_fd()

+ +
+
+ + + + + + + + +
int fuse_session_fd (struct fuse_session * se)
+
+

Return file descriptor for communication with kernel.

+

The file selector can be used to integrate FUSE with a custom event loop. Whenever data is available for reading on the provided fd, the event loop should call fuse_session_receive_buf followed by fuse_session_process_buf to process the request.

+

The returned file descriptor is valid until fuse_session_unmount is called.

+
Parameters
+ + +
sethe session
+
+
+
Returns
a file descriptor
+ +

Definition at line 3534 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_loop()

+ +
+
+ + + + + + + + +
int fuse_session_loop (struct fuse_session * se)
+
+

Enter a single threaded, blocking event loop.

+

When the event loop terminates because the connection to the FUSE kernel module has been closed, this function returns zero. This happens when the filesystem is unmounted regularly (by the filesystem owner or root running the umount(8) or fusermount(1) command), or if connection is explicitly severed by writing 1 to theabort file in /sys/fs/fuse/connections/NNN. The only way to distinguish between these two conditions is to check if the filesystem is still mounted after the session loop returns.

+

When some error occurs during request processing, the function returns a negated errno(3) value.

+

If the loop has been terminated because of a signal handler installed by fuse_set_signal_handlers(), this function returns the (positive) signal value that triggered the exit.

+
Parameters
+ + +
sethe session
+
+
+
Returns
0, -errno, or a signal value
+ +

Definition at line 19 of file fuse_loop.c.

+ +
+
+ +

◆ fuse_session_mount()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_session_mount (struct fuse_session * se,
const char * mountpoint 
)
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
sesession object
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 3473 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_process_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_session_process_buf (struct fuse_session * se,
const struct fuse_bufbuf 
)
+
+

Process a raw request supplied in a generic buffer

+

The fuse_buf may contain a memory buffer or a pipe file descriptor.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf containing the request
+
+
+ +

Definition at line 2830 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_receive_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_session_receive_buf (struct fuse_session * se,
struct fuse_bufbuf 
)
+
+

Read a raw request from the kernel into the supplied buffer.

+

Depending on file system options, system capabilities, and request size the request is either read into a memory buffer or spliced into a temporary pipe.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf to store the request in
+
+
+
Returns
the actual size of the raw request, or -errno on error
+ +

Definition at line 3285 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_reset()

+ +
+
+ + + + + + + + +
void fuse_session_reset (struct fuse_session * se)
+
+

Reset the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_unmount()

+ +
+
+ + + + + + + + +
void fuse_session_unmount (struct fuse_session * se)
+
+

Ensure that file system is unmounted.

+

In regular operation, the file system is typically unmounted by the user calling umount(8) or fusermount(1), which then terminates the FUSE session loop. However, the session loop may also terminate as a result of an explicit call to fuse_session_exit() (e.g. by a signal handler installed by fuse_set_signal_handler()). In this case the filesystem remains mounted, but any attempt to access it will block (while the filesystem process is still running) or give an ESHUTDOWN error (after the filesystem process has terminated).

+

If the communication channel with the FUSE kernel module is still open (i.e., if the session loop was terminated by an explicit call to fuse_session_exit()), this function will close it and unmount the filesystem. If the communication channel has been closed by the kernel, this method will do (almost) nothing.

+

NOTE: The above semantics mean that if the connection to the kernel is terminated via the /sys/fs/fuse/connections/NNN/abort file, this method will not unmount the filesystem.

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 3539 of file fuse_lowlevel.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2include_2fuse__lowlevel_8h_source.html b/doc/html/fuse-3_817_84_2include_2fuse__lowlevel_8h_source.html new file mode 100644 index 0000000..8c7889f --- /dev/null +++ b/doc/html/fuse-3_817_84_2include_2fuse__lowlevel_8h_source.html @@ -0,0 +1,683 @@ + + + + + + + +libfuse: fuse-3.17.4/include/fuse_lowlevel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_lowlevel.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_LOWLEVEL_H_
+
10#define FUSE_LOWLEVEL_H_
+
11
+
21#ifndef FUSE_USE_VERSION
+
22#error FUSE_USE_VERSION not defined
+
23#endif
+
24
+
25#include "fuse_common.h"
+
26
+
27#include <stddef.h>
+
28#include <utime.h>
+
29#include <fcntl.h>
+
30#include <sys/types.h>
+
31#include <sys/stat.h>
+
32#include <sys/statvfs.h>
+
33#include <sys/uio.h>
+
34
+
35#ifdef __cplusplus
+
36extern "C" {
+
37#endif
+
38
+
39/* ----------------------------------------------------------- *
+
40 * Miscellaneous definitions *
+
41 * ----------------------------------------------------------- */
+
42
+
44#define FUSE_ROOT_ID 1
+
45
+
47typedef uint64_t fuse_ino_t;
+
48
+
50typedef struct fuse_req *fuse_req_t;
+
51
+
57struct fuse_session;
+
58
+
+ + +
69
+
80 uint64_t generation;
+
81
+
89 struct stat attr;
+
90
+ +
96
+ +
102};
+
+
103
+
+
112struct fuse_ctx {
+
114 uid_t uid;
+
115
+
117 gid_t gid;
+
118
+
120 pid_t pid;
+
121
+
123 mode_t umask;
+
124};
+
+
125
+
126struct fuse_forget_data {
+
127 fuse_ino_t ino;
+
128 uint64_t nlookup;
+
129};
+
130
+
131struct fuse_custom_io {
+
132 ssize_t (*writev)(int fd, struct iovec *iov, int count, void *userdata);
+
133 ssize_t (*read)(int fd, void *buf, size_t buf_len, void *userdata);
+
134 ssize_t (*splice_receive)(int fdin, off_t *offin, int fdout,
+
135 off_t *offout, size_t len,
+
136 unsigned int flags, void *userdata);
+
137 ssize_t (*splice_send)(int fdin, off_t *offin, int fdout,
+
138 off_t *offout, size_t len,
+
139 unsigned int flags, void *userdata);
+
140 int (*clone_fd)(int master_fd);
+
141};
+
142
+
+ +
149 FUSE_LL_INVALIDATE = 0,
+
150 FUSE_LL_EXPIRE_ONLY = (1 << 0),
+
151};
+
+
152
+
153/* 'to_set' flags in setattr */
+
154#define FUSE_SET_ATTR_MODE (1 << 0)
+
155#define FUSE_SET_ATTR_UID (1 << 1)
+
156#define FUSE_SET_ATTR_GID (1 << 2)
+
157#define FUSE_SET_ATTR_SIZE (1 << 3)
+
158#define FUSE_SET_ATTR_ATIME (1 << 4)
+
159#define FUSE_SET_ATTR_MTIME (1 << 5)
+
160#define FUSE_SET_ATTR_ATIME_NOW (1 << 7)
+
161#define FUSE_SET_ATTR_MTIME_NOW (1 << 8)
+
162#define FUSE_SET_ATTR_FORCE (1 << 9)
+
163#define FUSE_SET_ATTR_CTIME (1 << 10)
+
164#define FUSE_SET_ATTR_KILL_SUID (1 << 11)
+
165#define FUSE_SET_ATTR_KILL_SGID (1 << 12)
+
166#define FUSE_SET_ATTR_FILE (1 << 13)
+
167#define FUSE_SET_ATTR_KILL_PRIV (1 << 14)
+
168#define FUSE_SET_ATTR_OPEN (1 << 15)
+
169#define FUSE_SET_ATTR_TIMES_SET (1 << 16)
+
170#define FUSE_SET_ATTR_TOUCH (1 << 17)
+
171
+
172/* ----------------------------------------------------------- *
+
173 * Request methods and replies *
+
174 * ----------------------------------------------------------- */
+
175
+
+ +
223 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
224
+
236 void (*destroy) (void *userdata);
+
237
+
249 void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
250
+
287 void (*forget) (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup);
+
288
+
308 void (*getattr) (fuse_req_t req, fuse_ino_t ino,
+
309 struct fuse_file_info *fi);
+
310
+
345 void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
346 int to_set, struct fuse_file_info *fi);
+
347
+
358 void (*readlink) (fuse_req_t req, fuse_ino_t ino);
+
359
+
376 void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
377 mode_t mode, dev_t rdev);
+
378
+
391 void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
392 mode_t mode);
+
393
+
409 void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
410
+
426 void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
427
+
440 void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent,
+
441 const char *name);
+
442
+
472 void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
473 fuse_ino_t newparent, const char *newname,
+
474 unsigned int flags);
+
475
+
488 void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
489 const char *newname);
+
490
+
554 void (*open) (fuse_req_t req, fuse_ino_t ino,
+
555 struct fuse_file_info *fi);
+
556
+
582 void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
583 struct fuse_file_info *fi);
+
584
+
611 void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf,
+
612 size_t size, off_t off, struct fuse_file_info *fi);
+
613
+
652 void (*flush) (fuse_req_t req, fuse_ino_t ino,
+
653 struct fuse_file_info *fi);
+
654
+
680 void (*release) (fuse_req_t req, fuse_ino_t ino,
+
681 struct fuse_file_info *fi);
+
682
+
702 void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
703 struct fuse_file_info *fi);
+
704
+
734 void (*opendir) (fuse_req_t req, fuse_ino_t ino,
+
735 struct fuse_file_info *fi);
+
736
+
780 void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
781 struct fuse_file_info *fi);
+
782
+ +
800 struct fuse_file_info *fi);
+
801
+
824 void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
825 struct fuse_file_info *fi);
+
826
+
837 void (*statfs) (fuse_req_t req, fuse_ino_t ino);
+
838
+
850 void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
851 const char *value, size_t size, int flags);
+
852
+
881 void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
882 size_t size);
+
883
+
912 void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size);
+
913
+
929 void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name);
+
930
+
951 void (*access) (fuse_req_t req, fuse_ino_t ino, int mask);
+
952
+
980 void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
981 mode_t mode, struct fuse_file_info *fi);
+
982
+
995 void (*getlk) (fuse_req_t req, fuse_ino_t ino,
+
996 struct fuse_file_info *fi, struct flock *lock);
+
997
+
1020 void (*setlk) (fuse_req_t req, fuse_ino_t ino,
+
1021 struct fuse_file_info *fi,
+
1022 struct flock *lock, int sleep);
+
1023
+
1044 void (*bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
1045 uint64_t idx);
+
1046
+
1047#if FUSE_USE_VERSION < 35
+
1048 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, int cmd,
+
1049 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1050 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1051#else
+
1080 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
1081 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1082 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1083#endif
+
1084
+
1117 void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
1118 struct fuse_pollhandle *ph);
+
1119
+ +
1148 struct fuse_bufvec *bufv, off_t off,
+
1149 struct fuse_file_info *fi);
+
1150
+
1163 void (*retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino,
+
1164 off_t offset, struct fuse_bufvec *bufv);
+
1165
+
1177 void (*forget_multi) (fuse_req_t req, size_t count,
+
1178 struct fuse_forget_data *forgets);
+
1179
+
1195 void (*flock) (fuse_req_t req, fuse_ino_t ino,
+
1196 struct fuse_file_info *fi, int op);
+
1197
+
1218 void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode,
+
1219 off_t offset, off_t length, struct fuse_file_info *fi);
+
1220
+
1246 void (*readdirplus) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
1247 struct fuse_file_info *fi);
+
1248
+ +
1280 off_t off_in, struct fuse_file_info *fi_in,
+
1281 fuse_ino_t ino_out, off_t off_out,
+
1282 struct fuse_file_info *fi_out, size_t len,
+
1283 int flags);
+
1284
+
1303 void (*lseek) (fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
1304 struct fuse_file_info *fi);
+
1305
+
1306
+
1325 void (*tmpfile) (fuse_req_t req, fuse_ino_t parent,
+
1326 mode_t mode, struct fuse_file_info *fi);
+
1327
+
1328};
+
+
1329
+
1351int fuse_reply_err(fuse_req_t req, int err);
+
1352
+
1363void fuse_reply_none(fuse_req_t req);
+
1364
+
1378int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e);
+
1379
+
1398int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+
1399 const struct fuse_file_info *fi);
+
1400
+
1412int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
1413 double attr_timeout);
+
1414
+
1425int fuse_reply_readlink(fuse_req_t req, const char *link);
+
1426
+
1439int fuse_passthrough_open(fuse_req_t req, int fd);
+
1440int fuse_passthrough_close(fuse_req_t req, int backing_id);
+
1441
+
1456int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi);
+
1457
+
1468int fuse_reply_write(fuse_req_t req, size_t count);
+
1469
+
1481int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size);
+
1482
+
1526int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+ +
1528
+
1540int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count);
+
1541
+
1552int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf);
+
1553
+
1564int fuse_reply_xattr(fuse_req_t req, size_t count);
+
1565
+
1576int fuse_reply_lock(fuse_req_t req, const struct flock *lock);
+
1577
+
1588int fuse_reply_bmap(fuse_req_t req, uint64_t idx);
+
1589
+
1590/* ----------------------------------------------------------- *
+
1591 * Filling a buffer in readdir *
+
1592 * ----------------------------------------------------------- */
+
1593
+
1621size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
1622 const char *name, const struct stat *stbuf,
+
1623 off_t off);
+
1624
+
1638size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
1639 const char *name,
+
1640 const struct fuse_entry_param *e, off_t off);
+
1641
+ +
1658 const struct iovec *in_iov, size_t in_count,
+
1659 const struct iovec *out_iov, size_t out_count);
+
1660
+
1672int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size);
+
1673
+
1685int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1686 int count);
+
1687
+
1694int fuse_reply_poll(fuse_req_t req, unsigned revents);
+
1695
+
1706int fuse_reply_lseek(fuse_req_t req, off_t off);
+
1707
+
1708/* ----------------------------------------------------------- *
+
1709 * Notification *
+
1710 * ----------------------------------------------------------- */
+
1711
+
1719int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph);
+
1720
+
1744int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
1745 off_t off, off_t len);
+
1746
+
1771int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
1772 const char *name, size_t namelen);
+
1773
+
1802int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
1803 const char *name, size_t namelen);
+
1804
+
1833int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
1834 fuse_ino_t parent, fuse_ino_t child,
+
1835 const char *name, size_t namelen);
+
1836
+
1862int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
1863 off_t offset, struct fuse_bufvec *bufv,
+ +
1894int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
1895 size_t size, off_t offset, void *cookie);
+
1896
+
1897
+
1898/* ----------------------------------------------------------- *
+
1899 * Utility functions *
+
1900 * ----------------------------------------------------------- */
+
1901
+
1908void *fuse_req_userdata(fuse_req_t req);
+
1909
+
1919const struct fuse_ctx *fuse_req_ctx(fuse_req_t req);
+
1920
+
1940int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]);
+
1941
+
1948typedef void (*fuse_interrupt_func_t)(fuse_req_t req, void *data);
+
1949
+ +
1962 void *data);
+
1963
+ +
1971
+
1972
+
1973/* ----------------------------------------------------------- *
+
1974 * Inquiry functions *
+
1975 * ----------------------------------------------------------- */
+
1976
+
1980void fuse_lowlevel_version(void);
+
1981
+
1987void fuse_lowlevel_help(void);
+
1988
+
1992void fuse_cmdline_help(void);
+
1993
+
1994/* ----------------------------------------------------------- *
+
1995 * Filesystem setup & teardown *
+
1996 * ----------------------------------------------------------- */
+
1997
+
+ +
2004 int singlethread;
+
2005 int foreground;
+
2006 int debug;
+
2007 int nodefault_subtype;
+
2008 char *mountpoint;
+
2009 int show_version;
+
2010 int show_help;
+
2011 int clone_fd;
+
2012 unsigned int max_idle_threads; /* discouraged, due to thread
+
2013 * destruct overhead */
+
2014
+
2015 /* Added in libfuse-3.12 */
+
2016 unsigned int max_threads;
+
2017};
+
+
2018
+
2037#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2038int fuse_parse_cmdline(struct fuse_args *args,
+
2039 struct fuse_cmdline_opts *opts);
+
2040#else
+
2041#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2042int fuse_parse_cmdline_30(struct fuse_args *args,
+
2043 struct fuse_cmdline_opts *opts);
+
2044#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_30(args, opts)
+
2045#else
+
2046int fuse_parse_cmdline_312(struct fuse_args *args,
+
2047 struct fuse_cmdline_opts *opts);
+
2048#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_312(args, opts)
+
2049#endif
+
2050#endif
+
2051
+
2052/* Do not call this directly, use fuse_session_new() instead */
+
2053struct fuse_session *
+
2054fuse_session_new_versioned(struct fuse_args *args,
+
2055 const struct fuse_lowlevel_ops *op, size_t op_size,
+
2056 struct libfuse_version *version, void *userdata);
+
2057
+
2086static inline struct fuse_session *
+
2087fuse_session_new_fn(struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
2088 size_t op_size, void *userdata)
+
2089{
+
2090 struct libfuse_version version = {
+
2091 .major = FUSE_MAJOR_VERSION,
+
2092 .minor = FUSE_MINOR_VERSION,
+
2093 .hotfix = FUSE_HOTFIX_VERSION,
+
2094 .padding = 0
+
2095 };
+
2096
+
2097 return fuse_session_new_versioned(args, op, op_size, &version,
+
2098 userdata);
+
2099}
+
2100#define fuse_session_new(args, op, op_size, userdata) \
+
2101 fuse_session_new_fn(args, op, op_size, userdata)
+
2102
+
2103/*
+
2104 * This should mostly not be called directly, but instead the
+
2105 * fuse_session_custom_io() should be used.
+
2106 */
+
2107int fuse_session_custom_io_317(struct fuse_session *se,
+
2108 const struct fuse_custom_io *io, size_t op_size, int fd);
+
2109
+
2137#if FUSE_MAKE_VERSION(3, 17) <= FUSE_USE_VERSION
+
2138static inline int fuse_session_custom_io(struct fuse_session *se,
+
2139 const struct fuse_custom_io *io, size_t op_size, int fd)
+
2140{
+
2141 return fuse_session_custom_io_317(se, io, op_size, fd);
+
2142}
+
2143#else
+
2144static inline int fuse_session_custom_io(struct fuse_session *se,
+
2145 const struct fuse_custom_io *io, int fd)
+
2146{
+
2147 return fuse_session_custom_io_317(se, io,
+
2148 offsetof(struct fuse_custom_io, clone_fd), fd);
+
2149}
+
2150#endif
+
2151
+
2160int fuse_session_mount(struct fuse_session *se, const char *mountpoint);
+
2161
+
2184int fuse_session_loop(struct fuse_session *se);
+
2185
+
2186#if FUSE_USE_VERSION < 32
+
2187 int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
2188 #define fuse_session_loop_mt(se, clone_fd) fuse_session_loop_mt_31(se, clone_fd)
+
2189#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2190 int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config *config);
+
2191 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_32(se, config)
+
2192#else
+
2193 #if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2205 int fuse_session_loop_mt(struct fuse_session *se, struct fuse_loop_config *config);
+
2206 #else
+
2207 int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
2208 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_312(se, config)
+
2209 #endif
+
2210#endif
+
2211
+
2224void fuse_session_exit(struct fuse_session *se);
+
2225
+
2231void fuse_session_reset(struct fuse_session *se);
+
2232
+
2239int fuse_session_exited(struct fuse_session *se);
+
2240
+
2265void fuse_session_unmount(struct fuse_session *se);
+
2266
+
2272void fuse_session_destroy(struct fuse_session *se);
+
2273
+
2274/* ----------------------------------------------------------- *
+
2275 * Custom event loop support *
+
2276 * ----------------------------------------------------------- */
+
2277
+
2292int fuse_session_fd(struct fuse_session *se);
+
2293
+
2302void fuse_session_process_buf(struct fuse_session *se,
+
2303 const struct fuse_buf *buf);
+
2304
+
2316int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf);
+
2317
+
2318#ifdef __cplusplus
+
2319}
+
2320#endif
+
2321
+
2322#endif /* FUSE_LOWLEVEL_H_ */
+
fuse_buf_copy_flags
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+ +
struct fuse_req * fuse_req_t
+
uint64_t fuse_ino_t
+ + + + + + + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
int32_t backing_id
+ + + +
void(* flock)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op)
+
void(* write)(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* lseek)(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi)
+
void(* listxattr)(fuse_req_t req, fuse_ino_t ino, size_t size)
+
void(* removexattr)(fuse_req_t req, fuse_ino_t ino, const char *name)
+
void(* forget_multi)(fuse_req_t req, size_t count, struct fuse_forget_data *forgets)
+
void(* retrieve_reply)(fuse_req_t req, void *cookie, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv)
+
void(* create)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi)
+
void(* mkdir)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
+
void(* symlink)(fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
+
void(* write_buf)(fuse_req_t req, fuse_ino_t ino, struct fuse_bufvec *bufv, off_t off, struct fuse_file_info *fi)
+
void(* rmdir)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* link)(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
+
void(* rename)(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
+
void(* copy_file_range)(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags)
+
void(* poll)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
void(* opendir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* mknod)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
+
void(* fallocate)(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi)
+
void(* setattr)(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
+
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* fsync)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* destroy)(void *userdata)
+
void(* forget)(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
void(* getattr)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* ioctl)(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
void(* open)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* getxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
+
void(* fsyncdir)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* read)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* setxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags)
+
void(* release)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* access)(fuse_req_t req, fuse_ino_t ino, int mask)
+
void(* releasedir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* tmpfile)(fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi)
+
void(* bmap)(fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx)
+
void(* readlink)(fuse_req_t req, fuse_ino_t ino)
+
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* statfs)(fuse_req_t req, fuse_ino_t ino)
+
void(* readdir)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+
void(* readdirplus)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* flush)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* unlink)(fuse_req_t req, fuse_ino_t parent, const char *name)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_84_2include_2fuse__mount__compat_8h_source.html b/doc/html/fuse-3_817_84_2include_2fuse__mount__compat_8h_source.html new file mode 100644 index 0000000..892962d --- /dev/null +++ b/doc/html/fuse-3_817_84_2include_2fuse__mount__compat_8h_source.html @@ -0,0 +1,109 @@ + + + + + + + +libfuse: fuse-3.17.4/include/fuse_mount_compat.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_mount_compat.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2023 Giulio Benetti <giulio.benetti@benettiengineering.com>
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LICENSE
+
9*/
+
10
+
11#ifndef FUSE_MOUNT_COMPAT_H_
+
12#define FUSE_MOUNT_COMPAT_H_
+
13
+
14#include <sys/mount.h>
+
15
+
16/* Some libc don't define MS_*, so define them manually
+
17 * (values taken from https://elixir.bootlin.com/linux/v6.10/source/include/uapi/linux/mount.h#L13 on)
+
18 */
+
19#ifndef MS_DIRSYNC
+
20#define MS_DIRSYNC 128
+
21#endif
+
22
+
23#ifndef MS_NOSYMFOLLOW
+
24#define MS_NOSYMFOLLOW 256
+
25#endif
+
26
+
27#ifndef MS_REC
+
28#define MS_REC 16384
+
29#endif
+
30
+
31#ifndef MS_PRIVATE
+
32#define MS_PRIVATE (1<<18)
+
33#endif
+
34
+
35#ifndef MS_LAZYTIME
+
36#define MS_LAZYTIME (1<<25)
+
37#endif
+
38
+
39#ifndef UMOUNT_DETACH
+
40#define UMOUNT_DETACH 0x00000002 /* Just detach from the tree */
+
41#endif
+
42#ifndef UMOUNT_NOFOLLOW
+
43#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */
+
44#endif
+
45#ifndef UMOUNT_UNUSED
+
46#define UMOUNT_UNUSED 0x80000000 /* Flag guaranteed to be unused */
+
47#endif
+
48
+
49#endif /* FUSE_MOUNT_COMPAT_H_ */
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2include_2fuse__opt_8h.html b/doc/html/fuse-3_817_84_2include_2fuse__opt_8h.html new file mode 100644 index 0000000..09fada0 --- /dev/null +++ b/doc/html/fuse-3_817_84_2include_2fuse__opt_8h.html @@ -0,0 +1,587 @@ + + + + + + + +libfuse: fuse-3.17.4/include/fuse_opt.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_opt.h File Reference
+
+
+ +

Go to the source code of this file.

+ + + + + + +

+Data Structures

struct  fuse_opt
 
struct  fuse_args
 
+ + + + + + + + + + + + + + + +

+Macros

#define FUSE_OPT_KEY(templ, key)   { templ, -1U, key }
 
#define FUSE_OPT_END   { NULL, 0, 0 }
 
#define FUSE_ARGS_INIT(argc, argv)   { argc, argv, 0 }
 
#define FUSE_OPT_KEY_OPT   -1
 
#define FUSE_OPT_KEY_NONOPT   -2
 
#define FUSE_OPT_KEY_KEEP   -3
 
#define FUSE_OPT_KEY_DISCARD   -4
 
+ + + +

+Typedefs

typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
 
+ + + + + + + + + + + + + + + +

+Functions

int fuse_opt_parse (struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
 
int fuse_opt_add_opt (char **opts, const char *opt)
 
int fuse_opt_add_opt_escaped (char **opts, const char *opt)
 
int fuse_opt_add_arg (struct fuse_args *args, const char *arg)
 
int fuse_opt_insert_arg (struct fuse_args *args, int pos, const char *arg)
 
void fuse_opt_free_args (struct fuse_args *args)
 
int fuse_opt_match (const struct fuse_opt opts[], const char *opt)
 
+

Detailed Description

+

This file defines the option parsing interface of FUSE

+ +

Definition in file fuse_opt.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ARGS_INIT

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_ARGS_INIT( argc,
 argv 
)   { argc, argv, 0 }
+
+

Initializer for 'struct fuse_args'

+ +

Definition at line 123 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_END

+ +
+
+ + + + +
#define FUSE_OPT_END   { NULL, 0, 0 }
+
+

Last option. An array of 'struct fuse_opt' must end with a NULL template value

+ +

Definition at line 104 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_OPT_KEY( templ,
 key 
)   { templ, -1U, key }
+
+

Key option. In case of a match, the processing function will be called with the specified key.

+ +

Definition at line 98 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_DISCARD

+ +
+
+ + + + +
#define FUSE_OPT_KEY_DISCARD   -4
+
+

Special key value for options to discard

+

Argument is not passed to processing function, but behave as if the processing function returned zero

+ +

Definition at line 153 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_KEEP

+ +
+
+ + + + +
#define FUSE_OPT_KEY_KEEP   -3
+
+

Special key value for options to keep

+

Argument is not passed to processing function, but behave as if the processing function returned 1

+ +

Definition at line 145 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_NONOPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_NONOPT   -2
+
+

Key value passed to the processing function for all non-options

+

Non-options are the arguments beginning with a character other than '-' or all arguments after the special '–' option

+ +

Definition at line 137 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_OPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_OPT   -1
+
+

Key value passed to the processing function if an option did not match any template

+ +

Definition at line 129 of file fuse_opt.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_opt_proc_t

+ +
+
+ + + + +
typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
+
+

Processing function

+

This function is called if

    +
  • option did not match any 'struct fuse_opt'
  • +
  • argument is a non-option
  • +
  • option did match and offset was set to -1
  • +
+

The 'arg' parameter will always contain the whole argument or option including the parameter if exists. A two-argument option ("-x foo") is always converted to single argument option of the form "-xfoo" before this function is called.

+

Options of the form '-ofoo' are passed to this function without the '-o' prefix.

+

The return value of this function determines whether this argument is to be inserted into the output argument vector, or discarded.

+
Parameters
+ + + + + +
datais the user data passed to the fuse_opt_parse() function
argis the whole argument or option
keydetermines why the processing function was called
outargsthe current output argument list
+
+
+
Returns
-1 on error, 0 if arg is to be discarded, 1 if arg should be kept
+ +

Definition at line 180 of file fuse_opt.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_opt_add_arg()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_arg (struct fuse_argsargs,
const char * arg 
)
+
+

Add an argument to a NULL terminated argument vector

+
Parameters
+ + + +
argsis the structure containing the current argument list
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 55 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_opt (char ** opts,
const char * opt 
)
+
+

Add an option to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 139 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt_escaped()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_opt_escaped (char ** opts,
const char * opt 
)
+
+

Add an option, escaping commas, to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 144 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_free_args()

+ +
+
+ + + + + + + + +
void fuse_opt_free_args (struct fuse_argsargs)
+
+

Free the contents of argument list

+

The structure itself is not freed

+
Parameters
+ + +
argsis the structure containing the argument list
+
+
+ +

Definition at line 34 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_insert_arg()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_insert_arg (struct fuse_argsargs,
int pos,
const char * arg 
)
+
+

Add an argument at the specified position in a NULL terminated argument vector

+

Adds the argument to the N-th position. This is useful for adding options at the beginning of the array which must not come after the special '–' option.

+
Parameters
+ + + + +
argsis the structure containing the current argument list
posis the position at which to add the argument
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 95 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_match()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_match (const struct fuse_opt opts[],
const char * opt 
)
+
+

Check if an option matches

+
Parameters
+ + + +
optsis the option description array
optis the option to match
+
+
+
Returns
1 if a match is found, 0 if not
+ +
+
+ +

◆ fuse_opt_parse()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_parse (struct fuse_argsargs,
void * data,
const struct fuse_opt opts[],
fuse_opt_proc_t proc 
)
+
+

Option parsing function

+

If 'args' was returned from a previous call to fuse_opt_parse() or it was constructed from

+

A NULL 'args' is equivalent to an empty argument vector

+

A NULL 'opts' is equivalent to an 'opts' array containing a single end marker

+

A NULL 'proc' is equivalent to a processing function always returning '1'

+
Parameters
+ + + + + +
argsis the input and output argument list
datais the user data
optsis the option description array
procis the processing function
+
+
+
Returns
-1 on error, 0 on success
+ +

Definition at line 398 of file fuse_opt.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2include_2fuse__opt_8h_source.html b/doc/html/fuse-3_817_84_2include_2fuse__opt_8h_source.html new file mode 100644 index 0000000..068922c --- /dev/null +++ b/doc/html/fuse-3_817_84_2include_2fuse__opt_8h_source.html @@ -0,0 +1,149 @@ + + + + + + + +libfuse: fuse-3.17.4/include/fuse_opt.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_opt.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_OPT_H_
+
10#define FUSE_OPT_H_
+
11
+
17#ifdef __cplusplus
+
18extern "C" {
+
19#endif
+
20
+
+
77struct fuse_opt {
+
79 const char *templ;
+
80
+
85 unsigned long offset;
+
86
+
91 int value;
+
92};
+
+
93
+
98#define FUSE_OPT_KEY(templ, key) { templ, -1U, key }
+
99
+
104#define FUSE_OPT_END { NULL, 0, 0 }
+
105
+
+
109struct fuse_args {
+
111 int argc;
+
112
+
114 char **argv;
+
115
+ +
118};
+
+
119
+
123#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
+
124
+
129#define FUSE_OPT_KEY_OPT -1
+
130
+
137#define FUSE_OPT_KEY_NONOPT -2
+
138
+
145#define FUSE_OPT_KEY_KEEP -3
+
146
+
153#define FUSE_OPT_KEY_DISCARD -4
+
154
+
180typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key,
+
181 struct fuse_args *outargs);
+
182
+
203int fuse_opt_parse(struct fuse_args *args, void *data,
+
204 const struct fuse_opt opts[], fuse_opt_proc_t proc);
+
205
+
213int fuse_opt_add_opt(char **opts, const char *opt);
+
214
+
222int fuse_opt_add_opt_escaped(char **opts, const char *opt);
+
223
+
231int fuse_opt_add_arg(struct fuse_args *args, const char *arg);
+
232
+
246int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg);
+
247
+
255void fuse_opt_free_args(struct fuse_args *args);
+
256
+
257
+
265int fuse_opt_match(const struct fuse_opt opts[], const char *opt);
+
266
+
267#ifdef __cplusplus
+
268}
+
269#endif
+
270
+
271#endif /* FUSE_OPT_H_ */
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
unsigned long offset
Definition fuse_opt.h:85
+
const char * templ
Definition fuse_opt.h:79
+
int value
Definition fuse_opt.h:91
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2buffer_8c_source.html b/doc/html/fuse-3_817_84_2lib_2buffer_8c_source.html new file mode 100644 index 0000000..60460a0 --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2buffer_8c_source.html @@ -0,0 +1,410 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/buffer.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
buffer.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2010 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Functions for dealing with `struct fuse_buf` and `struct
+
6 fuse_bufvec`.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_lowlevel.h"
+
17#include <string.h>
+
18#include <unistd.h>
+
19#include <errno.h>
+
20#include <assert.h>
+
21
+
+
22size_t fuse_buf_size(const struct fuse_bufvec *bufv)
+
23{
+
24 size_t i;
+
25 size_t size = 0;
+
26
+
27 for (i = 0; i < bufv->count; i++) {
+
28 if (bufv->buf[i].size >= SIZE_MAX - size)
+
29 return SIZE_MAX;
+
30
+
31 size += bufv->buf[i].size;
+
32 }
+
33
+
34 return size;
+
35}
+
+
36
+
37static size_t min_size(size_t s1, size_t s2)
+
38{
+
39 return s1 < s2 ? s1 : s2;
+
40}
+
41
+
42static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off,
+
43 const struct fuse_buf *src, size_t src_off,
+
44 size_t len)
+
45{
+
46 ssize_t res = 0;
+
47 size_t copied = 0;
+
48
+
49 while (len) {
+
50 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
51 res = pwrite(dst->fd, (char *)src->mem + src_off, len,
+
52 dst->pos + dst_off);
+
53 } else {
+
54 res = write(dst->fd, (char *)src->mem + src_off, len);
+
55 }
+
56 if (res == -1) {
+
57 if (!copied)
+
58 return -errno;
+
59 break;
+
60 }
+
61 if (res == 0)
+
62 break;
+
63
+
64 copied += res;
+
65 if (!(dst->flags & FUSE_BUF_FD_RETRY))
+
66 break;
+
67
+
68 src_off += res;
+
69 dst_off += res;
+
70 len -= res;
+
71 }
+
72
+
73 return copied;
+
74}
+
75
+
76static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off,
+
77 const struct fuse_buf *src, size_t src_off,
+
78 size_t len)
+
79{
+
80 ssize_t res = 0;
+
81 size_t copied = 0;
+
82
+
83 while (len) {
+
84 if (src->flags & FUSE_BUF_FD_SEEK) {
+
85 res = pread(src->fd, (char *)dst->mem + dst_off, len,
+
86 src->pos + src_off);
+
87 } else {
+
88 res = read(src->fd, (char *)dst->mem + dst_off, len);
+
89 }
+
90 if (res == -1) {
+
91 if (!copied)
+
92 return -errno;
+
93 break;
+
94 }
+
95 if (res == 0)
+
96 break;
+
97
+
98 copied += res;
+
99 if (!(src->flags & FUSE_BUF_FD_RETRY))
+
100 break;
+
101
+
102 dst_off += res;
+
103 src_off += res;
+
104 len -= res;
+
105 }
+
106
+
107 return copied;
+
108}
+
109
+
110static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
+
111 const struct fuse_buf *src, size_t src_off,
+
112 size_t len)
+
113{
+
114 char buf[4096];
+
115 struct fuse_buf tmp = {
+
116 .size = sizeof(buf),
+
117 .flags = 0,
+
118 };
+
119 ssize_t res;
+
120 size_t copied = 0;
+
121
+
122 tmp.mem = buf;
+
123
+
124 while (len) {
+
125 size_t this_len = min_size(tmp.size, len);
+
126 size_t read_len;
+
127
+
128 res = fuse_buf_read(&tmp, 0, src, src_off, this_len);
+
129 if (res < 0) {
+
130 if (!copied)
+
131 return res;
+
132 break;
+
133 }
+
134 if (res == 0)
+
135 break;
+
136
+
137 read_len = res;
+
138 res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len);
+
139 if (res < 0) {
+
140 if (!copied)
+
141 return res;
+
142 break;
+
143 }
+
144 if (res == 0)
+
145 break;
+
146
+
147 copied += res;
+
148
+
149 if (res < this_len)
+
150 break;
+
151
+
152 dst_off += res;
+
153 src_off += res;
+
154 len -= res;
+
155 }
+
156
+
157 return copied;
+
158}
+
159
+
160#ifdef HAVE_SPLICE
+
161static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
162 const struct fuse_buf *src, size_t src_off,
+
163 size_t len, enum fuse_buf_copy_flags flags)
+
164{
+
165 int splice_flags = 0;
+
166 off_t *srcpos = NULL;
+
167 off_t *dstpos = NULL;
+
168 off_t srcpos_val;
+
169 off_t dstpos_val;
+
170 ssize_t res;
+
171 size_t copied = 0;
+
172
+ +
174 splice_flags |= SPLICE_F_MOVE;
+ +
176 splice_flags |= SPLICE_F_NONBLOCK;
+
177
+
178 if (src->flags & FUSE_BUF_FD_SEEK) {
+
179 srcpos_val = src->pos + src_off;
+
180 srcpos = &srcpos_val;
+
181 }
+
182 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
183 dstpos_val = dst->pos + dst_off;
+
184 dstpos = &dstpos_val;
+
185 }
+
186
+
187 while (len) {
+
188 res = splice(src->fd, srcpos, dst->fd, dstpos, len,
+
189 splice_flags);
+
190 if (res == -1) {
+
191 if (copied)
+
192 break;
+
193
+
194 if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE))
+
195 return -errno;
+
196
+
197 /* Maybe splice is not supported for this combination */
+
198 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off,
+
199 len);
+
200 }
+
201 if (res == 0)
+
202 break;
+
203
+
204 copied += res;
+
205 if (!(src->flags & FUSE_BUF_FD_RETRY) &&
+
206 !(dst->flags & FUSE_BUF_FD_RETRY)) {
+
207 break;
+
208 }
+
209
+
210 len -= res;
+
211 }
+
212
+
213 return copied;
+
214}
+
215#else
+
216static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
217 const struct fuse_buf *src, size_t src_off,
+
218 size_t len, enum fuse_buf_copy_flags flags)
+
219{
+
220 (void) flags;
+
221
+
222 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
223}
+
224#endif
+
225
+
226
+
227static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
+
228 const struct fuse_buf *src, size_t src_off,
+
229 size_t len, enum fuse_buf_copy_flags flags)
+
230{
+
231 int src_is_fd = src->flags & FUSE_BUF_IS_FD;
+
232 int dst_is_fd = dst->flags & FUSE_BUF_IS_FD;
+
233
+
234 if (!src_is_fd && !dst_is_fd) {
+
235 char *dstmem = (char *)dst->mem + dst_off;
+
236 char *srcmem = (char *)src->mem + src_off;
+
237
+
238 if (dstmem != srcmem) {
+
239 if (dstmem + len <= srcmem || srcmem + len <= dstmem)
+
240 memcpy(dstmem, srcmem, len);
+
241 else
+
242 memmove(dstmem, srcmem, len);
+
243 }
+
244
+
245 return len;
+
246 } else if (!src_is_fd) {
+
247 return fuse_buf_write(dst, dst_off, src, src_off, len);
+
248 } else if (!dst_is_fd) {
+
249 return fuse_buf_read(dst, dst_off, src, src_off, len);
+
250 } else if (flags & FUSE_BUF_NO_SPLICE) {
+
251 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
252 } else {
+
253 return fuse_buf_splice(dst, dst_off, src, src_off, len, flags);
+
254 }
+
255}
+
256
+
257static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv)
+
258{
+
259 if (bufv->idx < bufv->count)
+
260 return &bufv->buf[bufv->idx];
+
261 else
+
262 return NULL;
+
263}
+
264
+
265static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len)
+
266{
+
267 const struct fuse_buf *buf = fuse_bufvec_current(bufv);
+
268
+
269 if (!buf)
+
270 return 0;
+
271
+
272 bufv->off += len;
+
273 assert(bufv->off <= buf->size);
+
274 if (bufv->off == buf->size) {
+
275 assert(bufv->idx < bufv->count);
+
276 bufv->idx++;
+
277 if (bufv->idx == bufv->count)
+
278 return 0;
+
279 bufv->off = 0;
+
280 }
+
281 return 1;
+
282}
+
283
+
+
284ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
+ +
286{
+
287 size_t copied = 0;
+
288
+
289 if (dstv == srcv)
+
290 return fuse_buf_size(dstv);
+
291
+
292 for (;;) {
+
293 const struct fuse_buf *src = fuse_bufvec_current(srcv);
+
294 const struct fuse_buf *dst = fuse_bufvec_current(dstv);
+
295 size_t src_len;
+
296 size_t dst_len;
+
297 size_t len;
+
298 ssize_t res;
+
299
+
300 if (src == NULL || dst == NULL)
+
301 break;
+
302
+
303 src_len = src->size - srcv->off;
+
304 dst_len = dst->size - dstv->off;
+
305 len = min_size(src_len, dst_len);
+
306
+
307 res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
+
308 if (res < 0) {
+
309 if (!copied)
+
310 return res;
+
311 break;
+
312 }
+
313 copied += res;
+
314
+
315 if (!fuse_bufvec_advance(srcv, res) ||
+
316 !fuse_bufvec_advance(dstv, res))
+
317 break;
+
318
+
319 if (res < len)
+
320 break;
+
321 }
+
322
+
323 return copied;
+
324}
+
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+ +
enum fuse_buf_flags flags
+ +
off_t pos
+
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2compat_8c_source.html b/doc/html/fuse-3_817_84_2lib_2compat_8c_source.html new file mode 100644 index 0000000..4b4cbb2 --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2compat_8c_source.html @@ -0,0 +1,148 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/compat.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
compat.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file COPYING.LIB.
+
11*/
+
12
+
13/* Description:
+
14 This file has compatibility symbols for platforms that do not
+
15 support version symboling
+
16*/
+
17
+
18#include "libfuse_config.h"
+
19
+
20#include <stddef.h>
+
21#include <stdint.h>
+
22
+
23struct fuse_args;
+ + +
26struct fuse_session;
+
27struct fuse_custom_io;
+
28struct fuse_operations;
+ +
30
+
34#if (!defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
35/* With current libfuse fuse_parse_cmdline is a macro pointing to the
+
36 * versioned function. Here in this file we need to provide the ABI symbol
+
37 * and the redirecting macro is conflicting.
+
38 */
+
39#ifdef fuse_parse_cmdline
+
40#undef fuse_parse_cmdline
+
41#endif
+
42int fuse_parse_cmdline_30(struct fuse_args *args,
+
43 struct fuse_cmdline_opts *opts);
+
44int fuse_parse_cmdline(struct fuse_args *args,
+
45 struct fuse_cmdline_opts *opts);
+
46int fuse_parse_cmdline(struct fuse_args *args,
+
47 struct fuse_cmdline_opts *opts)
+
48{
+
49 return fuse_parse_cmdline_30(args, opts);
+
50}
+
51
+
52int fuse_session_custom_io_30(struct fuse_session *se,
+
53 const struct fuse_custom_io *io, int fd);
+
54int fuse_session_custom_io(struct fuse_session *se,
+
55 const struct fuse_custom_io *io, int fd);
+
56int fuse_session_custom_io(struct fuse_session *se,
+
57 const struct fuse_custom_io *io, int fd)
+
58
+
59{
+
60 return fuse_session_custom_io_30(se, io, fd);
+
61}
+
62
+
63#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
64
+
65int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
66 size_t op_size, void *user_data);
+
67int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
68 size_t op_size, void *user_data);
+
69int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
70 size_t op_size, void *user_data)
+
71{
+
72 return fuse_main_real_30(argc, argv, op, op_size, user_data);
+
73}
+
74
+
75struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
76 const struct fuse_lowlevel_ops *op,
+
77 size_t op_size, void *userdata);
+
78struct fuse_session *fuse_session_new(struct fuse_args *args,
+
79 const struct fuse_lowlevel_ops *op,
+
80 size_t op_size, void *userdata);
+
81struct fuse_session *fuse_session_new(struct fuse_args *args,
+
82 const struct fuse_lowlevel_ops *op,
+
83 size_t op_size, void *userdata)
+
84{
+
85 return fuse_session_new_30(args, op, op_size, userdata);
+
86}
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2cuse__lowlevel_8c_source.html b/doc/html/fuse-3_817_84_2lib_2cuse__lowlevel_8c_source.html new file mode 100644 index 0000000..d0e2a35 --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2cuse__lowlevel_8c_source.html @@ -0,0 +1,451 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/cuse_lowlevel.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_lowlevel.c
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file COPYING.LIB.
+
8*/
+
9
+
10#include "fuse_config.h"
+
11#include "cuse_lowlevel.h"
+
12#include "fuse_kernel.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15
+
16#include <stdio.h>
+
17#include <string.h>
+
18#include <stdlib.h>
+
19#include <stddef.h>
+
20#include <errno.h>
+
21#include <unistd.h>
+
22
+
23struct cuse_data {
+
24 struct cuse_lowlevel_ops clop;
+
25 unsigned max_read;
+
26 unsigned dev_major;
+
27 unsigned dev_minor;
+
28 unsigned flags;
+
29 unsigned dev_info_len;
+
30 char dev_info[];
+
31};
+
32
+
33static struct cuse_lowlevel_ops *req_clop(fuse_req_t req)
+
34{
+
35 return &req->se->cuse_data->clop;
+
36}
+
37
+
38static void cuse_fll_open(fuse_req_t req, fuse_ino_t ino,
+
39 struct fuse_file_info *fi)
+
40{
+
41 (void)ino;
+
42 req_clop(req)->open(req, fi);
+
43}
+
44
+
45static void cuse_fll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
46 off_t off, struct fuse_file_info *fi)
+
47{
+
48 (void)ino;
+
49 req_clop(req)->read(req, size, off, fi);
+
50}
+
51
+
52static void cuse_fll_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+
53 size_t size, off_t off, struct fuse_file_info *fi)
+
54{
+
55 (void)ino;
+
56 req_clop(req)->write(req, buf, size, off, fi);
+
57}
+
58
+
59static void cuse_fll_flush(fuse_req_t req, fuse_ino_t ino,
+
60 struct fuse_file_info *fi)
+
61{
+
62 (void)ino;
+
63 req_clop(req)->flush(req, fi);
+
64}
+
65
+
66static void cuse_fll_release(fuse_req_t req, fuse_ino_t ino,
+
67 struct fuse_file_info *fi)
+
68{
+
69 (void)ino;
+
70 req_clop(req)->release(req, fi);
+
71}
+
72
+
73static void cuse_fll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
74 struct fuse_file_info *fi)
+
75{
+
76 (void)ino;
+
77 req_clop(req)->fsync(req, datasync, fi);
+
78}
+
79
+
80static void cuse_fll_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg,
+
81 struct fuse_file_info *fi, unsigned int flags,
+
82 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
83{
+
84 (void)ino;
+
85 req_clop(req)->ioctl(req, cmd, arg, fi, flags, in_buf, in_bufsz,
+
86 out_bufsz);
+
87}
+
88
+
89static void cuse_fll_poll(fuse_req_t req, fuse_ino_t ino,
+
90 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
91{
+
92 (void)ino;
+
93 req_clop(req)->poll(req, fi, ph);
+
94}
+
95
+
96static size_t cuse_pack_info(int argc, const char **argv, char *buf)
+
97{
+
98 size_t size = 0;
+
99 int i;
+
100
+
101 for (i = 0; i < argc; i++) {
+
102 size_t len;
+
103
+
104 len = strlen(argv[i]) + 1;
+
105 size += len;
+
106 if (buf) {
+
107 memcpy(buf, argv[i], len);
+
108 buf += len;
+
109 }
+
110 }
+
111
+
112 return size;
+
113}
+
114
+
115static struct cuse_data *cuse_prep_data(const struct cuse_info *ci,
+
116 const struct cuse_lowlevel_ops *clop)
+
117{
+
118 struct cuse_data *cd;
+
119 size_t dev_info_len;
+
120
+
121 dev_info_len = cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv,
+
122 NULL);
+
123
+
124 if (dev_info_len > CUSE_INIT_INFO_MAX) {
+
125 fuse_log(FUSE_LOG_ERR, "cuse: dev_info (%zu) too large, limit=%u\n",
+
126 dev_info_len, CUSE_INIT_INFO_MAX);
+
127 return NULL;
+
128 }
+
129
+
130 cd = calloc(1, sizeof(*cd) + dev_info_len);
+
131 if (!cd) {
+
132 fuse_log(FUSE_LOG_ERR, "cuse: failed to allocate cuse_data\n");
+
133 return NULL;
+
134 }
+
135
+
136 memcpy(&cd->clop, clop, sizeof(cd->clop));
+
137 cd->max_read = 131072;
+
138 cd->dev_major = ci->dev_major;
+
139 cd->dev_minor = ci->dev_minor;
+
140 cd->dev_info_len = dev_info_len;
+
141 cd->flags = ci->flags;
+
142 cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info);
+
143
+
144 return cd;
+
145}
+
146
+
147struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
148 const struct cuse_info *ci,
+
149 const struct cuse_lowlevel_ops *clop,
+
150 void *userdata)
+
151{
+
152 struct fuse_lowlevel_ops lop;
+
153 struct cuse_data *cd;
+
154 struct fuse_session *se;
+
155
+
156 cd = cuse_prep_data(ci, clop);
+
157 if (!cd)
+
158 return NULL;
+
159
+
160 memset(&lop, 0, sizeof(lop));
+
161 lop.init = clop->init;
+
162 lop.destroy = clop->destroy;
+
163 lop.open = clop->open ? cuse_fll_open : NULL;
+
164 lop.read = clop->read ? cuse_fll_read : NULL;
+
165 lop.write = clop->write ? cuse_fll_write : NULL;
+
166 lop.flush = clop->flush ? cuse_fll_flush : NULL;
+
167 lop.release = clop->release ? cuse_fll_release : NULL;
+
168 lop.fsync = clop->fsync ? cuse_fll_fsync : NULL;
+
169 lop.ioctl = clop->ioctl ? cuse_fll_ioctl : NULL;
+
170 lop.poll = clop->poll ? cuse_fll_poll : NULL;
+
171
+
172 se = fuse_session_new(args, &lop, sizeof(lop), userdata);
+
173 if (!se) {
+
174 free(cd);
+
175 return NULL;
+
176 }
+
177 se->cuse_data = cd;
+
178
+
179 return se;
+
180}
+
181
+
182static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg,
+
183 char *dev_info, unsigned dev_info_len)
+
184{
+
185 struct iovec iov[3];
+
186
+
187 iov[1].iov_base = arg;
+
188 iov[1].iov_len = sizeof(struct cuse_init_out);
+
189 iov[2].iov_base = dev_info;
+
190 iov[2].iov_len = dev_info_len;
+
191
+
192 return fuse_send_reply_iov_nofree(req, 0, iov, 3);
+
193}
+
194
+
195void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
196{
+
197 struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
+
198 struct cuse_init_out outarg;
+
199 struct fuse_session *se = req->se;
+
200 struct cuse_data *cd = se->cuse_data;
+
201 size_t bufsize = se->bufsize;
+
202 struct cuse_lowlevel_ops *clop = req_clop(req);
+
203
+
204 (void) nodeid;
+
205 if (se->debug) {
+
206 fuse_log(FUSE_LOG_DEBUG, "CUSE_INIT: %u.%u\n", arg->major, arg->minor);
+
207 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
208 }
+
209 se->conn.proto_major = arg->major;
+
210 se->conn.proto_minor = arg->minor;
+
211
+
212 /* XXX This is not right.*/
+
213 se->conn.capable_ext = 0;
+
214 se->conn.want_ext = 0;
+
215
+
216 if (arg->major < 7) {
+
217 fuse_log(FUSE_LOG_ERR, "cuse: unsupported protocol version: %u.%u\n",
+
218 arg->major, arg->minor);
+
219 fuse_reply_err(req, EPROTO);
+
220 return;
+
221 }
+
222
+
223 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
224 fuse_log(FUSE_LOG_ERR, "cuse: warning: buffer size too small: %zu\n",
+
225 bufsize);
+
226 bufsize = FUSE_MIN_READ_BUFFER;
+
227 }
+
228
+
229 bufsize -= 4096;
+
230 if (bufsize < se->conn.max_write)
+
231 se->conn.max_write = bufsize;
+
232
+
233 se->got_init = 1;
+
234 if (se->op.init)
+
235 se->op.init(se->userdata, &se->conn);
+
236
+
237 memset(&outarg, 0, sizeof(outarg));
+
238 outarg.major = FUSE_KERNEL_VERSION;
+
239 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
240 outarg.flags = cd->flags;
+
241 outarg.max_read = cd->max_read;
+
242 outarg.max_write = se->conn.max_write;
+
243 outarg.dev_major = cd->dev_major;
+
244 outarg.dev_minor = cd->dev_minor;
+
245
+
246 if (se->debug) {
+
247 fuse_log(FUSE_LOG_DEBUG, " CUSE_INIT: %u.%u\n",
+
248 outarg.major, outarg.minor);
+
249 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
250 fuse_log(FUSE_LOG_DEBUG, " max_read=0x%08x\n", outarg.max_read);
+
251 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
252 fuse_log(FUSE_LOG_DEBUG, " dev_major=%u\n", outarg.dev_major);
+
253 fuse_log(FUSE_LOG_DEBUG, " dev_minor=%u\n", outarg.dev_minor);
+
254 fuse_log(FUSE_LOG_DEBUG, " dev_info: %.*s\n", cd->dev_info_len,
+
255 cd->dev_info);
+
256 }
+
257
+
258 cuse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len);
+
259
+
260 if (clop->init_done)
+
261 clop->init_done(se->userdata);
+
262
+
263 fuse_free_req(req);
+
264}
+
265
+
266struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
267 const struct cuse_info *ci,
+
268 const struct cuse_lowlevel_ops *clop,
+
269 int *multithreaded, void *userdata)
+
270{
+
271 const char *devname = "/dev/cuse";
+
272 static const struct fuse_opt kill_subtype_opts[] = {
+ + +
275 };
+
276 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
277 struct fuse_session *se;
+
278 struct fuse_cmdline_opts opts;
+
279 int fd;
+
280 int res;
+
281
+
282 if (fuse_parse_cmdline(&args, &opts) == -1)
+
283 return NULL;
+
284 *multithreaded = !opts.singlethread;
+
285
+
286 /* Remove subtype= option */
+
287 res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL);
+
288 if (res == -1)
+
289 goto out1;
+
290
+
291 /*
+
292 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
293 * would ensue.
+
294 */
+
295 do {
+
296 fd = open("/dev/null", O_RDWR);
+
297 if (fd > 2)
+
298 close(fd);
+
299 } while (fd >= 0 && fd <= 2);
+
300
+
301 se = cuse_lowlevel_new(&args, ci, clop, userdata);
+
302 if (se == NULL)
+
303 goto out1;
+
304
+
305 fd = open(devname, O_RDWR);
+
306 if (fd == -1) {
+
307 if (errno == ENODEV || errno == ENOENT)
+
308 fuse_log(FUSE_LOG_ERR, "cuse: device not found, try 'modprobe cuse' first\n");
+
309 else
+
310 fuse_log(FUSE_LOG_ERR, "cuse: failed to open %s: %s\n",
+
311 devname, strerror(errno));
+
312 goto err_se;
+
313 }
+
314 se->fd = fd;
+
315
+
316 res = fuse_set_signal_handlers(se);
+
317 if (res == -1)
+
318 goto err_se;
+
319
+
320 res = fuse_daemonize(opts.foreground);
+
321 if (res == -1)
+
322 goto err_sig;
+
323
+
324 fuse_opt_free_args(&args);
+
325 return se;
+
326
+
327err_sig:
+ +
329err_se:
+ +
331out1:
+
332 free(opts.mountpoint);
+
333 fuse_opt_free_args(&args);
+
334 return NULL;
+
335}
+
336
+
337void cuse_lowlevel_teardown(struct fuse_session *se)
+
338{
+ + +
341}
+
342
+
343int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
344 const struct cuse_lowlevel_ops *clop, void *userdata)
+
345{
+
346 struct fuse_session *se;
+
347 int multithreaded;
+
348 int res;
+
349
+
350 se = cuse_lowlevel_setup(argc, argv, ci, clop, &multithreaded,
+
351 userdata);
+
352 if (se == NULL)
+
353 return 1;
+
354
+
355 if (multithreaded) {
+
356 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
357 res = fuse_session_loop_mt(se, config);
+
358 fuse_loop_cfg_destroy(config);
+
359 }
+
360 else
+
361 res = fuse_session_loop(se);
+
362
+
363 cuse_lowlevel_teardown(se);
+
364 if (res == -1)
+
365 return 1;
+
366
+
367 return 0;
+
368}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
uint64_t fuse_ino_t
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + + + +
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2fuse_8c_source.html b/doc/html/fuse-3_817_84_2lib_2fuse_8c_source.html new file mode 100644 index 0000000..c3550a0 --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2fuse_8c_source.html @@ -0,0 +1,5433 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/fuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the high-level FUSE API on top of the low-level
+
6 API.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13#include "fuse.h"
+
14#include <pthread.h>
+
15
+
16#include "fuse_config.h"
+
17#include "fuse_i.h"
+
18#include "fuse_lowlevel.h"
+
19#include "fuse_opt.h"
+
20#include "fuse_misc.h"
+
21#include "fuse_kernel.h"
+
22#include "util.h"
+
23
+
24#include <stdint.h>
+
25#include <stdio.h>
+
26#include <string.h>
+
27#include <stdlib.h>
+
28#include <stddef.h>
+
29#include <stdbool.h>
+
30#include <unistd.h>
+
31#include <time.h>
+
32#include <fcntl.h>
+
33#include <limits.h>
+
34#include <errno.h>
+
35#include <signal.h>
+
36#include <dlfcn.h>
+
37#include <assert.h>
+
38#include <poll.h>
+
39#include <sys/param.h>
+
40#include <sys/uio.h>
+
41#include <sys/time.h>
+
42#include <sys/mman.h>
+
43#include <sys/file.h>
+
44
+
45#define FUSE_NODE_SLAB 1
+
46
+
47#ifndef MAP_ANONYMOUS
+
48#undef FUSE_NODE_SLAB
+
49#endif
+
50
+
51#ifndef RENAME_EXCHANGE
+
52#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */
+
53#endif
+
54
+
55#define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1
+
56
+
57#define FUSE_UNKNOWN_INO 0xffffffff
+
58#define OFFSET_MAX 0x7fffffffffffffffLL
+
59
+
60#define NODE_TABLE_MIN_SIZE 8192
+
61
+
62struct fuse_fs {
+
63 struct fuse_operations op;
+
64 void *user_data;
+
65 int debug;
+
66};
+
67
+
68struct fusemod_so {
+
69 void *handle;
+
70 int ctr;
+
71};
+
72
+
73struct lock_queue_element {
+
74 struct lock_queue_element *next;
+
75 pthread_cond_t cond;
+
76 fuse_ino_t nodeid1;
+
77 const char *name1;
+
78 char **path1;
+
79 struct node **wnode1;
+
80 fuse_ino_t nodeid2;
+
81 const char *name2;
+
82 char **path2;
+
83 struct node **wnode2;
+
84 int err;
+
85 bool done : 1;
+
86};
+
87
+
88struct node_table {
+
89 struct node **array;
+
90 size_t use;
+
91 size_t size;
+
92 size_t split;
+
93};
+
94
+
95#define list_entry(ptr, type, member) \
+
96 container_of(ptr, type, member)
+
97
+
98struct list_head {
+
99 struct list_head *next;
+
100 struct list_head *prev;
+
101};
+
102
+
103struct node_slab {
+
104 struct list_head list; /* must be the first member */
+
105 struct list_head freelist;
+
106 int used;
+
107};
+
108
+
109struct fuse {
+
110 struct fuse_session *se;
+
111 struct node_table name_table;
+
112 struct node_table id_table;
+
113 struct list_head lru_table;
+
114 fuse_ino_t ctr;
+
115 unsigned int generation;
+
116 unsigned int hidectr;
+
117 pthread_mutex_t lock;
+
118 struct fuse_config conf;
+
119 int intr_installed;
+
120 struct fuse_fs *fs;
+
121 struct lock_queue_element *lockq;
+
122 int pagesize;
+
123 struct list_head partial_slabs;
+
124 struct list_head full_slabs;
+
125 pthread_t prune_thread;
+
126};
+
127
+
128struct lock {
+
129 int type;
+
130 off_t start;
+
131 off_t end;
+
132 pid_t pid;
+
133 uint64_t owner;
+
134 struct lock *next;
+
135};
+
136
+
137struct node {
+
138 struct node *name_next;
+
139 struct node *id_next;
+
140 fuse_ino_t nodeid;
+
141 unsigned int generation;
+
142 int refctr;
+
143 struct node *parent;
+
144 char *name;
+
145 uint64_t nlookup;
+
146 int open_count;
+
147 struct timespec stat_updated;
+
148 struct timespec mtime;
+
149 off_t size;
+
150 struct lock *locks;
+
151 unsigned int is_hidden : 1;
+
152 unsigned int cache_valid : 1;
+
153 int treelock;
+
154 char inline_name[32];
+
155};
+
156
+
157#define TREELOCK_WRITE -1
+
158#define TREELOCK_WAIT_OFFSET INT_MIN
+
159
+
160struct node_lru {
+
161 struct node node;
+
162 struct list_head lru;
+
163 struct timespec forget_time;
+
164};
+
165
+
166struct fuse_direntry {
+
167 struct stat stat;
+
168 enum fuse_fill_dir_flags flags;
+
169 char *name;
+
170 struct fuse_direntry *next;
+
171};
+
172
+
173struct fuse_dh {
+
174 pthread_mutex_t lock;
+
175 struct fuse *fuse;
+
176 fuse_req_t req;
+
177 char *contents;
+
178 struct fuse_direntry *first;
+
179 struct fuse_direntry **last;
+
180 unsigned len;
+
181 unsigned size;
+
182 unsigned needlen;
+
183 int filled;
+
184 uint64_t fh;
+
185 int error;
+
186 fuse_ino_t nodeid;
+
187};
+
188
+
189struct fuse_context_i {
+
190 struct fuse_context ctx;
+
191 fuse_req_t req;
+
192};
+
193
+
194/* Defined by FUSE_REGISTER_MODULE() in lib/modules/subdir.c and iconv.c. */
+
195extern fuse_module_factory_t fuse_module_subdir_factory;
+
196#ifdef HAVE_ICONV
+
197extern fuse_module_factory_t fuse_module_iconv_factory;
+
198#endif
+
199
+
200static pthread_key_t fuse_context_key;
+
201static pthread_mutex_t fuse_context_lock = PTHREAD_MUTEX_INITIALIZER;
+
202static int fuse_context_ref;
+
203static struct fuse_module *fuse_modules = NULL;
+
204
+
205static int fuse_register_module(const char *name,
+
206 fuse_module_factory_t factory,
+
207 struct fusemod_so *so)
+
208{
+
209 struct fuse_module *mod;
+
210
+
211 mod = calloc(1, sizeof(struct fuse_module));
+
212 if (!mod) {
+
213 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module\n");
+
214 return -1;
+
215 }
+
216 mod->name = strdup(name);
+
217 if (!mod->name) {
+
218 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module name\n");
+
219 free(mod);
+
220 return -1;
+
221 }
+
222 mod->factory = factory;
+
223 mod->ctr = 0;
+
224 mod->so = so;
+
225 if (mod->so)
+
226 mod->so->ctr++;
+
227 mod->next = fuse_modules;
+
228 fuse_modules = mod;
+
229
+
230 return 0;
+
231}
+
232
+
233static void fuse_unregister_module(struct fuse_module *m)
+
234{
+
235 struct fuse_module **mp;
+
236 for (mp = &fuse_modules; *mp; mp = &(*mp)->next) {
+
237 if (*mp == m) {
+
238 *mp = (*mp)->next;
+
239 break;
+
240 }
+
241 }
+
242 free(m->name);
+
243 free(m);
+
244}
+
245
+
246static int fuse_load_so_module(const char *module)
+
247{
+
248 int ret = -1;
+
249 char *tmp;
+
250 struct fusemod_so *so;
+
251 fuse_module_factory_t *factory;
+
252
+
253 tmp = malloc(strlen(module) + 64);
+
254 if (!tmp) {
+
255 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
256 return -1;
+
257 }
+
258 sprintf(tmp, "libfusemod_%s.so", module);
+
259 so = calloc(1, sizeof(struct fusemod_so));
+
260 if (!so) {
+
261 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module so\n");
+
262 goto out;
+
263 }
+
264
+
265 so->handle = dlopen(tmp, RTLD_NOW);
+
266 if (so->handle == NULL) {
+
267 fuse_log(FUSE_LOG_ERR, "fuse: dlopen(%s) failed: %s\n",
+
268 tmp, dlerror());
+
269 goto out_free_so;
+
270 }
+
271
+
272 sprintf(tmp, "fuse_module_%s_factory", module);
+
273 factory = (fuse_module_factory_t*)dlsym(so->handle, tmp);
+
274 if (factory == NULL) {
+
275 fuse_log(FUSE_LOG_ERR, "fuse: symbol <%s> not found in module: %s\n",
+
276 tmp, dlerror());
+
277 goto out_dlclose;
+
278 }
+
279 ret = fuse_register_module(module, *factory, so);
+
280 if (ret)
+
281 goto out_dlclose;
+
282
+
283out:
+
284 free(tmp);
+
285 return ret;
+
286
+
287out_dlclose:
+
288 dlclose(so->handle);
+
289out_free_so:
+
290 free(so);
+
291 goto out;
+
292}
+
293
+
294static struct fuse_module *fuse_find_module(const char *module)
+
295{
+
296 struct fuse_module *m;
+
297 for (m = fuse_modules; m; m = m->next) {
+
298 if (strcmp(module, m->name) == 0) {
+
299 m->ctr++;
+
300 break;
+
301 }
+
302 }
+
303 return m;
+
304}
+
305
+
306static struct fuse_module *fuse_get_module(const char *module)
+
307{
+
308 struct fuse_module *m;
+
309
+
310 pthread_mutex_lock(&fuse_context_lock);
+
311 m = fuse_find_module(module);
+
312 if (!m) {
+
313 int err = fuse_load_so_module(module);
+
314 if (!err)
+
315 m = fuse_find_module(module);
+
316 }
+
317 pthread_mutex_unlock(&fuse_context_lock);
+
318 return m;
+
319}
+
320
+
321static void fuse_put_module(struct fuse_module *m)
+
322{
+
323 pthread_mutex_lock(&fuse_context_lock);
+
324 if (m->so)
+
325 assert(m->ctr > 0);
+
326 /* Builtin modules may already have m->ctr == 0 */
+
327 if (m->ctr > 0)
+
328 m->ctr--;
+
329 if (!m->ctr && m->so) {
+
330 struct fusemod_so *so = m->so;
+
331 assert(so->ctr > 0);
+
332 so->ctr--;
+
333 if (!so->ctr) {
+
334 struct fuse_module **mp;
+
335 for (mp = &fuse_modules; *mp;) {
+
336 if ((*mp)->so == so)
+
337 fuse_unregister_module(*mp);
+
338 else
+
339 mp = &(*mp)->next;
+
340 }
+
341 dlclose(so->handle);
+
342 free(so);
+
343 }
+
344 } else if (!m->ctr) {
+
345 fuse_unregister_module(m);
+
346 }
+
347 pthread_mutex_unlock(&fuse_context_lock);
+
348}
+
349
+
350static void init_list_head(struct list_head *list)
+
351{
+
352 list->next = list;
+
353 list->prev = list;
+
354}
+
355
+
356static int list_empty(const struct list_head *head)
+
357{
+
358 return head->next == head;
+
359}
+
360
+
361static void list_add(struct list_head *new, struct list_head *prev,
+
362 struct list_head *next)
+
363{
+
364 next->prev = new;
+
365 new->next = next;
+
366 new->prev = prev;
+
367 prev->next = new;
+
368}
+
369
+
370static inline void list_add_head(struct list_head *new, struct list_head *head)
+
371{
+
372 list_add(new, head, head->next);
+
373}
+
374
+
375static inline void list_add_tail(struct list_head *new, struct list_head *head)
+
376{
+
377 list_add(new, head->prev, head);
+
378}
+
379
+
380static inline void list_del(struct list_head *entry)
+
381{
+
382 struct list_head *prev = entry->prev;
+
383 struct list_head *next = entry->next;
+
384
+
385 next->prev = prev;
+
386 prev->next = next;
+
387}
+
388
+
389static inline int lru_enabled(struct fuse *f)
+
390{
+
391 return f->conf.remember > 0;
+
392}
+
393
+
394static struct node_lru *node_lru(struct node *node)
+
395{
+
396 return (struct node_lru *) node;
+
397}
+
398
+
399static size_t get_node_size(struct fuse *f)
+
400{
+
401 if (lru_enabled(f))
+
402 return sizeof(struct node_lru);
+
403 else
+
404 return sizeof(struct node);
+
405}
+
406
+
407#ifdef FUSE_NODE_SLAB
+
408static struct node_slab *list_to_slab(struct list_head *head)
+
409{
+
410 return (struct node_slab *) head;
+
411}
+
412
+
413static struct node_slab *node_to_slab(struct fuse *f, struct node *node)
+
414{
+
415 return (struct node_slab *) (((uintptr_t) node) & ~((uintptr_t) f->pagesize - 1));
+
416}
+
417
+
418static int alloc_slab(struct fuse *f)
+
419{
+
420 void *mem;
+
421 struct node_slab *slab;
+
422 char *start;
+
423 size_t num;
+
424 size_t i;
+
425 size_t node_size = get_node_size(f);
+
426
+
427 mem = mmap(NULL, f->pagesize, PROT_READ | PROT_WRITE,
+
428 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
429
+
430 if (mem == MAP_FAILED)
+
431 return -1;
+
432
+
433 slab = mem;
+
434 init_list_head(&slab->freelist);
+
435 slab->used = 0;
+
436 num = (f->pagesize - sizeof(struct node_slab)) / node_size;
+
437
+
438 start = (char *) mem + f->pagesize - num * node_size;
+
439 for (i = 0; i < num; i++) {
+
440 struct list_head *n;
+
441
+
442 n = (struct list_head *) (start + i * node_size);
+
443 list_add_tail(n, &slab->freelist);
+
444 }
+
445 list_add_tail(&slab->list, &f->partial_slabs);
+
446
+
447 return 0;
+
448}
+
449
+
450static struct node *alloc_node(struct fuse *f)
+
451{
+
452 struct node_slab *slab;
+
453 struct list_head *node;
+
454
+
455 if (list_empty(&f->partial_slabs)) {
+
456 int res = alloc_slab(f);
+
457 if (res != 0)
+
458 return NULL;
+
459 }
+
460 slab = list_to_slab(f->partial_slabs.next);
+
461 slab->used++;
+
462 node = slab->freelist.next;
+
463 list_del(node);
+
464 if (list_empty(&slab->freelist)) {
+
465 list_del(&slab->list);
+
466 list_add_tail(&slab->list, &f->full_slabs);
+
467 }
+
468 memset(node, 0, sizeof(struct node));
+
469
+
470 return (struct node *) node;
+
471}
+
472
+
473static void free_slab(struct fuse *f, struct node_slab *slab)
+
474{
+
475 int res;
+
476
+
477 list_del(&slab->list);
+
478 res = munmap(slab, f->pagesize);
+
479 if (res == -1)
+
480 fuse_log(FUSE_LOG_WARNING, "fuse warning: munmap(%p) failed\n",
+
481 slab);
+
482}
+
483
+
484static void free_node_mem(struct fuse *f, struct node *node)
+
485{
+
486 struct node_slab *slab = node_to_slab(f, node);
+
487 struct list_head *n = (struct list_head *) node;
+
488
+
489 slab->used--;
+
490 if (slab->used) {
+
491 if (list_empty(&slab->freelist)) {
+
492 list_del(&slab->list);
+
493 list_add_tail(&slab->list, &f->partial_slabs);
+
494 }
+
495 list_add_head(n, &slab->freelist);
+
496 } else {
+
497 free_slab(f, slab);
+
498 }
+
499}
+
500#else
+
501static struct node *alloc_node(struct fuse *f)
+
502{
+
503 return (struct node *) calloc(1, get_node_size(f));
+
504}
+
505
+
506static void free_node_mem(struct fuse *f, struct node *node)
+
507{
+
508 (void) f;
+
509 free(node);
+
510}
+
511#endif
+
512
+
513static size_t id_hash(struct fuse *f, fuse_ino_t ino)
+
514{
+
515 uint64_t hash = ((uint32_t) ino * 2654435761U) % f->id_table.size;
+
516 uint64_t oldhash = hash % (f->id_table.size / 2);
+
517
+
518 if (oldhash >= f->id_table.split)
+
519 return oldhash;
+
520 else
+
521 return hash;
+
522}
+
523
+
524static struct node *get_node_nocheck(struct fuse *f, fuse_ino_t nodeid)
+
525{
+
526 size_t hash = id_hash(f, nodeid);
+
527 struct node *node;
+
528
+
529 for (node = f->id_table.array[hash]; node != NULL; node = node->id_next)
+
530 if (node->nodeid == nodeid)
+
531 return node;
+
532
+
533 return NULL;
+
534}
+
535
+
536static struct node *get_node(struct fuse *f, fuse_ino_t nodeid)
+
537{
+
538 struct node *node = get_node_nocheck(f, nodeid);
+
539 if (!node) {
+
540 fuse_log(FUSE_LOG_ERR, "fuse internal error: node %llu not found\n",
+
541 (unsigned long long) nodeid);
+
542 abort();
+
543 }
+
544 return node;
+
545}
+
546
+
547static void curr_time(struct timespec *now);
+
548static double diff_timespec(const struct timespec *t1,
+
549 const struct timespec *t2);
+
550
+
551static void remove_node_lru(struct node *node)
+
552{
+
553 struct node_lru *lnode = node_lru(node);
+
554 list_del(&lnode->lru);
+
555 init_list_head(&lnode->lru);
+
556}
+
557
+
558static void set_forget_time(struct fuse *f, struct node *node)
+
559{
+
560 struct node_lru *lnode = node_lru(node);
+
561
+
562 list_del(&lnode->lru);
+
563 list_add_tail(&lnode->lru, &f->lru_table);
+
564 curr_time(&lnode->forget_time);
+
565}
+
566
+
567static void free_node(struct fuse *f, struct node *node)
+
568{
+
569 if (node->name != node->inline_name)
+
570 free(node->name);
+
571 free_node_mem(f, node);
+
572}
+
573
+
574static void node_table_reduce(struct node_table *t)
+
575{
+
576 size_t newsize = t->size / 2;
+
577 void *newarray;
+
578
+
579 if (newsize < NODE_TABLE_MIN_SIZE)
+
580 return;
+
581
+
582 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
583 if (newarray != NULL)
+
584 t->array = newarray;
+
585
+
586 t->size = newsize;
+
587 t->split = t->size / 2;
+
588}
+
589
+
590static void remerge_id(struct fuse *f)
+
591{
+
592 struct node_table *t = &f->id_table;
+
593 int iter;
+
594
+
595 if (t->split == 0)
+
596 node_table_reduce(t);
+
597
+
598 for (iter = 8; t->split > 0 && iter; iter--) {
+
599 struct node **upper;
+
600
+
601 t->split--;
+
602 upper = &t->array[t->split + t->size / 2];
+
603 if (*upper) {
+
604 struct node **nodep;
+
605
+
606 for (nodep = &t->array[t->split]; *nodep;
+
607 nodep = &(*nodep)->id_next);
+
608
+
609 *nodep = *upper;
+
610 *upper = NULL;
+
611 break;
+
612 }
+
613 }
+
614}
+
615
+
616static void unhash_id(struct fuse *f, struct node *node)
+
617{
+
618 struct node **nodep = &f->id_table.array[id_hash(f, node->nodeid)];
+
619
+
620 for (; *nodep != NULL; nodep = &(*nodep)->id_next)
+
621 if (*nodep == node) {
+
622 *nodep = node->id_next;
+
623 f->id_table.use--;
+
624
+
625 if(f->id_table.use < f->id_table.size / 4)
+
626 remerge_id(f);
+
627 return;
+
628 }
+
629}
+
630
+
631static int node_table_resize(struct node_table *t)
+
632{
+
633 size_t newsize = t->size * 2;
+
634 void *newarray;
+
635
+
636 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
637 if (newarray == NULL)
+
638 return -1;
+
639
+
640 t->array = newarray;
+
641 memset(t->array + t->size, 0, t->size * sizeof(struct node *));
+
642 t->size = newsize;
+
643 t->split = 0;
+
644
+
645 return 0;
+
646}
+
647
+
648static void rehash_id(struct fuse *f)
+
649{
+
650 struct node_table *t = &f->id_table;
+
651 struct node **nodep;
+
652 struct node **next;
+
653 size_t hash;
+
654
+
655 if (t->split == t->size / 2)
+
656 return;
+
657
+
658 hash = t->split;
+
659 t->split++;
+
660 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
661 struct node *node = *nodep;
+
662 size_t newhash = id_hash(f, node->nodeid);
+
663
+
664 if (newhash != hash) {
+
665 next = nodep;
+
666 *nodep = node->id_next;
+
667 node->id_next = t->array[newhash];
+
668 t->array[newhash] = node;
+
669 } else {
+
670 next = &node->id_next;
+
671 }
+
672 }
+
673 if (t->split == t->size / 2)
+
674 node_table_resize(t);
+
675}
+
676
+
677static void hash_id(struct fuse *f, struct node *node)
+
678{
+
679 size_t hash = id_hash(f, node->nodeid);
+
680 node->id_next = f->id_table.array[hash];
+
681 f->id_table.array[hash] = node;
+
682 f->id_table.use++;
+
683
+
684 if (f->id_table.use >= f->id_table.size / 2)
+
685 rehash_id(f);
+
686}
+
687
+
688static size_t name_hash(struct fuse *f, fuse_ino_t parent,
+
689 const char *name)
+
690{
+
691 uint64_t hash = parent;
+
692 uint64_t oldhash;
+
693
+
694 for (; *name; name++)
+
695 hash = hash * 31 + (unsigned char) *name;
+
696
+
697 hash %= f->name_table.size;
+
698 oldhash = hash % (f->name_table.size / 2);
+
699 if (oldhash >= f->name_table.split)
+
700 return oldhash;
+
701 else
+
702 return hash;
+
703}
+
704
+
705static void unref_node(struct fuse *f, struct node *node);
+
706
+
707static void remerge_name(struct fuse *f)
+
708{
+
709 struct node_table *t = &f->name_table;
+
710 int iter;
+
711
+
712 if (t->split == 0)
+
713 node_table_reduce(t);
+
714
+
715 for (iter = 8; t->split > 0 && iter; iter--) {
+
716 struct node **upper;
+
717
+
718 t->split--;
+
719 upper = &t->array[t->split + t->size / 2];
+
720 if (*upper) {
+
721 struct node **nodep;
+
722
+
723 for (nodep = &t->array[t->split]; *nodep;
+
724 nodep = &(*nodep)->name_next);
+
725
+
726 *nodep = *upper;
+
727 *upper = NULL;
+
728 break;
+
729 }
+
730 }
+
731}
+
732
+
733static void unhash_name(struct fuse *f, struct node *node)
+
734{
+
735 if (node->name) {
+
736 size_t hash = name_hash(f, node->parent->nodeid, node->name);
+
737 struct node **nodep = &f->name_table.array[hash];
+
738
+
739 for (; *nodep != NULL; nodep = &(*nodep)->name_next)
+
740 if (*nodep == node) {
+
741 *nodep = node->name_next;
+
742 node->name_next = NULL;
+
743 unref_node(f, node->parent);
+
744 if (node->name != node->inline_name)
+
745 free(node->name);
+
746 node->name = NULL;
+
747 node->parent = NULL;
+
748 f->name_table.use--;
+
749
+
750 if (f->name_table.use < f->name_table.size / 4)
+
751 remerge_name(f);
+
752 return;
+
753 }
+
754 fuse_log(FUSE_LOG_ERR,
+
755 "fuse internal error: unable to unhash node: %llu\n",
+
756 (unsigned long long) node->nodeid);
+
757 abort();
+
758 }
+
759}
+
760
+
761static void rehash_name(struct fuse *f)
+
762{
+
763 struct node_table *t = &f->name_table;
+
764 struct node **nodep;
+
765 struct node **next;
+
766 size_t hash;
+
767
+
768 if (t->split == t->size / 2)
+
769 return;
+
770
+
771 hash = t->split;
+
772 t->split++;
+
773 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
774 struct node *node = *nodep;
+
775 size_t newhash = name_hash(f, node->parent->nodeid, node->name);
+
776
+
777 if (newhash != hash) {
+
778 next = nodep;
+
779 *nodep = node->name_next;
+
780 node->name_next = t->array[newhash];
+
781 t->array[newhash] = node;
+
782 } else {
+
783 next = &node->name_next;
+
784 }
+
785 }
+
786 if (t->split == t->size / 2)
+
787 node_table_resize(t);
+
788}
+
789
+
790static int hash_name(struct fuse *f, struct node *node, fuse_ino_t parentid,
+
791 const char *name)
+
792{
+
793 size_t hash = name_hash(f, parentid, name);
+
794 struct node *parent = get_node(f, parentid);
+
795 if (strlen(name) < sizeof(node->inline_name)) {
+
796 strcpy(node->inline_name, name);
+
797 node->name = node->inline_name;
+
798 } else {
+
799 node->name = strdup(name);
+
800 if (node->name == NULL)
+
801 return -1;
+
802 }
+
803
+
804 parent->refctr ++;
+
805 node->parent = parent;
+
806 node->name_next = f->name_table.array[hash];
+
807 f->name_table.array[hash] = node;
+
808 f->name_table.use++;
+
809
+
810 if (f->name_table.use >= f->name_table.size / 2)
+
811 rehash_name(f);
+
812
+
813 return 0;
+
814}
+
815
+
816static void delete_node(struct fuse *f, struct node *node)
+
817{
+
818 if (f->conf.debug)
+
819 fuse_log(FUSE_LOG_DEBUG, "DELETE: %llu\n",
+
820 (unsigned long long) node->nodeid);
+
821
+
822 assert(node->treelock == 0);
+
823 unhash_name(f, node);
+
824 if (lru_enabled(f))
+
825 remove_node_lru(node);
+
826 unhash_id(f, node);
+
827 free_node(f, node);
+
828}
+
829
+
830static void unref_node(struct fuse *f, struct node *node)
+
831{
+
832 assert(node->refctr > 0);
+
833 node->refctr --;
+
834 if (!node->refctr)
+
835 delete_node(f, node);
+
836}
+
837
+
838static fuse_ino_t next_id(struct fuse *f)
+
839{
+
840 do {
+
841 f->ctr = (f->ctr + 1) & 0xffffffff;
+
842 if (!f->ctr)
+
843 f->generation ++;
+
844 } while (f->ctr == 0 || f->ctr == FUSE_UNKNOWN_INO ||
+
845 get_node_nocheck(f, f->ctr) != NULL);
+
846 return f->ctr;
+
847}
+
848
+
849static struct node *lookup_node(struct fuse *f, fuse_ino_t parent,
+
850 const char *name)
+
851{
+
852 size_t hash = name_hash(f, parent, name);
+
853 struct node *node;
+
854
+
855 for (node = f->name_table.array[hash]; node != NULL; node = node->name_next)
+
856 if (node->parent->nodeid == parent &&
+
857 strcmp(node->name, name) == 0)
+
858 return node;
+
859
+
860 return NULL;
+
861}
+
862
+
863static void inc_nlookup(struct node *node)
+
864{
+
865 if (!node->nlookup)
+
866 node->refctr++;
+
867 node->nlookup++;
+
868}
+
869
+
870static struct node *find_node(struct fuse *f, fuse_ino_t parent,
+
871 const char *name)
+
872{
+
873 struct node *node;
+
874
+
875 pthread_mutex_lock(&f->lock);
+
876 if (!name)
+
877 node = get_node(f, parent);
+
878 else
+
879 node = lookup_node(f, parent, name);
+
880 if (node == NULL) {
+
881 node = alloc_node(f);
+
882 if (node == NULL)
+
883 goto out_err;
+
884
+
885 node->nodeid = next_id(f);
+
886 node->generation = f->generation;
+
887 if (f->conf.remember)
+
888 inc_nlookup(node);
+
889
+
890 if (hash_name(f, node, parent, name) == -1) {
+
891 free_node(f, node);
+
892 node = NULL;
+
893 goto out_err;
+
894 }
+
895 hash_id(f, node);
+
896 if (lru_enabled(f)) {
+
897 struct node_lru *lnode = node_lru(node);
+
898 init_list_head(&lnode->lru);
+
899 }
+
900 } else if (lru_enabled(f) && node->nlookup == 1) {
+
901 remove_node_lru(node);
+
902 }
+
903 inc_nlookup(node);
+
904out_err:
+
905 pthread_mutex_unlock(&f->lock);
+
906 return node;
+
907}
+
908
+
909static int lookup_path_in_cache(struct fuse *f,
+
910 const char *path, fuse_ino_t *inop)
+
911{
+
912 char *tmp = strdup(path);
+
913 if (!tmp)
+
914 return -ENOMEM;
+
915
+
916 pthread_mutex_lock(&f->lock);
+
917 fuse_ino_t ino = FUSE_ROOT_ID;
+
918
+
919 int err = 0;
+
920 char *save_ptr;
+
921 char *path_element = strtok_r(tmp, "/", &save_ptr);
+
922 while (path_element != NULL) {
+
923 struct node *node = lookup_node(f, ino, path_element);
+
924 if (node == NULL) {
+
925 err = -ENOENT;
+
926 break;
+
927 }
+
928 ino = node->nodeid;
+
929 path_element = strtok_r(NULL, "/", &save_ptr);
+
930 }
+
931 pthread_mutex_unlock(&f->lock);
+
932 free(tmp);
+
933
+
934 if (!err)
+
935 *inop = ino;
+
936 return err;
+
937}
+
938
+
939static char *add_name(char **buf, unsigned *bufsize, char *s, const char *name)
+
940{
+
941 size_t len = strlen(name);
+
942
+
943 if (s - len <= *buf) {
+
944 unsigned pathlen = *bufsize - (s - *buf);
+
945 unsigned newbufsize = *bufsize;
+
946 char *newbuf;
+
947
+
948 while (newbufsize < pathlen + len + 1) {
+
949 if (newbufsize >= 0x80000000)
+
950 newbufsize = 0xffffffff;
+
951 else
+
952 newbufsize *= 2;
+
953 }
+
954
+
955 newbuf = realloc(*buf, newbufsize);
+
956 if (newbuf == NULL)
+
957 return NULL;
+
958
+
959 *buf = newbuf;
+
960 s = newbuf + newbufsize - pathlen;
+
961 memmove(s, newbuf + *bufsize - pathlen, pathlen);
+
962 *bufsize = newbufsize;
+
963 }
+
964 s -= len;
+
965 memcpy(s, name, len);
+
966 s--;
+
967 *s = '/';
+
968
+
969 return s;
+
970}
+
971
+
972static void unlock_path(struct fuse *f, fuse_ino_t nodeid, struct node *wnode,
+
973 struct node *end)
+
974{
+
975 struct node *node;
+
976
+
977 if (wnode) {
+
978 assert(wnode->treelock == TREELOCK_WRITE);
+
979 wnode->treelock = 0;
+
980 }
+
981
+
982 for (node = get_node(f, nodeid);
+
983 node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) {
+
984 assert(node->treelock != 0);
+
985 assert(node->treelock != TREELOCK_WAIT_OFFSET);
+
986 assert(node->treelock != TREELOCK_WRITE);
+
987 node->treelock--;
+
988 if (node->treelock == TREELOCK_WAIT_OFFSET)
+
989 node->treelock = 0;
+
990 }
+
991}
+
992
+
993static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
994 char **path, struct node **wnodep, bool need_lock)
+
995{
+
996 unsigned bufsize = 256;
+
997 char *buf;
+
998 char *s;
+
999 struct node *node;
+
1000 struct node *wnode = NULL;
+
1001 int err;
+
1002
+
1003 *path = NULL;
+
1004
+
1005 err = -ENOMEM;
+
1006 buf = malloc(bufsize);
+
1007 if (buf == NULL)
+
1008 goto out_err;
+
1009
+
1010 s = buf + bufsize - 1;
+
1011 *s = '\0';
+
1012
+
1013 if (name != NULL) {
+
1014 s = add_name(&buf, &bufsize, s, name);
+
1015 err = -ENOMEM;
+
1016 if (s == NULL)
+
1017 goto out_free;
+
1018 }
+
1019
+
1020 if (wnodep) {
+
1021 assert(need_lock);
+
1022 wnode = lookup_node(f, nodeid, name);
+
1023 if (wnode) {
+
1024 if (wnode->treelock != 0) {
+
1025 if (wnode->treelock > 0)
+
1026 wnode->treelock += TREELOCK_WAIT_OFFSET;
+
1027 err = -EAGAIN;
+
1028 goto out_free;
+
1029 }
+
1030 wnode->treelock = TREELOCK_WRITE;
+
1031 }
+
1032 }
+
1033
+
1034 for (node = get_node(f, nodeid); node->nodeid != FUSE_ROOT_ID;
+
1035 node = node->parent) {
+
1036 err = -ESTALE;
+
1037 if (node->name == NULL || node->parent == NULL)
+
1038 goto out_unlock;
+
1039
+
1040 err = -ENOMEM;
+
1041 s = add_name(&buf, &bufsize, s, node->name);
+
1042 if (s == NULL)
+
1043 goto out_unlock;
+
1044
+
1045 if (need_lock) {
+
1046 err = -EAGAIN;
+
1047 if (node->treelock < 0)
+
1048 goto out_unlock;
+
1049
+
1050 node->treelock++;
+
1051 }
+
1052 }
+
1053
+
1054 if (s[0])
+
1055 memmove(buf, s, bufsize - (s - buf));
+
1056 else
+
1057 strcpy(buf, "/");
+
1058
+
1059 *path = buf;
+
1060 if (wnodep)
+
1061 *wnodep = wnode;
+
1062
+
1063 return 0;
+
1064
+
1065 out_unlock:
+
1066 if (need_lock)
+
1067 unlock_path(f, nodeid, wnode, node);
+
1068 out_free:
+
1069 free(buf);
+
1070
+
1071 out_err:
+
1072 return err;
+
1073}
+
1074
+
1075static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1076 fuse_ino_t nodeid2, const char *name2,
+
1077 char **path1, char **path2,
+
1078 struct node **wnode1, struct node **wnode2)
+
1079{
+
1080 int err;
+
1081
+
1082 /* FIXME: locking two paths needs deadlock checking */
+
1083 err = try_get_path(f, nodeid1, name1, path1, wnode1, true);
+
1084 if (!err) {
+
1085 err = try_get_path(f, nodeid2, name2, path2, wnode2, true);
+
1086 if (err) {
+
1087 struct node *wn1 = wnode1 ? *wnode1 : NULL;
+
1088
+
1089 unlock_path(f, nodeid1, wn1, NULL);
+
1090 free(*path1);
+
1091 }
+
1092 }
+
1093 return err;
+
1094}
+
1095
+
1096static void queue_element_wakeup(struct fuse *f, struct lock_queue_element *qe)
+
1097{
+
1098 int err;
+
1099
+
1100 if (!qe->path1) {
+
1101 /* Just waiting for it to be unlocked */
+
1102 if (get_node(f, qe->nodeid1)->treelock == 0)
+
1103 pthread_cond_signal(&qe->cond);
+
1104
+
1105 return;
+
1106 }
+
1107
+
1108 if (qe->done)
+
1109 return; // Don't try to double-lock the element
+
1110
+
1111 if (!qe->path2) {
+
1112 err = try_get_path(f, qe->nodeid1, qe->name1, qe->path1,
+
1113 qe->wnode1, true);
+
1114 } else {
+
1115 err = try_get_path2(f, qe->nodeid1, qe->name1, qe->nodeid2,
+
1116 qe->name2, qe->path1, qe->path2, qe->wnode1,
+
1117 qe->wnode2);
+
1118 }
+
1119
+
1120 if (err == -EAGAIN)
+
1121 return; /* keep trying */
+
1122
+
1123 qe->err = err;
+
1124 qe->done = true;
+
1125 pthread_cond_signal(&qe->cond);
+
1126}
+
1127
+
1128static void wake_up_queued(struct fuse *f)
+
1129{
+
1130 struct lock_queue_element *qe;
+
1131
+
1132 for (qe = f->lockq; qe != NULL; qe = qe->next)
+
1133 queue_element_wakeup(f, qe);
+
1134}
+
1135
+
1136static void debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid,
+
1137 const char *name, bool wr)
+
1138{
+
1139 if (f->conf.debug) {
+
1140 struct node *wnode = NULL;
+
1141
+
1142 if (wr)
+
1143 wnode = lookup_node(f, nodeid, name);
+
1144
+
1145 if (wnode) {
+
1146 fuse_log(FUSE_LOG_DEBUG, "%s %llu (w)\n",
+
1147 msg, (unsigned long long) wnode->nodeid);
+
1148 } else {
+
1149 fuse_log(FUSE_LOG_DEBUG, "%s %llu\n",
+
1150 msg, (unsigned long long) nodeid);
+
1151 }
+
1152 }
+
1153}
+
1154
+
1155static void queue_path(struct fuse *f, struct lock_queue_element *qe)
+
1156{
+
1157 struct lock_queue_element **qp;
+
1158
+
1159 qe->done = false;
+
1160 pthread_cond_init(&qe->cond, NULL);
+
1161 qe->next = NULL;
+
1162 for (qp = &f->lockq; *qp != NULL; qp = &(*qp)->next);
+
1163 *qp = qe;
+
1164}
+
1165
+
1166static void dequeue_path(struct fuse *f, struct lock_queue_element *qe)
+
1167{
+
1168 struct lock_queue_element **qp;
+
1169
+
1170 pthread_cond_destroy(&qe->cond);
+
1171 for (qp = &f->lockq; *qp != qe; qp = &(*qp)->next);
+
1172 *qp = qe->next;
+
1173}
+
1174
+
1175static int wait_path(struct fuse *f, struct lock_queue_element *qe)
+
1176{
+
1177 queue_path(f, qe);
+
1178
+
1179 do {
+
1180 pthread_cond_wait(&qe->cond, &f->lock);
+
1181 } while (!qe->done);
+
1182
+
1183 dequeue_path(f, qe);
+
1184
+
1185 return qe->err;
+
1186}
+
1187
+
1188static int get_path_common(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1189 char **path, struct node **wnode)
+
1190{
+
1191 int err;
+
1192
+
1193 pthread_mutex_lock(&f->lock);
+
1194 err = try_get_path(f, nodeid, name, path, wnode, true);
+
1195 if (err == -EAGAIN) {
+
1196 struct lock_queue_element qe = {
+
1197 .nodeid1 = nodeid,
+
1198 .name1 = name,
+
1199 .path1 = path,
+
1200 .wnode1 = wnode,
+
1201 };
+
1202 debug_path(f, "QUEUE PATH", nodeid, name, !!wnode);
+
1203 err = wait_path(f, &qe);
+
1204 debug_path(f, "DEQUEUE PATH", nodeid, name, !!wnode);
+
1205 }
+
1206 pthread_mutex_unlock(&f->lock);
+
1207
+
1208 return err;
+
1209}
+
1210
+
1211static int get_path(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1212{
+
1213 return get_path_common(f, nodeid, NULL, path, NULL);
+
1214}
+
1215
+
1216static int get_path_nullok(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1217{
+
1218 int err = 0;
+
1219
+
1220 if (f->conf.nullpath_ok) {
+
1221 *path = NULL;
+
1222 } else {
+
1223 err = get_path_common(f, nodeid, NULL, path, NULL);
+
1224 if (err == -ESTALE)
+
1225 err = 0;
+
1226 }
+
1227
+
1228 return err;
+
1229}
+
1230
+
1231static int get_path_name(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1232 char **path)
+
1233{
+
1234 return get_path_common(f, nodeid, name, path, NULL);
+
1235}
+
1236
+
1237static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1238 char **path, struct node **wnode)
+
1239{
+
1240 return get_path_common(f, nodeid, name, path, wnode);
+
1241}
+
1242
+
1243#if defined(__FreeBSD__)
+
1244#define CHECK_DIR_LOOP
+
1245#endif
+
1246
+
1247#if defined(CHECK_DIR_LOOP)
+
1248static int check_dir_loop(struct fuse *f,
+
1249 fuse_ino_t nodeid1, const char *name1,
+
1250 fuse_ino_t nodeid2, const char *name2)
+
1251{
+
1252 struct node *node, *node1, *node2;
+
1253 fuse_ino_t id1, id2;
+
1254
+
1255 node1 = lookup_node(f, nodeid1, name1);
+
1256 id1 = node1 ? node1->nodeid : nodeid1;
+
1257
+
1258 node2 = lookup_node(f, nodeid2, name2);
+
1259 id2 = node2 ? node2->nodeid : nodeid2;
+
1260
+
1261 for (node = get_node(f, id2); node->nodeid != FUSE_ROOT_ID;
+
1262 node = node->parent) {
+
1263 if (node->name == NULL || node->parent == NULL)
+
1264 break;
+
1265
+
1266 if (node->nodeid != id2 && node->nodeid == id1)
+
1267 return -EINVAL;
+
1268 }
+
1269
+
1270 if (node2)
+
1271 {
+
1272 for (node = get_node(f, id1); node->nodeid != FUSE_ROOT_ID;
+
1273 node = node->parent) {
+
1274 if (node->name == NULL || node->parent == NULL)
+
1275 break;
+
1276
+
1277 if (node->nodeid != id1 && node->nodeid == id2)
+
1278 return -ENOTEMPTY;
+
1279 }
+
1280 }
+
1281
+
1282 return 0;
+
1283}
+
1284#endif
+
1285
+
1286static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1287 fuse_ino_t nodeid2, const char *name2,
+
1288 char **path1, char **path2,
+
1289 struct node **wnode1, struct node **wnode2)
+
1290{
+
1291 int err;
+
1292
+
1293 pthread_mutex_lock(&f->lock);
+
1294
+
1295#if defined(CHECK_DIR_LOOP)
+
1296 if (name1)
+
1297 {
+
1298 // called during rename; perform dir loop check
+
1299 err = check_dir_loop(f, nodeid1, name1, nodeid2, name2);
+
1300 if (err)
+
1301 goto out_unlock;
+
1302 }
+
1303#endif
+
1304
+
1305 err = try_get_path2(f, nodeid1, name1, nodeid2, name2,
+
1306 path1, path2, wnode1, wnode2);
+
1307 if (err == -EAGAIN) {
+
1308 struct lock_queue_element qe = {
+
1309 .nodeid1 = nodeid1,
+
1310 .name1 = name1,
+
1311 .path1 = path1,
+
1312 .wnode1 = wnode1,
+
1313 .nodeid2 = nodeid2,
+
1314 .name2 = name2,
+
1315 .path2 = path2,
+
1316 .wnode2 = wnode2,
+
1317 };
+
1318
+
1319 debug_path(f, "QUEUE PATH1", nodeid1, name1, !!wnode1);
+
1320 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1321 err = wait_path(f, &qe);
+
1322 debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1);
+
1323 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1324 }
+
1325
+
1326#if defined(CHECK_DIR_LOOP)
+
1327out_unlock:
+
1328#endif
+
1329 pthread_mutex_unlock(&f->lock);
+
1330
+
1331 return err;
+
1332}
+
1333
+
1334static void free_path_wrlock(struct fuse *f, fuse_ino_t nodeid,
+
1335 struct node *wnode, char *path)
+
1336{
+
1337 pthread_mutex_lock(&f->lock);
+
1338 unlock_path(f, nodeid, wnode, NULL);
+
1339 if (f->lockq)
+
1340 wake_up_queued(f);
+
1341 pthread_mutex_unlock(&f->lock);
+
1342 free(path);
+
1343}
+
1344
+
1345static void free_path(struct fuse *f, fuse_ino_t nodeid, char *path)
+
1346{
+
1347 if (path)
+
1348 free_path_wrlock(f, nodeid, NULL, path);
+
1349}
+
1350
+
1351static void free_path2(struct fuse *f, fuse_ino_t nodeid1, fuse_ino_t nodeid2,
+
1352 struct node *wnode1, struct node *wnode2,
+
1353 char *path1, char *path2)
+
1354{
+
1355 pthread_mutex_lock(&f->lock);
+
1356 unlock_path(f, nodeid1, wnode1, NULL);
+
1357 unlock_path(f, nodeid2, wnode2, NULL);
+
1358 wake_up_queued(f);
+
1359 pthread_mutex_unlock(&f->lock);
+
1360 free(path1);
+
1361 free(path2);
+
1362}
+
1363
+
1364static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup)
+
1365{
+
1366 struct node *node;
+
1367 if (nodeid == FUSE_ROOT_ID)
+
1368 return;
+
1369 pthread_mutex_lock(&f->lock);
+
1370 node = get_node(f, nodeid);
+
1371
+
1372 /*
+
1373 * Node may still be locked due to interrupt idiocy in open,
+
1374 * create and opendir
+
1375 */
+
1376 while (node->nlookup == nlookup && node->treelock) {
+
1377 struct lock_queue_element qe = {
+
1378 .nodeid1 = nodeid,
+
1379 };
+
1380
+
1381 debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false);
+
1382 queue_path(f, &qe);
+
1383
+
1384 do {
+
1385 pthread_cond_wait(&qe.cond, &f->lock);
+
1386 } while (node->nlookup == nlookup && node->treelock);
+
1387
+
1388 dequeue_path(f, &qe);
+
1389 debug_path(f, "DEQUEUE_PATH (forget)", nodeid, NULL, false);
+
1390 }
+
1391
+
1392 assert(node->nlookup >= nlookup);
+
1393 node->nlookup -= nlookup;
+
1394 if (!node->nlookup) {
+
1395 unref_node(f, node);
+
1396 } else if (lru_enabled(f) && node->nlookup == 1) {
+
1397 set_forget_time(f, node);
+
1398 }
+
1399 pthread_mutex_unlock(&f->lock);
+
1400}
+
1401
+
1402static void unlink_node(struct fuse *f, struct node *node)
+
1403{
+
1404 if (f->conf.remember) {
+
1405 assert(node->nlookup > 1);
+
1406 node->nlookup--;
+
1407 }
+
1408 unhash_name(f, node);
+
1409}
+
1410
+
1411static void remove_node(struct fuse *f, fuse_ino_t dir, const char *name)
+
1412{
+
1413 struct node *node;
+
1414
+
1415 pthread_mutex_lock(&f->lock);
+
1416 node = lookup_node(f, dir, name);
+
1417 if (node != NULL)
+
1418 unlink_node(f, node);
+
1419 pthread_mutex_unlock(&f->lock);
+
1420}
+
1421
+
1422static int rename_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1423 fuse_ino_t newdir, const char *newname, int hide)
+
1424{
+
1425 struct node *node;
+
1426 struct node *newnode;
+
1427 int err = 0;
+
1428
+
1429 pthread_mutex_lock(&f->lock);
+
1430 node = lookup_node(f, olddir, oldname);
+
1431 newnode = lookup_node(f, newdir, newname);
+
1432 if (node == NULL)
+
1433 goto out;
+
1434
+
1435 if (newnode != NULL) {
+
1436 if (hide) {
+
1437 fuse_log(FUSE_LOG_ERR, "fuse: hidden file got created during hiding\n");
+
1438 err = -EBUSY;
+
1439 goto out;
+
1440 }
+
1441 unlink_node(f, newnode);
+
1442 }
+
1443
+
1444 unhash_name(f, node);
+
1445 if (hash_name(f, node, newdir, newname) == -1) {
+
1446 err = -ENOMEM;
+
1447 goto out;
+
1448 }
+
1449
+
1450 if (hide)
+
1451 node->is_hidden = 1;
+
1452
+
1453out:
+
1454 pthread_mutex_unlock(&f->lock);
+
1455 return err;
+
1456}
+
1457
+
1458static int exchange_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1459 fuse_ino_t newdir, const char *newname)
+
1460{
+
1461 struct node *oldnode;
+
1462 struct node *newnode;
+
1463 int err;
+
1464
+
1465 pthread_mutex_lock(&f->lock);
+
1466 oldnode = lookup_node(f, olddir, oldname);
+
1467 newnode = lookup_node(f, newdir, newname);
+
1468
+
1469 if (oldnode)
+
1470 unhash_name(f, oldnode);
+
1471 if (newnode)
+
1472 unhash_name(f, newnode);
+
1473
+
1474 err = -ENOMEM;
+
1475 if (oldnode) {
+
1476 if (hash_name(f, oldnode, newdir, newname) == -1)
+
1477 goto out;
+
1478 }
+
1479 if (newnode) {
+
1480 if (hash_name(f, newnode, olddir, oldname) == -1)
+
1481 goto out;
+
1482 }
+
1483 err = 0;
+
1484out:
+
1485 pthread_mutex_unlock(&f->lock);
+
1486 return err;
+
1487}
+
1488
+
1489static void set_stat(struct fuse *f, fuse_ino_t nodeid, struct stat *stbuf)
+
1490{
+
1491 if (!f->conf.use_ino)
+
1492 stbuf->st_ino = nodeid;
+
1493 if (f->conf.set_mode) {
+
1494 if (f->conf.dmask && S_ISDIR(stbuf->st_mode))
+
1495 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1496 (0777 & ~f->conf.dmask);
+
1497 else if (f->conf.fmask)
+
1498 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1499 (0777 & ~f->conf.fmask);
+
1500 else
+
1501 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1502 (0777 & ~f->conf.umask);
+
1503 }
+
1504 if (f->conf.set_uid)
+
1505 stbuf->st_uid = f->conf.uid;
+
1506 if (f->conf.set_gid)
+
1507 stbuf->st_gid = f->conf.gid;
+
1508}
+
1509
+
1510static struct fuse *req_fuse(fuse_req_t req)
+
1511{
+
1512 return (struct fuse *) fuse_req_userdata(req);
+
1513}
+
1514
+
1515static void fuse_intr_sighandler(int sig)
+
1516{
+
1517 (void) sig;
+
1518 /* Nothing to do */
+
1519}
+
1520
+
1521struct fuse_intr_data {
+
1522 pthread_t id;
+
1523 pthread_cond_t cond;
+
1524 int finished;
+
1525};
+
1526
+
1527static void fuse_interrupt(fuse_req_t req, void *d_)
+
1528{
+
1529 struct fuse_intr_data *d = d_;
+
1530 struct fuse *f = req_fuse(req);
+
1531
+
1532 if (d->id == pthread_self())
+
1533 return;
+
1534
+
1535 pthread_mutex_lock(&f->lock);
+
1536 while (!d->finished) {
+
1537 struct timeval now;
+
1538 struct timespec timeout;
+
1539
+
1540 pthread_kill(d->id, f->conf.intr_signal);
+
1541 gettimeofday(&now, NULL);
+
1542 timeout.tv_sec = now.tv_sec + 1;
+
1543 timeout.tv_nsec = now.tv_usec * 1000;
+
1544 pthread_cond_timedwait(&d->cond, &f->lock, &timeout);
+
1545 }
+
1546 pthread_mutex_unlock(&f->lock);
+
1547}
+
1548
+
1549static void fuse_do_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1550 struct fuse_intr_data *d)
+
1551{
+
1552 pthread_mutex_lock(&f->lock);
+
1553 d->finished = 1;
+
1554 pthread_cond_broadcast(&d->cond);
+
1555 pthread_mutex_unlock(&f->lock);
+
1556 fuse_req_interrupt_func(req, NULL, NULL);
+
1557 pthread_cond_destroy(&d->cond);
+
1558}
+
1559
+
1560static void fuse_do_prepare_interrupt(fuse_req_t req, struct fuse_intr_data *d)
+
1561{
+
1562 d->id = pthread_self();
+
1563 pthread_cond_init(&d->cond, NULL);
+
1564 d->finished = 0;
+
1565 fuse_req_interrupt_func(req, fuse_interrupt, d);
+
1566}
+
1567
+
1568static inline void fuse_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1569 struct fuse_intr_data *d)
+
1570{
+
1571 if (f->conf.intr)
+
1572 fuse_do_finish_interrupt(f, req, d);
+
1573}
+
1574
+
1575static inline void fuse_prepare_interrupt(struct fuse *f, fuse_req_t req,
+
1576 struct fuse_intr_data *d)
+
1577{
+
1578 if (f->conf.intr)
+
1579 fuse_do_prepare_interrupt(req, d);
+
1580}
+
1581
+
1582static const char* file_info_string(struct fuse_file_info *fi,
+
1583 char* buf, size_t len)
+
1584{
+
1585 if(fi == NULL)
+
1586 return "NULL";
+
1587 snprintf(buf, len, "%llu", (unsigned long long) fi->fh);
+
1588 return buf;
+
1589}
+
1590
+
1591int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1592 struct fuse_file_info *fi)
+
1593{
+
1594 fuse_get_context()->private_data = fs->user_data;
+
1595 if (fs->op.getattr) {
+
1596 if (fs->debug) {
+
1597 char buf[10];
+
1598 fuse_log(FUSE_LOG_DEBUG, "getattr[%s] %s\n",
+
1599 file_info_string(fi, buf, sizeof(buf)),
+
1600 path);
+
1601 }
+
1602 return fs->op.getattr(path, buf, fi);
+
1603 } else {
+
1604 return -ENOSYS;
+
1605 }
+
1606}
+
1607
+
1608int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1609 const char *newpath, unsigned int flags)
+
1610{
+
1611 fuse_get_context()->private_data = fs->user_data;
+
1612 if (fs->op.rename) {
+
1613 if (fs->debug)
+
1614 fuse_log(FUSE_LOG_DEBUG, "rename %s %s 0x%x\n", oldpath, newpath,
+
1615 flags);
+
1616
+
1617 return fs->op.rename(oldpath, newpath, flags);
+
1618 } else {
+
1619 return -ENOSYS;
+
1620 }
+
1621}
+
1622
+
1623int fuse_fs_unlink(struct fuse_fs *fs, const char *path)
+
1624{
+
1625 fuse_get_context()->private_data = fs->user_data;
+
1626 if (fs->op.unlink) {
+
1627 if (fs->debug)
+
1628 fuse_log(FUSE_LOG_DEBUG, "unlink %s\n", path);
+
1629
+
1630 return fs->op.unlink(path);
+
1631 } else {
+
1632 return -ENOSYS;
+
1633 }
+
1634}
+
1635
+
1636int fuse_fs_rmdir(struct fuse_fs *fs, const char *path)
+
1637{
+
1638 fuse_get_context()->private_data = fs->user_data;
+
1639 if (fs->op.rmdir) {
+
1640 if (fs->debug)
+
1641 fuse_log(FUSE_LOG_DEBUG, "rmdir %s\n", path);
+
1642
+
1643 return fs->op.rmdir(path);
+
1644 } else {
+
1645 return -ENOSYS;
+
1646 }
+
1647}
+
1648
+
1649int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path)
+
1650{
+
1651 fuse_get_context()->private_data = fs->user_data;
+
1652 if (fs->op.symlink) {
+
1653 if (fs->debug)
+
1654 fuse_log(FUSE_LOG_DEBUG, "symlink %s %s\n", linkname, path);
+
1655
+
1656 return fs->op.symlink(linkname, path);
+
1657 } else {
+
1658 return -ENOSYS;
+
1659 }
+
1660}
+
1661
+
1662int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath)
+
1663{
+
1664 fuse_get_context()->private_data = fs->user_data;
+
1665 if (fs->op.link) {
+
1666 if (fs->debug)
+
1667 fuse_log(FUSE_LOG_DEBUG, "link %s %s\n", oldpath, newpath);
+
1668
+
1669 return fs->op.link(oldpath, newpath);
+
1670 } else {
+
1671 return -ENOSYS;
+
1672 }
+
1673}
+
1674
+
1675int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1676 struct fuse_file_info *fi)
+
1677{
+
1678 fuse_get_context()->private_data = fs->user_data;
+
1679 if (fs->op.release) {
+
1680 if (fs->debug)
+
1681 fuse_log(FUSE_LOG_DEBUG, "release%s[%llu] flags: 0x%x\n",
+
1682 fi->flush ? "+flush" : "",
+
1683 (unsigned long long) fi->fh, fi->flags);
+
1684
+
1685 return fs->op.release(path, fi);
+
1686 } else {
+
1687 return 0;
+
1688 }
+
1689}
+
1690
+
1691int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1692 struct fuse_file_info *fi)
+
1693{
+
1694 fuse_get_context()->private_data = fs->user_data;
+
1695 if (fs->op.opendir) {
+
1696 int err;
+
1697
+
1698 if (fs->debug)
+
1699 fuse_log(FUSE_LOG_DEBUG, "opendir flags: 0x%x %s\n", fi->flags,
+
1700 path);
+
1701
+
1702 err = fs->op.opendir(path, fi);
+
1703
+
1704 if (fs->debug && !err)
+
1705 fuse_log(FUSE_LOG_DEBUG, " opendir[%llu] flags: 0x%x %s\n",
+
1706 (unsigned long long) fi->fh, fi->flags, path);
+
1707
+
1708 return err;
+
1709 } else {
+
1710 return 0;
+
1711 }
+
1712}
+
1713
+
1714int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1715 struct fuse_file_info *fi)
+
1716{
+
1717 fuse_get_context()->private_data = fs->user_data;
+
1718 if (fs->op.open) {
+
1719 int err;
+
1720
+
1721 if (fs->debug)
+
1722 fuse_log(FUSE_LOG_DEBUG, "open flags: 0x%x %s\n", fi->flags,
+
1723 path);
+
1724
+
1725 err = fs->op.open(path, fi);
+
1726
+
1727 if (fs->debug && !err)
+
1728 fuse_log(FUSE_LOG_DEBUG, " open[%llu] flags: 0x%x %s\n",
+
1729 (unsigned long long) fi->fh, fi->flags, path);
+
1730
+
1731 return err;
+
1732 } else {
+
1733 return 0;
+
1734 }
+
1735}
+
1736
+
1737static void fuse_free_buf(struct fuse_bufvec *buf)
+
1738{
+
1739 if (buf != NULL) {
+
1740 size_t i;
+
1741
+
1742 for (i = 0; i < buf->count; i++)
+
1743 if (!(buf->buf[i].flags & FUSE_BUF_IS_FD))
+
1744 free(buf->buf[i].mem);
+
1745 free(buf);
+
1746 }
+
1747}
+
1748
+
1749int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1750 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1751 struct fuse_file_info *fi)
+
1752{
+
1753 fuse_get_context()->private_data = fs->user_data;
+
1754 if (fs->op.read || fs->op.read_buf) {
+
1755 int res;
+
1756
+
1757 if (fs->debug)
+
1758 fuse_log(FUSE_LOG_DEBUG,
+
1759 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1760 (unsigned long long) fi->fh,
+
1761 size, (unsigned long long) off, fi->flags);
+
1762
+
1763 if (fs->op.read_buf) {
+
1764 res = fs->op.read_buf(path, bufp, size, off, fi);
+
1765 } else {
+
1766 struct fuse_bufvec *buf;
+
1767 void *mem;
+
1768
+
1769 buf = malloc(sizeof(struct fuse_bufvec));
+
1770 if (buf == NULL)
+
1771 return -ENOMEM;
+
1772
+
1773 mem = malloc(size);
+
1774 if (mem == NULL) {
+
1775 free(buf);
+
1776 return -ENOMEM;
+
1777 }
+
1778 *buf = FUSE_BUFVEC_INIT(size);
+
1779 buf->buf[0].mem = mem;
+
1780 *bufp = buf;
+
1781
+
1782 res = fs->op.read(path, mem, size, off, fi);
+
1783 if (res >= 0)
+
1784 buf->buf[0].size = res;
+
1785 }
+
1786
+
1787 if (fs->debug && res >= 0)
+
1788 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %zu bytes from %llu\n",
+
1789 (unsigned long long) fi->fh,
+
1790 fuse_buf_size(*bufp),
+
1791 (unsigned long long) off);
+
1792 if (res >= 0 && fuse_buf_size(*bufp) > size)
+
1793 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1794
+
1795 if (res < 0)
+
1796 return res;
+
1797
+
1798 return 0;
+
1799 } else {
+
1800 return -ENOSYS;
+
1801 }
+
1802}
+
1803
+
1804int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size,
+
1805 off_t off, struct fuse_file_info *fi)
+
1806{
+
1807 fuse_get_context()->private_data = fs->user_data;
+
1808 if (fs->op.read || fs->op.read_buf) {
+
1809 int res;
+
1810
+
1811 if (fs->debug)
+
1812 fuse_log(FUSE_LOG_DEBUG,
+
1813 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1814 (unsigned long long) fi->fh,
+
1815 size, (unsigned long long) off, fi->flags);
+
1816
+
1817 if (fs->op.read_buf) {
+
1818 struct fuse_bufvec *buf = NULL;
+
1819
+
1820 res = fs->op.read_buf(path, &buf, size, off, fi);
+
1821 if (res == 0) {
+
1822 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size);
+
1823
+
1824 dst.buf[0].mem = mem;
+
1825 res = fuse_buf_copy(&dst, buf, 0);
+
1826 }
+
1827 fuse_free_buf(buf);
+
1828 } else {
+
1829 res = fs->op.read(path, mem, size, off, fi);
+
1830 }
+
1831
+
1832 if (fs->debug && res >= 0)
+
1833 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %u bytes from %llu\n",
+
1834 (unsigned long long) fi->fh,
+
1835 res,
+
1836 (unsigned long long) off);
+
1837 if (res >= 0 && res > (int) size)
+
1838 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1839
+
1840 return res;
+
1841 } else {
+
1842 return -ENOSYS;
+
1843 }
+
1844}
+
1845
+
1846int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1847 struct fuse_bufvec *buf, off_t off,
+
1848 struct fuse_file_info *fi)
+
1849{
+
1850 fuse_get_context()->private_data = fs->user_data;
+
1851 if (fs->op.write_buf || fs->op.write) {
+
1852 int res;
+
1853 size_t size = fuse_buf_size(buf);
+
1854
+
1855 assert(buf->idx == 0 && buf->off == 0);
+
1856 if (fs->debug)
+
1857 fuse_log(FUSE_LOG_DEBUG,
+
1858 "write%s[%llu] %zu bytes to %llu flags: 0x%x\n",
+
1859 fi->writepage ? "page" : "",
+
1860 (unsigned long long) fi->fh,
+
1861 size,
+
1862 (unsigned long long) off,
+
1863 fi->flags);
+
1864
+
1865 if (fs->op.write_buf) {
+
1866 res = fs->op.write_buf(path, buf, off, fi);
+
1867 } else {
+
1868 void *mem = NULL;
+
1869 struct fuse_buf *flatbuf;
+
1870 struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size);
+
1871
+
1872 if (buf->count == 1 &&
+
1873 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
1874 flatbuf = &buf->buf[0];
+
1875 } else {
+
1876 res = -ENOMEM;
+
1877 mem = malloc(size);
+
1878 if (mem == NULL)
+
1879 goto out;
+
1880
+
1881 tmp.buf[0].mem = mem;
+
1882 res = fuse_buf_copy(&tmp, buf, 0);
+
1883 if (res <= 0)
+
1884 goto out_free;
+
1885
+
1886 tmp.buf[0].size = res;
+
1887 flatbuf = &tmp.buf[0];
+
1888 }
+
1889
+
1890 res = fs->op.write(path, flatbuf->mem, flatbuf->size,
+
1891 off, fi);
+
1892out_free:
+
1893 free(mem);
+
1894 }
+
1895out:
+
1896 if (fs->debug && res >= 0)
+
1897 fuse_log(FUSE_LOG_DEBUG, " write%s[%llu] %u bytes to %llu\n",
+
1898 fi->writepage ? "page" : "",
+
1899 (unsigned long long) fi->fh, res,
+
1900 (unsigned long long) off);
+
1901 if (res > (int) size)
+
1902 fuse_log(FUSE_LOG_ERR, "fuse: wrote too many bytes\n");
+
1903
+
1904 return res;
+
1905 } else {
+
1906 return -ENOSYS;
+
1907 }
+
1908}
+
1909
+
1910int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem,
+
1911 size_t size, off_t off, struct fuse_file_info *fi)
+
1912{
+
1913 struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(size);
+
1914
+
1915 bufv.buf[0].mem = (void *) mem;
+
1916
+
1917 return fuse_fs_write_buf(fs, path, &bufv, off, fi);
+
1918}
+
1919
+
1920int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1921 struct fuse_file_info *fi)
+
1922{
+
1923 fuse_get_context()->private_data = fs->user_data;
+
1924 if (fs->op.fsync) {
+
1925 if (fs->debug)
+
1926 fuse_log(FUSE_LOG_DEBUG, "fsync[%llu] datasync: %i\n",
+
1927 (unsigned long long) fi->fh, datasync);
+
1928
+
1929 return fs->op.fsync(path, datasync, fi);
+
1930 } else {
+
1931 return -ENOSYS;
+
1932 }
+
1933}
+
1934
+
1935int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1936 struct fuse_file_info *fi)
+
1937{
+
1938 fuse_get_context()->private_data = fs->user_data;
+
1939 if (fs->op.fsyncdir) {
+
1940 if (fs->debug)
+
1941 fuse_log(FUSE_LOG_DEBUG, "fsyncdir[%llu] datasync: %i\n",
+
1942 (unsigned long long) fi->fh, datasync);
+
1943
+
1944 return fs->op.fsyncdir(path, datasync, fi);
+
1945 } else {
+
1946 return -ENOSYS;
+
1947 }
+
1948}
+
1949
+
1950int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1951 struct fuse_file_info *fi)
+
1952{
+
1953 fuse_get_context()->private_data = fs->user_data;
+
1954 if (fs->op.flush) {
+
1955 if (fs->debug)
+
1956 fuse_log(FUSE_LOG_DEBUG, "flush[%llu]\n",
+
1957 (unsigned long long) fi->fh);
+
1958
+
1959 return fs->op.flush(path, fi);
+
1960 } else {
+
1961 return -ENOSYS;
+
1962 }
+
1963}
+
1964
+
1965int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf)
+
1966{
+
1967 fuse_get_context()->private_data = fs->user_data;
+
1968 if (fs->op.statfs) {
+
1969 if (fs->debug)
+
1970 fuse_log(FUSE_LOG_DEBUG, "statfs %s\n", path);
+
1971
+
1972 return fs->op.statfs(path, buf);
+
1973 } else {
+
1974 buf->f_namemax = 255;
+
1975 buf->f_bsize = 512;
+
1976 return 0;
+
1977 }
+
1978}
+
1979
+
1980int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1981 struct fuse_file_info *fi)
+
1982{
+
1983 fuse_get_context()->private_data = fs->user_data;
+
1984 if (fs->op.releasedir) {
+
1985 if (fs->debug)
+
1986 fuse_log(FUSE_LOG_DEBUG, "releasedir[%llu] flags: 0x%x\n",
+
1987 (unsigned long long) fi->fh, fi->flags);
+
1988
+
1989 return fs->op.releasedir(path, fi);
+
1990 } else {
+
1991 return 0;
+
1992 }
+
1993}
+
1994
+
1995int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
1996 fuse_fill_dir_t filler, off_t off,
+
1997 struct fuse_file_info *fi,
+
1998 enum fuse_readdir_flags flags)
+
1999{
+
2000 fuse_get_context()->private_data = fs->user_data;
+
2001 if (fs->op.readdir) {
+
2002 if (fs->debug) {
+
2003 fuse_log(FUSE_LOG_DEBUG, "readdir%s[%llu] from %llu\n",
+
2004 (flags & FUSE_READDIR_PLUS) ? "plus" : "",
+
2005 (unsigned long long) fi->fh,
+
2006 (unsigned long long) off);
+
2007 }
+
2008
+
2009 return fs->op.readdir(path, buf, filler, off, fi, flags);
+
2010 } else {
+
2011 return -ENOSYS;
+
2012 }
+
2013}
+
2014
+
2015int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
2016 struct fuse_file_info *fi)
+
2017{
+
2018 fuse_get_context()->private_data = fs->user_data;
+
2019 if (fs->op.create) {
+
2020 int err;
+
2021
+
2022 if (fs->debug)
+
2023 fuse_log(FUSE_LOG_DEBUG,
+
2024 "create flags: 0x%x %s 0%o umask=0%03o\n",
+
2025 fi->flags, path, mode,
+
2026 fuse_get_context()->umask);
+
2027
+
2028 err = fs->op.create(path, mode, fi);
+
2029
+
2030 if (fs->debug && !err)
+
2031 fuse_log(FUSE_LOG_DEBUG, " create[%llu] flags: 0x%x %s\n",
+
2032 (unsigned long long) fi->fh, fi->flags, path);
+
2033
+
2034 return err;
+
2035 } else {
+
2036 return -ENOSYS;
+
2037 }
+
2038}
+
2039
+
2040int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
2041 struct fuse_file_info *fi, int cmd, struct flock *lock)
+
2042{
+
2043 fuse_get_context()->private_data = fs->user_data;
+
2044 if (fs->op.lock) {
+
2045 if (fs->debug)
+
2046 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n",
+
2047 (unsigned long long) fi->fh,
+
2048 (cmd == F_GETLK ? "F_GETLK" :
+
2049 (cmd == F_SETLK ? "F_SETLK" :
+
2050 (cmd == F_SETLKW ? "F_SETLKW" : "???"))),
+
2051 (lock->l_type == F_RDLCK ? "F_RDLCK" :
+
2052 (lock->l_type == F_WRLCK ? "F_WRLCK" :
+
2053 (lock->l_type == F_UNLCK ? "F_UNLCK" :
+
2054 "???"))),
+
2055 (unsigned long long) lock->l_start,
+
2056 (unsigned long long) lock->l_len,
+
2057 (unsigned long long) lock->l_pid);
+
2058
+
2059 return fs->op.lock(path, fi, cmd, lock);
+
2060 } else {
+
2061 return -ENOSYS;
+
2062 }
+
2063}
+
2064
+
2065int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
2066 struct fuse_file_info *fi, int op)
+
2067{
+
2068 fuse_get_context()->private_data = fs->user_data;
+
2069 if (fs->op.flock) {
+
2070 if (fs->debug) {
+
2071 int xop = op & ~LOCK_NB;
+
2072
+
2073 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s%s\n",
+
2074 (unsigned long long) fi->fh,
+
2075 xop == LOCK_SH ? "LOCK_SH" :
+
2076 (xop == LOCK_EX ? "LOCK_EX" :
+
2077 (xop == LOCK_UN ? "LOCK_UN" : "???")),
+
2078 (op & LOCK_NB) ? "|LOCK_NB" : "");
+
2079 }
+
2080 return fs->op.flock(path, fi, op);
+
2081 } else {
+
2082 return -ENOSYS;
+
2083 }
+
2084}
+
2085
+
2086int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid,
+
2087 gid_t gid, struct fuse_file_info *fi)
+
2088{
+
2089 fuse_get_context()->private_data = fs->user_data;
+
2090 if (fs->op.chown) {
+
2091 if (fs->debug) {
+
2092 char buf[10];
+
2093 fuse_log(FUSE_LOG_DEBUG, "chown[%s] %s %lu %lu\n",
+
2094 file_info_string(fi, buf, sizeof(buf)),
+
2095 path, (unsigned long) uid, (unsigned long) gid);
+
2096 }
+
2097 return fs->op.chown(path, uid, gid, fi);
+
2098 } else {
+
2099 return -ENOSYS;
+
2100 }
+
2101}
+
2102
+
2103int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
2104 struct fuse_file_info *fi)
+
2105{
+
2106 fuse_get_context()->private_data = fs->user_data;
+
2107 if (fs->op.truncate) {
+
2108 if (fs->debug) {
+
2109 char buf[10];
+
2110 fuse_log(FUSE_LOG_DEBUG, "truncate[%s] %llu\n",
+
2111 file_info_string(fi, buf, sizeof(buf)),
+
2112 (unsigned long long) size);
+
2113 }
+
2114 return fs->op.truncate(path, size, fi);
+
2115 } else {
+
2116 return -ENOSYS;
+
2117 }
+
2118}
+
2119
+
2120int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
2121 const struct timespec tv[2], struct fuse_file_info *fi)
+
2122{
+
2123 fuse_get_context()->private_data = fs->user_data;
+
2124 if (fs->op.utimens) {
+
2125 if (fs->debug) {
+
2126 char buf[10];
+
2127 fuse_log(FUSE_LOG_DEBUG, "utimens[%s] %s %li.%09lu %li.%09lu\n",
+
2128 file_info_string(fi, buf, sizeof(buf)),
+
2129 path, tv[0].tv_sec, tv[0].tv_nsec,
+
2130 tv[1].tv_sec, tv[1].tv_nsec);
+
2131 }
+
2132 return fs->op.utimens(path, tv, fi);
+
2133 } else {
+
2134 return -ENOSYS;
+
2135 }
+
2136}
+
2137
+
2138int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask)
+
2139{
+
2140 fuse_get_context()->private_data = fs->user_data;
+
2141 if (fs->op.access) {
+
2142 if (fs->debug)
+
2143 fuse_log(FUSE_LOG_DEBUG, "access %s 0%o\n", path, mask);
+
2144
+
2145 return fs->op.access(path, mask);
+
2146 } else {
+
2147 return -ENOSYS;
+
2148 }
+
2149}
+
2150
+
2151int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
2152 size_t len)
+
2153{
+
2154 fuse_get_context()->private_data = fs->user_data;
+
2155 if (fs->op.readlink) {
+
2156 if (fs->debug)
+
2157 fuse_log(FUSE_LOG_DEBUG, "readlink %s %lu\n", path,
+
2158 (unsigned long) len);
+
2159
+
2160 return fs->op.readlink(path, buf, len);
+
2161 } else {
+
2162 return -ENOSYS;
+
2163 }
+
2164}
+
2165
+
2166int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2167 dev_t rdev)
+
2168{
+
2169 fuse_get_context()->private_data = fs->user_data;
+
2170 if (fs->op.mknod) {
+
2171 if (fs->debug)
+
2172 fuse_log(FUSE_LOG_DEBUG, "mknod %s 0%o 0x%llx umask=0%03o\n",
+
2173 path, mode, (unsigned long long) rdev,
+
2174 fuse_get_context()->umask);
+
2175
+
2176 return fs->op.mknod(path, mode, rdev);
+
2177 } else {
+
2178 return -ENOSYS;
+
2179 }
+
2180}
+
2181
+
2182int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode)
+
2183{
+
2184 fuse_get_context()->private_data = fs->user_data;
+
2185 if (fs->op.mkdir) {
+
2186 if (fs->debug)
+
2187 fuse_log(FUSE_LOG_DEBUG, "mkdir %s 0%o umask=0%03o\n",
+
2188 path, mode, fuse_get_context()->umask);
+
2189
+
2190 return fs->op.mkdir(path, mode);
+
2191 } else {
+
2192 return -ENOSYS;
+
2193 }
+
2194}
+
2195
+
2196int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2197 const char *value, size_t size, int flags)
+
2198{
+
2199 fuse_get_context()->private_data = fs->user_data;
+
2200 if (fs->op.setxattr) {
+
2201 if (fs->debug)
+
2202 fuse_log(FUSE_LOG_DEBUG, "setxattr %s %s %lu 0x%x\n",
+
2203 path, name, (unsigned long) size, flags);
+
2204
+
2205 return fs->op.setxattr(path, name, value, size, flags);
+
2206 } else {
+
2207 return -ENOSYS;
+
2208 }
+
2209}
+
2210
+
2211int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2212 char *value, size_t size)
+
2213{
+
2214 fuse_get_context()->private_data = fs->user_data;
+
2215 if (fs->op.getxattr) {
+
2216 if (fs->debug)
+
2217 fuse_log(FUSE_LOG_DEBUG, "getxattr %s %s %lu\n",
+
2218 path, name, (unsigned long) size);
+
2219
+
2220 return fs->op.getxattr(path, name, value, size);
+
2221 } else {
+
2222 return -ENOSYS;
+
2223 }
+
2224}
+
2225
+
2226int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
2227 size_t size)
+
2228{
+
2229 fuse_get_context()->private_data = fs->user_data;
+
2230 if (fs->op.listxattr) {
+
2231 if (fs->debug)
+
2232 fuse_log(FUSE_LOG_DEBUG, "listxattr %s %lu\n",
+
2233 path, (unsigned long) size);
+
2234
+
2235 return fs->op.listxattr(path, list, size);
+
2236 } else {
+
2237 return -ENOSYS;
+
2238 }
+
2239}
+
2240
+
2241int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
2242 uint64_t *idx)
+
2243{
+
2244 fuse_get_context()->private_data = fs->user_data;
+
2245 if (fs->op.bmap) {
+
2246 if (fs->debug)
+
2247 fuse_log(FUSE_LOG_DEBUG, "bmap %s blocksize: %lu index: %llu\n",
+
2248 path, (unsigned long) blocksize,
+
2249 (unsigned long long) *idx);
+
2250
+
2251 return fs->op.bmap(path, blocksize, idx);
+
2252 } else {
+
2253 return -ENOSYS;
+
2254 }
+
2255}
+
2256
+
2257int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name)
+
2258{
+
2259 fuse_get_context()->private_data = fs->user_data;
+
2260 if (fs->op.removexattr) {
+
2261 if (fs->debug)
+
2262 fuse_log(FUSE_LOG_DEBUG, "removexattr %s %s\n", path, name);
+
2263
+
2264 return fs->op.removexattr(path, name);
+
2265 } else {
+
2266 return -ENOSYS;
+
2267 }
+
2268}
+
2269
+
2270int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
2271 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
2272 void *data)
+
2273{
+
2274 fuse_get_context()->private_data = fs->user_data;
+
2275 if (fs->op.ioctl) {
+
2276 if (fs->debug)
+
2277 fuse_log(FUSE_LOG_DEBUG, "ioctl[%llu] 0x%x flags: 0x%x\n",
+
2278 (unsigned long long) fi->fh, cmd, flags);
+
2279
+
2280 return fs->op.ioctl(path, cmd, arg, fi, flags, data);
+
2281 } else
+
2282 return -ENOSYS;
+
2283}
+
2284
+
2285int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
2286 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
2287 unsigned *reventsp)
+
2288{
+
2289 fuse_get_context()->private_data = fs->user_data;
+
2290 if (fs->op.poll) {
+
2291 int res;
+
2292
+
2293 if (fs->debug)
+
2294 fuse_log(FUSE_LOG_DEBUG, "poll[%llu] ph: %p, events 0x%x\n",
+
2295 (unsigned long long) fi->fh, ph,
+
2296 fi->poll_events);
+
2297
+
2298 res = fs->op.poll(path, fi, ph, reventsp);
+
2299
+
2300 if (fs->debug && !res)
+
2301 fuse_log(FUSE_LOG_DEBUG, " poll[%llu] revents: 0x%x\n",
+
2302 (unsigned long long) fi->fh, *reventsp);
+
2303
+
2304 return res;
+
2305 } else
+
2306 return -ENOSYS;
+
2307}
+
2308
+
2309int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
2310 off_t offset, off_t length, struct fuse_file_info *fi)
+
2311{
+
2312 fuse_get_context()->private_data = fs->user_data;
+
2313 if (fs->op.fallocate) {
+
2314 if (fs->debug)
+
2315 fuse_log(FUSE_LOG_DEBUG, "fallocate %s mode %x, offset: %llu, length: %llu\n",
+
2316 path,
+
2317 mode,
+
2318 (unsigned long long) offset,
+
2319 (unsigned long long) length);
+
2320
+
2321 return fs->op.fallocate(path, mode, offset, length, fi);
+
2322 } else
+
2323 return -ENOSYS;
+
2324}
+
2325
+
2326ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
2327 struct fuse_file_info *fi_in, off_t off_in,
+
2328 const char *path_out,
+
2329 struct fuse_file_info *fi_out, off_t off_out,
+
2330 size_t len, int flags)
+
2331{
+
2332 fuse_get_context()->private_data = fs->user_data;
+
2333 if (fs->op.copy_file_range) {
+
2334 if (fs->debug)
+
2335 fuse_log(FUSE_LOG_DEBUG, "copy_file_range from %s:%llu to "
+
2336 "%s:%llu, length: %llu\n",
+
2337 path_in,
+
2338 (unsigned long long) off_in,
+
2339 path_out,
+
2340 (unsigned long long) off_out,
+
2341 (unsigned long long) len);
+
2342
+
2343 return fs->op.copy_file_range(path_in, fi_in, off_in, path_out,
+
2344 fi_out, off_out, len, flags);
+
2345 } else
+
2346 return -ENOSYS;
+
2347}
+
2348
+
2349off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
2350 struct fuse_file_info *fi)
+
2351{
+
2352 fuse_get_context()->private_data = fs->user_data;
+
2353 if (fs->op.lseek) {
+
2354 if (fs->debug) {
+
2355 char buf[10];
+
2356 fuse_log(FUSE_LOG_DEBUG, "lseek[%s] %llu %d\n",
+
2357 file_info_string(fi, buf, sizeof(buf)),
+
2358 (unsigned long long) off, whence);
+
2359 }
+
2360 return fs->op.lseek(path, off, whence, fi);
+
2361 } else {
+
2362 return -ENOSYS;
+
2363 }
+
2364}
+
2365
+
2366static int is_open(struct fuse *f, fuse_ino_t dir, const char *name)
+
2367{
+
2368 struct node *node;
+
2369 int isopen = 0;
+
2370 pthread_mutex_lock(&f->lock);
+
2371 node = lookup_node(f, dir, name);
+
2372 if (node && node->open_count > 0)
+
2373 isopen = 1;
+
2374 pthread_mutex_unlock(&f->lock);
+
2375 return isopen;
+
2376}
+
2377
+
2378static char *hidden_name(struct fuse *f, fuse_ino_t dir, const char *oldname,
+
2379 char *newname, size_t bufsize)
+
2380{
+
2381 struct stat buf;
+
2382 struct node *node;
+
2383 struct node *newnode;
+
2384 char *newpath;
+
2385 int res;
+
2386 int failctr = 10;
+
2387
+
2388 do {
+
2389 pthread_mutex_lock(&f->lock);
+
2390 node = lookup_node(f, dir, oldname);
+
2391 if (node == NULL) {
+
2392 pthread_mutex_unlock(&f->lock);
+
2393 return NULL;
+
2394 }
+
2395 do {
+
2396 f->hidectr ++;
+
2397 snprintf(newname, bufsize, ".fuse_hidden%08x%08x",
+
2398 (unsigned int) node->nodeid, f->hidectr);
+
2399 newnode = lookup_node(f, dir, newname);
+
2400 } while(newnode);
+
2401
+
2402 res = try_get_path(f, dir, newname, &newpath, NULL, false);
+
2403 pthread_mutex_unlock(&f->lock);
+
2404 if (res)
+
2405 break;
+
2406
+
2407 memset(&buf, 0, sizeof(buf));
+
2408 res = fuse_fs_getattr(f->fs, newpath, &buf, NULL);
+
2409 if (res == -ENOENT)
+
2410 break;
+
2411 free(newpath);
+
2412 newpath = NULL;
+
2413 } while(res == 0 && --failctr);
+
2414
+
2415 return newpath;
+
2416}
+
2417
+
2418static int hide_node(struct fuse *f, const char *oldpath,
+
2419 fuse_ino_t dir, const char *oldname)
+
2420{
+
2421 char newname[64];
+
2422 char *newpath;
+
2423 int err = -EBUSY;
+
2424
+
2425 newpath = hidden_name(f, dir, oldname, newname, sizeof(newname));
+
2426 if (newpath) {
+
2427 err = fuse_fs_rename(f->fs, oldpath, newpath, 0);
+
2428 if (!err)
+
2429 err = rename_node(f, dir, oldname, dir, newname, 1);
+
2430 free(newpath);
+
2431 }
+
2432 return err;
+
2433}
+
2434
+
2435static int mtime_eq(const struct stat *stbuf, const struct timespec *ts)
+
2436{
+
2437 return stbuf->st_mtime == ts->tv_sec &&
+
2438 ST_MTIM_NSEC(stbuf) == ts->tv_nsec;
+
2439}
+
2440
+
2441#ifndef CLOCK_MONOTONIC
+
2442#define CLOCK_MONOTONIC CLOCK_REALTIME
+
2443#endif
+
2444
+
2445static void curr_time(struct timespec *now)
+
2446{
+
2447 static clockid_t clockid = CLOCK_MONOTONIC;
+
2448 int res = clock_gettime(clockid, now);
+
2449 if (res == -1 && errno == EINVAL) {
+
2450 clockid = CLOCK_REALTIME;
+
2451 res = clock_gettime(clockid, now);
+
2452 }
+
2453 if (res == -1) {
+
2454 perror("fuse: clock_gettime");
+
2455 abort();
+
2456 }
+
2457}
+
2458
+
2459static void update_stat(struct node *node, const struct stat *stbuf)
+
2460{
+
2461 if (node->cache_valid && (!mtime_eq(stbuf, &node->mtime) ||
+
2462 stbuf->st_size != node->size))
+
2463 node->cache_valid = 0;
+
2464 node->mtime.tv_sec = stbuf->st_mtime;
+
2465 node->mtime.tv_nsec = ST_MTIM_NSEC(stbuf);
+
2466 node->size = stbuf->st_size;
+
2467 curr_time(&node->stat_updated);
+
2468}
+
2469
+
2470static int do_lookup(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
2471 struct fuse_entry_param *e)
+
2472{
+
2473 struct node *node;
+
2474
+
2475 node = find_node(f, nodeid, name);
+
2476 if (node == NULL)
+
2477 return -ENOMEM;
+
2478
+
2479 e->ino = node->nodeid;
+
2480 e->generation = node->generation;
+
2481 e->entry_timeout = f->conf.entry_timeout;
+
2482 e->attr_timeout = f->conf.attr_timeout;
+
2483 if (f->conf.auto_cache) {
+
2484 pthread_mutex_lock(&f->lock);
+
2485 update_stat(node, &e->attr);
+
2486 pthread_mutex_unlock(&f->lock);
+
2487 }
+
2488 set_stat(f, e->ino, &e->attr);
+
2489 return 0;
+
2490}
+
2491
+
2492static int lookup_path(struct fuse *f, fuse_ino_t nodeid,
+
2493 const char *name, const char *path,
+
2494 struct fuse_entry_param *e, struct fuse_file_info *fi)
+
2495{
+
2496 int res;
+
2497
+
2498 memset(e, 0, sizeof(struct fuse_entry_param));
+
2499 res = fuse_fs_getattr(f->fs, path, &e->attr, fi);
+
2500 if (res == 0) {
+
2501 res = do_lookup(f, nodeid, name, e);
+
2502 if (res == 0 && f->conf.debug) {
+
2503 fuse_log(FUSE_LOG_DEBUG, " NODEID: %llu\n",
+
2504 (unsigned long long) e->ino);
+
2505 }
+
2506 }
+
2507 return res;
+
2508}
+
2509
+
2510static struct fuse_context_i *fuse_get_context_internal(void)
+
2511{
+
2512 return (struct fuse_context_i *) pthread_getspecific(fuse_context_key);
+
2513}
+
2514
+
2515static struct fuse_context_i *fuse_create_context(struct fuse *f)
+
2516{
+
2517 struct fuse_context_i *c = fuse_get_context_internal();
+
2518 if (c == NULL) {
+
2519 c = (struct fuse_context_i *)
+
2520 calloc(1, sizeof(struct fuse_context_i));
+
2521 if (c == NULL) {
+
2522 /* This is hard to deal with properly, so just
+
2523 abort. If memory is so low that the
+
2524 context cannot be allocated, there's not
+
2525 much hope for the filesystem anyway */
+
2526 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate thread specific data\n");
+
2527 abort();
+
2528 }
+
2529 pthread_setspecific(fuse_context_key, c);
+
2530 } else {
+
2531 memset(c, 0, sizeof(*c));
+
2532 }
+
2533 c->ctx.fuse = f;
+
2534
+
2535 return c;
+
2536}
+
2537
+
2538static void fuse_freecontext(void *data)
+
2539{
+
2540 free(data);
+
2541}
+
2542
+
2543static int fuse_create_context_key(void)
+
2544{
+
2545 int err = 0;
+
2546 pthread_mutex_lock(&fuse_context_lock);
+
2547 if (!fuse_context_ref) {
+
2548 err = pthread_key_create(&fuse_context_key, fuse_freecontext);
+
2549 if (err) {
+
2550 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
2551 strerror(err));
+
2552 pthread_mutex_unlock(&fuse_context_lock);
+
2553 return -1;
+
2554 }
+
2555 }
+
2556 fuse_context_ref++;
+
2557 pthread_mutex_unlock(&fuse_context_lock);
+
2558 return 0;
+
2559}
+
2560
+
2561static void fuse_delete_context_key(void)
+
2562{
+
2563 pthread_mutex_lock(&fuse_context_lock);
+
2564 fuse_context_ref--;
+
2565 if (!fuse_context_ref) {
+
2566 free(pthread_getspecific(fuse_context_key));
+
2567 pthread_key_delete(fuse_context_key);
+
2568 }
+
2569 pthread_mutex_unlock(&fuse_context_lock);
+
2570}
+
2571
+
2572static struct fuse *req_fuse_prepare(fuse_req_t req)
+
2573{
+
2574 struct fuse_context_i *c = fuse_create_context(req_fuse(req));
+
2575 const struct fuse_ctx *ctx = fuse_req_ctx(req);
+
2576 c->req = req;
+
2577 c->ctx.uid = ctx->uid;
+
2578 c->ctx.gid = ctx->gid;
+
2579 c->ctx.pid = ctx->pid;
+
2580 c->ctx.umask = ctx->umask;
+
2581 return c->ctx.fuse;
+
2582}
+
2583
+
2584static inline void reply_err(fuse_req_t req, int err)
+
2585{
+
2586 /* fuse_reply_err() uses non-negated errno values */
+
2587 fuse_reply_err(req, -err);
+
2588}
+
2589
+
2590static void reply_entry(fuse_req_t req, const struct fuse_entry_param *e,
+
2591 int err)
+
2592{
+
2593 if (!err) {
+
2594 struct fuse *f = req_fuse(req);
+
2595 if (fuse_reply_entry(req, e) == -ENOENT) {
+
2596 /* Skip forget for negative result */
+
2597 if (e->ino != 0)
+
2598 forget_node(f, e->ino, 1);
+
2599 }
+
2600 } else
+
2601 reply_err(req, err);
+
2602}
+
2603
+
2604void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
2605 struct fuse_config *cfg)
+
2606{
+
2607 fuse_get_context()->private_data = fs->user_data;
+
2608 if (!fs->op.write_buf)
+ +
2610 if (!fs->op.lock)
+ +
2612 if (!fs->op.flock)
+ +
2614 if (fs->op.init)
+
2615 fs->user_data = fs->op.init(conn, cfg);
+
2616}
+
2617
+
2618static int fuse_init_intr_signal(int signum, int *installed);
+
2619
+
2620static void fuse_lib_init(void *data, struct fuse_conn_info *conn)
+
2621{
+
2622 struct fuse *f = (struct fuse *) data;
+
2623
+
2624 fuse_create_context(f);
+ +
2626 fuse_fs_init(f->fs, conn, &f->conf);
+
2627
+
2628 if (f->conf.intr) {
+
2629 if (fuse_init_intr_signal(f->conf.intr_signal,
+
2630 &f->intr_installed) == -1)
+
2631 fuse_log(FUSE_LOG_ERR, "fuse: failed to init interrupt signal\n");
+
2632 } else {
+
2633 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
2634 conn->no_interrupt = 1;
+
2635 }
+
2636}
+
2637
+
2638void fuse_fs_destroy(struct fuse_fs *fs)
+
2639{
+
2640 fuse_get_context()->private_data = fs->user_data;
+
2641 if (fs->op.destroy)
+
2642 fs->op.destroy(fs->user_data);
+
2643}
+
2644
+
2645static void fuse_lib_destroy(void *data)
+
2646{
+
2647 struct fuse *f = (struct fuse *) data;
+
2648
+
2649 fuse_create_context(f);
+
2650 fuse_fs_destroy(f->fs);
+
2651}
+
2652
+
2653static void fuse_lib_lookup(fuse_req_t req, fuse_ino_t parent,
+
2654 const char *name)
+
2655{
+
2656 struct fuse *f = req_fuse_prepare(req);
+
2657 struct fuse_entry_param e;
+
2658 char *path;
+
2659 int err;
+
2660 struct node *dot = NULL;
+
2661
+
2662 if (name[0] == '.') {
+
2663 int len = strlen(name);
+
2664
+
2665 if (len == 1 || (name[1] == '.' && len == 2)) {
+
2666 pthread_mutex_lock(&f->lock);
+
2667 if (len == 1) {
+
2668 if (f->conf.debug)
+
2669 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOT\n");
+
2670 dot = get_node_nocheck(f, parent);
+
2671 if (dot == NULL) {
+
2672 pthread_mutex_unlock(&f->lock);
+
2673 reply_entry(req, &e, -ESTALE);
+
2674 return;
+
2675 }
+
2676 dot->refctr++;
+
2677 } else {
+
2678 if (f->conf.debug)
+
2679 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOTDOT\n");
+
2680 parent = get_node(f, parent)->parent->nodeid;
+
2681 }
+
2682 pthread_mutex_unlock(&f->lock);
+
2683 name = NULL;
+
2684 }
+
2685 }
+
2686
+
2687 err = get_path_name(f, parent, name, &path);
+
2688 if (!err) {
+
2689 struct fuse_intr_data d;
+
2690 if (f->conf.debug)
+
2691 fuse_log(FUSE_LOG_DEBUG, "LOOKUP %s\n", path);
+
2692 fuse_prepare_interrupt(f, req, &d);
+
2693 err = lookup_path(f, parent, name, path, &e, NULL);
+
2694 if (err == -ENOENT && f->conf.negative_timeout != 0.0) {
+
2695 e.ino = 0;
+
2696 e.entry_timeout = f->conf.negative_timeout;
+
2697 err = 0;
+
2698 }
+
2699 fuse_finish_interrupt(f, req, &d);
+
2700 free_path(f, parent, path);
+
2701 }
+
2702 if (dot) {
+
2703 pthread_mutex_lock(&f->lock);
+
2704 unref_node(f, dot);
+
2705 pthread_mutex_unlock(&f->lock);
+
2706 }
+
2707 reply_entry(req, &e, err);
+
2708}
+
2709
+
2710static void do_forget(struct fuse *f, fuse_ino_t ino, uint64_t nlookup)
+
2711{
+
2712 if (f->conf.debug)
+
2713 fuse_log(FUSE_LOG_DEBUG, "FORGET %llu/%llu\n", (unsigned long long)ino,
+
2714 (unsigned long long) nlookup);
+
2715 forget_node(f, ino, nlookup);
+
2716}
+
2717
+
2718static void fuse_lib_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
2719{
+
2720 do_forget(req_fuse(req), ino, nlookup);
+
2721 fuse_reply_none(req);
+
2722}
+
2723
+
2724static void fuse_lib_forget_multi(fuse_req_t req, size_t count,
+
2725 struct fuse_forget_data *forgets)
+
2726{
+
2727 struct fuse *f = req_fuse(req);
+
2728 size_t i;
+
2729
+
2730 for (i = 0; i < count; i++)
+
2731 do_forget(f, forgets[i].ino, forgets[i].nlookup);
+
2732
+
2733 fuse_reply_none(req);
+
2734}
+
2735
+
2736
+
2737static void fuse_lib_getattr(fuse_req_t req, fuse_ino_t ino,
+
2738 struct fuse_file_info *fi)
+
2739{
+
2740 struct fuse *f = req_fuse_prepare(req);
+
2741 struct stat buf;
+
2742 char *path;
+
2743 int err;
+
2744
+
2745 memset(&buf, 0, sizeof(buf));
+
2746
+
2747 if (fi != NULL)
+
2748 err = get_path_nullok(f, ino, &path);
+
2749 else
+
2750 err = get_path(f, ino, &path);
+
2751 if (!err) {
+
2752 struct fuse_intr_data d;
+
2753 fuse_prepare_interrupt(f, req, &d);
+
2754 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2755 fuse_finish_interrupt(f, req, &d);
+
2756 free_path(f, ino, path);
+
2757 }
+
2758 if (!err) {
+
2759 struct node *node;
+
2760
+
2761 pthread_mutex_lock(&f->lock);
+
2762 node = get_node(f, ino);
+
2763 if (node->is_hidden && buf.st_nlink > 0)
+
2764 buf.st_nlink--;
+
2765 if (f->conf.auto_cache)
+
2766 update_stat(node, &buf);
+
2767 pthread_mutex_unlock(&f->lock);
+
2768 set_stat(f, ino, &buf);
+
2769 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2770 } else
+
2771 reply_err(req, err);
+
2772}
+
2773
+
2774int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2775 struct fuse_file_info *fi)
+
2776{
+
2777 fuse_get_context()->private_data = fs->user_data;
+
2778 if (fs->op.chmod) {
+
2779 if (fs->debug) {
+
2780 char buf[10];
+
2781 fuse_log(FUSE_LOG_DEBUG, "chmod[%s] %s %llo\n",
+
2782 file_info_string(fi, buf, sizeof(buf)),
+
2783 path, (unsigned long long) mode);
+
2784 }
+
2785 return fs->op.chmod(path, mode, fi);
+
2786 }
+
2787 else
+
2788 return -ENOSYS;
+
2789}
+
2790
+
2791static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
2792 int valid, struct fuse_file_info *fi)
+
2793{
+
2794 struct fuse *f = req_fuse_prepare(req);
+
2795 struct stat buf;
+
2796 char *path;
+
2797 int err;
+
2798
+
2799 memset(&buf, 0, sizeof(buf));
+
2800 if (fi != NULL)
+
2801 err = get_path_nullok(f, ino, &path);
+
2802 else
+
2803 err = get_path(f, ino, &path);
+
2804 if (!err) {
+
2805 struct fuse_intr_data d;
+
2806 fuse_prepare_interrupt(f, req, &d);
+
2807 err = 0;
+
2808 if (!err && (valid & FUSE_SET_ATTR_MODE))
+
2809 err = fuse_fs_chmod(f->fs, path, attr->st_mode, fi);
+
2810 if (!err && (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))) {
+
2811 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
2812 attr->st_uid : (uid_t) -1;
+
2813 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
2814 attr->st_gid : (gid_t) -1;
+
2815 err = fuse_fs_chown(f->fs, path, uid, gid, fi);
+
2816 }
+
2817 if (!err && (valid & FUSE_SET_ATTR_SIZE)) {
+
2818 err = fuse_fs_truncate(f->fs, path,
+
2819 attr->st_size, fi);
+
2820 }
+
2821#ifdef HAVE_UTIMENSAT
+
2822 if (!err &&
+
2823 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) {
+
2824 struct timespec tv[2];
+
2825
+
2826 tv[0].tv_sec = 0;
+
2827 tv[1].tv_sec = 0;
+
2828 tv[0].tv_nsec = UTIME_OMIT;
+
2829 tv[1].tv_nsec = UTIME_OMIT;
+
2830
+
2831 if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
2832 tv[0].tv_nsec = UTIME_NOW;
+
2833 else if (valid & FUSE_SET_ATTR_ATIME)
+
2834 tv[0] = attr->st_atim;
+
2835
+
2836 if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
2837 tv[1].tv_nsec = UTIME_NOW;
+
2838 else if (valid & FUSE_SET_ATTR_MTIME)
+
2839 tv[1] = attr->st_mtim;
+
2840
+
2841 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2842 } else
+
2843#endif
+
2844 if (!err &&
+
2845 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) ==
+
2846 (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
2847 struct timespec tv[2];
+
2848 tv[0].tv_sec = attr->st_atime;
+
2849 tv[0].tv_nsec = ST_ATIM_NSEC(attr);
+
2850 tv[1].tv_sec = attr->st_mtime;
+
2851 tv[1].tv_nsec = ST_MTIM_NSEC(attr);
+
2852 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2853 }
+
2854 if (!err) {
+
2855 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2856 }
+
2857 fuse_finish_interrupt(f, req, &d);
+
2858 free_path(f, ino, path);
+
2859 }
+
2860 if (!err) {
+
2861 if (f->conf.auto_cache) {
+
2862 pthread_mutex_lock(&f->lock);
+
2863 update_stat(get_node(f, ino), &buf);
+
2864 pthread_mutex_unlock(&f->lock);
+
2865 }
+
2866 set_stat(f, ino, &buf);
+
2867 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2868 } else
+
2869 reply_err(req, err);
+
2870}
+
2871
+
2872static void fuse_lib_access(fuse_req_t req, fuse_ino_t ino, int mask)
+
2873{
+
2874 struct fuse *f = req_fuse_prepare(req);
+
2875 char *path;
+
2876 int err;
+
2877
+
2878 err = get_path(f, ino, &path);
+
2879 if (!err) {
+
2880 struct fuse_intr_data d;
+
2881
+
2882 fuse_prepare_interrupt(f, req, &d);
+
2883 err = fuse_fs_access(f->fs, path, mask);
+
2884 fuse_finish_interrupt(f, req, &d);
+
2885 free_path(f, ino, path);
+
2886 }
+
2887 reply_err(req, err);
+
2888}
+
2889
+
2890static void fuse_lib_readlink(fuse_req_t req, fuse_ino_t ino)
+
2891{
+
2892 struct fuse *f = req_fuse_prepare(req);
+
2893 char linkname[PATH_MAX + 1];
+
2894 char *path;
+
2895 int err;
+
2896
+
2897 err = get_path(f, ino, &path);
+
2898 if (!err) {
+
2899 struct fuse_intr_data d;
+
2900 fuse_prepare_interrupt(f, req, &d);
+
2901 err = fuse_fs_readlink(f->fs, path, linkname, sizeof(linkname));
+
2902 fuse_finish_interrupt(f, req, &d);
+
2903 free_path(f, ino, path);
+
2904 }
+
2905 if (!err) {
+
2906 linkname[PATH_MAX] = '\0';
+
2907 fuse_reply_readlink(req, linkname);
+
2908 } else
+
2909 reply_err(req, err);
+
2910}
+
2911
+
2912static void fuse_lib_mknod(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2913 mode_t mode, dev_t rdev)
+
2914{
+
2915 struct fuse *f = req_fuse_prepare(req);
+
2916 struct fuse_entry_param e;
+
2917 char *path;
+
2918 int err;
+
2919
+
2920 err = get_path_name(f, parent, name, &path);
+
2921 if (!err) {
+
2922 struct fuse_intr_data d;
+
2923
+
2924 fuse_prepare_interrupt(f, req, &d);
+
2925 err = -ENOSYS;
+
2926 if (S_ISREG(mode)) {
+
2927 struct fuse_file_info fi;
+
2928
+
2929 memset(&fi, 0, sizeof(fi));
+
2930 fi.flags = O_CREAT | O_EXCL | O_WRONLY;
+
2931 err = fuse_fs_create(f->fs, path, mode, &fi);
+
2932 if (!err) {
+
2933 err = lookup_path(f, parent, name, path, &e,
+
2934 &fi);
+
2935 fuse_fs_release(f->fs, path, &fi);
+
2936 }
+
2937 }
+
2938 if (err == -ENOSYS) {
+
2939 err = fuse_fs_mknod(f->fs, path, mode, rdev);
+
2940 if (!err)
+
2941 err = lookup_path(f, parent, name, path, &e,
+
2942 NULL);
+
2943 }
+
2944 fuse_finish_interrupt(f, req, &d);
+
2945 free_path(f, parent, path);
+
2946 }
+
2947 reply_entry(req, &e, err);
+
2948}
+
2949
+
2950static void fuse_lib_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2951 mode_t mode)
+
2952{
+
2953 struct fuse *f = req_fuse_prepare(req);
+
2954 struct fuse_entry_param e;
+
2955 char *path;
+
2956 int err;
+
2957
+
2958 err = get_path_name(f, parent, name, &path);
+
2959 if (!err) {
+
2960 struct fuse_intr_data d;
+
2961
+
2962 fuse_prepare_interrupt(f, req, &d);
+
2963 err = fuse_fs_mkdir(f->fs, path, mode);
+
2964 if (!err)
+
2965 err = lookup_path(f, parent, name, path, &e, NULL);
+
2966 fuse_finish_interrupt(f, req, &d);
+
2967 free_path(f, parent, path);
+
2968 }
+
2969 reply_entry(req, &e, err);
+
2970}
+
2971
+
2972static void fuse_lib_unlink(fuse_req_t req, fuse_ino_t parent,
+
2973 const char *name)
+
2974{
+
2975 struct fuse *f = req_fuse_prepare(req);
+
2976 struct node *wnode;
+
2977 char *path;
+
2978 int err;
+
2979
+
2980 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
2981 if (!err) {
+
2982 struct fuse_intr_data d;
+
2983
+
2984 fuse_prepare_interrupt(f, req, &d);
+
2985 if (!f->conf.hard_remove && is_open(f, parent, name)) {
+
2986 err = hide_node(f, path, parent, name);
+
2987 if (!err) {
+
2988 /* we have hidden the node so now check again under a lock in case it is not used any more */
+
2989 if (!is_open(f, parent, wnode->name)) {
+
2990 char *unlinkpath;
+
2991
+
2992 /* get the hidden file path, to unlink it */
+
2993 if (try_get_path(f, wnode->nodeid, NULL, &unlinkpath, NULL, false) == 0) {
+
2994 err = fuse_fs_unlink(f->fs, unlinkpath);
+
2995 if (!err)
+
2996 remove_node(f, parent, wnode->name);
+
2997 free(unlinkpath);
+
2998 }
+
2999 }
+
3000 }
+
3001 } else {
+
3002 err = fuse_fs_unlink(f->fs, path);
+
3003 if (!err)
+
3004 remove_node(f, parent, name);
+
3005 }
+
3006 fuse_finish_interrupt(f, req, &d);
+
3007 free_path_wrlock(f, parent, wnode, path);
+
3008 }
+
3009 reply_err(req, err);
+
3010}
+
3011
+
3012static void fuse_lib_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
3013{
+
3014 struct fuse *f = req_fuse_prepare(req);
+
3015 struct node *wnode;
+
3016 char *path;
+
3017 int err;
+
3018
+
3019 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
3020 if (!err) {
+
3021 struct fuse_intr_data d;
+
3022
+
3023 fuse_prepare_interrupt(f, req, &d);
+
3024 err = fuse_fs_rmdir(f->fs, path);
+
3025 fuse_finish_interrupt(f, req, &d);
+
3026 if (!err)
+
3027 remove_node(f, parent, name);
+
3028 free_path_wrlock(f, parent, wnode, path);
+
3029 }
+
3030 reply_err(req, err);
+
3031}
+
3032
+
3033static void fuse_lib_symlink(fuse_req_t req, const char *linkname,
+
3034 fuse_ino_t parent, const char *name)
+
3035{
+
3036 struct fuse *f = req_fuse_prepare(req);
+
3037 struct fuse_entry_param e;
+
3038 char *path;
+
3039 int err;
+
3040
+
3041 err = get_path_name(f, parent, name, &path);
+
3042 if (!err) {
+
3043 struct fuse_intr_data d;
+
3044
+
3045 fuse_prepare_interrupt(f, req, &d);
+
3046 err = fuse_fs_symlink(f->fs, linkname, path);
+
3047 if (!err)
+
3048 err = lookup_path(f, parent, name, path, &e, NULL);
+
3049 fuse_finish_interrupt(f, req, &d);
+
3050 free_path(f, parent, path);
+
3051 }
+
3052 reply_entry(req, &e, err);
+
3053}
+
3054
+
3055static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir,
+
3056 const char *oldname, fuse_ino_t newdir,
+
3057 const char *newname, unsigned int flags)
+
3058{
+
3059 struct fuse *f = req_fuse_prepare(req);
+
3060 char *oldpath;
+
3061 char *newpath;
+
3062 struct node *wnode1;
+
3063 struct node *wnode2;
+
3064 int err;
+
3065
+
3066 err = get_path2(f, olddir, oldname, newdir, newname,
+
3067 &oldpath, &newpath, &wnode1, &wnode2);
+
3068 if (!err) {
+
3069 struct fuse_intr_data d;
+
3070 err = 0;
+
3071 fuse_prepare_interrupt(f, req, &d);
+
3072 if (!f->conf.hard_remove && !(flags & RENAME_EXCHANGE) &&
+
3073 is_open(f, newdir, newname))
+
3074 err = hide_node(f, newpath, newdir, newname);
+
3075 if (!err) {
+
3076 err = fuse_fs_rename(f->fs, oldpath, newpath, flags);
+
3077 if (!err) {
+
3078 if (flags & RENAME_EXCHANGE) {
+
3079 err = exchange_node(f, olddir, oldname,
+
3080 newdir, newname);
+
3081 } else {
+
3082 err = rename_node(f, olddir, oldname,
+
3083 newdir, newname, 0);
+
3084 }
+
3085 }
+
3086 }
+
3087 fuse_finish_interrupt(f, req, &d);
+
3088 free_path2(f, olddir, newdir, wnode1, wnode2, oldpath, newpath);
+
3089 }
+
3090 reply_err(req, err);
+
3091}
+
3092
+
3093static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
3094 const char *newname)
+
3095{
+
3096 struct fuse *f = req_fuse_prepare(req);
+
3097 struct fuse_entry_param e;
+
3098 char *oldpath;
+
3099 char *newpath;
+
3100 int err;
+
3101
+
3102 err = get_path2(f, ino, NULL, newparent, newname,
+
3103 &oldpath, &newpath, NULL, NULL);
+
3104 if (!err) {
+
3105 struct fuse_intr_data d;
+
3106
+
3107 fuse_prepare_interrupt(f, req, &d);
+
3108 err = fuse_fs_link(f->fs, oldpath, newpath);
+
3109 if (!err)
+
3110 err = lookup_path(f, newparent, newname, newpath,
+
3111 &e, NULL);
+
3112 fuse_finish_interrupt(f, req, &d);
+
3113 free_path2(f, ino, newparent, NULL, NULL, oldpath, newpath);
+
3114 }
+
3115 reply_entry(req, &e, err);
+
3116}
+
3117
+
3118static void fuse_do_release(struct fuse *f, fuse_ino_t ino, const char *path,
+
3119 struct fuse_file_info *fi)
+
3120{
+
3121 struct node *node;
+
3122 int unlink_hidden = 0;
+
3123
+
3124 fuse_fs_release(f->fs, path, fi);
+
3125
+
3126 pthread_mutex_lock(&f->lock);
+
3127 node = get_node(f, ino);
+
3128 assert(node->open_count > 0);
+
3129 --node->open_count;
+
3130 if (node->is_hidden && !node->open_count) {
+
3131 unlink_hidden = 1;
+
3132 node->is_hidden = 0;
+
3133 }
+
3134 pthread_mutex_unlock(&f->lock);
+
3135
+
3136 if(unlink_hidden) {
+
3137 if (path) {
+
3138 fuse_fs_unlink(f->fs, path);
+
3139 } else if (f->conf.nullpath_ok) {
+
3140 char *unlinkpath;
+
3141
+
3142 if (get_path(f, ino, &unlinkpath) == 0)
+
3143 fuse_fs_unlink(f->fs, unlinkpath);
+
3144
+
3145 free_path(f, ino, unlinkpath);
+
3146 }
+
3147 }
+
3148}
+
3149
+
3150static void fuse_lib_create(fuse_req_t req, fuse_ino_t parent,
+
3151 const char *name, mode_t mode,
+
3152 struct fuse_file_info *fi)
+
3153{
+
3154 struct fuse *f = req_fuse_prepare(req);
+
3155 struct fuse_intr_data d;
+
3156 struct fuse_entry_param e;
+
3157 char *path;
+
3158 int err;
+
3159
+
3160 err = get_path_name(f, parent, name, &path);
+
3161 if (!err) {
+
3162 fuse_prepare_interrupt(f, req, &d);
+
3163 err = fuse_fs_create(f->fs, path, mode, fi);
+
3164 if (!err) {
+
3165 err = lookup_path(f, parent, name, path, &e, fi);
+
3166 if (err)
+
3167 fuse_fs_release(f->fs, path, fi);
+
3168 else if (!S_ISREG(e.attr.st_mode)) {
+
3169 err = -EIO;
+
3170 fuse_fs_release(f->fs, path, fi);
+
3171 forget_node(f, e.ino, 1);
+
3172 } else {
+
3173 if (f->conf.direct_io)
+
3174 fi->direct_io = 1;
+
3175 if (f->conf.kernel_cache)
+
3176 fi->keep_cache = 1;
+
3177 if (fi->direct_io &&
+
3178 f->conf.parallel_direct_writes)
+
3179 fi->parallel_direct_writes = 1;
+
3180 }
+
3181 }
+
3182 fuse_finish_interrupt(f, req, &d);
+
3183 }
+
3184 if (!err) {
+
3185 pthread_mutex_lock(&f->lock);
+
3186 get_node(f, e.ino)->open_count++;
+
3187 pthread_mutex_unlock(&f->lock);
+
3188 if (fuse_reply_create(req, &e, fi) == -ENOENT) {
+
3189 /* The open syscall was interrupted, so it
+
3190 must be cancelled */
+
3191 fuse_do_release(f, e.ino, path, fi);
+
3192 forget_node(f, e.ino, 1);
+
3193 }
+
3194 } else {
+
3195 reply_err(req, err);
+
3196 }
+
3197
+
3198 free_path(f, parent, path);
+
3199}
+
3200
+
3201static double diff_timespec(const struct timespec *t1,
+
3202 const struct timespec *t2)
+
3203{
+
3204 return (t1->tv_sec - t2->tv_sec) +
+
3205 ((double) t1->tv_nsec - (double) t2->tv_nsec) / 1000000000.0;
+
3206}
+
3207
+
3208static void open_auto_cache(struct fuse *f, fuse_ino_t ino, const char *path,
+
3209 struct fuse_file_info *fi)
+
3210{
+
3211 struct node *node;
+
3212
+
3213 pthread_mutex_lock(&f->lock);
+
3214 node = get_node(f, ino);
+
3215 if (node->cache_valid) {
+
3216 struct timespec now;
+
3217
+
3218 curr_time(&now);
+
3219 if (diff_timespec(&now, &node->stat_updated) >
+
3220 f->conf.ac_attr_timeout) {
+
3221 struct stat stbuf;
+
3222 int err;
+
3223 pthread_mutex_unlock(&f->lock);
+
3224 err = fuse_fs_getattr(f->fs, path, &stbuf, fi);
+
3225 pthread_mutex_lock(&f->lock);
+
3226 if (!err)
+
3227 update_stat(node, &stbuf);
+
3228 else
+
3229 node->cache_valid = 0;
+
3230 }
+
3231 }
+
3232 if (node->cache_valid)
+
3233 fi->keep_cache = 1;
+
3234
+
3235 node->cache_valid = 1;
+
3236 pthread_mutex_unlock(&f->lock);
+
3237}
+
3238
+
3239static void fuse_lib_open(fuse_req_t req, fuse_ino_t ino,
+
3240 struct fuse_file_info *fi)
+
3241{
+
3242 struct fuse *f = req_fuse_prepare(req);
+
3243 struct fuse_intr_data d;
+
3244 char *path;
+
3245 int err;
+
3246
+
3247 err = get_path(f, ino, &path);
+
3248 if (!err) {
+
3249 fuse_prepare_interrupt(f, req, &d);
+
3250 err = fuse_fs_open(f->fs, path, fi);
+
3251 if (!err) {
+
3252 if (f->conf.direct_io)
+
3253 fi->direct_io = 1;
+
3254 if (f->conf.kernel_cache)
+
3255 fi->keep_cache = 1;
+
3256
+
3257 if (f->conf.auto_cache)
+
3258 open_auto_cache(f, ino, path, fi);
+
3259
+
3260 if (f->conf.no_rofd_flush &&
+
3261 (fi->flags & O_ACCMODE) == O_RDONLY)
+
3262 fi->noflush = 1;
+
3263
+
3264 if (fi->direct_io && f->conf.parallel_direct_writes)
+
3265 fi->parallel_direct_writes = 1;
+
3266
+
3267 }
+
3268 fuse_finish_interrupt(f, req, &d);
+
3269 }
+
3270 if (!err) {
+
3271 pthread_mutex_lock(&f->lock);
+
3272 get_node(f, ino)->open_count++;
+
3273 pthread_mutex_unlock(&f->lock);
+
3274 if (fuse_reply_open(req, fi) == -ENOENT) {
+
3275 /* The open syscall was interrupted, so it
+
3276 must be cancelled */
+
3277 fuse_do_release(f, ino, path, fi);
+
3278 }
+
3279 } else
+
3280 reply_err(req, err);
+
3281
+
3282 free_path(f, ino, path);
+
3283}
+
3284
+
3285static void fuse_lib_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3286 off_t off, struct fuse_file_info *fi)
+
3287{
+
3288 struct fuse *f = req_fuse_prepare(req);
+
3289 struct fuse_bufvec *buf = NULL;
+
3290 char *path;
+
3291 int res;
+
3292
+
3293 res = get_path_nullok(f, ino, &path);
+
3294 if (res == 0) {
+
3295 struct fuse_intr_data d;
+
3296
+
3297 fuse_prepare_interrupt(f, req, &d);
+
3298 res = fuse_fs_read_buf(f->fs, path, &buf, size, off, fi);
+
3299 fuse_finish_interrupt(f, req, &d);
+
3300 free_path(f, ino, path);
+
3301 }
+
3302
+
3303 if (res == 0)
+ +
3305 else
+
3306 reply_err(req, res);
+
3307
+
3308 fuse_free_buf(buf);
+
3309}
+
3310
+
3311static void fuse_lib_write_buf(fuse_req_t req, fuse_ino_t ino,
+
3312 struct fuse_bufvec *buf, off_t off,
+
3313 struct fuse_file_info *fi)
+
3314{
+
3315 struct fuse *f = req_fuse_prepare(req);
+
3316 char *path;
+
3317 int res;
+
3318
+
3319 res = get_path_nullok(f, ino, &path);
+
3320 if (res == 0) {
+
3321 struct fuse_intr_data d;
+
3322
+
3323 fuse_prepare_interrupt(f, req, &d);
+
3324 res = fuse_fs_write_buf(f->fs, path, buf, off, fi);
+
3325 fuse_finish_interrupt(f, req, &d);
+
3326 free_path(f, ino, path);
+
3327 }
+
3328
+
3329 if (res >= 0)
+
3330 fuse_reply_write(req, res);
+
3331 else
+
3332 reply_err(req, res);
+
3333}
+
3334
+
3335static void fuse_lib_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3336 struct fuse_file_info *fi)
+
3337{
+
3338 struct fuse *f = req_fuse_prepare(req);
+
3339 char *path;
+
3340 int err;
+
3341
+
3342 err = get_path_nullok(f, ino, &path);
+
3343 if (!err) {
+
3344 struct fuse_intr_data d;
+
3345
+
3346 fuse_prepare_interrupt(f, req, &d);
+
3347 err = fuse_fs_fsync(f->fs, path, datasync, fi);
+
3348 fuse_finish_interrupt(f, req, &d);
+
3349 free_path(f, ino, path);
+
3350 }
+
3351 reply_err(req, err);
+
3352}
+
3353
+
3354static struct fuse_dh *get_dirhandle(const struct fuse_file_info *llfi,
+
3355 struct fuse_file_info *fi)
+
3356{
+
3357 struct fuse_dh *dh = (struct fuse_dh *) (uintptr_t) llfi->fh;
+
3358 memset(fi, 0, sizeof(struct fuse_file_info));
+
3359 fi->fh = dh->fh;
+
3360 return dh;
+
3361}
+
3362
+
3363static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino,
+
3364 struct fuse_file_info *llfi)
+
3365{
+
3366 struct fuse *f = req_fuse_prepare(req);
+
3367 struct fuse_intr_data d;
+
3368 struct fuse_dh *dh;
+
3369 struct fuse_file_info fi;
+
3370 char *path;
+
3371 int err;
+
3372
+
3373 dh = (struct fuse_dh *) malloc(sizeof(struct fuse_dh));
+
3374 if (dh == NULL) {
+
3375 reply_err(req, -ENOMEM);
+
3376 return;
+
3377 }
+
3378 memset(dh, 0, sizeof(struct fuse_dh));
+
3379 dh->fuse = f;
+
3380 dh->contents = NULL;
+
3381 dh->first = NULL;
+
3382 dh->len = 0;
+
3383 dh->filled = 0;
+
3384 dh->nodeid = ino;
+
3385 pthread_mutex_init(&dh->lock, NULL);
+
3386
+
3387 llfi->fh = (uintptr_t) dh;
+
3388
+
3389 memset(&fi, 0, sizeof(fi));
+
3390 fi.flags = llfi->flags;
+
3391
+
3392 err = get_path(f, ino, &path);
+
3393 if (!err) {
+
3394 fuse_prepare_interrupt(f, req, &d);
+
3395 err = fuse_fs_opendir(f->fs, path, &fi);
+
3396 fuse_finish_interrupt(f, req, &d);
+
3397 dh->fh = fi.fh;
+
3398 llfi->cache_readdir = fi.cache_readdir;
+
3399 llfi->keep_cache = fi.keep_cache;
+
3400 }
+
3401 if (!err) {
+
3402 if (fuse_reply_open(req, llfi) == -ENOENT) {
+
3403 /* The opendir syscall was interrupted, so it
+
3404 must be cancelled */
+
3405 fuse_fs_releasedir(f->fs, path, &fi);
+
3406 pthread_mutex_destroy(&dh->lock);
+
3407 free(dh);
+
3408 }
+
3409 } else {
+
3410 reply_err(req, err);
+
3411 pthread_mutex_destroy(&dh->lock);
+
3412 free(dh);
+
3413 }
+
3414 free_path(f, ino, path);
+
3415}
+
3416
+
3417static int extend_contents(struct fuse_dh *dh, unsigned minsize)
+
3418{
+
3419 if (minsize > dh->size) {
+
3420 char *newptr;
+
3421 unsigned newsize = dh->size;
+
3422 if (!newsize)
+
3423 newsize = 1024;
+
3424 while (newsize < minsize) {
+
3425 if (newsize >= 0x80000000)
+
3426 newsize = 0xffffffff;
+
3427 else
+
3428 newsize *= 2;
+
3429 }
+
3430
+
3431 newptr = (char *) realloc(dh->contents, newsize);
+
3432 if (!newptr) {
+
3433 dh->error = -ENOMEM;
+
3434 return -1;
+
3435 }
+
3436 dh->contents = newptr;
+
3437 dh->size = newsize;
+
3438 }
+
3439 return 0;
+
3440}
+
3441
+
3442static int fuse_add_direntry_to_dh(struct fuse_dh *dh, const char *name,
+
3443 struct stat *st, enum fuse_fill_dir_flags flags)
+
3444{
+
3445 struct fuse_direntry *de;
+
3446
+
3447 de = malloc(sizeof(struct fuse_direntry));
+
3448 if (!de) {
+
3449 dh->error = -ENOMEM;
+
3450 return -1;
+
3451 }
+
3452 de->name = strdup(name);
+
3453 if (!de->name) {
+
3454 dh->error = -ENOMEM;
+
3455 free(de);
+
3456 return -1;
+
3457 }
+
3458 de->flags = flags;
+
3459 de->stat = *st;
+
3460 de->next = NULL;
+
3461
+
3462 *dh->last = de;
+
3463 dh->last = &de->next;
+
3464
+
3465 return 0;
+
3466}
+
3467
+
3468static fuse_ino_t lookup_nodeid(struct fuse *f, fuse_ino_t parent,
+
3469 const char *name)
+
3470{
+
3471 struct node *node;
+
3472 fuse_ino_t res = FUSE_UNKNOWN_INO;
+
3473
+
3474 pthread_mutex_lock(&f->lock);
+
3475 node = lookup_node(f, parent, name);
+
3476 if (node)
+
3477 res = node->nodeid;
+
3478 pthread_mutex_unlock(&f->lock);
+
3479
+
3480 return res;
+
3481}
+
3482
+
3483static int fill_dir(void *dh_, const char *name, const struct stat *statp,
+
3484 off_t off, enum fuse_fill_dir_flags flags)
+
3485{
+
3486 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3487 struct stat stbuf;
+
3488
+
3489 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3490 dh->error = -EIO;
+
3491 return 1;
+
3492 }
+
3493
+
3494 if (statp)
+
3495 stbuf = *statp;
+
3496 else {
+
3497 memset(&stbuf, 0, sizeof(stbuf));
+
3498 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3499 }
+
3500
+
3501 if (!dh->fuse->conf.use_ino) {
+
3502 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3503 if (dh->fuse->conf.readdir_ino) {
+
3504 stbuf.st_ino = (ino_t)
+
3505 lookup_nodeid(dh->fuse, dh->nodeid, name);
+
3506 }
+
3507 }
+
3508
+
3509 if (off) {
+
3510 size_t newlen;
+
3511
+
3512 if (dh->filled) {
+
3513 dh->error = -EIO;
+
3514 return 1;
+
3515 }
+
3516
+
3517 if (dh->first) {
+
3518 dh->error = -EIO;
+
3519 return 1;
+
3520 }
+
3521
+
3522 if (extend_contents(dh, dh->needlen) == -1)
+
3523 return 1;
+
3524
+
3525 newlen = dh->len +
+
3526 fuse_add_direntry(dh->req, dh->contents + dh->len,
+
3527 dh->needlen - dh->len, name,
+
3528 &stbuf, off);
+
3529 if (newlen > dh->needlen)
+
3530 return 1;
+
3531
+
3532 dh->len = newlen;
+
3533 } else {
+
3534 dh->filled = 1;
+
3535
+
3536 if (fuse_add_direntry_to_dh(dh, name, &stbuf, flags) == -1)
+
3537 return 1;
+
3538 }
+
3539 return 0;
+
3540}
+
3541
+
3542static int is_dot_or_dotdot(const char *name)
+
3543{
+
3544 return name[0] == '.' && (name[1] == '\0' ||
+
3545 (name[1] == '.' && name[2] == '\0'));
+
3546}
+
3547
+
3548static int fill_dir_plus(void *dh_, const char *name, const struct stat *statp,
+
3549 off_t off, enum fuse_fill_dir_flags flags)
+
3550{
+
3551 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3552 struct fuse_entry_param e = {
+
3553 /* ino=0 tells the kernel to ignore readdirplus stat info */
+
3554 .ino = 0,
+
3555 };
+
3556 struct fuse *f = dh->fuse;
+
3557 int res;
+
3558
+
3559 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3560 dh->error = -EIO;
+
3561 return 1;
+
3562 }
+
3563
+
3564 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3565 e.attr = *statp;
+
3566 } else {
+
3567 e.attr.st_ino = FUSE_UNKNOWN_INO;
+
3568 if (statp) {
+
3569 e.attr.st_mode = statp->st_mode;
+
3570 if (f->conf.use_ino)
+
3571 e.attr.st_ino = statp->st_ino;
+
3572 }
+
3573 if (!f->conf.use_ino && f->conf.readdir_ino) {
+
3574 e.attr.st_ino = (ino_t)
+
3575 lookup_nodeid(f, dh->nodeid, name);
+
3576 }
+
3577 }
+
3578
+
3579 if (off) {
+
3580 size_t newlen;
+
3581
+
3582 if (dh->filled) {
+
3583 dh->error = -EIO;
+
3584 return 1;
+
3585 }
+
3586
+
3587 if (dh->first) {
+
3588 dh->error = -EIO;
+
3589 return 1;
+
3590 }
+
3591 if (extend_contents(dh, dh->needlen) == -1)
+
3592 return 1;
+
3593
+
3594 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3595 if (!is_dot_or_dotdot(name)) {
+
3596 res = do_lookup(f, dh->nodeid, name, &e);
+
3597 if (res) {
+
3598 dh->error = res;
+
3599 return 1;
+
3600 }
+
3601 }
+
3602 }
+
3603
+
3604 newlen = dh->len +
+
3605 fuse_add_direntry_plus(dh->req, dh->contents + dh->len,
+
3606 dh->needlen - dh->len, name,
+
3607 &e, off);
+
3608 if (newlen > dh->needlen)
+
3609 return 1;
+
3610 dh->len = newlen;
+
3611 } else {
+
3612 dh->filled = 1;
+
3613
+
3614 if (fuse_add_direntry_to_dh(dh, name, &e.attr, flags) == -1)
+
3615 return 1;
+
3616 }
+
3617
+
3618 return 0;
+
3619}
+
3620
+
3621static void free_direntries(struct fuse_direntry *de)
+
3622{
+
3623 while (de) {
+
3624 struct fuse_direntry *next = de->next;
+
3625 free(de->name);
+
3626 free(de);
+
3627 de = next;
+
3628 }
+
3629}
+
3630
+
3631static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3632 size_t size, off_t off, struct fuse_dh *dh,
+
3633 struct fuse_file_info *fi,
+
3634 enum fuse_readdir_flags flags)
+
3635{
+
3636 char *path;
+
3637 int err;
+
3638
+
3639 if (f->fs->op.readdir)
+
3640 err = get_path_nullok(f, ino, &path);
+
3641 else
+
3642 err = get_path(f, ino, &path);
+
3643 if (!err) {
+
3644 struct fuse_intr_data d;
+
3645 fuse_fill_dir_t filler = fill_dir;
+
3646
+
3647 if (flags & FUSE_READDIR_PLUS)
+
3648 filler = fill_dir_plus;
+
3649
+
3650 free_direntries(dh->first);
+
3651 dh->first = NULL;
+
3652 dh->last = &dh->first;
+
3653 dh->len = 0;
+
3654 dh->error = 0;
+
3655 dh->needlen = size;
+
3656 dh->filled = 0;
+
3657 dh->req = req;
+
3658 fuse_prepare_interrupt(f, req, &d);
+
3659 err = fuse_fs_readdir(f->fs, path, dh, filler, off, fi, flags);
+
3660 fuse_finish_interrupt(f, req, &d);
+
3661 dh->req = NULL;
+
3662 if (!err)
+
3663 err = dh->error;
+
3664 if (err)
+
3665 dh->filled = 0;
+
3666 free_path(f, ino, path);
+
3667 }
+
3668 return err;
+
3669}
+
3670
+
3671static int readdir_fill_from_list(fuse_req_t req, struct fuse_dh *dh,
+
3672 off_t off, enum fuse_readdir_flags flags)
+
3673{
+
3674 off_t pos;
+
3675 struct fuse_direntry *de = dh->first;
+
3676 int res;
+
3677
+
3678 dh->len = 0;
+
3679
+
3680 if (extend_contents(dh, dh->needlen) == -1)
+
3681 return dh->error;
+
3682
+
3683 for (pos = 0; pos < off; pos++) {
+
3684 if (!de)
+
3685 break;
+
3686
+
3687 de = de->next;
+
3688 }
+
3689 while (de) {
+
3690 char *p = dh->contents + dh->len;
+
3691 unsigned rem = dh->needlen - dh->len;
+
3692 unsigned thislen;
+
3693 unsigned newlen;
+
3694 pos++;
+
3695
+
3696 if (flags & FUSE_READDIR_PLUS) {
+
3697 struct fuse_entry_param e = {
+
3698 .ino = 0,
+
3699 .attr = de->stat,
+
3700 };
+
3701
+
3702 if (de->flags & FUSE_FILL_DIR_PLUS &&
+
3703 !is_dot_or_dotdot(de->name)) {
+
3704 res = do_lookup(dh->fuse, dh->nodeid,
+
3705 de->name, &e);
+
3706 if (res) {
+
3707 dh->error = res;
+
3708 return 1;
+
3709 }
+
3710 }
+
3711
+
3712 thislen = fuse_add_direntry_plus(req, p, rem,
+
3713 de->name, &e, pos);
+
3714 } else {
+
3715 thislen = fuse_add_direntry(req, p, rem,
+
3716 de->name, &de->stat, pos);
+
3717 }
+
3718 newlen = dh->len + thislen;
+
3719 if (newlen > dh->needlen)
+
3720 break;
+
3721 dh->len = newlen;
+
3722 de = de->next;
+
3723 }
+
3724 return 0;
+
3725}
+
3726
+
3727static void fuse_readdir_common(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3728 off_t off, struct fuse_file_info *llfi,
+
3729 enum fuse_readdir_flags flags)
+
3730{
+
3731 struct fuse *f = req_fuse_prepare(req);
+
3732 struct fuse_file_info fi;
+
3733 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3734 int err;
+
3735
+
3736 pthread_mutex_lock(&dh->lock);
+
3737 /* According to SUS, directory contents need to be refreshed on
+
3738 rewinddir() */
+
3739 if (!off)
+
3740 dh->filled = 0;
+
3741
+
3742 if (!dh->filled) {
+
3743 err = readdir_fill(f, req, ino, size, off, dh, &fi, flags);
+
3744 if (err) {
+
3745 reply_err(req, err);
+
3746 goto out;
+
3747 }
+
3748 }
+
3749 if (dh->filled) {
+
3750 dh->needlen = size;
+
3751 err = readdir_fill_from_list(req, dh, off, flags);
+
3752 if (err) {
+
3753 reply_err(req, err);
+
3754 goto out;
+
3755 }
+
3756 }
+
3757 fuse_reply_buf(req, dh->contents, dh->len);
+
3758out:
+
3759 pthread_mutex_unlock(&dh->lock);
+
3760}
+
3761
+
3762static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3763 off_t off, struct fuse_file_info *llfi)
+
3764{
+
3765 fuse_readdir_common(req, ino, size, off, llfi, 0);
+
3766}
+
3767
+
3768static void fuse_lib_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3769 off_t off, struct fuse_file_info *llfi)
+
3770{
+
3771 fuse_readdir_common(req, ino, size, off, llfi, FUSE_READDIR_PLUS);
+
3772}
+
3773
+
3774static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino,
+
3775 struct fuse_file_info *llfi)
+
3776{
+
3777 struct fuse *f = req_fuse_prepare(req);
+
3778 struct fuse_intr_data d;
+
3779 struct fuse_file_info fi;
+
3780 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3781 char *path;
+
3782
+
3783 get_path_nullok(f, ino, &path);
+
3784
+
3785 fuse_prepare_interrupt(f, req, &d);
+
3786 fuse_fs_releasedir(f->fs, path, &fi);
+
3787 fuse_finish_interrupt(f, req, &d);
+
3788 free_path(f, ino, path);
+
3789
+
3790 pthread_mutex_lock(&dh->lock);
+
3791 pthread_mutex_unlock(&dh->lock);
+
3792 pthread_mutex_destroy(&dh->lock);
+
3793 free_direntries(dh->first);
+
3794 free(dh->contents);
+
3795 free(dh);
+
3796 reply_err(req, 0);
+
3797}
+
3798
+
3799static void fuse_lib_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3800 struct fuse_file_info *llfi)
+
3801{
+
3802 struct fuse *f = req_fuse_prepare(req);
+
3803 struct fuse_file_info fi;
+
3804 char *path;
+
3805 int err;
+
3806
+
3807 get_dirhandle(llfi, &fi);
+
3808
+
3809 err = get_path_nullok(f, ino, &path);
+
3810 if (!err) {
+
3811 struct fuse_intr_data d;
+
3812 fuse_prepare_interrupt(f, req, &d);
+
3813 err = fuse_fs_fsyncdir(f->fs, path, datasync, &fi);
+
3814 fuse_finish_interrupt(f, req, &d);
+
3815 free_path(f, ino, path);
+
3816 }
+
3817 reply_err(req, err);
+
3818}
+
3819
+
3820static void fuse_lib_statfs(fuse_req_t req, fuse_ino_t ino)
+
3821{
+
3822 struct fuse *f = req_fuse_prepare(req);
+
3823 struct statvfs buf;
+
3824 char *path = NULL;
+
3825 int err = 0;
+
3826
+
3827 memset(&buf, 0, sizeof(buf));
+
3828 if (ino)
+
3829 err = get_path(f, ino, &path);
+
3830
+
3831 if (!err) {
+
3832 struct fuse_intr_data d;
+
3833 fuse_prepare_interrupt(f, req, &d);
+
3834 err = fuse_fs_statfs(f->fs, path ? path : "/", &buf);
+
3835 fuse_finish_interrupt(f, req, &d);
+
3836 free_path(f, ino, path);
+
3837 }
+
3838
+
3839 if (!err)
+
3840 fuse_reply_statfs(req, &buf);
+
3841 else
+
3842 reply_err(req, err);
+
3843}
+
3844
+
3845static void fuse_lib_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3846 const char *value, size_t size, int flags)
+
3847{
+
3848 struct fuse *f = req_fuse_prepare(req);
+
3849 char *path;
+
3850 int err;
+
3851
+
3852 err = get_path(f, ino, &path);
+
3853 if (!err) {
+
3854 struct fuse_intr_data d;
+
3855 fuse_prepare_interrupt(f, req, &d);
+
3856 err = fuse_fs_setxattr(f->fs, path, name, value, size, flags);
+
3857 fuse_finish_interrupt(f, req, &d);
+
3858 free_path(f, ino, path);
+
3859 }
+
3860 reply_err(req, err);
+
3861}
+
3862
+
3863static int common_getxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3864 const char *name, char *value, size_t size)
+
3865{
+
3866 int err;
+
3867 char *path;
+
3868
+
3869 err = get_path(f, ino, &path);
+
3870 if (!err) {
+
3871 struct fuse_intr_data d;
+
3872 fuse_prepare_interrupt(f, req, &d);
+
3873 err = fuse_fs_getxattr(f->fs, path, name, value, size);
+
3874 fuse_finish_interrupt(f, req, &d);
+
3875 free_path(f, ino, path);
+
3876 }
+
3877 return err;
+
3878}
+
3879
+
3880static void fuse_lib_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3881 size_t size)
+
3882{
+
3883 struct fuse *f = req_fuse_prepare(req);
+
3884 int res;
+
3885
+
3886 if (size) {
+
3887 char *value = (char *) malloc(size);
+
3888 if (value == NULL) {
+
3889 reply_err(req, -ENOMEM);
+
3890 return;
+
3891 }
+
3892 res = common_getxattr(f, req, ino, name, value, size);
+
3893 if (res > 0)
+
3894 fuse_reply_buf(req, value, res);
+
3895 else
+
3896 reply_err(req, res);
+
3897 free(value);
+
3898 } else {
+
3899 res = common_getxattr(f, req, ino, name, NULL, 0);
+
3900 if (res >= 0)
+
3901 fuse_reply_xattr(req, res);
+
3902 else
+
3903 reply_err(req, res);
+
3904 }
+
3905}
+
3906
+
3907static int common_listxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3908 char *list, size_t size)
+
3909{
+
3910 char *path;
+
3911 int err;
+
3912
+
3913 err = get_path(f, ino, &path);
+
3914 if (!err) {
+
3915 struct fuse_intr_data d;
+
3916 fuse_prepare_interrupt(f, req, &d);
+
3917 err = fuse_fs_listxattr(f->fs, path, list, size);
+
3918 fuse_finish_interrupt(f, req, &d);
+
3919 free_path(f, ino, path);
+
3920 }
+
3921 return err;
+
3922}
+
3923
+
3924static void fuse_lib_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
3925{
+
3926 struct fuse *f = req_fuse_prepare(req);
+
3927 int res;
+
3928
+
3929 if (size) {
+
3930 char *list = (char *) malloc(size);
+
3931 if (list == NULL) {
+
3932 reply_err(req, -ENOMEM);
+
3933 return;
+
3934 }
+
3935 res = common_listxattr(f, req, ino, list, size);
+
3936 if (res > 0)
+
3937 fuse_reply_buf(req, list, res);
+
3938 else
+
3939 reply_err(req, res);
+
3940 free(list);
+
3941 } else {
+
3942 res = common_listxattr(f, req, ino, NULL, 0);
+
3943 if (res >= 0)
+
3944 fuse_reply_xattr(req, res);
+
3945 else
+
3946 reply_err(req, res);
+
3947 }
+
3948}
+
3949
+
3950static void fuse_lib_removexattr(fuse_req_t req, fuse_ino_t ino,
+
3951 const char *name)
+
3952{
+
3953 struct fuse *f = req_fuse_prepare(req);
+
3954 char *path;
+
3955 int err;
+
3956
+
3957 err = get_path(f, ino, &path);
+
3958 if (!err) {
+
3959 struct fuse_intr_data d;
+
3960 fuse_prepare_interrupt(f, req, &d);
+
3961 err = fuse_fs_removexattr(f->fs, path, name);
+
3962 fuse_finish_interrupt(f, req, &d);
+
3963 free_path(f, ino, path);
+
3964 }
+
3965 reply_err(req, err);
+
3966}
+
3967
+
3968static struct lock *locks_conflict(struct node *node, const struct lock *lock)
+
3969{
+
3970 struct lock *l;
+
3971
+
3972 for (l = node->locks; l; l = l->next)
+
3973 if (l->owner != lock->owner &&
+
3974 lock->start <= l->end && l->start <= lock->end &&
+
3975 (l->type == F_WRLCK || lock->type == F_WRLCK))
+
3976 break;
+
3977
+
3978 return l;
+
3979}
+
3980
+
3981static void delete_lock(struct lock **lockp)
+
3982{
+
3983 struct lock *l = *lockp;
+
3984 *lockp = l->next;
+
3985 free(l);
+
3986}
+
3987
+
3988static void insert_lock(struct lock **pos, struct lock *lock)
+
3989{
+
3990 lock->next = *pos;
+
3991 *pos = lock;
+
3992}
+
3993
+
3994static int locks_insert(struct node *node, struct lock *lock)
+
3995{
+
3996 struct lock **lp;
+
3997 struct lock *newl1 = NULL;
+
3998 struct lock *newl2 = NULL;
+
3999
+
4000 if (lock->type != F_UNLCK || lock->start != 0 ||
+
4001 lock->end != OFFSET_MAX) {
+
4002 newl1 = malloc(sizeof(struct lock));
+
4003 newl2 = malloc(sizeof(struct lock));
+
4004
+
4005 if (!newl1 || !newl2) {
+
4006 free(newl1);
+
4007 free(newl2);
+
4008 return -ENOLCK;
+
4009 }
+
4010 }
+
4011
+
4012 for (lp = &node->locks; *lp;) {
+
4013 struct lock *l = *lp;
+
4014 if (l->owner != lock->owner)
+
4015 goto skip;
+
4016
+
4017 if (lock->type == l->type) {
+
4018 if (l->end < lock->start - 1)
+
4019 goto skip;
+
4020 if (lock->end < l->start - 1)
+
4021 break;
+
4022 if (l->start <= lock->start && lock->end <= l->end)
+
4023 goto out;
+
4024 if (l->start < lock->start)
+
4025 lock->start = l->start;
+
4026 if (lock->end < l->end)
+
4027 lock->end = l->end;
+
4028 goto delete;
+
4029 } else {
+
4030 if (l->end < lock->start)
+
4031 goto skip;
+
4032 if (lock->end < l->start)
+
4033 break;
+
4034 if (lock->start <= l->start && l->end <= lock->end)
+
4035 goto delete;
+
4036 if (l->end <= lock->end) {
+
4037 l->end = lock->start - 1;
+
4038 goto skip;
+
4039 }
+
4040 if (lock->start <= l->start) {
+
4041 l->start = lock->end + 1;
+
4042 break;
+
4043 }
+
4044 *newl2 = *l;
+
4045 newl2->start = lock->end + 1;
+
4046 l->end = lock->start - 1;
+
4047 insert_lock(&l->next, newl2);
+
4048 newl2 = NULL;
+
4049 }
+
4050 skip:
+
4051 lp = &l->next;
+
4052 continue;
+
4053
+
4054 delete:
+
4055 delete_lock(lp);
+
4056 }
+
4057 if (lock->type != F_UNLCK) {
+
4058 *newl1 = *lock;
+
4059 insert_lock(lp, newl1);
+
4060 newl1 = NULL;
+
4061 }
+
4062out:
+
4063 free(newl1);
+
4064 free(newl2);
+
4065 return 0;
+
4066}
+
4067
+
4068static void flock_to_lock(struct flock *flock, struct lock *lock)
+
4069{
+
4070 memset(lock, 0, sizeof(struct lock));
+
4071 lock->type = flock->l_type;
+
4072 lock->start = flock->l_start;
+
4073 lock->end =
+
4074 flock->l_len ? flock->l_start + flock->l_len - 1 : OFFSET_MAX;
+
4075 lock->pid = flock->l_pid;
+
4076}
+
4077
+
4078static void lock_to_flock(struct lock *lock, struct flock *flock)
+
4079{
+
4080 flock->l_type = lock->type;
+
4081 flock->l_start = lock->start;
+
4082 flock->l_len =
+
4083 (lock->end == OFFSET_MAX) ? 0 : lock->end - lock->start + 1;
+
4084 flock->l_pid = lock->pid;
+
4085}
+
4086
+
4087static int fuse_flush_common(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
4088 const char *path, struct fuse_file_info *fi)
+
4089{
+
4090 struct fuse_intr_data d;
+
4091 struct flock lock;
+
4092 struct lock l;
+
4093 int err;
+
4094 int errlock;
+
4095
+
4096 fuse_prepare_interrupt(f, req, &d);
+
4097 memset(&lock, 0, sizeof(lock));
+
4098 lock.l_type = F_UNLCK;
+
4099 lock.l_whence = SEEK_SET;
+
4100 err = fuse_fs_flush(f->fs, path, fi);
+
4101 errlock = fuse_fs_lock(f->fs, path, fi, F_SETLK, &lock);
+
4102 fuse_finish_interrupt(f, req, &d);
+
4103
+
4104 if (errlock != -ENOSYS) {
+
4105 flock_to_lock(&lock, &l);
+
4106 l.owner = fi->lock_owner;
+
4107 pthread_mutex_lock(&f->lock);
+
4108 locks_insert(get_node(f, ino), &l);
+
4109 pthread_mutex_unlock(&f->lock);
+
4110
+
4111 /* if op.lock() is defined FLUSH is needed regardless
+
4112 of op.flush() */
+
4113 if (err == -ENOSYS)
+
4114 err = 0;
+
4115 }
+
4116 return err;
+
4117}
+
4118
+
4119static void fuse_lib_release(fuse_req_t req, fuse_ino_t ino,
+
4120 struct fuse_file_info *fi)
+
4121{
+
4122 struct fuse *f = req_fuse_prepare(req);
+
4123 struct fuse_intr_data d;
+
4124 char *path;
+
4125 int err = 0;
+
4126
+
4127 get_path_nullok(f, ino, &path);
+
4128 if (fi->flush) {
+
4129 err = fuse_flush_common(f, req, ino, path, fi);
+
4130 if (err == -ENOSYS)
+
4131 err = 0;
+
4132 }
+
4133
+
4134 fuse_prepare_interrupt(f, req, &d);
+
4135 fuse_do_release(f, ino, path, fi);
+
4136 fuse_finish_interrupt(f, req, &d);
+
4137 free_path(f, ino, path);
+
4138
+
4139 reply_err(req, err);
+
4140}
+
4141
+
4142static void fuse_lib_flush(fuse_req_t req, fuse_ino_t ino,
+
4143 struct fuse_file_info *fi)
+
4144{
+
4145 struct fuse *f = req_fuse_prepare(req);
+
4146 char *path;
+
4147 int err;
+
4148
+
4149 get_path_nullok(f, ino, &path);
+
4150 err = fuse_flush_common(f, req, ino, path, fi);
+
4151 free_path(f, ino, path);
+
4152
+
4153 reply_err(req, err);
+
4154}
+
4155
+
4156static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino,
+
4157 struct fuse_file_info *fi, struct flock *lock,
+
4158 int cmd)
+
4159{
+
4160 struct fuse *f = req_fuse_prepare(req);
+
4161 char *path;
+
4162 int err;
+
4163
+
4164 err = get_path_nullok(f, ino, &path);
+
4165 if (!err) {
+
4166 struct fuse_intr_data d;
+
4167 fuse_prepare_interrupt(f, req, &d);
+
4168 err = fuse_fs_lock(f->fs, path, fi, cmd, lock);
+
4169 fuse_finish_interrupt(f, req, &d);
+
4170 free_path(f, ino, path);
+
4171 }
+
4172 return err;
+
4173}
+
4174
+
4175static void fuse_lib_getlk(fuse_req_t req, fuse_ino_t ino,
+
4176 struct fuse_file_info *fi, struct flock *lock)
+
4177{
+
4178 int err;
+
4179 struct lock l;
+
4180 struct lock *conflict;
+
4181 struct fuse *f = req_fuse(req);
+
4182
+
4183 flock_to_lock(lock, &l);
+
4184 l.owner = fi->lock_owner;
+
4185 pthread_mutex_lock(&f->lock);
+
4186 conflict = locks_conflict(get_node(f, ino), &l);
+
4187 if (conflict)
+
4188 lock_to_flock(conflict, lock);
+
4189 pthread_mutex_unlock(&f->lock);
+
4190 if (!conflict)
+
4191 err = fuse_lock_common(req, ino, fi, lock, F_GETLK);
+
4192 else
+
4193 err = 0;
+
4194
+
4195 if (!err)
+
4196 fuse_reply_lock(req, lock);
+
4197 else
+
4198 reply_err(req, err);
+
4199}
+
4200
+
4201static void fuse_lib_setlk(fuse_req_t req, fuse_ino_t ino,
+
4202 struct fuse_file_info *fi, struct flock *lock,
+
4203 int sleep)
+
4204{
+
4205 int err = fuse_lock_common(req, ino, fi, lock,
+
4206 sleep ? F_SETLKW : F_SETLK);
+
4207 if (!err) {
+
4208 struct fuse *f = req_fuse(req);
+
4209 struct lock l;
+
4210 flock_to_lock(lock, &l);
+
4211 l.owner = fi->lock_owner;
+
4212 pthread_mutex_lock(&f->lock);
+
4213 locks_insert(get_node(f, ino), &l);
+
4214 pthread_mutex_unlock(&f->lock);
+
4215 }
+
4216 reply_err(req, err);
+
4217}
+
4218
+
4219static void fuse_lib_flock(fuse_req_t req, fuse_ino_t ino,
+
4220 struct fuse_file_info *fi, int op)
+
4221{
+
4222 struct fuse *f = req_fuse_prepare(req);
+
4223 char *path;
+
4224 int err;
+
4225
+
4226 err = get_path_nullok(f, ino, &path);
+
4227 if (err == 0) {
+
4228 struct fuse_intr_data d;
+
4229 fuse_prepare_interrupt(f, req, &d);
+
4230 err = fuse_fs_flock(f->fs, path, fi, op);
+
4231 fuse_finish_interrupt(f, req, &d);
+
4232 free_path(f, ino, path);
+
4233 }
+
4234 reply_err(req, err);
+
4235}
+
4236
+
4237static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
4238 uint64_t idx)
+
4239{
+
4240 struct fuse *f = req_fuse_prepare(req);
+
4241 struct fuse_intr_data d;
+
4242 char *path;
+
4243 int err;
+
4244
+
4245 err = get_path(f, ino, &path);
+
4246 if (!err) {
+
4247 fuse_prepare_interrupt(f, req, &d);
+
4248 err = fuse_fs_bmap(f->fs, path, blocksize, &idx);
+
4249 fuse_finish_interrupt(f, req, &d);
+
4250 free_path(f, ino, path);
+
4251 }
+
4252 if (!err)
+
4253 fuse_reply_bmap(req, idx);
+
4254 else
+
4255 reply_err(req, err);
+
4256}
+
4257
+
4258static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
4259 void *arg, struct fuse_file_info *llfi,
+
4260 unsigned int flags, const void *in_buf,
+
4261 size_t in_bufsz, size_t out_bufsz)
+
4262{
+
4263 struct fuse *f = req_fuse_prepare(req);
+
4264 struct fuse_intr_data d;
+
4265 struct fuse_file_info fi;
+
4266 char *path, *out_buf = NULL;
+
4267 int err;
+
4268
+
4269 err = -EPERM;
+
4270 if (flags & FUSE_IOCTL_UNRESTRICTED)
+
4271 goto err;
+
4272
+
4273 if (flags & FUSE_IOCTL_DIR)
+
4274 get_dirhandle(llfi, &fi);
+
4275 else
+
4276 fi = *llfi;
+
4277
+
4278 if (out_bufsz) {
+
4279 err = -ENOMEM;
+
4280 out_buf = malloc(out_bufsz);
+
4281 if (!out_buf)
+
4282 goto err;
+
4283 }
+
4284
+
4285 assert(!in_bufsz || !out_bufsz || in_bufsz == out_bufsz);
+
4286 if (out_buf && in_bufsz)
+
4287 memcpy(out_buf, in_buf, in_bufsz);
+
4288
+
4289 err = get_path_nullok(f, ino, &path);
+
4290 if (err)
+
4291 goto err;
+
4292
+
4293 fuse_prepare_interrupt(f, req, &d);
+
4294
+
4295 err = fuse_fs_ioctl(f->fs, path, cmd, arg, &fi, flags,
+
4296 out_buf ? out_buf : (void *)in_buf);
+
4297
+
4298 fuse_finish_interrupt(f, req, &d);
+
4299 free_path(f, ino, path);
+
4300
+
4301 if (err < 0)
+
4302 goto err;
+
4303 fuse_reply_ioctl(req, err, out_buf, out_bufsz);
+
4304 goto out;
+
4305err:
+
4306 reply_err(req, err);
+
4307out:
+
4308 free(out_buf);
+
4309}
+
4310
+
4311static void fuse_lib_poll(fuse_req_t req, fuse_ino_t ino,
+
4312 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
4313{
+
4314 struct fuse *f = req_fuse_prepare(req);
+
4315 struct fuse_intr_data d;
+
4316 char *path;
+
4317 int err;
+
4318 unsigned revents = 0;
+
4319
+
4320 err = get_path_nullok(f, ino, &path);
+
4321 if (!err) {
+
4322 fuse_prepare_interrupt(f, req, &d);
+
4323 err = fuse_fs_poll(f->fs, path, fi, ph, &revents);
+
4324 fuse_finish_interrupt(f, req, &d);
+
4325 free_path(f, ino, path);
+
4326 }
+
4327 if (!err)
+
4328 fuse_reply_poll(req, revents);
+
4329 else
+
4330 reply_err(req, err);
+
4331}
+
4332
+
4333static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
4334 off_t offset, off_t length, struct fuse_file_info *fi)
+
4335{
+
4336 struct fuse *f = req_fuse_prepare(req);
+
4337 struct fuse_intr_data d;
+
4338 char *path;
+
4339 int err;
+
4340
+
4341 err = get_path_nullok(f, ino, &path);
+
4342 if (!err) {
+
4343 fuse_prepare_interrupt(f, req, &d);
+
4344 err = fuse_fs_fallocate(f->fs, path, mode, offset, length, fi);
+
4345 fuse_finish_interrupt(f, req, &d);
+
4346 free_path(f, ino, path);
+
4347 }
+
4348 reply_err(req, err);
+
4349}
+
4350
+
4351static void fuse_lib_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in,
+
4352 off_t off_in, struct fuse_file_info *fi_in,
+
4353 fuse_ino_t nodeid_out, off_t off_out,
+
4354 struct fuse_file_info *fi_out, size_t len,
+
4355 int flags)
+
4356{
+
4357 struct fuse *f = req_fuse_prepare(req);
+
4358 struct fuse_intr_data d;
+
4359 char *path_in, *path_out;
+
4360 int err;
+
4361 ssize_t res;
+
4362
+
4363 err = get_path_nullok(f, nodeid_in, &path_in);
+
4364 if (err) {
+
4365 reply_err(req, err);
+
4366 return;
+
4367 }
+
4368
+
4369 err = get_path_nullok(f, nodeid_out, &path_out);
+
4370 if (err) {
+
4371 free_path(f, nodeid_in, path_in);
+
4372 reply_err(req, err);
+
4373 return;
+
4374 }
+
4375
+
4376 fuse_prepare_interrupt(f, req, &d);
+
4377 res = fuse_fs_copy_file_range(f->fs, path_in, fi_in, off_in, path_out,
+
4378 fi_out, off_out, len, flags);
+
4379 fuse_finish_interrupt(f, req, &d);
+
4380
+
4381 if (res >= 0)
+
4382 fuse_reply_write(req, res);
+
4383 else
+
4384 reply_err(req, res);
+
4385
+
4386 free_path(f, nodeid_in, path_in);
+
4387 free_path(f, nodeid_out, path_out);
+
4388}
+
4389
+
4390static void fuse_lib_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
4391 struct fuse_file_info *fi)
+
4392{
+
4393 struct fuse *f = req_fuse_prepare(req);
+
4394 struct fuse_intr_data d;
+
4395 char *path;
+
4396 int err;
+
4397 off_t res;
+
4398
+
4399 err = get_path(f, ino, &path);
+
4400 if (err) {
+
4401 reply_err(req, err);
+
4402 return;
+
4403 }
+
4404
+
4405 fuse_prepare_interrupt(f, req, &d);
+
4406 res = fuse_fs_lseek(f->fs, path, off, whence, fi);
+
4407 fuse_finish_interrupt(f, req, &d);
+
4408 free_path(f, ino, path);
+
4409 if (res >= 0)
+
4410 fuse_reply_lseek(req, res);
+
4411 else
+
4412 reply_err(req, res);
+
4413}
+
4414
+
4415static int clean_delay(struct fuse *f)
+
4416{
+
4417 /*
+
4418 * This is calculating the delay between clean runs. To
+
4419 * reduce the number of cleans we are doing them 10 times
+
4420 * within the remember window.
+
4421 */
+
4422 int min_sleep = 60;
+
4423 int max_sleep = 3600;
+
4424 int sleep_time = f->conf.remember / 10;
+
4425
+
4426 if (sleep_time > max_sleep)
+
4427 return max_sleep;
+
4428 if (sleep_time < min_sleep)
+
4429 return min_sleep;
+
4430 return sleep_time;
+
4431}
+
4432
+
+
4433int fuse_clean_cache(struct fuse *f)
+
4434{
+
4435 struct node_lru *lnode;
+
4436 struct list_head *curr, *next;
+
4437 struct node *node;
+
4438 struct timespec now;
+
4439
+
4440 pthread_mutex_lock(&f->lock);
+
4441
+
4442 curr_time(&now);
+
4443
+
4444 for (curr = f->lru_table.next; curr != &f->lru_table; curr = next) {
+
4445 double age;
+
4446
+
4447 next = curr->next;
+
4448 lnode = list_entry(curr, struct node_lru, lru);
+
4449 node = &lnode->node;
+
4450
+
4451 age = diff_timespec(&now, &lnode->forget_time);
+
4452 if (age <= f->conf.remember)
+
4453 break;
+
4454
+
4455 assert(node->nlookup == 1);
+
4456
+
4457 /* Don't forget active directories */
+
4458 if (node->refctr > 1)
+
4459 continue;
+
4460
+
4461 node->nlookup = 0;
+
4462 unhash_name(f, node);
+
4463 unref_node(f, node);
+
4464 }
+
4465 pthread_mutex_unlock(&f->lock);
+
4466
+
4467 return clean_delay(f);
+
4468}
+
+
4469
+
4470static struct fuse_lowlevel_ops fuse_path_ops = {
+
4471 .init = fuse_lib_init,
+
4472 .destroy = fuse_lib_destroy,
+
4473 .lookup = fuse_lib_lookup,
+
4474 .forget = fuse_lib_forget,
+
4475 .forget_multi = fuse_lib_forget_multi,
+
4476 .getattr = fuse_lib_getattr,
+
4477 .setattr = fuse_lib_setattr,
+
4478 .access = fuse_lib_access,
+
4479 .readlink = fuse_lib_readlink,
+
4480 .mknod = fuse_lib_mknod,
+
4481 .mkdir = fuse_lib_mkdir,
+
4482 .unlink = fuse_lib_unlink,
+
4483 .rmdir = fuse_lib_rmdir,
+
4484 .symlink = fuse_lib_symlink,
+
4485 .rename = fuse_lib_rename,
+
4486 .link = fuse_lib_link,
+
4487 .create = fuse_lib_create,
+
4488 .open = fuse_lib_open,
+
4489 .read = fuse_lib_read,
+
4490 .write_buf = fuse_lib_write_buf,
+
4491 .flush = fuse_lib_flush,
+
4492 .release = fuse_lib_release,
+
4493 .fsync = fuse_lib_fsync,
+
4494 .opendir = fuse_lib_opendir,
+
4495 .readdir = fuse_lib_readdir,
+
4496 .readdirplus = fuse_lib_readdirplus,
+
4497 .releasedir = fuse_lib_releasedir,
+
4498 .fsyncdir = fuse_lib_fsyncdir,
+
4499 .statfs = fuse_lib_statfs,
+
4500 .setxattr = fuse_lib_setxattr,
+
4501 .getxattr = fuse_lib_getxattr,
+
4502 .listxattr = fuse_lib_listxattr,
+
4503 .removexattr = fuse_lib_removexattr,
+
4504 .getlk = fuse_lib_getlk,
+
4505 .setlk = fuse_lib_setlk,
+
4506 .flock = fuse_lib_flock,
+
4507 .bmap = fuse_lib_bmap,
+
4508 .ioctl = fuse_lib_ioctl,
+
4509 .poll = fuse_lib_poll,
+
4510 .fallocate = fuse_lib_fallocate,
+
4511 .copy_file_range = fuse_lib_copy_file_range,
+
4512 .lseek = fuse_lib_lseek,
+
4513};
+
4514
+
4515int fuse_notify_poll(struct fuse_pollhandle *ph)
+
4516{
+
4517 return fuse_lowlevel_notify_poll(ph);
+
4518}
+
4519
+
+
4520struct fuse_session *fuse_get_session(struct fuse *f)
+
4521{
+
4522 return f->se;
+
4523}
+
+
4524
+
4525static int fuse_session_loop_remember(struct fuse *f)
+
4526{
+
4527 struct fuse_session *se = f->se;
+
4528 int res = 0;
+
4529 struct timespec now;
+
4530 time_t next_clean;
+
4531 struct pollfd fds = {
+
4532 .fd = se->fd,
+
4533 .events = POLLIN
+
4534 };
+
4535 struct fuse_buf fbuf = {
+
4536 .mem = NULL,
+
4537 };
+
4538
+
4539 curr_time(&now);
+
4540 next_clean = now.tv_sec;
+
4541 while (!fuse_session_exited(se)) {
+
4542 unsigned timeout;
+
4543
+
4544 curr_time(&now);
+
4545 if (now.tv_sec < next_clean)
+
4546 timeout = next_clean - now.tv_sec;
+
4547 else
+
4548 timeout = 0;
+
4549
+
4550 res = poll(&fds, 1, timeout * 1000);
+
4551 if (res == -1) {
+
4552 if (errno == EINTR)
+
4553 continue;
+
4554 else
+
4555 break;
+
4556 } else if (res > 0) {
+
4557 res = fuse_session_receive_buf_internal(se, &fbuf,
+
4558 NULL);
+
4559 if (res == -EINTR)
+
4560 continue;
+
4561 if (res <= 0)
+
4562 break;
+
4563
+
4564 fuse_session_process_buf_internal(se, &fbuf, NULL);
+
4565 } else {
+
4566 timeout = fuse_clean_cache(f);
+
4567 curr_time(&now);
+
4568 next_clean = now.tv_sec + timeout;
+
4569 }
+
4570 }
+
4571
+
4572 free(fbuf.mem);
+ +
4574 return res < 0 ? -1 : 0;
+
4575}
+
4576
+
+
4577int fuse_loop(struct fuse *f)
+
4578{
+
4579 if (!f)
+
4580 return -1;
+
4581
+
4582 if (lru_enabled(f))
+
4583 return fuse_session_loop_remember(f);
+
4584
+
4585 return fuse_session_loop(f->se);
+
4586}
+
+
4587
+
4588FUSE_SYMVER("fuse_loop_mt_312", "fuse_loop_mt@@FUSE_3.12")
+
4589int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config)
+
4590{
+
4591 if (f == NULL)
+
4592 return -1;
+
4593
+
4594 int res = fuse_start_cleanup_thread(f);
+
4595 if (res)
+
4596 return -1;
+
4597
+
4598 res = fuse_session_loop_mt_312(fuse_get_session(f), config);
+ +
4600 return res;
+
4601}
+
4602
+
4603int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1);
+
4604FUSE_SYMVER("fuse_loop_mt_32", "fuse_loop_mt@FUSE_3.2")
+
4605int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1)
+
4606{
+
4607 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4608 if (config == NULL)
+
4609 return ENOMEM;
+
4610
+
4611 fuse_loop_cfg_convert(config, config_v1);
+
4612
+
4613 int res = fuse_loop_mt_312(f, config);
+
4614
+
4615 fuse_loop_cfg_destroy(config);
+
4616
+
4617 return res;
+
4618}
+
4619
+
4620int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
4621FUSE_SYMVER("fuse_loop_mt_31", "fuse_loop_mt@FUSE_3.0")
+
4622int fuse_loop_mt_31(struct fuse *f, int clone_fd)
+
4623{
+
4624 int err;
+
4625 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4626
+
4627 if (config == NULL)
+
4628 return ENOMEM;
+
4629
+
4630 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
4631
+
4632 err = fuse_loop_mt_312(f, config);
+
4633
+
4634 fuse_loop_cfg_destroy(config);
+
4635
+
4636 return err;
+
4637}
+
4638
+
+
4639void fuse_exit(struct fuse *f)
+
4640{
+
4641 fuse_session_exit(f->se);
+
4642}
+
+
4643
+
+ +
4645{
+
4646 struct fuse_context_i *c = fuse_get_context_internal();
+
4647
+
4648 if (c)
+
4649 return &c->ctx;
+
4650 else
+
4651 return NULL;
+
4652}
+
+
4653
+
+
4654int fuse_getgroups(int size, gid_t list[])
+
4655{
+
4656 struct fuse_context_i *c = fuse_get_context_internal();
+
4657 if (!c)
+
4658 return -EINVAL;
+
4659
+
4660 return fuse_req_getgroups(c->req, size, list);
+
4661}
+
+
4662
+
+ +
4664{
+
4665 struct fuse_context_i *c = fuse_get_context_internal();
+
4666
+
4667 if (c)
+
4668 return fuse_req_interrupted(c->req);
+
4669 else
+
4670 return 0;
+
4671}
+
+
4672
+
+
4673int fuse_invalidate_path(struct fuse *f, const char *path) {
+
4674 fuse_ino_t ino;
+
4675 int err = lookup_path_in_cache(f, path, &ino);
+
4676 if (err) {
+
4677 return err;
+
4678 }
+
4679
+
4680 return fuse_lowlevel_notify_inval_inode(f->se, ino, 0, 0);
+
4681}
+
+
4682
+
4683#define FUSE_LIB_OPT(t, p, v) { t, offsetof(struct fuse_config, p), v }
+
4684
+
4685static const struct fuse_opt fuse_lib_opts[] = {
+ + +
4688 FUSE_LIB_OPT("debug", debug, 1),
+
4689 FUSE_LIB_OPT("-d", debug, 1),
+
4690 FUSE_LIB_OPT("kernel_cache", kernel_cache, 1),
+
4691 FUSE_LIB_OPT("auto_cache", auto_cache, 1),
+
4692 FUSE_LIB_OPT("noauto_cache", auto_cache, 0),
+
4693 FUSE_LIB_OPT("no_rofd_flush", no_rofd_flush, 1),
+
4694 FUSE_LIB_OPT("umask=", set_mode, 1),
+
4695 FUSE_LIB_OPT("umask=%o", umask, 0),
+
4696 FUSE_LIB_OPT("fmask=", set_mode, 1),
+
4697 FUSE_LIB_OPT("fmask=%o", fmask, 0),
+
4698 FUSE_LIB_OPT("dmask=", set_mode, 1),
+
4699 FUSE_LIB_OPT("dmask=%o", dmask, 0),
+
4700 FUSE_LIB_OPT("uid=", set_uid, 1),
+
4701 FUSE_LIB_OPT("uid=%d", uid, 0),
+
4702 FUSE_LIB_OPT("gid=", set_gid, 1),
+
4703 FUSE_LIB_OPT("gid=%d", gid, 0),
+
4704 FUSE_LIB_OPT("entry_timeout=%lf", entry_timeout, 0),
+
4705 FUSE_LIB_OPT("attr_timeout=%lf", attr_timeout, 0),
+
4706 FUSE_LIB_OPT("ac_attr_timeout=%lf", ac_attr_timeout, 0),
+
4707 FUSE_LIB_OPT("ac_attr_timeout=", ac_attr_timeout_set, 1),
+
4708 FUSE_LIB_OPT("negative_timeout=%lf", negative_timeout, 0),
+
4709 FUSE_LIB_OPT("noforget", remember, -1),
+
4710 FUSE_LIB_OPT("remember=%u", remember, 0),
+
4711 FUSE_LIB_OPT("modules=%s", modules, 0),
+
4712 FUSE_LIB_OPT("parallel_direct_write=%d", parallel_direct_writes, 0),
+ +
4714};
+
4715
+
4716static int fuse_lib_opt_proc(void *data, const char *arg, int key,
+
4717 struct fuse_args *outargs)
+
4718{
+
4719 (void) arg; (void) outargs; (void) data; (void) key;
+
4720
+
4721 /* Pass through unknown options */
+
4722 return 1;
+
4723}
+
4724
+
4725
+
4726static const struct fuse_opt fuse_help_opts[] = {
+
4727 FUSE_LIB_OPT("modules=%s", modules, 1),
+
4728 FUSE_OPT_KEY("modules=%s", FUSE_OPT_KEY_KEEP),
+ +
4730};
+
4731
+
4732static void print_module_help(const char *name,
+ +
4734{
+
4735 struct fuse_args a = FUSE_ARGS_INIT(0, NULL);
+
4736 if (fuse_opt_add_arg(&a, "") == -1 ||
+
4737 fuse_opt_add_arg(&a, "-h") == -1)
+
4738 return;
+
4739 printf("\nOptions for %s module:\n", name);
+
4740 (*fac)(&a, NULL);
+ +
4742}
+
4743
+
+
4744void fuse_lib_help(struct fuse_args *args)
+
4745{
+
4746 /* These are not all options, but only the ones that
+
4747 may be of interest to an end-user */
+
4748 printf(
+
4749" -o kernel_cache cache files in kernel\n"
+
4750" -o [no]auto_cache enable caching based on modification times (off)\n"
+
4751" -o no_rofd_flush disable flushing of read-only fd on close (off)\n"
+
4752" -o umask=M set file permissions (octal)\n"
+
4753" -o fmask=M set file permissions (octal)\n"
+
4754" -o dmask=M set dir permissions (octal)\n"
+
4755" -o uid=N set file owner\n"
+
4756" -o gid=N set file group\n"
+
4757" -o entry_timeout=T cache timeout for names (1.0s)\n"
+
4758" -o negative_timeout=T cache timeout for deleted names (0.0s)\n"
+
4759" -o attr_timeout=T cache timeout for attributes (1.0s)\n"
+
4760" -o ac_attr_timeout=T auto cache timeout for attributes (attr_timeout)\n"
+
4761" -o noforget never forget cached inodes\n"
+
4762" -o remember=T remember cached inodes for T seconds (0s)\n"
+
4763" -o modules=M1[:M2...] names of modules to push onto filesystem stack\n");
+
4764
+
4765
+
4766 /* Print low-level help */
+ +
4768
+
4769 /* Print help for builtin modules */
+
4770 print_module_help("subdir", &fuse_module_subdir_factory);
+
4771#ifdef HAVE_ICONV
+
4772 print_module_help("iconv", &fuse_module_iconv_factory);
+
4773#endif
+
4774
+
4775 /* Parse command line options in case we need to
+
4776 activate more modules */
+
4777 struct fuse_config conf = { .modules = NULL };
+
4778 if (fuse_opt_parse(args, &conf, fuse_help_opts,
+
4779 fuse_lib_opt_proc) == -1
+
4780 || !conf.modules)
+
4781 return;
+
4782
+
4783 char *module;
+
4784 char *next;
+
4785 struct fuse_module *m;
+
4786
+
4787 // Iterate over all modules
+
4788 for (module = conf.modules; module; module = next) {
+
4789 char *p;
+
4790 for (p = module; *p && *p != ':'; p++);
+
4791 next = *p ? p + 1 : NULL;
+
4792 *p = '\0';
+
4793
+
4794 m = fuse_get_module(module);
+
4795 if (m)
+
4796 print_module_help(module, &m->factory);
+
4797 }
+
4798}
+
+
4799
+
4800static int fuse_init_intr_signal(int signum, int *installed)
+
4801{
+
4802 struct sigaction old_sa;
+
4803
+
4804 if (sigaction(signum, NULL, &old_sa) == -1) {
+
4805 perror("fuse: cannot get old signal handler");
+
4806 return -1;
+
4807 }
+
4808
+
4809 if (old_sa.sa_handler == SIG_DFL) {
+
4810 struct sigaction sa;
+
4811
+
4812 memset(&sa, 0, sizeof(struct sigaction));
+
4813 sa.sa_handler = fuse_intr_sighandler;
+
4814 sigemptyset(&sa.sa_mask);
+
4815
+
4816 if (sigaction(signum, &sa, NULL) == -1) {
+
4817 perror("fuse: cannot set interrupt signal handler");
+
4818 return -1;
+
4819 }
+
4820 *installed = 1;
+
4821 }
+
4822 return 0;
+
4823}
+
4824
+
4825static void fuse_restore_intr_signal(int signum)
+
4826{
+
4827 struct sigaction sa;
+
4828
+
4829 memset(&sa, 0, sizeof(struct sigaction));
+
4830 sa.sa_handler = SIG_DFL;
+
4831 sigaction(signum, &sa, NULL);
+
4832}
+
4833
+
4834
+
4835static int fuse_push_module(struct fuse *f, const char *module,
+
4836 struct fuse_args *args)
+
4837{
+
4838 struct fuse_fs *fs[2] = { f->fs, NULL };
+
4839 struct fuse_fs *newfs;
+
4840 struct fuse_module *m = fuse_get_module(module);
+
4841
+
4842 if (!m)
+
4843 return -1;
+
4844
+
4845 newfs = m->factory(args, fs);
+
4846 if (!newfs) {
+
4847 fuse_put_module(m);
+
4848 return -1;
+
4849 }
+
4850 f->fs = newfs;
+
4851 return 0;
+
4852}
+
4853
+
+
4854struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
4855 void *user_data)
+
4856{
+
4857 struct fuse_fs *fs;
+
4858
+
4859 if (sizeof(struct fuse_operations) < op_size) {
+
4860 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not not work\n");
+
4861 op_size = sizeof(struct fuse_operations);
+
4862 }
+
4863
+
4864 fs = (struct fuse_fs *) calloc(1, sizeof(struct fuse_fs));
+
4865 if (!fs) {
+
4866 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse_fs object\n");
+
4867 return NULL;
+
4868 }
+
4869
+
4870 fs->user_data = user_data;
+
4871 if (op)
+
4872 memcpy(&fs->op, op, op_size);
+
4873 return fs;
+
4874}
+
+
4875
+
4876static int node_table_init(struct node_table *t)
+
4877{
+
4878 t->size = NODE_TABLE_MIN_SIZE;
+
4879 t->array = (struct node **) calloc(1, sizeof(struct node *) * t->size);
+
4880 if (t->array == NULL) {
+
4881 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
4882 return -1;
+
4883 }
+
4884 t->use = 0;
+
4885 t->split = 0;
+
4886
+
4887 return 0;
+
4888}
+
4889
+
4890static void *fuse_prune_nodes(void *fuse)
+
4891{
+
4892 struct fuse *f = fuse;
+
4893 int sleep_time;
+
4894
+
4895#ifdef HAVE_PTHREAD_SETNAME_NP
+
4896 pthread_setname_np(pthread_self(), "fuse_prune_nodes");
+
4897#endif
+
4898
+
4899 while(1) {
+
4900 sleep_time = fuse_clean_cache(f);
+
4901 sleep(sleep_time);
+
4902 }
+
4903 return NULL;
+
4904}
+
4905
+
+
4906int fuse_start_cleanup_thread(struct fuse *f)
+
4907{
+
4908 if (lru_enabled(f))
+
4909 return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f);
+
4910
+
4911 return 0;
+
4912}
+
+
4913
+
+
4914void fuse_stop_cleanup_thread(struct fuse *f)
+
4915{
+
4916 if (lru_enabled(f)) {
+
4917 pthread_mutex_lock(&f->lock);
+
4918 pthread_cancel(f->prune_thread);
+
4919 pthread_mutex_unlock(&f->lock);
+
4920 pthread_join(f->prune_thread, NULL);
+
4921 }
+
4922}
+
+
4923
+
4924/*
+
4925 * Not supposed to be called directly, but supposed to be called
+
4926 * through the fuse_new macro
+
4927 */
+
4928struct fuse *_fuse_new_31(struct fuse_args *args,
+
4929 const struct fuse_operations *op, size_t op_size,
+
4930 struct libfuse_version *version, void *user_data)
+
4931{
+
4932 struct fuse *f;
+
4933 struct node *root;
+
4934 struct fuse_fs *fs;
+
4935 struct fuse_lowlevel_ops llop = fuse_path_ops;
+
4936
+
4937 f = (struct fuse *) calloc(1, sizeof(struct fuse));
+
4938 if (f == NULL) {
+
4939 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
4940 goto out;
+
4941 }
+
4942
+
4943 f->conf.entry_timeout = 1.0;
+
4944 f->conf.attr_timeout = 1.0;
+
4945 f->conf.negative_timeout = 0.0;
+
4946 f->conf.intr_signal = FUSE_DEFAULT_INTR_SIGNAL;
+
4947
+
4948 /* Parse options */
+
4949 if (fuse_opt_parse(args, &f->conf, fuse_lib_opts,
+
4950 fuse_lib_opt_proc) == -1)
+
4951 goto out_free;
+
4952
+
4953 pthread_mutex_lock(&fuse_context_lock);
+
4954 static int builtin_modules_registered = 0;
+
4955 /* Have the builtin modules already been registered? */
+
4956 if (builtin_modules_registered == 0) {
+
4957 /* If not, register them. */
+
4958 fuse_register_module("subdir", fuse_module_subdir_factory, NULL);
+
4959#ifdef HAVE_ICONV
+
4960 fuse_register_module("iconv", fuse_module_iconv_factory, NULL);
+
4961#endif
+
4962 builtin_modules_registered= 1;
+
4963 }
+
4964 pthread_mutex_unlock(&fuse_context_lock);
+
4965
+
4966 if (fuse_create_context_key() == -1)
+
4967 goto out_free;
+
4968
+
4969 fs = fuse_fs_new(op, op_size, user_data);
+
4970 if (!fs)
+
4971 goto out_delete_context_key;
+
4972
+
4973 f->fs = fs;
+
4974
+
4975 /* Oh f**k, this is ugly! */
+
4976 if (!fs->op.lock) {
+
4977 llop.getlk = NULL;
+
4978 llop.setlk = NULL;
+
4979 }
+
4980
+
4981 f->pagesize = getpagesize();
+
4982 init_list_head(&f->partial_slabs);
+
4983 init_list_head(&f->full_slabs);
+
4984 init_list_head(&f->lru_table);
+
4985
+
4986 if (f->conf.modules) {
+
4987 char *module;
+
4988 char *next;
+
4989
+
4990 for (module = f->conf.modules; module; module = next) {
+
4991 char *p;
+
4992 for (p = module; *p && *p != ':'; p++);
+
4993 next = *p ? p + 1 : NULL;
+
4994 *p = '\0';
+
4995 if (module[0] &&
+
4996 fuse_push_module(f, module, args) == -1)
+
4997 goto out_free_fs;
+
4998 }
+
4999 }
+
5000
+
5001 if (!f->conf.ac_attr_timeout_set)
+
5002 f->conf.ac_attr_timeout = f->conf.attr_timeout;
+
5003
+
5004#if defined(__FreeBSD__) || defined(__NetBSD__)
+
5005 /*
+
5006 * In FreeBSD, we always use these settings as inode numbers
+
5007 * are needed to make getcwd(3) work.
+
5008 */
+
5009 f->conf.readdir_ino = 1;
+
5010#endif
+
5011
+
5012 /* not declared globally, to restrict usage of this function */
+
5013 struct fuse_session *fuse_session_new_versioned(
+
5014 struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
5015 size_t op_size, struct libfuse_version *version,
+
5016 void *userdata);
+
5017 f->se = fuse_session_new_versioned(args, &llop, sizeof(llop), version,
+
5018 f);
+
5019 if (f->se == NULL)
+
5020 goto out_free_fs;
+
5021
+
5022 if (f->conf.debug) {
+
5023 fuse_log(FUSE_LOG_DEBUG, "nullpath_ok: %i\n", f->conf.nullpath_ok);
+
5024 }
+
5025
+
5026 /* Trace topmost layer by default */
+
5027 f->fs->debug = f->conf.debug;
+
5028 f->ctr = 0;
+
5029 f->generation = 0;
+
5030 if (node_table_init(&f->name_table) == -1)
+
5031 goto out_free_session;
+
5032
+
5033 if (node_table_init(&f->id_table) == -1)
+
5034 goto out_free_name_table;
+
5035
+
5036 pthread_mutex_init(&f->lock, NULL);
+
5037
+
5038 root = alloc_node(f);
+
5039 if (root == NULL) {
+
5040 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
5041 goto out_free_id_table;
+
5042 }
+
5043 if (lru_enabled(f)) {
+
5044 struct node_lru *lnode = node_lru(root);
+
5045 init_list_head(&lnode->lru);
+
5046 }
+
5047
+
5048 strcpy(root->inline_name, "/");
+
5049 root->name = root->inline_name;
+
5050 root->parent = NULL;
+
5051 root->nodeid = FUSE_ROOT_ID;
+
5052 inc_nlookup(root);
+
5053 hash_id(f, root);
+
5054
+
5055 return f;
+
5056
+
5057out_free_id_table:
+
5058 free(f->id_table.array);
+
5059out_free_name_table:
+
5060 free(f->name_table.array);
+
5061out_free_session:
+
5062 fuse_session_destroy(f->se);
+
5063out_free_fs:
+
5064 free(f->fs);
+
5065 free(f->conf.modules);
+
5066out_delete_context_key:
+
5067 fuse_delete_context_key();
+
5068out_free:
+
5069 free(f);
+
5070out:
+
5071 return NULL;
+
5072}
+
5073
+
5074/* Emulates 3.0-style fuse_new(), which processes --help */
+
5075FUSE_SYMVER("_fuse_new_30", "_fuse_new@FUSE_3.0")
+
5076struct fuse *_fuse_new_30(struct fuse_args *args,
+
5077 const struct fuse_operations *op,
+
5078 size_t op_size,
+
5079 struct libfuse_version *version,
+
5080 void *user_data)
+
5081{
+
5082 struct fuse_config conf = {0};
+
5083
+
5084 const struct fuse_opt opts[] = {
+
5085 FUSE_LIB_OPT("-h", show_help, 1),
+
5086 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5088 };
+
5089
+
5090 if (fuse_opt_parse(args, &conf, opts,
+
5091 fuse_lib_opt_proc) == -1)
+
5092 return NULL;
+
5093
+
5094 if (conf.show_help) {
+
5095 fuse_lib_help(args);
+
5096 return NULL;
+
5097 } else
+
5098 return _fuse_new_31(args, op, op_size, version, user_data);
+
5099}
+
5100
+
5101/* ABI compat version */
+
5102struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
5103 size_t op_size, void *user_data);
+
5104FUSE_SYMVER("fuse_new_31", "fuse_new@FUSE_3.1")
+
5105struct fuse *fuse_new_31(struct fuse_args *args,
+
5106 const struct fuse_operations *op,
+
5107 size_t op_size, void *user_data)
+
5108{
+
5109 /* unknown version */
+
5110 struct libfuse_version version = { 0 };
+
5111
+
5112 return _fuse_new_31(args, op, op_size, &version, user_data);
+
5113}
+
5114
+
5115/*
+
5116 * ABI compat version
+
5117 * Emulates 3.0-style fuse_new(), which processes --help
+
5118 */
+
5119struct fuse *fuse_new_30(struct fuse_args *args, const struct fuse_operations *op,
+
5120 size_t op_size, void *user_data);
+
5121FUSE_SYMVER("fuse_new_30", "fuse_new@FUSE_3.0")
+
5122struct fuse *fuse_new_30(struct fuse_args *args,
+
5123 const struct fuse_operations *op,
+
5124 size_t op_size, void *user_data)
+
5125{
+
5126 struct fuse_config conf = {0};
+
5127
+
5128 const struct fuse_opt opts[] = {
+
5129 FUSE_LIB_OPT("-h", show_help, 1),
+
5130 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5132 };
+
5133
+
5134 if (fuse_opt_parse(args, &conf, opts,
+
5135 fuse_lib_opt_proc) == -1)
+
5136 return NULL;
+
5137
+
5138 if (conf.show_help) {
+
5139 fuse_lib_help(args);
+
5140 return NULL;
+
5141 } else
+
5142 return fuse_new_31(args, op, op_size, user_data);
+
5143}
+
5144
+
5145
+
+
5146void fuse_destroy(struct fuse *f)
+
5147{
+
5148 size_t i;
+
5149
+
5150 if (f->conf.intr && f->intr_installed)
+
5151 fuse_restore_intr_signal(f->conf.intr_signal);
+
5152
+
5153 if (f->fs) {
+
5154 fuse_create_context(f);
+
5155
+
5156 for (i = 0; i < f->id_table.size; i++) {
+
5157 struct node *node;
+
5158
+
5159 for (node = f->id_table.array[i]; node != NULL;
+
5160 node = node->id_next) {
+
5161 if (node->is_hidden) {
+
5162 char *path;
+
5163 if (try_get_path(f, node->nodeid, NULL, &path, NULL, false) == 0) {
+
5164 fuse_fs_unlink(f->fs, path);
+
5165 free(path);
+
5166 }
+
5167 }
+
5168 }
+
5169 }
+
5170 }
+
5171 for (i = 0; i < f->id_table.size; i++) {
+
5172 struct node *node;
+
5173 struct node *next;
+
5174
+
5175 for (node = f->id_table.array[i]; node != NULL; node = next) {
+
5176 next = node->id_next;
+
5177 free_node(f, node);
+
5178 f->id_table.use--;
+
5179 }
+
5180 }
+
5181 assert(list_empty(&f->partial_slabs));
+
5182 assert(list_empty(&f->full_slabs));
+
5183
+
5184 while (fuse_modules) {
+
5185 fuse_put_module(fuse_modules);
+
5186 }
+
5187 free(f->id_table.array);
+
5188 free(f->name_table.array);
+
5189 pthread_mutex_destroy(&f->lock);
+
5190 fuse_session_destroy(f->se);
+
5191 free(f->fs);
+
5192 free(f->conf.modules);
+
5193 free(f);
+
5194 fuse_delete_context_key();
+
5195}
+
+
5196
+
+
5197int fuse_mount(struct fuse *f, const char *mountpoint) {
+
5198 return fuse_session_mount(fuse_get_session(f), mountpoint);
+
5199}
+
+
5200
+
5201
+
+
5202void fuse_unmount(struct fuse *f) {
+ +
5204}
+
+
5205
+
+ +
5207{
+
5208 return FUSE_VERSION;
+
5209}
+
+
5210
+
+
5211const char *fuse_pkgversion(void)
+
5212{
+
5213 return PACKAGE_VERSION;
+
5214}
+
+
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4654
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5197
+
int fuse_interrupted(void)
Definition fuse.c:4663
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5146
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4906
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1383
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4639
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4433
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5202
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4914
+
fuse_fill_dir_flags
Definition fuse.h:58
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_SPLICE_READ
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_IS_FD
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
int fuse_version(void)
Definition fuse.c:5206
+
@ FUSE_BUF_SPLICE_MOVE
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+ + +
enum fuse_buf_flags flags
+
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + + +
int32_t show_help
Definition fuse.h:279
+ +
uint32_t no_interrupt
+ +
void * private_data
Definition fuse.h:874
+ + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:60
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t noflush
Definition fuse_common.h:93
+
uint32_t flush
Definition fuse_common.h:74
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + + +
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2fuse__i_8h_source.html b/doc/html/fuse-3_817_84_2lib_2fuse__i_8h_source.html new file mode 100644 index 0000000..be1c68d --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2fuse__i_8h_source.html @@ -0,0 +1,264 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/fuse_i.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_i.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include "fuse.h"
+
10#include "fuse_lowlevel.h"
+
11#include "util.h"
+
12
+
13#include <stdint.h>
+
14#include <stdbool.h>
+
15#include <errno.h>
+
16#include <stdatomic.h>
+
17
+
18#define MIN(a, b) \
+
19({ \
+
20 typeof(a) _a = (a); \
+
21 typeof(b) _b = (b); \
+
22 _a < _b ? _a : _b; \
+
23})
+
24
+
25struct mount_opts;
+
26
+
27struct fuse_req {
+
28 struct fuse_session *se;
+
29 uint64_t unique;
+
30 _Atomic int ref_cnt;
+
31 pthread_mutex_t lock;
+
32 struct fuse_ctx ctx;
+
33 struct fuse_chan *ch;
+
34 int interrupted;
+
35 unsigned int ioctl_64bit : 1;
+
36 union {
+
37 struct {
+
38 uint64_t unique;
+
39 } i;
+
40 struct {
+ +
42 void *data;
+
43 } ni;
+
44 } u;
+
45 struct fuse_req *next;
+
46 struct fuse_req *prev;
+
47};
+
48
+
49struct fuse_notify_req {
+
50 uint64_t unique;
+
51 void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t,
+
52 const void *, const struct fuse_buf *);
+
53 struct fuse_notify_req *next;
+
54 struct fuse_notify_req *prev;
+
55};
+
56
+
57struct fuse_session {
+
58 _Atomic(char *)mountpoint;
+
59 volatile int exited;
+
60 int fd;
+
61 struct fuse_custom_io *io;
+
62 struct mount_opts *mo;
+
63 int debug;
+
64 int deny_others;
+
65 struct fuse_lowlevel_ops op;
+
66 int got_init;
+
67 struct cuse_data *cuse_data;
+
68 void *userdata;
+
69 uid_t owner;
+
70 struct fuse_conn_info conn;
+
71 struct fuse_req list;
+
72 struct fuse_req interrupts;
+
73 pthread_mutex_t lock;
+
74 int got_destroy;
+
75 pthread_key_t pipe_key;
+
76 int broken_splice_nonblock;
+
77 uint64_t notify_ctr;
+
78 struct fuse_notify_req notify_list;
+
79 _Atomic size_t bufsize;
+
80 int error;
+
81
+
82 /* This is useful if any kind of ABI incompatibility is found at
+
83 * a later version, to 'fix' it at run time.
+
84 */
+
85 struct libfuse_version version;
+
86
+
87 /* true if reading requests from /dev/fuse are handled internally */
+
88 bool buf_reallocable;
+
89
+
90 /*
+
91 * conn->want and conn_want_ext options set by libfuse , needed
+
92 * to correctly convert want to want_ext
+
93 */
+
94 uint32_t conn_want;
+
95 uint64_t conn_want_ext;
+
96};
+
97
+
98struct fuse_chan {
+
99 pthread_mutex_t lock;
+
100 int ctr;
+
101 int fd;
+
102};
+
103
+
+ +
112 char *name;
+
113 fuse_module_factory_t factory;
+
114 struct fuse_module *next;
+
115 struct fusemod_so *so;
+
116 int ctr;
+
117};
+
+
118
+
127#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
128struct fuse_loop_config
+
129{
+
130 /* verififier that a correct struct was was passed. This is especially
+
131 * needed, as versions below (3, 12) were using a public struct
+
132 * (now called fuse_loop_config_v1), which was hard to extend with
+
133 * additional parameters, without risking that file system implementations
+
134 * would not have noticed and might either pass uninitialized members
+
135 * or even too small structs.
+
136 * fuse_loop_config_v1 has clone_fd at this offset, which should be either 0
+
137 * or 1. v2 or even higher version just need to set a value here
+
138 * which not conflicting and very unlikely as having been set by
+
139 * file system implementation.
+
140 */
+
141 int version_id;
+
142
+
147 int clone_fd;
+ +
160
+
166 unsigned int max_threads;
+
167};
+
168#endif
+
169
+
170/* ----------------------------------------------------------- *
+
171 * Channel interface (when using -o clone_fd) *
+
172 * ----------------------------------------------------------- */
+
173
+
180struct fuse_chan *fuse_chan_get(struct fuse_chan *ch);
+
181
+
187void fuse_chan_put(struct fuse_chan *ch);
+
188
+
189struct mount_opts *parse_mount_opts(struct fuse_args *args);
+
190void destroy_mount_opts(struct mount_opts *mo);
+
191void fuse_mount_version(void);
+
192unsigned get_max_read(struct mount_opts *o);
+
193void fuse_kern_unmount(const char *mountpoint, int fd);
+
194int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo);
+
195
+
196int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
197 int count);
+
198void fuse_free_req(fuse_req_t req);
+
199
+
200void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg);
+
201
+
202int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg);
+
203
+
204void fuse_buf_free(struct fuse_buf *buf);
+
205
+
206int fuse_session_receive_buf_internal(struct fuse_session *se,
+
207 struct fuse_buf *buf,
+
208 struct fuse_chan *ch);
+
209void fuse_session_process_buf_internal(struct fuse_session *se,
+
210 const struct fuse_buf *buf,
+
211 struct fuse_chan *ch);
+
212
+
213struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
214 size_t op_size, void *private_data);
+
215int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config);
+
216int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
217
+
223int fuse_loop_cfg_verify(struct fuse_loop_config *config);
+
224
+
225
+
226/*
+
227 * This can be changed dynamically on recent kernels through the
+
228 * /proc/sys/fs/fuse/max_pages_limit interface.
+
229 *
+
230 * Older kernels will always use the default value.
+
231 */
+
232#define FUSE_DEFAULT_MAX_PAGES_LIMIT 256
+
233#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32
+
234
+
235/* room needed in buffer to accommodate header */
+
236#define FUSE_BUFFER_HEADER_SIZE 0x1000
+
237
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1383
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
struct fuse_req * fuse_req_t
+
uint64_t fuse_ino_t
+ + + + + + +
unsigned int max_threads
Definition fuse_i.h:166
+
int max_idle_threads
Definition fuse_i.h:159
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2fuse__log_8c_source.html b/doc/html/fuse-3_817_84_2lib_2fuse__log_8c_source.html new file mode 100644 index 0000000..d51fc59 --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2fuse__log_8c_source.html @@ -0,0 +1,172 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/fuse_log.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_log.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB
+
9*/
+
10
+
11#include "fuse_log.h"
+
12
+
13#include <stdarg.h>
+
14#include <stdio.h>
+
15#include <stdbool.h>
+
16#include <syslog.h>
+
17
+
18#define MAX_SYSLOG_LINE_LEN 512
+
19
+
20static bool to_syslog = false;
+
21
+
22static void default_log_func(__attribute__((unused)) enum fuse_log_level level,
+
23 const char *fmt, va_list ap)
+
24{
+
25 if (to_syslog) {
+
26 int sys_log_level = LOG_ERR;
+
27
+
28 /*
+
29 * with glibc fuse_log_level has identical values as
+
30 * syslog levels, but we also support BSD - better we convert to
+
31 * be sure.
+
32 */
+
33 switch (level) {
+
34 case FUSE_LOG_DEBUG:
+
35 sys_log_level = LOG_DEBUG;
+
36 break;
+
37 case FUSE_LOG_INFO:
+
38 sys_log_level = LOG_INFO;
+
39 break;
+
40 case FUSE_LOG_NOTICE:
+
41 sys_log_level = LOG_NOTICE;
+
42 break;
+
43 case FUSE_LOG_WARNING:
+
44 sys_log_level = LOG_WARNING;
+
45 break;
+
46 case FUSE_LOG_ERR:
+
47 sys_log_level = LOG_ERR;
+
48 break;
+
49 case FUSE_LOG_CRIT:
+
50 sys_log_level = LOG_CRIT;
+
51 break;
+
52 case FUSE_LOG_ALERT:
+
53 sys_log_level = LOG_ALERT;
+
54 break;
+
55 case FUSE_LOG_EMERG:
+
56 sys_log_level = LOG_EMERG;
+
57 }
+
58
+
59 char log[MAX_SYSLOG_LINE_LEN];
+
60 vsnprintf(log, MAX_SYSLOG_LINE_LEN, fmt, ap);
+
61 syslog(sys_log_level, "%s", log);
+
62 } else {
+
63 vfprintf(stderr, fmt, ap);
+
64 }
+
65}
+
66
+
67static fuse_log_func_t log_func = default_log_func;
+
68
+
+ +
70{
+
71 if (!func)
+
72 func = default_log_func;
+
73
+
74 log_func = func;
+
75}
+
+
+ +
+
77void fuse_log(enum fuse_log_level level, const char *fmt, ...)
+
78{
+
79 va_list ap;
+
80
+
81 va_start(ap, fmt);
+
82 log_func(level, fmt, ap);
+
83 va_end(ap);
+
84}
+
+
+
85
+
+
86void fuse_log_enable_syslog(const char *ident, int option, int facility)
+
87{
+
88 to_syslog = true;
+
89
+
90 openlog(ident, option, facility);
+
91}
+
+
92
+
+ +
94{
+
95 closelog();
+
96}
+
+
void fuse_log_close_syslog(void)
Definition fuse_log.c:93
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:86
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:69
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2fuse__loop_8c_source.html b/doc/html/fuse-3_817_84_2lib_2fuse__loop_8c_source.html new file mode 100644 index 0000000..430b8ec --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2fuse__loop_8c_source.html @@ -0,0 +1,114 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/fuse_loop.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_loop.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the single-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14
+
15#include <stdio.h>
+
16#include <stdlib.h>
+
17#include <errno.h>
+
18
+
+
19int fuse_session_loop(struct fuse_session *se)
+
20{
+
21 int res = 0;
+
22 struct fuse_buf fbuf = {
+
23 .mem = NULL,
+
24 };
+
25
+
26 while (!fuse_session_exited(se)) {
+
27 res = fuse_session_receive_buf_internal(se, &fbuf, NULL);
+
28
+
29 if (res == -EINTR)
+
30 continue;
+
31 if (res <= 0)
+
32 break;
+
33
+
34 fuse_session_process_buf(se, &fbuf);
+
35 }
+
36
+
37 fuse_buf_free(&fbuf);
+
38 if(res > 0)
+
39 /* No error, just the length of the most recently read
+
40 request */
+
41 res = 0;
+
42 if(se->error != 0)
+
43 res = se->error;
+ +
45 return res;
+
46}
+
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_reset(struct fuse_session *se)
+ +
void * mem
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2fuse__loop__mt_8c_source.html b/doc/html/fuse-3_817_84_2lib_2fuse__loop__mt_8c_source.html new file mode 100644 index 0000000..45e6f89 --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2fuse__loop__mt_8c_source.html @@ -0,0 +1,607 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/fuse_loop_mt.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_loop_mt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the multi-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11#define _GNU_SOURCE
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_lowlevel.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_kernel.h"
+
17#include "fuse_i.h"
+
18#include "util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <string.h>
+
23#include <unistd.h>
+
24#include <signal.h>
+
25#include <semaphore.h>
+
26#include <errno.h>
+
27#include <sys/time.h>
+
28#include <sys/ioctl.h>
+
29#include <assert.h>
+
30#include <limits.h>
+
31
+
32/* Environment var controlling the thread stack size */
+
33#define ENVNAME_THREAD_STACK "FUSE_THREAD_STACK"
+
34
+
35#define FUSE_LOOP_MT_V2_IDENTIFIER INT_MAX - 2
+
36#define FUSE_LOOP_MT_DEF_CLONE_FD 0
+
37#define FUSE_LOOP_MT_DEF_MAX_THREADS 10
+
38#define FUSE_LOOP_MT_DEF_IDLE_THREADS -1 /* thread destruction is disabled
+
39 * by default */
+
40
+
41/* an arbitrary large value that cannot be valid */
+
42#define FUSE_LOOP_MT_MAX_THREADS (100U * 1000)
+
43
+
44struct fuse_worker {
+
45 struct fuse_worker *prev;
+
46 struct fuse_worker *next;
+
47 pthread_t thread_id;
+
48
+
49 // We need to include fuse_buf so that we can properly free
+
50 // it when a thread is terminated by pthread_cancel().
+
51 struct fuse_buf fbuf;
+
52 struct fuse_chan *ch;
+
53 struct fuse_mt *mt;
+
54};
+
55
+
56struct fuse_mt {
+
57 pthread_mutex_t lock;
+
58 int numworker;
+
59 int numavail;
+
60 struct fuse_session *se;
+
61 struct fuse_worker main;
+
62 sem_t finish;
+
63 int exit;
+
64 int error;
+
65 int clone_fd;
+
66 int max_idle;
+
67 int max_threads;
+
68};
+
69
+
70static struct fuse_chan *fuse_chan_new(int fd)
+
71{
+
72 struct fuse_chan *ch = (struct fuse_chan *) malloc(sizeof(*ch));
+
73 if (ch == NULL) {
+
74 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate channel\n");
+
75 return NULL;
+
76 }
+
77
+
78 memset(ch, 0, sizeof(*ch));
+
79 ch->fd = fd;
+
80 ch->ctr = 1;
+
81 pthread_mutex_init(&ch->lock, NULL);
+
82
+
83 return ch;
+
84}
+
85
+
86struct fuse_chan *fuse_chan_get(struct fuse_chan *ch)
+
87{
+
88 assert(ch->ctr > 0);
+
89 pthread_mutex_lock(&ch->lock);
+
90 ch->ctr++;
+
91 pthread_mutex_unlock(&ch->lock);
+
92
+
93 return ch;
+
94}
+
95
+
96void fuse_chan_put(struct fuse_chan *ch)
+
97{
+
98 if (ch == NULL)
+
99 return;
+
100 pthread_mutex_lock(&ch->lock);
+
101 ch->ctr--;
+
102 if (!ch->ctr) {
+
103 pthread_mutex_unlock(&ch->lock);
+
104 close(ch->fd);
+
105 pthread_mutex_destroy(&ch->lock);
+
106 free(ch);
+
107 } else
+
108 pthread_mutex_unlock(&ch->lock);
+
109}
+
110
+
111static void list_add_worker(struct fuse_worker *w, struct fuse_worker *next)
+
112{
+
113 struct fuse_worker *prev = next->prev;
+
114 w->next = next;
+
115 w->prev = prev;
+
116 prev->next = w;
+
117 next->prev = w;
+
118}
+
119
+
120static void list_del_worker(struct fuse_worker *w)
+
121{
+
122 struct fuse_worker *prev = w->prev;
+
123 struct fuse_worker *next = w->next;
+
124 prev->next = next;
+
125 next->prev = prev;
+
126}
+
127
+
128static int fuse_loop_start_thread(struct fuse_mt *mt);
+
129
+
130static void *fuse_do_work(void *data)
+
131{
+
132 struct fuse_worker *w = (struct fuse_worker *) data;
+
133 struct fuse_mt *mt = w->mt;
+
134
+
135#ifdef HAVE_PTHREAD_SETNAME_NP
+
136 pthread_setname_np(pthread_self(), "fuse_worker");
+
137#endif
+
138
+
139 while (!fuse_session_exited(mt->se)) {
+
140 int isforget = 0;
+
141 int res;
+
142
+
143 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
144 res = fuse_session_receive_buf_internal(mt->se, &w->fbuf,
+
145 w->ch);
+
146 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+
147 if (res == -EINTR)
+
148 continue;
+
149 if (res <= 0) {
+
150 if (res < 0) {
+
151 fuse_session_exit(mt->se);
+
152 mt->error = res;
+
153 }
+
154 break;
+
155 }
+
156
+
157 pthread_mutex_lock(&mt->lock);
+
158 if (mt->exit) {
+
159 pthread_mutex_unlock(&mt->lock);
+
160 return NULL;
+
161 }
+
162
+
163 /*
+
164 * This disgusting hack is needed so that zillions of threads
+
165 * are not created on a burst of FORGET messages
+
166 */
+
167 if (!(w->fbuf.flags & FUSE_BUF_IS_FD)) {
+
168 struct fuse_in_header *in = w->fbuf.mem;
+
169
+
170 if (in->opcode == FUSE_FORGET ||
+
171 in->opcode == FUSE_BATCH_FORGET)
+
172 isforget = 1;
+
173 }
+
174
+
175 if (!isforget)
+
176 mt->numavail--;
+
177 if (mt->numavail == 0 && mt->numworker < mt->max_threads)
+
178 fuse_loop_start_thread(mt);
+
179 pthread_mutex_unlock(&mt->lock);
+
180
+
181 fuse_session_process_buf_internal(mt->se, &w->fbuf, w->ch);
+
182
+
183 pthread_mutex_lock(&mt->lock);
+
184 if (!isforget)
+
185 mt->numavail++;
+
186
+
187 /* creating and destroying threads is rather expensive - and there is
+
188 * not much gain from destroying existing threads. It is therefore
+
189 * discouraged to set max_idle to anything else than -1. If there
+
190 * is indeed a good reason to destruct threads it should be done
+
191 * delayed, a moving average might be useful for that.
+
192 */
+
193 if (mt->max_idle != -1 && mt->numavail > mt->max_idle && mt->numworker > 1) {
+
194 if (mt->exit) {
+
195 pthread_mutex_unlock(&mt->lock);
+
196 return NULL;
+
197 }
+
198 list_del_worker(w);
+
199 mt->numavail--;
+
200 mt->numworker--;
+
201 pthread_mutex_unlock(&mt->lock);
+
202
+
203 pthread_detach(w->thread_id);
+
204 fuse_buf_free(&w->fbuf);
+
205 fuse_chan_put(w->ch);
+
206 free(w);
+
207 return NULL;
+
208 }
+
209 pthread_mutex_unlock(&mt->lock);
+
210 }
+
211
+
212 sem_post(&mt->finish);
+
213
+
214 return NULL;
+
215}
+
216
+
217int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg)
+
218{
+
219 sigset_t oldset;
+
220 sigset_t newset;
+
221 int res;
+
222 pthread_attr_t attr;
+
223 char *stack_size;
+
224
+
225 /* Override default stack size
+
226 * XXX: This should ideally be a parameter option. It is rather
+
227 * well hidden here.
+
228 */
+
229 pthread_attr_init(&attr);
+
230 stack_size = getenv(ENVNAME_THREAD_STACK);
+
231 if (stack_size) {
+
232 long size;
+
233
+
234 res = libfuse_strtol(stack_size, &size);
+
235 if (res)
+
236 fuse_log(FUSE_LOG_ERR, "fuse: invalid stack size: %s\n",
+
237 stack_size);
+
238 else if (pthread_attr_setstacksize(&attr, size))
+
239 fuse_log(FUSE_LOG_ERR, "fuse: could not set stack size: %ld\n",
+
240 size);
+
241 }
+
242
+
243 /* Disallow signal reception in worker threads */
+
244 sigemptyset(&newset);
+
245 sigaddset(&newset, SIGTERM);
+
246 sigaddset(&newset, SIGINT);
+
247 sigaddset(&newset, SIGHUP);
+
248 sigaddset(&newset, SIGQUIT);
+
249 pthread_sigmask(SIG_BLOCK, &newset, &oldset);
+
250 res = pthread_create(thread_id, &attr, func, arg);
+
251 pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+
252 pthread_attr_destroy(&attr);
+
253 if (res != 0) {
+
254 fuse_log(FUSE_LOG_ERR, "fuse: error creating thread: %s\n",
+
255 strerror(res));
+
256 return -1;
+
257 }
+
258
+
259 return 0;
+
260}
+
261
+
262static int fuse_clone_chan_fd_default(struct fuse_session *se)
+
263{
+
264 int res;
+
265 int clonefd;
+
266 uint32_t masterfd;
+
267 const char *devname = "/dev/fuse";
+
268
+
269#ifndef O_CLOEXEC
+
270#define O_CLOEXEC 0
+
271#endif
+
272 clonefd = open(devname, O_RDWR | O_CLOEXEC);
+
273 if (clonefd == -1) {
+
274 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n", devname,
+
275 strerror(errno));
+
276 return -1;
+
277 }
+
278 if (!O_CLOEXEC) {
+
279 res = fcntl(clonefd, F_SETFD, FD_CLOEXEC);
+
280 if (res == -1) {
+
281 fuse_log(FUSE_LOG_ERR, "fuse: failed to set CLOEXEC: %s\n",
+
282 strerror(errno));
+
283 close(clonefd);
+
284 return -1;
+
285 }
+
286 }
+
287
+
288 masterfd = se->fd;
+
289 res = ioctl(clonefd, FUSE_DEV_IOC_CLONE, &masterfd);
+
290 if (res == -1) {
+
291 fuse_log(FUSE_LOG_ERR, "fuse: failed to clone device fd: %s\n",
+
292 strerror(errno));
+
293 close(clonefd);
+
294 return -1;
+
295 }
+
296 return clonefd;
+
297}
+
298
+
299static struct fuse_chan *fuse_clone_chan(struct fuse_mt *mt)
+
300{
+
301 int clonefd;
+
302 struct fuse_session *se = mt->se;
+
303 struct fuse_chan *newch;
+
304
+
305 if (se->io != NULL) {
+
306 if (se->io->clone_fd != NULL)
+
307 clonefd = se->io->clone_fd(se->fd);
+
308 else
+
309 return NULL;
+
310 } else {
+
311 clonefd = fuse_clone_chan_fd_default(se);
+
312 }
+
313 if (clonefd < 0)
+
314 return NULL;
+
315
+
316 newch = fuse_chan_new(clonefd);
+
317 if (newch == NULL)
+
318 close(clonefd);
+
319
+
320 return newch;
+
321}
+
322
+
323static int fuse_loop_start_thread(struct fuse_mt *mt)
+
324{
+
325 int res;
+
326
+
327 struct fuse_worker *w = malloc(sizeof(struct fuse_worker));
+
328 if (!w) {
+
329 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate worker structure\n");
+
330 return -1;
+
331 }
+
332 memset(w, 0, sizeof(struct fuse_worker));
+
333 w->fbuf.mem = NULL;
+
334 w->mt = mt;
+
335
+
336 w->ch = NULL;
+
337 if (mt->clone_fd) {
+
338 w->ch = fuse_clone_chan(mt);
+
339 if(!w->ch) {
+
340 /* Don't attempt this again */
+
341 fuse_log(FUSE_LOG_ERR, "fuse: trying to continue "
+
342 "without -o clone_fd.\n");
+
343 mt->clone_fd = 0;
+
344 }
+
345 }
+
346
+
347 res = fuse_start_thread(&w->thread_id, fuse_do_work, w);
+
348 if (res == -1) {
+
349 fuse_chan_put(w->ch);
+
350 free(w);
+
351 return -1;
+
352 }
+
353 list_add_worker(w, &mt->main);
+
354 mt->numavail ++;
+
355 mt->numworker ++;
+
356
+
357 return 0;
+
358}
+
359
+
360static void fuse_join_worker(struct fuse_mt *mt, struct fuse_worker *w)
+
361{
+
362 pthread_join(w->thread_id, NULL);
+
363 pthread_mutex_lock(&mt->lock);
+
364 list_del_worker(w);
+
365 pthread_mutex_unlock(&mt->lock);
+
366 fuse_buf_free(&w->fbuf);
+
367 fuse_chan_put(w->ch);
+
368 free(w);
+
369}
+
370
+
371int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
372FUSE_SYMVER("fuse_session_loop_mt_312", "fuse_session_loop_mt@@FUSE_3.12")
+
373int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config)
+
374{
+
375int err;
+
376 struct fuse_mt mt;
+
377 struct fuse_worker *w;
+
378 int created_config = 0;
+
379
+
380 if (config) {
+
381 err = fuse_loop_cfg_verify(config);
+
382 if (err)
+
383 return err;
+
384 } else {
+
385 /* The caller does not care about parameters - use the default */
+
386 config = fuse_loop_cfg_create();
+
387 created_config = 1;
+
388 }
+
389
+
390
+
391 memset(&mt, 0, sizeof(struct fuse_mt));
+
392 mt.se = se;
+
393 mt.clone_fd = config->clone_fd;
+
394 mt.error = 0;
+
395 mt.numworker = 0;
+
396 mt.numavail = 0;
+
397 mt.max_idle = config->max_idle_threads;
+
398 mt.max_threads = config->max_threads;
+
399 mt.main.thread_id = pthread_self();
+
400 mt.main.prev = mt.main.next = &mt.main;
+
401 sem_init(&mt.finish, 0, 0);
+
402 pthread_mutex_init(&mt.lock, NULL);
+
403
+
404 pthread_mutex_lock(&mt.lock);
+
405 err = fuse_loop_start_thread(&mt);
+
406 pthread_mutex_unlock(&mt.lock);
+
407 if (!err) {
+
408 /* sem_wait() is interruptible */
+
409 while (!fuse_session_exited(se))
+
410 sem_wait(&mt.finish);
+
411
+
412 pthread_mutex_lock(&mt.lock);
+
413 for (w = mt.main.next; w != &mt.main; w = w->next)
+
414 pthread_cancel(w->thread_id);
+
415 mt.exit = 1;
+
416 pthread_mutex_unlock(&mt.lock);
+
417
+
418 while (mt.main.next != &mt.main)
+
419 fuse_join_worker(&mt, mt.main.next);
+
420
+
421 err = mt.error;
+
422 }
+
423
+
424 pthread_mutex_destroy(&mt.lock);
+
425 sem_destroy(&mt.finish);
+
426 if(se->error != 0)
+
427 err = se->error;
+ +
429
+
430 if (created_config) {
+
431 fuse_loop_cfg_destroy(config);
+
432 config = NULL;
+
433 }
+
434
+
435 return err;
+
436}
+
437
+
438int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1);
+
439FUSE_SYMVER("fuse_session_loop_mt_32", "fuse_session_loop_mt@FUSE_3.2")
+
440int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1)
+
441{
+
442 int err;
+
443 struct fuse_loop_config *config = NULL;
+
444
+
445 if (config_v1 != NULL) {
+
446 /* convert the given v1 config */
+
447 config = fuse_loop_cfg_create();
+
448 if (config == NULL)
+
449 return ENOMEM;
+
450
+
451 fuse_loop_cfg_convert(config, config_v1);
+
452 }
+
453
+
454 err = fuse_session_loop_mt_312(se, config);
+
455
+
456 fuse_loop_cfg_destroy(config);
+
457
+
458 return err;
+
459}
+
460
+
461
+
462int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
463FUSE_SYMVER("fuse_session_loop_mt_31", "fuse_session_loop_mt@FUSE_3.0")
+
464int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd)
+
465{
+
466 int err;
+
467 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
468 if (clone_fd > 0)
+
469 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
470 err = fuse_session_loop_mt_312(se, config);
+
471
+
472 fuse_loop_cfg_destroy(config);
+
473
+
474 return err;
+
475}
+
476
+
477struct fuse_loop_config *fuse_loop_cfg_create(void)
+
478{
+
479 struct fuse_loop_config *config = calloc(1, sizeof(*config));
+
480 if (config == NULL)
+
481 return NULL;
+
482
+
483 config->version_id = FUSE_LOOP_MT_V2_IDENTIFIER;
+
484 config->max_idle_threads = FUSE_LOOP_MT_DEF_IDLE_THREADS;
+
485 config->max_threads = FUSE_LOOP_MT_DEF_MAX_THREADS;
+
486 config->clone_fd = FUSE_LOOP_MT_DEF_CLONE_FD;
+
487
+
488 return config;
+
489}
+
490
+
491void fuse_loop_cfg_destroy(struct fuse_loop_config *config)
+
492{
+
493 free(config);
+
494}
+
495
+
496int fuse_loop_cfg_verify(struct fuse_loop_config *config)
+
497{
+
498 if (config->version_id != FUSE_LOOP_MT_V2_IDENTIFIER)
+
499 return -EINVAL;
+
500
+
501 return 0;
+
502}
+
503
+
504void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
505 struct fuse_loop_config_v1 *v1_conf)
+
506{
+
507 fuse_loop_cfg_set_idle_threads(config, v1_conf->max_idle_threads);
+
508
+
509 fuse_loop_cfg_set_clone_fd(config, v1_conf->clone_fd);
+
510}
+
511
+
512void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
513 unsigned int value)
+
514{
+
515 if (value > FUSE_LOOP_MT_MAX_THREADS) {
+
516 if (value != UINT_MAX)
+
517 fuse_log(FUSE_LOG_ERR,
+
518 "Ignoring invalid max threads value "
+
519 "%u > max (%u).\n", value,
+
520 FUSE_LOOP_MT_MAX_THREADS);
+
521 return;
+
522 }
+
523 config->max_idle_threads = value;
+
524}
+
525
+
526void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
527 unsigned int value)
+
528{
+
529 config->max_threads = value;
+
530}
+
531
+
532void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
533 unsigned int value)
+
534{
+
535 config->clone_fd = value;
+
536}
+
537
+
@ FUSE_BUF_IS_FD
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_exited(struct fuse_session *se)
+
void fuse_session_reset(struct fuse_session *se)
+ + + +
unsigned int max_threads
Definition fuse_i.h:166
+
unsigned int max_idle_threads
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2fuse__lowlevel_8c_source.html b/doc/html/fuse-3_817_84_2lib_2fuse__lowlevel_8c_source.html new file mode 100644 index 0000000..006f38e --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2fuse__lowlevel_8c_source.html @@ -0,0 +1,3916 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/fuse_lowlevel.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_lowlevel.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of (most of) the low-level FUSE API. The session loop
+
6 functions are implemented in separate files.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_kernel.h"
+
17#include "fuse_opt.h"
+
18#include "fuse_misc.h"
+
19#include "mount_util.h"
+
20#include "util.h"
+
21
+
22#include <stdint.h>
+
23#include <stdbool.h>
+
24#include <stdio.h>
+
25#include <stdlib.h>
+
26#include <stddef.h>
+
27#include <stdalign.h>
+
28#include <string.h>
+
29#include <unistd.h>
+
30#include <limits.h>
+
31#include <errno.h>
+
32#include <assert.h>
+
33#include <sys/file.h>
+
34#include <sys/ioctl.h>
+
35
+
36#ifndef F_LINUX_SPECIFIC_BASE
+
37#define F_LINUX_SPECIFIC_BASE 1024
+
38#endif
+
39#ifndef F_SETPIPE_SZ
+
40#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7)
+
41#endif
+
42
+
43
+
44#define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg)))
+
45#define OFFSET_MAX 0x7fffffffffffffffLL
+
46
+
47struct fuse_pollhandle {
+
48 uint64_t kh;
+
49 struct fuse_session *se;
+
50};
+
51
+
52static size_t pagesize;
+
53
+
54static __attribute__((constructor)) void fuse_ll_init_pagesize(void)
+
55{
+
56 pagesize = getpagesize();
+
57}
+
58
+
59static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr)
+
60{
+
61 attr->ino = stbuf->st_ino;
+
62 attr->mode = stbuf->st_mode;
+
63 attr->nlink = stbuf->st_nlink;
+
64 attr->uid = stbuf->st_uid;
+
65 attr->gid = stbuf->st_gid;
+
66 attr->rdev = stbuf->st_rdev;
+
67 attr->size = stbuf->st_size;
+
68 attr->blksize = stbuf->st_blksize;
+
69 attr->blocks = stbuf->st_blocks;
+
70 attr->atime = stbuf->st_atime;
+
71 attr->mtime = stbuf->st_mtime;
+
72 attr->ctime = stbuf->st_ctime;
+
73 attr->atimensec = ST_ATIM_NSEC(stbuf);
+
74 attr->mtimensec = ST_MTIM_NSEC(stbuf);
+
75 attr->ctimensec = ST_CTIM_NSEC(stbuf);
+
76}
+
77
+
78static void convert_attr(const struct fuse_setattr_in *attr, struct stat *stbuf)
+
79{
+
80 stbuf->st_mode = attr->mode;
+
81 stbuf->st_uid = attr->uid;
+
82 stbuf->st_gid = attr->gid;
+
83 stbuf->st_size = attr->size;
+
84 stbuf->st_atime = attr->atime;
+
85 stbuf->st_mtime = attr->mtime;
+
86 stbuf->st_ctime = attr->ctime;
+
87 ST_ATIM_NSEC_SET(stbuf, attr->atimensec);
+
88 ST_MTIM_NSEC_SET(stbuf, attr->mtimensec);
+
89 ST_CTIM_NSEC_SET(stbuf, attr->ctimensec);
+
90}
+
91
+
92static size_t iov_length(const struct iovec *iov, size_t count)
+
93{
+
94 size_t seg;
+
95 size_t ret = 0;
+
96
+
97 for (seg = 0; seg < count; seg++)
+
98 ret += iov[seg].iov_len;
+
99 return ret;
+
100}
+
101
+
102static void list_init_req(struct fuse_req *req)
+
103{
+
104 req->next = req;
+
105 req->prev = req;
+
106}
+
107
+
108static void list_del_req(struct fuse_req *req)
+
109{
+
110 struct fuse_req *prev = req->prev;
+
111 struct fuse_req *next = req->next;
+
112 prev->next = next;
+
113 next->prev = prev;
+
114}
+
115
+
116static void list_add_req(struct fuse_req *req, struct fuse_req *next)
+
117{
+
118 struct fuse_req *prev = next->prev;
+
119 req->next = next;
+
120 req->prev = prev;
+
121 prev->next = req;
+
122 next->prev = req;
+
123}
+
124
+
125static void destroy_req(fuse_req_t req)
+
126{
+
127 assert(req->ch == NULL);
+
128 pthread_mutex_destroy(&req->lock);
+
129 free(req);
+
130}
+
131
+
132void fuse_free_req(fuse_req_t req)
+
133{
+
134 int ctr;
+
135 struct fuse_session *se = req->se;
+
136
+
137 if (se->conn.no_interrupt) {
+
138 ctr = --req->ref_cnt;
+
139 fuse_chan_put(req->ch);
+
140 req->ch = NULL;
+
141 } else {
+
142 pthread_mutex_lock(&se->lock);
+
143 req->u.ni.func = NULL;
+
144 req->u.ni.data = NULL;
+
145 list_del_req(req);
+
146 ctr = --req->ref_cnt;
+
147 fuse_chan_put(req->ch);
+
148 req->ch = NULL;
+
149 pthread_mutex_unlock(&se->lock);
+
150 }
+
151 if (!ctr)
+
152 destroy_req(req);
+
153}
+
154
+
155static struct fuse_req *fuse_ll_alloc_req(struct fuse_session *se)
+
156{
+
157 struct fuse_req *req;
+
158
+
159 req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req));
+
160 if (req == NULL) {
+
161 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate request\n");
+
162 } else {
+
163 req->se = se;
+
164 req->ref_cnt = 1;
+
165 list_init_req(req);
+
166 pthread_mutex_init(&req->lock, NULL);
+
167 }
+
168
+
169 return req;
+
170}
+
171
+
172/* Send data. If *ch* is NULL, send via session master fd */
+
173static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch,
+
174 struct iovec *iov, int count)
+
175{
+
176 struct fuse_out_header *out = iov[0].iov_base;
+
177
+
178 assert(se != NULL);
+
179 out->len = iov_length(iov, count);
+
180 if (se->debug) {
+
181 if (out->unique == 0) {
+
182 fuse_log(FUSE_LOG_DEBUG, "NOTIFY: code=%d length=%u\n",
+
183 out->error, out->len);
+
184 } else if (out->error) {
+
185 fuse_log(FUSE_LOG_DEBUG,
+
186 " unique: %llu, error: %i (%s), outsize: %i\n",
+
187 (unsigned long long) out->unique, out->error,
+
188 strerror(-out->error), out->len);
+
189 } else {
+
190 fuse_log(FUSE_LOG_DEBUG,
+
191 " unique: %llu, success, outsize: %i\n",
+
192 (unsigned long long) out->unique, out->len);
+
193 }
+
194 }
+
195
+
196 ssize_t res;
+
197 if (se->io != NULL)
+
198 /* se->io->writev is never NULL if se->io is not NULL as
+
199 specified by fuse_session_custom_io()*/
+
200 res = se->io->writev(ch ? ch->fd : se->fd, iov, count,
+
201 se->userdata);
+
202 else
+
203 res = writev(ch ? ch->fd : se->fd, iov, count);
+
204
+
205 int err = errno;
+
206
+
207 if (res == -1) {
+
208 /* ENOENT means the operation was interrupted */
+
209 if (!fuse_session_exited(se) && err != ENOENT)
+
210 perror("fuse: writing device");
+
211 return -err;
+
212 }
+
213
+
214 return 0;
+
215}
+
216
+
217
+
218int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
219 int count)
+
220{
+
221 struct fuse_out_header out;
+
222
+
223#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
+
224 const char *str = strerrordesc_np(error * -1);
+
225 if ((str == NULL && error != 0) || error > 0) {
+
226#else
+
227 if (error <= -1000 || error > 0) {
+
228#endif
+
229 fuse_log(FUSE_LOG_ERR, "fuse: bad error value: %i\n", error);
+
230 error = -ERANGE;
+
231 }
+
232
+
233 out.unique = req->unique;
+
234 out.error = error;
+
235
+
236 iov[0].iov_base = &out;
+
237 iov[0].iov_len = sizeof(struct fuse_out_header);
+
238
+
239 return fuse_send_msg(req->se, req->ch, iov, count);
+
240}
+
241
+
242static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov,
+
243 int count)
+
244{
+
245 int res;
+
246
+
247 res = fuse_send_reply_iov_nofree(req, error, iov, count);
+
248 fuse_free_req(req);
+
249 return res;
+
250}
+
251
+
252static int send_reply(fuse_req_t req, int error, const void *arg,
+
253 size_t argsize)
+
254{
+
255 struct iovec iov[2];
+
256 int count = 1;
+
257 if (argsize) {
+
258 iov[1].iov_base = (void *) arg;
+
259 iov[1].iov_len = argsize;
+
260 count++;
+
261 }
+
262 return send_reply_iov(req, error, iov, count);
+
263}
+
264
+
+
265int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
266{
+
267 int res;
+
268 struct iovec *padded_iov;
+
269
+
270 padded_iov = malloc((count + 1) * sizeof(struct iovec));
+
271 if (padded_iov == NULL)
+
272 return fuse_reply_err(req, ENOMEM);
+
273
+
274 memcpy(padded_iov + 1, iov, count * sizeof(struct iovec));
+
275 count++;
+
276
+
277 res = send_reply_iov(req, 0, padded_iov, count);
+
278 free(padded_iov);
+
279
+
280 return res;
+
281}
+
+
282
+
283
+
284/* `buf` is allowed to be empty so that the proper size may be
+
285 allocated by the caller */
+
+
286size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
287 const char *name, const struct stat *stbuf, off_t off)
+
288{
+
289 (void)req;
+
290 size_t namelen;
+
291 size_t entlen;
+
292 size_t entlen_padded;
+
293 struct fuse_dirent *dirent;
+
294
+
295 namelen = strlen(name);
+
296 entlen = FUSE_NAME_OFFSET + namelen;
+
297 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
298
+
299 if ((buf == NULL) || (entlen_padded > bufsize))
+
300 return entlen_padded;
+
301
+
302 dirent = (struct fuse_dirent*) buf;
+
303 dirent->ino = stbuf->st_ino;
+
304 dirent->off = off;
+
305 dirent->namelen = namelen;
+
306 dirent->type = (stbuf->st_mode & S_IFMT) >> 12;
+
307 memcpy(dirent->name, name, namelen);
+
308 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
309
+
310 return entlen_padded;
+
311}
+
+
312
+
313static void convert_statfs(const struct statvfs *stbuf,
+
314 struct fuse_kstatfs *kstatfs)
+
315{
+
316 kstatfs->bsize = stbuf->f_bsize;
+
317 kstatfs->frsize = stbuf->f_frsize;
+
318 kstatfs->blocks = stbuf->f_blocks;
+
319 kstatfs->bfree = stbuf->f_bfree;
+
320 kstatfs->bavail = stbuf->f_bavail;
+
321 kstatfs->files = stbuf->f_files;
+
322 kstatfs->ffree = stbuf->f_ffree;
+
323 kstatfs->namelen = stbuf->f_namemax;
+
324}
+
325
+
326static int send_reply_ok(fuse_req_t req, const void *arg, size_t argsize)
+
327{
+
328 return send_reply(req, 0, arg, argsize);
+
329}
+
330
+
+
331int fuse_reply_err(fuse_req_t req, int err)
+
332{
+
333 return send_reply(req, -err, NULL, 0);
+
334}
+
+
335
+
+ +
337{
+
338 fuse_free_req(req);
+
339}
+
+
340
+
341static unsigned long calc_timeout_sec(double t)
+
342{
+
343 if (t > (double) ULONG_MAX)
+
344 return ULONG_MAX;
+
345 else if (t < 0.0)
+
346 return 0;
+
347 else
+
348 return (unsigned long) t;
+
349}
+
350
+
351static unsigned int calc_timeout_nsec(double t)
+
352{
+
353 double f = t - (double) calc_timeout_sec(t);
+
354 if (f < 0.0)
+
355 return 0;
+
356 else if (f >= 0.999999999)
+
357 return 999999999;
+
358 else
+
359 return (unsigned int) (f * 1.0e9);
+
360}
+
361
+
362static void fill_entry(struct fuse_entry_out *arg,
+
363 const struct fuse_entry_param *e)
+
364{
+
365 arg->nodeid = e->ino;
+
366 arg->generation = e->generation;
+
367 arg->entry_valid = calc_timeout_sec(e->entry_timeout);
+
368 arg->entry_valid_nsec = calc_timeout_nsec(e->entry_timeout);
+
369 arg->attr_valid = calc_timeout_sec(e->attr_timeout);
+
370 arg->attr_valid_nsec = calc_timeout_nsec(e->attr_timeout);
+
371 convert_stat(&e->attr, &arg->attr);
+
372}
+
373
+
374/* `buf` is allowed to be empty so that the proper size may be
+
375 allocated by the caller */
+
+
376size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
377 const char *name,
+
378 const struct fuse_entry_param *e, off_t off)
+
379{
+
380 (void)req;
+
381 size_t namelen;
+
382 size_t entlen;
+
383 size_t entlen_padded;
+
384
+
385 namelen = strlen(name);
+
386 entlen = FUSE_NAME_OFFSET_DIRENTPLUS + namelen;
+
387 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
388 if ((buf == NULL) || (entlen_padded > bufsize))
+
389 return entlen_padded;
+
390
+
391 struct fuse_direntplus *dp = (struct fuse_direntplus *) buf;
+
392 memset(&dp->entry_out, 0, sizeof(dp->entry_out));
+
393 fill_entry(&dp->entry_out, e);
+
394
+
395 struct fuse_dirent *dirent = &dp->dirent;
+
396 dirent->ino = e->attr.st_ino;
+
397 dirent->off = off;
+
398 dirent->namelen = namelen;
+
399 dirent->type = (e->attr.st_mode & S_IFMT) >> 12;
+
400 memcpy(dirent->name, name, namelen);
+
401 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
402
+
403 return entlen_padded;
+
404}
+
+
405
+
406static void fill_open(struct fuse_open_out *arg,
+
407 const struct fuse_file_info *f)
+
408{
+
409 arg->fh = f->fh;
+
410 if (f->backing_id > 0) {
+
411 arg->backing_id = f->backing_id;
+
412 arg->open_flags |= FOPEN_PASSTHROUGH;
+
413 }
+
414 if (f->direct_io)
+
415 arg->open_flags |= FOPEN_DIRECT_IO;
+
416 if (f->keep_cache)
+
417 arg->open_flags |= FOPEN_KEEP_CACHE;
+
418 if (f->cache_readdir)
+
419 arg->open_flags |= FOPEN_CACHE_DIR;
+
420 if (f->nonseekable)
+
421 arg->open_flags |= FOPEN_NONSEEKABLE;
+
422 if (f->noflush)
+
423 arg->open_flags |= FOPEN_NOFLUSH;
+ +
425 arg->open_flags |= FOPEN_PARALLEL_DIRECT_WRITES;
+
426}
+
427
+
+ +
429{
+
430 struct fuse_entry_out arg;
+
431 size_t size = req->se->conn.proto_minor < 9 ?
+
432 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg);
+
433
+
434 /* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant
+
435 negative entry */
+
436 if (!e->ino && req->se->conn.proto_minor < 4)
+
437 return fuse_reply_err(req, ENOENT);
+
438
+
439 memset(&arg, 0, sizeof(arg));
+
440 fill_entry(&arg, e);
+
441 return send_reply_ok(req, &arg, size);
+
442}
+
+
443
+
+ +
445 const struct fuse_file_info *f)
+
446{
+
447 alignas(uint64_t) char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)];
+
448 size_t entrysize = req->se->conn.proto_minor < 9 ?
+
449 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(struct fuse_entry_out);
+
450 struct fuse_entry_out *earg = (struct fuse_entry_out *) buf;
+
451 struct fuse_open_out *oarg = (struct fuse_open_out *) (buf + entrysize);
+
452
+
453 memset(buf, 0, sizeof(buf));
+
454 fill_entry(earg, e);
+
455 fill_open(oarg, f);
+
456 return send_reply_ok(req, buf,
+
457 entrysize + sizeof(struct fuse_open_out));
+
458}
+
+
459
+
+
460int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
461 double attr_timeout)
+
462{
+
463 struct fuse_attr_out arg;
+
464 size_t size = req->se->conn.proto_minor < 9 ?
+
465 FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg);
+
466
+
467 memset(&arg, 0, sizeof(arg));
+
468 arg.attr_valid = calc_timeout_sec(attr_timeout);
+
469 arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
+
470 convert_stat(attr, &arg.attr);
+
471
+
472 return send_reply_ok(req, &arg, size);
+
473}
+
+
474
+
+
475int fuse_reply_readlink(fuse_req_t req, const char *linkname)
+
476{
+
477 return send_reply_ok(req, linkname, strlen(linkname));
+
478}
+
+
479
+
+ +
481{
+
482 struct fuse_backing_map map = { .fd = fd };
+
483 int ret;
+
484
+
485 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_OPEN, &map);
+
486 if (ret <= 0) {
+
487 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_open: %s\n", strerror(errno));
+
488 return 0;
+
489 }
+
490
+
491 return ret;
+
492}
+
+
493
+
494int fuse_passthrough_close(fuse_req_t req, int backing_id)
+
495{
+
496 int ret;
+
497
+
498 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_CLOSE, &backing_id);
+
499 if (ret < 0)
+
500 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_close: %s\n", strerror(errno));
+
501
+
502 return ret;
+
503}
+
504
+
+ +
506{
+
507 struct fuse_open_out arg;
+
508
+
509 memset(&arg, 0, sizeof(arg));
+
510 fill_open(&arg, f);
+
511 return send_reply_ok(req, &arg, sizeof(arg));
+
512}
+
+
513
+
+
514int fuse_reply_write(fuse_req_t req, size_t count)
+
515{
+
516 struct fuse_write_out arg;
+
517
+
518 memset(&arg, 0, sizeof(arg));
+
519 arg.size = count;
+
520
+
521 return send_reply_ok(req, &arg, sizeof(arg));
+
522}
+
+
523
+
+
524int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
525{
+
526 return send_reply_ok(req, buf, size);
+
527}
+
+
528
+
529static int fuse_send_data_iov_fallback(struct fuse_session *se,
+
530 struct fuse_chan *ch,
+
531 struct iovec *iov, int iov_count,
+
532 struct fuse_bufvec *buf,
+
533 size_t len)
+
534{
+
535 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
536 void *mbuf;
+
537 int res;
+
538
+
539 /* Optimize common case */
+
540 if (buf->count == 1 && buf->idx == 0 && buf->off == 0 &&
+
541 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
542 /* FIXME: also avoid memory copy if there are multiple buffers
+
543 but none of them contain an fd */
+
544
+
545 iov[iov_count].iov_base = buf->buf[0].mem;
+
546 iov[iov_count].iov_len = len;
+
547 iov_count++;
+
548 return fuse_send_msg(se, ch, iov, iov_count);
+
549 }
+
550
+
551 res = posix_memalign(&mbuf, pagesize, len);
+
552 if (res != 0)
+
553 return res;
+
554
+
555 mem_buf.buf[0].mem = mbuf;
+
556 res = fuse_buf_copy(&mem_buf, buf, 0);
+
557 if (res < 0) {
+
558 free(mbuf);
+
559 return -res;
+
560 }
+
561 len = res;
+
562
+
563 iov[iov_count].iov_base = mbuf;
+
564 iov[iov_count].iov_len = len;
+
565 iov_count++;
+
566 res = fuse_send_msg(se, ch, iov, iov_count);
+
567 free(mbuf);
+
568
+
569 return res;
+
570}
+
571
+
572struct fuse_ll_pipe {
+
573 size_t size;
+
574 int can_grow;
+
575 int pipe[2];
+
576};
+
577
+
578static void fuse_ll_pipe_free(struct fuse_ll_pipe *llp)
+
579{
+
580 close(llp->pipe[0]);
+
581 close(llp->pipe[1]);
+
582 free(llp);
+
583}
+
584
+
585#ifdef HAVE_SPLICE
+
586#if !defined(HAVE_PIPE2) || !defined(O_CLOEXEC)
+
587static int fuse_pipe(int fds[2])
+
588{
+
589 int rv = pipe(fds);
+
590
+
591 if (rv == -1)
+
592 return rv;
+
593
+
594 if (fcntl(fds[0], F_SETFL, O_NONBLOCK) == -1 ||
+
595 fcntl(fds[1], F_SETFL, O_NONBLOCK) == -1 ||
+
596 fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 ||
+
597 fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) {
+
598 close(fds[0]);
+
599 close(fds[1]);
+
600 rv = -1;
+
601 }
+
602 return rv;
+
603}
+
604#else
+
605static int fuse_pipe(int fds[2])
+
606{
+
607 return pipe2(fds, O_CLOEXEC | O_NONBLOCK);
+
608}
+
609#endif
+
610
+
611static struct fuse_ll_pipe *fuse_ll_get_pipe(struct fuse_session *se)
+
612{
+
613 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
614 if (llp == NULL) {
+
615 int res;
+
616
+
617 llp = malloc(sizeof(struct fuse_ll_pipe));
+
618 if (llp == NULL)
+
619 return NULL;
+
620
+
621 res = fuse_pipe(llp->pipe);
+
622 if (res == -1) {
+
623 free(llp);
+
624 return NULL;
+
625 }
+
626
+
627 /*
+
628 *the default size is 16 pages on linux
+
629 */
+
630 llp->size = pagesize * 16;
+
631 llp->can_grow = 1;
+
632
+
633 pthread_setspecific(se->pipe_key, llp);
+
634 }
+
635
+
636 return llp;
+
637}
+
638#endif
+
639
+
640static void fuse_ll_clear_pipe(struct fuse_session *se)
+
641{
+
642 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
643 if (llp) {
+
644 pthread_setspecific(se->pipe_key, NULL);
+
645 fuse_ll_pipe_free(llp);
+
646 }
+
647}
+
648
+
649#if defined(HAVE_SPLICE) && defined(HAVE_VMSPLICE)
+
650static int read_back(int fd, char *buf, size_t len)
+
651{
+
652 int res;
+
653
+
654 res = read(fd, buf, len);
+
655 if (res == -1) {
+
656 fuse_log(FUSE_LOG_ERR, "fuse: internal error: failed to read back from pipe: %s\n", strerror(errno));
+
657 return -EIO;
+
658 }
+
659 if (res != len) {
+
660 fuse_log(FUSE_LOG_ERR, "fuse: internal error: short read back from pipe: %i from %zi\n", res, len);
+
661 return -EIO;
+
662 }
+
663 return 0;
+
664}
+
665
+
666static int grow_pipe_to_max(int pipefd)
+
667{
+
668 int res;
+
669 long max;
+
670 long maxfd;
+
671 char buf[32];
+
672
+
673 maxfd = open("/proc/sys/fs/pipe-max-size", O_RDONLY);
+
674 if (maxfd < 0)
+
675 return -errno;
+
676
+
677 res = read(maxfd, buf, sizeof(buf) - 1);
+
678 if (res < 0) {
+
679 int saved_errno;
+
680
+
681 saved_errno = errno;
+
682 close(maxfd);
+
683 return -saved_errno;
+
684 }
+
685 close(maxfd);
+
686 buf[res] = '\0';
+
687
+
688 res = libfuse_strtol(buf, &max);
+
689 if (res)
+
690 return res;
+
691 res = fcntl(pipefd, F_SETPIPE_SZ, max);
+
692 if (res < 0)
+
693 return -errno;
+
694 return max;
+
695}
+
696
+
697static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
698 struct iovec *iov, int iov_count,
+
699 struct fuse_bufvec *buf, unsigned int flags)
+
700{
+
701 int res;
+
702 size_t len = fuse_buf_size(buf);
+
703 struct fuse_out_header *out = iov[0].iov_base;
+
704 struct fuse_ll_pipe *llp;
+
705 int splice_flags;
+
706 size_t pipesize;
+
707 size_t total_buf_size;
+
708 size_t idx;
+
709 size_t headerlen;
+
710 struct fuse_bufvec pipe_buf = FUSE_BUFVEC_INIT(len);
+
711
+
712 if (se->broken_splice_nonblock)
+
713 goto fallback;
+
714
+
715 if (flags & FUSE_BUF_NO_SPLICE)
+
716 goto fallback;
+
717
+
718 total_buf_size = 0;
+
719 for (idx = buf->idx; idx < buf->count; idx++) {
+
720 total_buf_size += buf->buf[idx].size;
+
721 if (idx == buf->idx)
+
722 total_buf_size -= buf->off;
+
723 }
+
724 if (total_buf_size < 2 * pagesize)
+
725 goto fallback;
+
726
+
727 if (se->conn.proto_minor < 14 ||
+
728 !(se->conn.want_ext & FUSE_CAP_SPLICE_WRITE))
+
729 goto fallback;
+
730
+
731 llp = fuse_ll_get_pipe(se);
+
732 if (llp == NULL)
+
733 goto fallback;
+
734
+
735
+
736 headerlen = iov_length(iov, iov_count);
+
737
+
738 out->len = headerlen + len;
+
739
+
740 /*
+
741 * Heuristic for the required pipe size, does not work if the
+
742 * source contains less than page size fragments
+
743 */
+
744 pipesize = pagesize * (iov_count + buf->count + 1) + out->len;
+
745
+
746 if (llp->size < pipesize) {
+
747 if (llp->can_grow) {
+
748 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, pipesize);
+
749 if (res == -1) {
+
750 res = grow_pipe_to_max(llp->pipe[0]);
+
751 if (res > 0)
+
752 llp->size = res;
+
753 llp->can_grow = 0;
+
754 goto fallback;
+
755 }
+
756 llp->size = res;
+
757 }
+
758 if (llp->size < pipesize)
+
759 goto fallback;
+
760 }
+
761
+
762
+
763 res = vmsplice(llp->pipe[1], iov, iov_count, SPLICE_F_NONBLOCK);
+
764 if (res == -1)
+
765 goto fallback;
+
766
+
767 if (res != headerlen) {
+
768 res = -EIO;
+
769 fuse_log(FUSE_LOG_ERR, "fuse: short vmsplice to pipe: %u/%zu\n", res,
+
770 headerlen);
+
771 goto clear_pipe;
+
772 }
+
773
+
774 pipe_buf.buf[0].flags = FUSE_BUF_IS_FD;
+
775 pipe_buf.buf[0].fd = llp->pipe[1];
+
776
+
777 res = fuse_buf_copy(&pipe_buf, buf,
+ +
779 if (res < 0) {
+
780 if (res == -EAGAIN || res == -EINVAL) {
+
781 /*
+
782 * Should only get EAGAIN on kernels with
+
783 * broken SPLICE_F_NONBLOCK support (<=
+
784 * 2.6.35) where this error or a short read is
+
785 * returned even if the pipe itself is not
+
786 * full
+
787 *
+
788 * EINVAL might mean that splice can't handle
+
789 * this combination of input and output.
+
790 */
+
791 if (res == -EAGAIN)
+
792 se->broken_splice_nonblock = 1;
+
793
+
794 pthread_setspecific(se->pipe_key, NULL);
+
795 fuse_ll_pipe_free(llp);
+
796 goto fallback;
+
797 }
+
798 res = -res;
+
799 goto clear_pipe;
+
800 }
+
801
+
802 if (res != 0 && res < len) {
+
803 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
804 void *mbuf;
+
805 size_t now_len = res;
+
806 /*
+
807 * For regular files a short count is either
+
808 * 1) due to EOF, or
+
809 * 2) because of broken SPLICE_F_NONBLOCK (see above)
+
810 *
+
811 * For other inputs it's possible that we overflowed
+
812 * the pipe because of small buffer fragments.
+
813 */
+
814
+
815 res = posix_memalign(&mbuf, pagesize, len);
+
816 if (res != 0)
+
817 goto clear_pipe;
+
818
+
819 mem_buf.buf[0].mem = mbuf;
+
820 mem_buf.off = now_len;
+
821 res = fuse_buf_copy(&mem_buf, buf, 0);
+
822 if (res > 0) {
+
823 char *tmpbuf;
+
824 size_t extra_len = res;
+
825 /*
+
826 * Trickiest case: got more data. Need to get
+
827 * back the data from the pipe and then fall
+
828 * back to regular write.
+
829 */
+
830 tmpbuf = malloc(headerlen);
+
831 if (tmpbuf == NULL) {
+
832 free(mbuf);
+
833 res = ENOMEM;
+
834 goto clear_pipe;
+
835 }
+
836 res = read_back(llp->pipe[0], tmpbuf, headerlen);
+
837 free(tmpbuf);
+
838 if (res != 0) {
+
839 free(mbuf);
+
840 goto clear_pipe;
+
841 }
+
842 res = read_back(llp->pipe[0], mbuf, now_len);
+
843 if (res != 0) {
+
844 free(mbuf);
+
845 goto clear_pipe;
+
846 }
+
847 len = now_len + extra_len;
+
848 iov[iov_count].iov_base = mbuf;
+
849 iov[iov_count].iov_len = len;
+
850 iov_count++;
+
851 res = fuse_send_msg(se, ch, iov, iov_count);
+
852 free(mbuf);
+
853 return res;
+
854 }
+
855 free(mbuf);
+
856 res = now_len;
+
857 }
+
858 len = res;
+
859 out->len = headerlen + len;
+
860
+
861 if (se->debug) {
+
862 fuse_log(FUSE_LOG_DEBUG,
+
863 " unique: %llu, success, outsize: %i (splice)\n",
+
864 (unsigned long long) out->unique, out->len);
+
865 }
+
866
+
867 splice_flags = 0;
+
868 if ((flags & FUSE_BUF_SPLICE_MOVE) &&
+
869 (se->conn.want_ext & FUSE_CAP_SPLICE_MOVE))
+
870 splice_flags |= SPLICE_F_MOVE;
+
871
+
872 if (se->io != NULL && se->io->splice_send != NULL) {
+
873 res = se->io->splice_send(llp->pipe[0], NULL,
+
874 ch ? ch->fd : se->fd, NULL, out->len,
+
875 splice_flags, se->userdata);
+
876 } else {
+
877 res = splice(llp->pipe[0], NULL, ch ? ch->fd : se->fd, NULL,
+
878 out->len, splice_flags);
+
879 }
+
880 if (res == -1) {
+
881 res = -errno;
+
882 perror("fuse: splice from pipe");
+
883 goto clear_pipe;
+
884 }
+
885 if (res != out->len) {
+
886 res = -EIO;
+
887 fuse_log(FUSE_LOG_ERR, "fuse: short splice from pipe: %u/%u\n",
+
888 res, out->len);
+
889 goto clear_pipe;
+
890 }
+
891 return 0;
+
892
+
893clear_pipe:
+
894 fuse_ll_clear_pipe(se);
+
895 return res;
+
896
+
897fallback:
+
898 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len);
+
899}
+
900#else
+
901static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
902 struct iovec *iov, int iov_count,
+
903 struct fuse_bufvec *buf, unsigned int flags)
+
904{
+
905 size_t len = fuse_buf_size(buf);
+
906 (void) flags;
+
907
+
908 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len);
+
909}
+
910#endif
+
911
+
+ +
913 enum fuse_buf_copy_flags flags)
+
914{
+
915 struct iovec iov[2];
+
916 struct fuse_out_header out;
+
917 int res;
+
918
+
919 iov[0].iov_base = &out;
+
920 iov[0].iov_len = sizeof(struct fuse_out_header);
+
921
+
922 out.unique = req->unique;
+
923 out.error = 0;
+
924
+
925 res = fuse_send_data_iov(req->se, req->ch, iov, 1, bufv, flags);
+
926 if (res <= 0) {
+
927 fuse_free_req(req);
+
928 return res;
+
929 } else {
+
930 return fuse_reply_err(req, res);
+
931 }
+
932}
+
+
933
+
+
934int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
935{
+
936 struct fuse_statfs_out arg;
+
937 size_t size = req->se->conn.proto_minor < 4 ?
+
938 FUSE_COMPAT_STATFS_SIZE : sizeof(arg);
+
939
+
940 memset(&arg, 0, sizeof(arg));
+
941 convert_statfs(stbuf, &arg.st);
+
942
+
943 return send_reply_ok(req, &arg, size);
+
944}
+
+
945
+
+
946int fuse_reply_xattr(fuse_req_t req, size_t count)
+
947{
+
948 struct fuse_getxattr_out arg;
+
949
+
950 memset(&arg, 0, sizeof(arg));
+
951 arg.size = count;
+
952
+
953 return send_reply_ok(req, &arg, sizeof(arg));
+
954}
+
+
955
+
+
956int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
957{
+
958 struct fuse_lk_out arg;
+
959
+
960 memset(&arg, 0, sizeof(arg));
+
961 arg.lk.type = lock->l_type;
+
962 if (lock->l_type != F_UNLCK) {
+
963 arg.lk.start = lock->l_start;
+
964 if (lock->l_len == 0)
+
965 arg.lk.end = OFFSET_MAX;
+
966 else
+
967 arg.lk.end = lock->l_start + lock->l_len - 1;
+
968 }
+
969 arg.lk.pid = lock->l_pid;
+
970 return send_reply_ok(req, &arg, sizeof(arg));
+
971}
+
+
972
+
+
973int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
974{
+
975 struct fuse_bmap_out arg;
+
976
+
977 memset(&arg, 0, sizeof(arg));
+
978 arg.block = idx;
+
979
+
980 return send_reply_ok(req, &arg, sizeof(arg));
+
981}
+
+
982
+
983static struct fuse_ioctl_iovec *fuse_ioctl_iovec_copy(const struct iovec *iov,
+
984 size_t count)
+
985{
+
986 struct fuse_ioctl_iovec *fiov;
+
987 size_t i;
+
988
+
989 fiov = malloc(sizeof(fiov[0]) * count);
+
990 if (!fiov)
+
991 return NULL;
+
992
+
993 for (i = 0; i < count; i++) {
+
994 fiov[i].base = (uintptr_t) iov[i].iov_base;
+
995 fiov[i].len = iov[i].iov_len;
+
996 }
+
997
+
998 return fiov;
+
999}
+
1000
+
+ +
1002 const struct iovec *in_iov, size_t in_count,
+
1003 const struct iovec *out_iov, size_t out_count)
+
1004{
+
1005 struct fuse_ioctl_out arg;
+
1006 struct fuse_ioctl_iovec *in_fiov = NULL;
+
1007 struct fuse_ioctl_iovec *out_fiov = NULL;
+
1008 struct iovec iov[4];
+
1009 size_t count = 1;
+
1010 int res;
+
1011
+
1012 memset(&arg, 0, sizeof(arg));
+
1013 arg.flags |= FUSE_IOCTL_RETRY;
+
1014 arg.in_iovs = in_count;
+
1015 arg.out_iovs = out_count;
+
1016 iov[count].iov_base = &arg;
+
1017 iov[count].iov_len = sizeof(arg);
+
1018 count++;
+
1019
+
1020 if (req->se->conn.proto_minor < 16) {
+
1021 if (in_count) {
+
1022 iov[count].iov_base = (void *)in_iov;
+
1023 iov[count].iov_len = sizeof(in_iov[0]) * in_count;
+
1024 count++;
+
1025 }
+
1026
+
1027 if (out_count) {
+
1028 iov[count].iov_base = (void *)out_iov;
+
1029 iov[count].iov_len = sizeof(out_iov[0]) * out_count;
+
1030 count++;
+
1031 }
+
1032 } else {
+
1033 /* Can't handle non-compat 64bit ioctls on 32bit */
+
1034 if (sizeof(void *) == 4 && req->ioctl_64bit) {
+
1035 res = fuse_reply_err(req, EINVAL);
+
1036 goto out;
+
1037 }
+
1038
+
1039 if (in_count) {
+
1040 in_fiov = fuse_ioctl_iovec_copy(in_iov, in_count);
+
1041 if (!in_fiov)
+
1042 goto enomem;
+
1043
+
1044 iov[count].iov_base = (void *)in_fiov;
+
1045 iov[count].iov_len = sizeof(in_fiov[0]) * in_count;
+
1046 count++;
+
1047 }
+
1048 if (out_count) {
+
1049 out_fiov = fuse_ioctl_iovec_copy(out_iov, out_count);
+
1050 if (!out_fiov)
+
1051 goto enomem;
+
1052
+
1053 iov[count].iov_base = (void *)out_fiov;
+
1054 iov[count].iov_len = sizeof(out_fiov[0]) * out_count;
+
1055 count++;
+
1056 }
+
1057 }
+
1058
+
1059 res = send_reply_iov(req, 0, iov, count);
+
1060out:
+
1061 free(in_fiov);
+
1062 free(out_fiov);
+
1063
+
1064 return res;
+
1065
+
1066enomem:
+
1067 res = fuse_reply_err(req, ENOMEM);
+
1068 goto out;
+
1069}
+
+
1070
+
+
1071int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
1072{
+
1073 struct fuse_ioctl_out arg;
+
1074 struct iovec iov[3];
+
1075 size_t count = 1;
+
1076
+
1077 memset(&arg, 0, sizeof(arg));
+
1078 arg.result = result;
+
1079 iov[count].iov_base = &arg;
+
1080 iov[count].iov_len = sizeof(arg);
+
1081 count++;
+
1082
+
1083 if (size) {
+
1084 iov[count].iov_base = (char *) buf;
+
1085 iov[count].iov_len = size;
+
1086 count++;
+
1087 }
+
1088
+
1089 return send_reply_iov(req, 0, iov, count);
+
1090}
+
+
1091
+
+
1092int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1093 int count)
+
1094{
+
1095 struct iovec *padded_iov;
+
1096 struct fuse_ioctl_out arg;
+
1097 int res;
+
1098
+
1099 padded_iov = malloc((count + 2) * sizeof(struct iovec));
+
1100 if (padded_iov == NULL)
+
1101 return fuse_reply_err(req, ENOMEM);
+
1102
+
1103 memset(&arg, 0, sizeof(arg));
+
1104 arg.result = result;
+
1105 padded_iov[1].iov_base = &arg;
+
1106 padded_iov[1].iov_len = sizeof(arg);
+
1107
+
1108 memcpy(&padded_iov[2], iov, count * sizeof(struct iovec));
+
1109
+
1110 res = send_reply_iov(req, 0, padded_iov, count + 2);
+
1111 free(padded_iov);
+
1112
+
1113 return res;
+
1114}
+
+
1115
+
+
1116int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
1117{
+
1118 struct fuse_poll_out arg;
+
1119
+
1120 memset(&arg, 0, sizeof(arg));
+
1121 arg.revents = revents;
+
1122
+
1123 return send_reply_ok(req, &arg, sizeof(arg));
+
1124}
+
+
1125
+
+
1126int fuse_reply_lseek(fuse_req_t req, off_t off)
+
1127{
+
1128 struct fuse_lseek_out arg;
+
1129
+
1130 memset(&arg, 0, sizeof(arg));
+
1131 arg.offset = off;
+
1132
+
1133 return send_reply_ok(req, &arg, sizeof(arg));
+
1134}
+
+
1135
+
1136static void do_lookup(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1137{
+
1138 char *name = (char *) inarg;
+
1139
+
1140 if (req->se->op.lookup)
+
1141 req->se->op.lookup(req, nodeid, name);
+
1142 else
+
1143 fuse_reply_err(req, ENOSYS);
+
1144}
+
1145
+
1146static void do_forget(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1147{
+
1148 struct fuse_forget_in *arg = (struct fuse_forget_in *) inarg;
+
1149
+
1150 if (req->se->op.forget)
+
1151 req->se->op.forget(req, nodeid, arg->nlookup);
+
1152 else
+
1153 fuse_reply_none(req);
+
1154}
+
1155
+
1156static void do_batch_forget(fuse_req_t req, fuse_ino_t nodeid,
+
1157 const void *inarg)
+
1158{
+
1159 struct fuse_batch_forget_in *arg = (void *) inarg;
+
1160 struct fuse_forget_one *param = (void *) PARAM(arg);
+
1161 unsigned int i;
+
1162
+
1163 (void) nodeid;
+
1164
+
1165 if (req->se->op.forget_multi) {
+
1166 req->se->op.forget_multi(req, arg->count,
+
1167 (struct fuse_forget_data *) param);
+
1168 } else if (req->se->op.forget) {
+
1169 for (i = 0; i < arg->count; i++) {
+
1170 struct fuse_forget_one *forget = &param[i];
+
1171 struct fuse_req *dummy_req;
+
1172
+
1173 dummy_req = fuse_ll_alloc_req(req->se);
+
1174 if (dummy_req == NULL)
+
1175 break;
+
1176
+
1177 dummy_req->unique = req->unique;
+
1178 dummy_req->ctx = req->ctx;
+
1179 dummy_req->ch = NULL;
+
1180
+
1181 req->se->op.forget(dummy_req, forget->nodeid,
+
1182 forget->nlookup);
+
1183 }
+
1184 fuse_reply_none(req);
+
1185 } else {
+
1186 fuse_reply_none(req);
+
1187 }
+
1188}
+
1189
+
1190static void do_getattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1191{
+
1192 struct fuse_file_info *fip = NULL;
+
1193 struct fuse_file_info fi;
+
1194
+
1195 if (req->se->conn.proto_minor >= 9) {
+
1196 struct fuse_getattr_in *arg = (struct fuse_getattr_in *) inarg;
+
1197
+
1198 if (arg->getattr_flags & FUSE_GETATTR_FH) {
+
1199 memset(&fi, 0, sizeof(fi));
+
1200 fi.fh = arg->fh;
+
1201 fip = &fi;
+
1202 }
+
1203 }
+
1204
+
1205 if (req->se->op.getattr)
+
1206 req->se->op.getattr(req, nodeid, fip);
+
1207 else
+
1208 fuse_reply_err(req, ENOSYS);
+
1209}
+
1210
+
1211static void do_setattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1212{
+
1213 struct fuse_setattr_in *arg = (struct fuse_setattr_in *) inarg;
+
1214
+
1215 if (req->se->op.setattr) {
+
1216 struct fuse_file_info *fi = NULL;
+
1217 struct fuse_file_info fi_store;
+
1218 struct stat stbuf;
+
1219 memset(&stbuf, 0, sizeof(stbuf));
+
1220 convert_attr(arg, &stbuf);
+
1221 if (arg->valid & FATTR_FH) {
+
1222 arg->valid &= ~FATTR_FH;
+
1223 memset(&fi_store, 0, sizeof(fi_store));
+
1224 fi = &fi_store;
+
1225 fi->fh = arg->fh;
+
1226 }
+
1227 arg->valid &=
+
1228 FUSE_SET_ATTR_MODE |
+
1229 FUSE_SET_ATTR_UID |
+
1230 FUSE_SET_ATTR_GID |
+
1231 FUSE_SET_ATTR_SIZE |
+
1232 FUSE_SET_ATTR_ATIME |
+
1233 FUSE_SET_ATTR_MTIME |
+
1234 FUSE_SET_ATTR_KILL_SUID |
+
1235 FUSE_SET_ATTR_KILL_SGID |
+
1236 FUSE_SET_ATTR_ATIME_NOW |
+
1237 FUSE_SET_ATTR_MTIME_NOW |
+
1238 FUSE_SET_ATTR_CTIME;
+
1239
+
1240 req->se->op.setattr(req, nodeid, &stbuf, arg->valid, fi);
+
1241 } else
+
1242 fuse_reply_err(req, ENOSYS);
+
1243}
+
1244
+
1245static void do_access(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1246{
+
1247 struct fuse_access_in *arg = (struct fuse_access_in *) inarg;
+
1248
+
1249 if (req->se->op.access)
+
1250 req->se->op.access(req, nodeid, arg->mask);
+
1251 else
+
1252 fuse_reply_err(req, ENOSYS);
+
1253}
+
1254
+
1255static void do_readlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1256{
+
1257 (void) inarg;
+
1258
+
1259 if (req->se->op.readlink)
+
1260 req->se->op.readlink(req, nodeid);
+
1261 else
+
1262 fuse_reply_err(req, ENOSYS);
+
1263}
+
1264
+
1265static void do_mknod(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1266{
+
1267 struct fuse_mknod_in *arg = (struct fuse_mknod_in *) inarg;
+
1268 char *name = PARAM(arg);
+
1269
+
1270 if (req->se->conn.proto_minor >= 12)
+
1271 req->ctx.umask = arg->umask;
+
1272 else
+
1273 name = (char *) inarg + FUSE_COMPAT_MKNOD_IN_SIZE;
+
1274
+
1275 if (req->se->op.mknod)
+
1276 req->se->op.mknod(req, nodeid, name, arg->mode, arg->rdev);
+
1277 else
+
1278 fuse_reply_err(req, ENOSYS);
+
1279}
+
1280
+
1281static void do_mkdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1282{
+
1283 struct fuse_mkdir_in *arg = (struct fuse_mkdir_in *) inarg;
+
1284
+
1285 if (req->se->conn.proto_minor >= 12)
+
1286 req->ctx.umask = arg->umask;
+
1287
+
1288 if (req->se->op.mkdir)
+
1289 req->se->op.mkdir(req, nodeid, PARAM(arg), arg->mode);
+
1290 else
+
1291 fuse_reply_err(req, ENOSYS);
+
1292}
+
1293
+
1294static void do_unlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1295{
+
1296 char *name = (char *) inarg;
+
1297
+
1298 if (req->se->op.unlink)
+
1299 req->se->op.unlink(req, nodeid, name);
+
1300 else
+
1301 fuse_reply_err(req, ENOSYS);
+
1302}
+
1303
+
1304static void do_rmdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1305{
+
1306 char *name = (char *) inarg;
+
1307
+
1308 if (req->se->op.rmdir)
+
1309 req->se->op.rmdir(req, nodeid, name);
+
1310 else
+
1311 fuse_reply_err(req, ENOSYS);
+
1312}
+
1313
+
1314static void do_symlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1315{
+
1316 char *name = (char *) inarg;
+
1317 char *linkname = ((char *) inarg) + strlen((char *) inarg) + 1;
+
1318
+
1319 if (req->se->op.symlink)
+
1320 req->se->op.symlink(req, linkname, nodeid, name);
+
1321 else
+
1322 fuse_reply_err(req, ENOSYS);
+
1323}
+
1324
+
1325static void do_rename(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1326{
+
1327 struct fuse_rename_in *arg = (struct fuse_rename_in *) inarg;
+
1328 char *oldname = PARAM(arg);
+
1329 char *newname = oldname + strlen(oldname) + 1;
+
1330
+
1331 if (req->se->op.rename)
+
1332 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1333 0);
+
1334 else
+
1335 fuse_reply_err(req, ENOSYS);
+
1336}
+
1337
+
1338static void do_rename2(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1339{
+
1340 struct fuse_rename2_in *arg = (struct fuse_rename2_in *) inarg;
+
1341 char *oldname = PARAM(arg);
+
1342 char *newname = oldname + strlen(oldname) + 1;
+
1343
+
1344 if (req->se->op.rename)
+
1345 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1346 arg->flags);
+
1347 else
+
1348 fuse_reply_err(req, ENOSYS);
+
1349}
+
1350
+
1351static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1352{
+
1353 struct fuse_link_in *arg = (struct fuse_link_in *) inarg;
+
1354
+
1355 if (req->se->op.link)
+
1356 req->se->op.link(req, arg->oldnodeid, nodeid, PARAM(arg));
+
1357 else
+
1358 fuse_reply_err(req, ENOSYS);
+
1359}
+
1360
+
1361static void do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1362{
+
1363 struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
+
1364
+
1365 if (req->se->op.tmpfile) {
+
1366 struct fuse_file_info fi;
+
1367
+
1368 memset(&fi, 0, sizeof(fi));
+
1369 fi.flags = arg->flags;
+
1370
+
1371 if (req->se->conn.proto_minor >= 12)
+
1372 req->ctx.umask = arg->umask;
+
1373
+
1374 req->se->op.tmpfile(req, nodeid, arg->mode, &fi);
+
1375 } else
+
1376 fuse_reply_err(req, ENOSYS);
+
1377}
+
1378
+
1379static void do_create(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1380{
+
1381 struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
+
1382
+
1383 if (req->se->op.create) {
+
1384 struct fuse_file_info fi;
+
1385 char *name = PARAM(arg);
+
1386
+
1387 memset(&fi, 0, sizeof(fi));
+
1388 fi.flags = arg->flags;
+
1389
+
1390 if (req->se->conn.proto_minor >= 12)
+
1391 req->ctx.umask = arg->umask;
+
1392 else
+
1393 name = (char *) inarg + sizeof(struct fuse_open_in);
+
1394
+
1395 req->se->op.create(req, nodeid, name, arg->mode, &fi);
+
1396 } else
+
1397 fuse_reply_err(req, ENOSYS);
+
1398}
+
1399
+
1400static void do_open(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1401{
+
1402 struct fuse_open_in *arg = (struct fuse_open_in *) inarg;
+
1403 struct fuse_file_info fi;
+
1404
+
1405 memset(&fi, 0, sizeof(fi));
+
1406 fi.flags = arg->flags;
+
1407
+
1408 if (req->se->op.open)
+
1409 req->se->op.open(req, nodeid, &fi);
+
1410 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPEN_SUPPORT)
+
1411 fuse_reply_err(req, ENOSYS);
+
1412 else
+
1413 fuse_reply_open(req, &fi);
+
1414}
+
1415
+
1416static void do_read(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1417{
+
1418 struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
1419
+
1420 if (req->se->op.read) {
+
1421 struct fuse_file_info fi;
+
1422
+
1423 memset(&fi, 0, sizeof(fi));
+
1424 fi.fh = arg->fh;
+
1425 if (req->se->conn.proto_minor >= 9) {
+
1426 fi.lock_owner = arg->lock_owner;
+
1427 fi.flags = arg->flags;
+
1428 }
+
1429 req->se->op.read(req, nodeid, arg->size, arg->offset, &fi);
+
1430 } else
+
1431 fuse_reply_err(req, ENOSYS);
+
1432}
+
1433
+
1434static void do_write(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1435{
+
1436 struct fuse_write_in *arg = (struct fuse_write_in *) inarg;
+
1437 struct fuse_file_info fi;
+
1438 char *param;
+
1439
+
1440 memset(&fi, 0, sizeof(fi));
+
1441 fi.fh = arg->fh;
+
1442 fi.writepage = (arg->write_flags & FUSE_WRITE_CACHE) != 0;
+
1443
+
1444 if (req->se->conn.proto_minor < 9) {
+
1445 param = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1446 } else {
+
1447 fi.lock_owner = arg->lock_owner;
+
1448 fi.flags = arg->flags;
+
1449 param = PARAM(arg);
+
1450 }
+
1451
+
1452 if (req->se->op.write)
+
1453 req->se->op.write(req, nodeid, param, arg->size,
+
1454 arg->offset, &fi);
+
1455 else
+
1456 fuse_reply_err(req, ENOSYS);
+
1457}
+
1458
+
1459static void do_write_buf(fuse_req_t req, fuse_ino_t nodeid, const void *inarg,
+
1460 const struct fuse_buf *ibuf)
+
1461{
+
1462 struct fuse_session *se = req->se;
+
1463 struct fuse_bufvec bufv = {
+
1464 .buf[0] = *ibuf,
+
1465 .count = 1,
+
1466 };
+
1467 struct fuse_write_in *arg = (struct fuse_write_in *) inarg;
+
1468 struct fuse_file_info fi;
+
1469
+
1470 memset(&fi, 0, sizeof(fi));
+
1471 fi.fh = arg->fh;
+
1472 fi.writepage = arg->write_flags & FUSE_WRITE_CACHE;
+
1473
+
1474 if (se->conn.proto_minor < 9) {
+
1475 bufv.buf[0].mem = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1476 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1477 FUSE_COMPAT_WRITE_IN_SIZE;
+
1478 assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD));
+
1479 } else {
+
1480 fi.lock_owner = arg->lock_owner;
+
1481 fi.flags = arg->flags;
+
1482 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
1483 bufv.buf[0].mem = PARAM(arg);
+
1484
+
1485 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1486 sizeof(struct fuse_write_in);
+
1487 }
+
1488 if (bufv.buf[0].size < arg->size) {
+
1489 fuse_log(FUSE_LOG_ERR, "fuse: do_write_buf: buffer size too small\n");
+
1490 fuse_reply_err(req, EIO);
+
1491 goto out;
+
1492 }
+
1493 bufv.buf[0].size = arg->size;
+
1494
+
1495 se->op.write_buf(req, nodeid, &bufv, arg->offset, &fi);
+
1496
+
1497out:
+
1498 /* Need to reset the pipe if ->write_buf() didn't consume all data */
+
1499 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
1500 fuse_ll_clear_pipe(se);
+
1501}
+
1502
+
1503static void do_flush(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1504{
+
1505 struct fuse_flush_in *arg = (struct fuse_flush_in *) inarg;
+
1506 struct fuse_file_info fi;
+
1507
+
1508 memset(&fi, 0, sizeof(fi));
+
1509 fi.fh = arg->fh;
+
1510 fi.flush = 1;
+
1511 if (req->se->conn.proto_minor >= 7)
+
1512 fi.lock_owner = arg->lock_owner;
+
1513
+
1514 if (req->se->op.flush)
+
1515 req->se->op.flush(req, nodeid, &fi);
+
1516 else
+
1517 fuse_reply_err(req, ENOSYS);
+
1518}
+
1519
+
1520static void do_release(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1521{
+
1522 struct fuse_release_in *arg = (struct fuse_release_in *) inarg;
+
1523 struct fuse_file_info fi;
+
1524
+
1525 memset(&fi, 0, sizeof(fi));
+
1526 fi.flags = arg->flags;
+
1527 fi.fh = arg->fh;
+
1528 if (req->se->conn.proto_minor >= 8) {
+
1529 fi.flush = (arg->release_flags & FUSE_RELEASE_FLUSH) ? 1 : 0;
+
1530 fi.lock_owner = arg->lock_owner;
+
1531 }
+
1532 if (arg->release_flags & FUSE_RELEASE_FLOCK_UNLOCK) {
+
1533 fi.flock_release = 1;
+
1534 fi.lock_owner = arg->lock_owner;
+
1535 }
+
1536
+
1537 if (req->se->op.release)
+
1538 req->se->op.release(req, nodeid, &fi);
+
1539 else
+
1540 fuse_reply_err(req, 0);
+
1541}
+
1542
+
1543static void do_fsync(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1544{
+
1545 struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg;
+
1546 struct fuse_file_info fi;
+
1547 int datasync = arg->fsync_flags & 1;
+
1548
+
1549 memset(&fi, 0, sizeof(fi));
+
1550 fi.fh = arg->fh;
+
1551
+
1552 if (req->se->op.fsync)
+
1553 req->se->op.fsync(req, nodeid, datasync, &fi);
+
1554 else
+
1555 fuse_reply_err(req, ENOSYS);
+
1556}
+
1557
+
1558static void do_opendir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1559{
+
1560 struct fuse_open_in *arg = (struct fuse_open_in *) inarg;
+
1561 struct fuse_file_info fi;
+
1562
+
1563 memset(&fi, 0, sizeof(fi));
+
1564 fi.flags = arg->flags;
+
1565
+
1566 if (req->se->op.opendir)
+
1567 req->se->op.opendir(req, nodeid, &fi);
+
1568 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPENDIR_SUPPORT)
+
1569 fuse_reply_err(req, ENOSYS);
+
1570 else
+
1571 fuse_reply_open(req, &fi);
+
1572}
+
1573
+
1574static void do_readdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1575{
+
1576 struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
1577 struct fuse_file_info fi;
+
1578
+
1579 memset(&fi, 0, sizeof(fi));
+
1580 fi.fh = arg->fh;
+
1581
+
1582 if (req->se->op.readdir)
+
1583 req->se->op.readdir(req, nodeid, arg->size, arg->offset, &fi);
+
1584 else
+
1585 fuse_reply_err(req, ENOSYS);
+
1586}
+
1587
+
1588static void do_readdirplus(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1589{
+
1590 struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
1591 struct fuse_file_info fi;
+
1592
+
1593 memset(&fi, 0, sizeof(fi));
+
1594 fi.fh = arg->fh;
+
1595
+
1596 if (req->se->op.readdirplus)
+
1597 req->se->op.readdirplus(req, nodeid, arg->size, arg->offset, &fi);
+
1598 else
+
1599 fuse_reply_err(req, ENOSYS);
+
1600}
+
1601
+
1602static void do_releasedir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1603{
+
1604 struct fuse_release_in *arg = (struct fuse_release_in *) inarg;
+
1605 struct fuse_file_info fi;
+
1606
+
1607 memset(&fi, 0, sizeof(fi));
+
1608 fi.flags = arg->flags;
+
1609 fi.fh = arg->fh;
+
1610
+
1611 if (req->se->op.releasedir)
+
1612 req->se->op.releasedir(req, nodeid, &fi);
+
1613 else
+
1614 fuse_reply_err(req, 0);
+
1615}
+
1616
+
1617static void do_fsyncdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1618{
+
1619 struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg;
+
1620 struct fuse_file_info fi;
+
1621 int datasync = arg->fsync_flags & 1;
+
1622
+
1623 memset(&fi, 0, sizeof(fi));
+
1624 fi.fh = arg->fh;
+
1625
+
1626 if (req->se->op.fsyncdir)
+
1627 req->se->op.fsyncdir(req, nodeid, datasync, &fi);
+
1628 else
+
1629 fuse_reply_err(req, ENOSYS);
+
1630}
+
1631
+
1632static void do_statfs(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1633{
+
1634 (void) nodeid;
+
1635 (void) inarg;
+
1636
+
1637 if (req->se->op.statfs)
+
1638 req->se->op.statfs(req, nodeid);
+
1639 else {
+
1640 struct statvfs buf = {
+
1641 .f_namemax = 255,
+
1642 .f_bsize = 512,
+
1643 };
+
1644 fuse_reply_statfs(req, &buf);
+
1645 }
+
1646}
+
1647
+
1648static void do_setxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1649{
+
1650 struct fuse_session *se = req->se;
+
1651 unsigned int xattr_ext = !!(se->conn.want_ext & FUSE_CAP_SETXATTR_EXT);
+
1652 struct fuse_setxattr_in *arg = (struct fuse_setxattr_in *) inarg;
+
1653 char *name = xattr_ext ? PARAM(arg) :
+
1654 (char *)arg + FUSE_COMPAT_SETXATTR_IN_SIZE;
+
1655 char *value = name + strlen(name) + 1;
+
1656
+
1657 /* XXX:The API should be extended to support extra_flags/setxattr_flags */
+
1658 if (req->se->op.setxattr)
+
1659 req->se->op.setxattr(req, nodeid, name, value, arg->size,
+
1660 arg->flags);
+
1661 else
+
1662 fuse_reply_err(req, ENOSYS);
+
1663}
+
1664
+
1665static void do_getxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1666{
+
1667 struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg;
+
1668
+
1669 if (req->se->op.getxattr)
+
1670 req->se->op.getxattr(req, nodeid, PARAM(arg), arg->size);
+
1671 else
+
1672 fuse_reply_err(req, ENOSYS);
+
1673}
+
1674
+
1675static void do_listxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1676{
+
1677 struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg;
+
1678
+
1679 if (req->se->op.listxattr)
+
1680 req->se->op.listxattr(req, nodeid, arg->size);
+
1681 else
+
1682 fuse_reply_err(req, ENOSYS);
+
1683}
+
1684
+
1685static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1686{
+
1687 char *name = (char *) inarg;
+
1688
+
1689 if (req->se->op.removexattr)
+
1690 req->se->op.removexattr(req, nodeid, name);
+
1691 else
+
1692 fuse_reply_err(req, ENOSYS);
+
1693}
+
1694
+
1695static void convert_fuse_file_lock(struct fuse_file_lock *fl,
+
1696 struct flock *flock)
+
1697{
+
1698 memset(flock, 0, sizeof(struct flock));
+
1699 flock->l_type = fl->type;
+
1700 flock->l_whence = SEEK_SET;
+
1701 flock->l_start = fl->start;
+
1702 if (fl->end == OFFSET_MAX)
+
1703 flock->l_len = 0;
+
1704 else
+
1705 flock->l_len = fl->end - fl->start + 1;
+
1706 flock->l_pid = fl->pid;
+
1707}
+
1708
+
1709static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1710{
+
1711 struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg;
+
1712 struct fuse_file_info fi;
+
1713 struct flock flock;
+
1714
+
1715 memset(&fi, 0, sizeof(fi));
+
1716 fi.fh = arg->fh;
+
1717 fi.lock_owner = arg->owner;
+
1718
+
1719 convert_fuse_file_lock(&arg->lk, &flock);
+
1720 if (req->se->op.getlk)
+
1721 req->se->op.getlk(req, nodeid, &fi, &flock);
+
1722 else
+
1723 fuse_reply_err(req, ENOSYS);
+
1724}
+
1725
+
1726static void do_setlk_common(fuse_req_t req, fuse_ino_t nodeid,
+
1727 const void *inarg, int sleep)
+
1728{
+
1729 struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg;
+
1730 struct fuse_file_info fi;
+
1731 struct flock flock;
+
1732
+
1733 memset(&fi, 0, sizeof(fi));
+
1734 fi.fh = arg->fh;
+
1735 fi.lock_owner = arg->owner;
+
1736
+
1737 if (arg->lk_flags & FUSE_LK_FLOCK) {
+
1738 int op = 0;
+
1739
+
1740 switch (arg->lk.type) {
+
1741 case F_RDLCK:
+
1742 op = LOCK_SH;
+
1743 break;
+
1744 case F_WRLCK:
+
1745 op = LOCK_EX;
+
1746 break;
+
1747 case F_UNLCK:
+
1748 op = LOCK_UN;
+
1749 break;
+
1750 }
+
1751 if (!sleep)
+
1752 op |= LOCK_NB;
+
1753
+
1754 if (req->se->op.flock)
+
1755 req->se->op.flock(req, nodeid, &fi, op);
+
1756 else
+
1757 fuse_reply_err(req, ENOSYS);
+
1758 } else {
+
1759 convert_fuse_file_lock(&arg->lk, &flock);
+
1760 if (req->se->op.setlk)
+
1761 req->se->op.setlk(req, nodeid, &fi, &flock, sleep);
+
1762 else
+
1763 fuse_reply_err(req, ENOSYS);
+
1764 }
+
1765}
+
1766
+
1767static void do_setlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1768{
+
1769 do_setlk_common(req, nodeid, inarg, 0);
+
1770}
+
1771
+
1772static void do_setlkw(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1773{
+
1774 do_setlk_common(req, nodeid, inarg, 1);
+
1775}
+
1776
+
1777static int find_interrupted(struct fuse_session *se, struct fuse_req *req)
+
1778{
+
1779 struct fuse_req *curr;
+
1780
+
1781 for (curr = se->list.next; curr != &se->list; curr = curr->next) {
+
1782 if (curr->unique == req->u.i.unique) {
+ +
1784 void *data;
+
1785
+
1786 curr->ref_cnt++;
+
1787 pthread_mutex_unlock(&se->lock);
+
1788
+
1789 /* Ugh, ugly locking */
+
1790 pthread_mutex_lock(&curr->lock);
+
1791 pthread_mutex_lock(&se->lock);
+
1792 curr->interrupted = 1;
+
1793 func = curr->u.ni.func;
+
1794 data = curr->u.ni.data;
+
1795 pthread_mutex_unlock(&se->lock);
+
1796 if (func)
+
1797 func(curr, data);
+
1798 pthread_mutex_unlock(&curr->lock);
+
1799
+
1800 pthread_mutex_lock(&se->lock);
+
1801 curr->ref_cnt--;
+
1802 if (!curr->ref_cnt) {
+
1803 destroy_req(curr);
+
1804 }
+
1805
+
1806 return 1;
+
1807 }
+
1808 }
+
1809 for (curr = se->interrupts.next; curr != &se->interrupts;
+
1810 curr = curr->next) {
+
1811 if (curr->u.i.unique == req->u.i.unique)
+
1812 return 1;
+
1813 }
+
1814 return 0;
+
1815}
+
1816
+
1817static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1818{
+
1819 struct fuse_interrupt_in *arg = (struct fuse_interrupt_in *) inarg;
+
1820 struct fuse_session *se = req->se;
+
1821
+
1822 (void) nodeid;
+
1823 if (se->debug)
+
1824 fuse_log(FUSE_LOG_DEBUG, "INTERRUPT: %llu\n",
+
1825 (unsigned long long) arg->unique);
+
1826
+
1827 req->u.i.unique = arg->unique;
+
1828
+
1829 pthread_mutex_lock(&se->lock);
+
1830 if (find_interrupted(se, req)) {
+
1831 fuse_chan_put(req->ch);
+
1832 req->ch = NULL;
+
1833 destroy_req(req);
+
1834 } else
+
1835 list_add_req(req, &se->interrupts);
+
1836 pthread_mutex_unlock(&se->lock);
+
1837}
+
1838
+
1839static struct fuse_req *check_interrupt(struct fuse_session *se,
+
1840 struct fuse_req *req)
+
1841{
+
1842 struct fuse_req *curr;
+
1843
+
1844 for (curr = se->interrupts.next; curr != &se->interrupts;
+
1845 curr = curr->next) {
+
1846 if (curr->u.i.unique == req->unique) {
+
1847 req->interrupted = 1;
+
1848 list_del_req(curr);
+
1849 fuse_chan_put(curr->ch);
+
1850 curr->ch = NULL;
+
1851 destroy_req(curr);
+
1852 return NULL;
+
1853 }
+
1854 }
+
1855 curr = se->interrupts.next;
+
1856 if (curr != &se->interrupts) {
+
1857 list_del_req(curr);
+
1858 list_init_req(curr);
+
1859 return curr;
+
1860 } else
+
1861 return NULL;
+
1862}
+
1863
+
1864static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1865{
+
1866 struct fuse_bmap_in *arg = (struct fuse_bmap_in *) inarg;
+
1867
+
1868 if (req->se->op.bmap)
+
1869 req->se->op.bmap(req, nodeid, arg->blocksize, arg->block);
+
1870 else
+
1871 fuse_reply_err(req, ENOSYS);
+
1872}
+
1873
+
1874static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1875{
+
1876 struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *) inarg;
+
1877 unsigned int flags = arg->flags;
+
1878 void *in_buf = arg->in_size ? PARAM(arg) : NULL;
+
1879 struct fuse_file_info fi;
+
1880
+
1881 if (flags & FUSE_IOCTL_DIR &&
+
1882 !(req->se->conn.want_ext & FUSE_CAP_IOCTL_DIR)) {
+
1883 fuse_reply_err(req, ENOTTY);
+
1884 return;
+
1885 }
+
1886
+
1887 memset(&fi, 0, sizeof(fi));
+
1888 fi.fh = arg->fh;
+
1889
+
1890 if (sizeof(void *) == 4 && req->se->conn.proto_minor >= 16 &&
+
1891 !(flags & FUSE_IOCTL_32BIT)) {
+
1892 req->ioctl_64bit = 1;
+
1893 }
+
1894
+
1895 if (req->se->op.ioctl)
+
1896 req->se->op.ioctl(req, nodeid, arg->cmd,
+
1897 (void *)(uintptr_t)arg->arg, &fi, flags,
+
1898 in_buf, arg->in_size, arg->out_size);
+
1899 else
+
1900 fuse_reply_err(req, ENOSYS);
+
1901}
+
1902
+
+
1903void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
1904{
+
1905 free(ph);
+
1906}
+
+
1907
+
1908static void do_poll(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1909{
+
1910 struct fuse_poll_in *arg = (struct fuse_poll_in *) inarg;
+
1911 struct fuse_file_info fi;
+
1912
+
1913 memset(&fi, 0, sizeof(fi));
+
1914 fi.fh = arg->fh;
+
1915 fi.poll_events = arg->events;
+
1916
+
1917 if (req->se->op.poll) {
+
1918 struct fuse_pollhandle *ph = NULL;
+
1919
+
1920 if (arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) {
+
1921 ph = malloc(sizeof(struct fuse_pollhandle));
+
1922 if (ph == NULL) {
+
1923 fuse_reply_err(req, ENOMEM);
+
1924 return;
+
1925 }
+
1926 ph->kh = arg->kh;
+
1927 ph->se = req->se;
+
1928 }
+
1929
+
1930 req->se->op.poll(req, nodeid, &fi, ph);
+
1931 } else {
+
1932 fuse_reply_err(req, ENOSYS);
+
1933 }
+
1934}
+
1935
+
1936static void do_fallocate(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1937{
+
1938 struct fuse_fallocate_in *arg = (struct fuse_fallocate_in *) inarg;
+
1939 struct fuse_file_info fi;
+
1940
+
1941 memset(&fi, 0, sizeof(fi));
+
1942 fi.fh = arg->fh;
+
1943
+
1944 if (req->se->op.fallocate)
+
1945 req->se->op.fallocate(req, nodeid, arg->mode, arg->offset, arg->length, &fi);
+
1946 else
+
1947 fuse_reply_err(req, ENOSYS);
+
1948}
+
1949
+
1950static void do_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in, const void *inarg)
+
1951{
+
1952 struct fuse_copy_file_range_in *arg = (struct fuse_copy_file_range_in *) inarg;
+
1953 struct fuse_file_info fi_in, fi_out;
+
1954
+
1955 memset(&fi_in, 0, sizeof(fi_in));
+
1956 fi_in.fh = arg->fh_in;
+
1957
+
1958 memset(&fi_out, 0, sizeof(fi_out));
+
1959 fi_out.fh = arg->fh_out;
+
1960
+
1961
+
1962 if (req->se->op.copy_file_range)
+
1963 req->se->op.copy_file_range(req, nodeid_in, arg->off_in,
+
1964 &fi_in, arg->nodeid_out,
+
1965 arg->off_out, &fi_out, arg->len,
+
1966 arg->flags);
+
1967 else
+
1968 fuse_reply_err(req, ENOSYS);
+
1969}
+
1970
+
1971static void do_lseek(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1972{
+
1973 struct fuse_lseek_in *arg = (struct fuse_lseek_in *) inarg;
+
1974 struct fuse_file_info fi;
+
1975
+
1976 memset(&fi, 0, sizeof(fi));
+
1977 fi.fh = arg->fh;
+
1978
+
1979 if (req->se->op.lseek)
+
1980 req->se->op.lseek(req, nodeid, arg->offset, arg->whence, &fi);
+
1981 else
+
1982 fuse_reply_err(req, ENOSYS);
+
1983}
+
1984
+
1985static bool want_flags_valid(uint64_t capable, uint64_t want)
+
1986{
+
1987 uint64_t unknown_flags = want & (~capable);
+
1988 if (unknown_flags != 0) {
+
1989 fuse_log(FUSE_LOG_ERR,
+
1990 "fuse: unknown connection 'want' flags: 0x%08lx\n",
+
1991 unknown_flags);
+
1992 return false;
+
1993 }
+
1994 return true;
+
1995}
+
1996
+
+ +
2001{
+
2002 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
2003
+
2004 /*
+
2005 * Convert want to want_ext if necessary.
+
2006 * For the high level interface this function might be called
+
2007 * twice, once from the high level interface and once from the
+
2008 * low level interface. Both, with different want_ext_default and
+
2009 * want_default values. In order to suppress a failure for the
+
2010 * second call, we check if the lower 32 bits of want_ext are
+
2011 * already set to the value of want.
+
2012 */
+
2013 if (conn->want != se->conn_want &&
+
2014 fuse_lower_32_bits(conn->want_ext) != conn->want) {
+
2015 if (conn->want_ext != se->conn_want_ext) {
+
2016 fuse_log(FUSE_LOG_ERR,
+
2017 "%s: Both conn->want_ext and conn->want are set.\n"
+
2018 "want=%x, want_ext=%lx, se->want=%lx se->want_ext=%lx\n",
+
2019 __func__, conn->want, conn->want_ext,
+
2020 se->conn_want, se->conn_want_ext);
+
2021 return -EINVAL;
+
2022 }
+
2023
+
2024 /* high bits from want_ext, low bits from want */
+
2025 conn->want_ext = fuse_higher_32_bits(conn->want_ext) |
+
2026 conn->want;
+
2027 }
+
2028
+
2029 /* ensure there won't be a second conversion */
+
2030 conn->want = fuse_lower_32_bits(conn->want_ext);
+
2031
+
2032 return 0;
+
2033}
+
+
2034
+
+ +
2036 uint64_t flag)
+
2037{
+
2038 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
2039
+
2040 if (conn->capable_ext & flag) {
+
2041 conn->want_ext |= flag;
+
2042 se->conn_want_ext |= flag;
+
2043 conn->want |= flag;
+
2044 se->conn_want |= flag;
+
2045 return true;
+
2046 }
+
2047 return false;
+
2048}
+
+
2049
+
+ +
2051 uint64_t flag)
+
2052{
+
2053 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
2054
+
2055 conn->want_ext &= ~flag;
+
2056 se->conn_want_ext &= ~flag;
+
2057 conn->want &= ~flag;
+
2058 se->conn_want &= ~flag;
+
2059}
+
+
2060
+
+ +
2062 uint64_t flag)
+
2063{
+
2064 return conn->capable_ext & flag ? true : false;
+
2065}
+
+
2066
+
2067
+
2068/* Prevent bogus data races (bogus since "init" is called before
+
2069 * multi-threading becomes relevant */
+
2070static __attribute__((no_sanitize("thread")))
+
2071void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2072{
+
2073 struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
+
2074 struct fuse_init_out outarg;
+
2075 struct fuse_session *se = req->se;
+
2076 size_t bufsize = se->bufsize;
+
2077 size_t outargsize = sizeof(outarg);
+
2078 uint64_t inargflags = 0;
+
2079 uint64_t outargflags = 0;
+
2080 bool buf_reallocable = se->buf_reallocable;
+
2081 (void) nodeid;
+
2082 if (se->debug) {
+
2083 fuse_log(FUSE_LOG_DEBUG, "INIT: %u.%u\n", arg->major, arg->minor);
+
2084 if (arg->major == 7 && arg->minor >= 6) {
+
2085 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
2086 fuse_log(FUSE_LOG_DEBUG, "max_readahead=0x%08x\n",
+
2087 arg->max_readahead);
+
2088 }
+
2089 }
+
2090 se->conn.proto_major = arg->major;
+
2091 se->conn.proto_minor = arg->minor;
+
2092 se->conn.capable_ext = 0;
+
2093 se->conn.want_ext = 0;
+
2094
+
2095 memset(&outarg, 0, sizeof(outarg));
+
2096 outarg.major = FUSE_KERNEL_VERSION;
+
2097 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
2098
+
2099 if (arg->major < 7) {
+
2100 fuse_log(FUSE_LOG_ERR, "fuse: unsupported protocol version: %u.%u\n",
+
2101 arg->major, arg->minor);
+
2102 fuse_reply_err(req, EPROTO);
+
2103 return;
+
2104 }
+
2105
+
2106 if (arg->major > 7) {
+
2107 /* Wait for a second INIT request with a 7.X version */
+
2108 send_reply_ok(req, &outarg, sizeof(outarg));
+
2109 return;
+
2110 }
+
2111
+
2112 if (arg->minor >= 6) {
+
2113 if (arg->max_readahead < se->conn.max_readahead)
+
2114 se->conn.max_readahead = arg->max_readahead;
+
2115 inargflags = arg->flags;
+
2116 if (inargflags & FUSE_INIT_EXT)
+
2117 inargflags = inargflags | (uint64_t) arg->flags2 << 32;
+
2118 if (inargflags & FUSE_ASYNC_READ)
+
2119 se->conn.capable_ext |= FUSE_CAP_ASYNC_READ;
+
2120 if (inargflags & FUSE_POSIX_LOCKS)
+
2121 se->conn.capable_ext |= FUSE_CAP_POSIX_LOCKS;
+
2122 if (inargflags & FUSE_ATOMIC_O_TRUNC)
+
2123 se->conn.capable_ext |= FUSE_CAP_ATOMIC_O_TRUNC;
+
2124 if (inargflags & FUSE_EXPORT_SUPPORT)
+
2125 se->conn.capable_ext |= FUSE_CAP_EXPORT_SUPPORT;
+
2126 if (inargflags & FUSE_DONT_MASK)
+
2127 se->conn.capable_ext |= FUSE_CAP_DONT_MASK;
+
2128 if (inargflags & FUSE_FLOCK_LOCKS)
+
2129 se->conn.capable_ext |= FUSE_CAP_FLOCK_LOCKS;
+
2130 if (inargflags & FUSE_AUTO_INVAL_DATA)
+
2131 se->conn.capable_ext |= FUSE_CAP_AUTO_INVAL_DATA;
+
2132 if (inargflags & FUSE_DO_READDIRPLUS)
+
2133 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS;
+
2134 if (inargflags & FUSE_READDIRPLUS_AUTO)
+
2135 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS_AUTO;
+
2136 if (inargflags & FUSE_ASYNC_DIO)
+
2137 se->conn.capable_ext |= FUSE_CAP_ASYNC_DIO;
+
2138 if (inargflags & FUSE_WRITEBACK_CACHE)
+
2139 se->conn.capable_ext |= FUSE_CAP_WRITEBACK_CACHE;
+
2140 if (inargflags & FUSE_NO_OPEN_SUPPORT)
+
2141 se->conn.capable_ext |= FUSE_CAP_NO_OPEN_SUPPORT;
+
2142 if (inargflags & FUSE_PARALLEL_DIROPS)
+
2143 se->conn.capable_ext |= FUSE_CAP_PARALLEL_DIROPS;
+
2144 if (inargflags & FUSE_POSIX_ACL)
+
2145 se->conn.capable_ext |= FUSE_CAP_POSIX_ACL;
+
2146 if (inargflags & FUSE_HANDLE_KILLPRIV)
+
2147 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV;
+
2148 if (inargflags & FUSE_HANDLE_KILLPRIV_V2)
+
2149 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV_V2;
+
2150 if (inargflags & FUSE_CACHE_SYMLINKS)
+
2151 se->conn.capable_ext |= FUSE_CAP_CACHE_SYMLINKS;
+
2152 if (inargflags & FUSE_NO_OPENDIR_SUPPORT)
+
2153 se->conn.capable_ext |= FUSE_CAP_NO_OPENDIR_SUPPORT;
+
2154 if (inargflags & FUSE_EXPLICIT_INVAL_DATA)
+
2155 se->conn.capable_ext |= FUSE_CAP_EXPLICIT_INVAL_DATA;
+
2156 if (inargflags & FUSE_SETXATTR_EXT)
+
2157 se->conn.capable_ext |= FUSE_CAP_SETXATTR_EXT;
+
2158 if (!(inargflags & FUSE_MAX_PAGES)) {
+
2159 size_t max_bufsize =
+
2160 FUSE_DEFAULT_MAX_PAGES_PER_REQ * getpagesize()
+
2161 + FUSE_BUFFER_HEADER_SIZE;
+
2162 if (bufsize > max_bufsize) {
+
2163 bufsize = max_bufsize;
+
2164 }
+
2165 buf_reallocable = false;
+
2166 }
+
2167 if (inargflags & FUSE_DIRECT_IO_ALLOW_MMAP)
+
2168 se->conn.capable_ext |= FUSE_CAP_DIRECT_IO_ALLOW_MMAP;
+
2169 if (arg->minor >= 38 || (inargflags & FUSE_HAS_EXPIRE_ONLY))
+
2170 se->conn.capable_ext |= FUSE_CAP_EXPIRE_ONLY;
+
2171 if (inargflags & FUSE_PASSTHROUGH)
+
2172 se->conn.capable_ext |= FUSE_CAP_PASSTHROUGH;
+
2173 if (inargflags & FUSE_NO_EXPORT_SUPPORT)
+
2174 se->conn.capable_ext |= FUSE_CAP_NO_EXPORT_SUPPORT;
+
2175 } else {
+
2176 se->conn.max_readahead = 0;
+
2177 }
+
2178
+
2179 if (se->conn.proto_minor >= 14) {
+
2180#ifdef HAVE_SPLICE
+
2181#ifdef HAVE_VMSPLICE
+
2182 if ((se->io == NULL) || (se->io->splice_send != NULL)) {
+
2183 se->conn.capable_ext |= FUSE_CAP_SPLICE_WRITE |
+ +
2185 }
+
2186#endif
+
2187 if ((se->io == NULL) || (se->io->splice_receive != NULL)) {
+
2188 se->conn.capable_ext |= FUSE_CAP_SPLICE_READ;
+
2189 }
+
2190#endif
+
2191 }
+
2192 if (se->conn.proto_minor >= 18)
+
2193 se->conn.capable_ext |= FUSE_CAP_IOCTL_DIR;
+
2194
+
2195 /* Default settings for modern filesystems.
+
2196 *
+
2197 * Most of these capabilities were disabled by default in
+
2198 * libfuse2 for backwards compatibility reasons. In libfuse3,
+
2199 * we can finally enable them by default (as long as they're
+
2200 * supported by the kernel).
+
2201 */
+
2202#define LL_SET_DEFAULT(cond, cap) \
+
2203 if ((cond)) \
+
2204 fuse_set_feature_flag(&se->conn, cap)
+
2205
+
2206 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_READ);
+
2207 LL_SET_DEFAULT(1, FUSE_CAP_AUTO_INVAL_DATA);
+
2208 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_DIO);
+
2209 LL_SET_DEFAULT(1, FUSE_CAP_IOCTL_DIR);
+
2210 LL_SET_DEFAULT(1, FUSE_CAP_ATOMIC_O_TRUNC);
+
2211 LL_SET_DEFAULT(se->op.write_buf, FUSE_CAP_SPLICE_READ);
+
2212 LL_SET_DEFAULT(se->op.getlk && se->op.setlk,
+ +
2214 LL_SET_DEFAULT(se->op.flock, FUSE_CAP_FLOCK_LOCKS);
+
2215 LL_SET_DEFAULT(se->op.readdirplus, FUSE_CAP_READDIRPLUS);
+
2216 LL_SET_DEFAULT(se->op.readdirplus && se->op.readdir,
+ +
2218
+
2219 /* This could safely become default, but libfuse needs an API extension
+
2220 * to support it
+
2221 * LL_SET_DEFAULT(1, FUSE_CAP_SETXATTR_EXT);
+
2222 */
+
2223
+
2224 se->conn.time_gran = 1;
+
2225
+
2226 se->got_init = 1;
+
2227 if (se->op.init) {
+
2228 // Apply the first 32 bits of capable_ext to capable
+
2229 se->conn.capable = fuse_lower_32_bits(se->conn.capable_ext);
+
2230
+
2231 se->op.init(se->userdata, &se->conn);
+
2232
+
2233 /*
+
2234 * se->conn.want is 32-bit value and deprecated in favour of
+
2235 * se->conn.want_ext
+
2236 * Userspace might still use conn.want - we need to convert it
+
2237 */
+ +
2239 }
+
2240
+
2241 if (!want_flags_valid(se->conn.capable_ext, se->conn.want_ext)) {
+
2242 fuse_reply_err(req, EPROTO);
+
2243 se->error = -EPROTO;
+ +
2245 return;
+
2246 }
+
2247
+
2248 unsigned max_read_mo = get_max_read(se->mo);
+
2249 if (se->conn.max_read != max_read_mo) {
+
2250 fuse_log(FUSE_LOG_ERR, "fuse: error: init() and fuse_session_new() "
+
2251 "requested different maximum read size (%u vs %u)\n",
+
2252 se->conn.max_read, max_read_mo);
+
2253 fuse_reply_err(req, EPROTO);
+
2254 se->error = -EPROTO;
+ +
2256 return;
+
2257 }
+
2258
+
2259 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
2260 fuse_log(FUSE_LOG_ERR,
+
2261 "fuse: warning: buffer size too small: %zu\n",
+
2262 bufsize);
+
2263 bufsize = FUSE_MIN_READ_BUFFER;
+
2264 }
+
2265
+
2266 if (buf_reallocable)
+
2267 bufsize = UINT_MAX;
+
2268 se->conn.max_write = MIN(se->conn.max_write, bufsize - FUSE_BUFFER_HEADER_SIZE);
+
2269 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
2270
+
2271 if (arg->flags & FUSE_MAX_PAGES) {
+
2272 outarg.flags |= FUSE_MAX_PAGES;
+
2273 outarg.max_pages = (se->conn.max_write - 1) / getpagesize() + 1;
+
2274 }
+
2275 outargflags = outarg.flags;
+
2276 /* Always enable big writes, this is superseded
+
2277 by the max_write option */
+
2278 outargflags |= FUSE_BIG_WRITES;
+
2279
+
2280 if (se->conn.want_ext & FUSE_CAP_ASYNC_READ)
+
2281 outargflags |= FUSE_ASYNC_READ;
+
2282 if (se->conn.want_ext & FUSE_CAP_POSIX_LOCKS)
+
2283 outargflags |= FUSE_POSIX_LOCKS;
+
2284 if (se->conn.want_ext & FUSE_CAP_ATOMIC_O_TRUNC)
+
2285 outargflags |= FUSE_ATOMIC_O_TRUNC;
+
2286 if (se->conn.want_ext & FUSE_CAP_EXPORT_SUPPORT)
+
2287 outargflags |= FUSE_EXPORT_SUPPORT;
+
2288 if (se->conn.want_ext & FUSE_CAP_DONT_MASK)
+
2289 outargflags |= FUSE_DONT_MASK;
+
2290 if (se->conn.want_ext & FUSE_CAP_FLOCK_LOCKS)
+
2291 outargflags |= FUSE_FLOCK_LOCKS;
+
2292 if (se->conn.want_ext & FUSE_CAP_AUTO_INVAL_DATA)
+
2293 outargflags |= FUSE_AUTO_INVAL_DATA;
+
2294 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS)
+
2295 outargflags |= FUSE_DO_READDIRPLUS;
+
2296 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS_AUTO)
+
2297 outargflags |= FUSE_READDIRPLUS_AUTO;
+
2298 if (se->conn.want_ext & FUSE_CAP_ASYNC_DIO)
+
2299 outargflags |= FUSE_ASYNC_DIO;
+
2300 if (se->conn.want_ext & FUSE_CAP_WRITEBACK_CACHE)
+
2301 outargflags |= FUSE_WRITEBACK_CACHE;
+
2302 if (se->conn.want_ext & FUSE_CAP_PARALLEL_DIROPS)
+
2303 outargflags |= FUSE_PARALLEL_DIROPS;
+
2304 if (se->conn.want_ext & FUSE_CAP_POSIX_ACL)
+
2305 outargflags |= FUSE_POSIX_ACL;
+
2306 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV)
+
2307 outargflags |= FUSE_HANDLE_KILLPRIV;
+
2308 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2)
+
2309 outargflags |= FUSE_HANDLE_KILLPRIV_V2;
+
2310 if (se->conn.want_ext & FUSE_CAP_CACHE_SYMLINKS)
+
2311 outargflags |= FUSE_CACHE_SYMLINKS;
+
2312 if (se->conn.want_ext & FUSE_CAP_EXPLICIT_INVAL_DATA)
+
2313 outargflags |= FUSE_EXPLICIT_INVAL_DATA;
+
2314 if (se->conn.want_ext & FUSE_CAP_SETXATTR_EXT)
+
2315 outargflags |= FUSE_SETXATTR_EXT;
+
2316 if (se->conn.want_ext & FUSE_CAP_DIRECT_IO_ALLOW_MMAP)
+
2317 outargflags |= FUSE_DIRECT_IO_ALLOW_MMAP;
+
2318 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH) {
+
2319 outargflags |= FUSE_PASSTHROUGH;
+
2320 /*
+
2321 * outarg.max_stack_depth includes the fuse stack layer,
+
2322 * so it is one more than max_backing_stack_depth.
+
2323 */
+
2324 outarg.max_stack_depth = se->conn.max_backing_stack_depth + 1;
+
2325 }
+
2326 if (se->conn.want_ext & FUSE_CAP_NO_EXPORT_SUPPORT)
+
2327 outargflags |= FUSE_NO_EXPORT_SUPPORT;
+
2328
+
2329 if (inargflags & FUSE_INIT_EXT) {
+
2330 outargflags |= FUSE_INIT_EXT;
+
2331 outarg.flags2 = outargflags >> 32;
+
2332 }
+
2333
+
2334 outarg.flags = outargflags;
+
2335
+
2336 outarg.max_readahead = se->conn.max_readahead;
+
2337 outarg.max_write = se->conn.max_write;
+
2338 if (se->conn.proto_minor >= 13) {
+
2339 if (se->conn.max_background >= (1 << 16))
+
2340 se->conn.max_background = (1 << 16) - 1;
+
2341 if (se->conn.congestion_threshold > se->conn.max_background)
+
2342 se->conn.congestion_threshold = se->conn.max_background;
+
2343 if (!se->conn.congestion_threshold) {
+
2344 se->conn.congestion_threshold =
+
2345 se->conn.max_background * 3 / 4;
+
2346 }
+
2347
+
2348 outarg.max_background = se->conn.max_background;
+
2349 outarg.congestion_threshold = se->conn.congestion_threshold;
+
2350 }
+
2351 if (se->conn.proto_minor >= 23)
+
2352 outarg.time_gran = se->conn.time_gran;
+
2353
+
2354 if (se->debug) {
+
2355 fuse_log(FUSE_LOG_DEBUG, " INIT: %u.%u\n", outarg.major, outarg.minor);
+
2356 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
2357 fuse_log(FUSE_LOG_DEBUG, " max_readahead=0x%08x\n",
+
2358 outarg.max_readahead);
+
2359 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
2360 fuse_log(FUSE_LOG_DEBUG, " max_background=%i\n",
+
2361 outarg.max_background);
+
2362 fuse_log(FUSE_LOG_DEBUG, " congestion_threshold=%i\n",
+
2363 outarg.congestion_threshold);
+
2364 fuse_log(FUSE_LOG_DEBUG, " time_gran=%u\n",
+
2365 outarg.time_gran);
+
2366 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH)
+
2367 fuse_log(FUSE_LOG_DEBUG, " max_stack_depth=%u\n",
+
2368 outarg.max_stack_depth);
+
2369 }
+
2370 if (arg->minor < 5)
+
2371 outargsize = FUSE_COMPAT_INIT_OUT_SIZE;
+
2372 else if (arg->minor < 23)
+
2373 outargsize = FUSE_COMPAT_22_INIT_OUT_SIZE;
+
2374
+
2375 send_reply_ok(req, &outarg, outargsize);
+
2376}
+
2377
+
2378static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2379{
+
2380 struct fuse_session *se = req->se;
+
2381 char *mountpoint;
+
2382
+
2383 (void) nodeid;
+
2384 (void) inarg;
+
2385
+
2386 mountpoint = atomic_exchange(&se->mountpoint, NULL);
+
2387 free(mountpoint);
+
2388
+
2389 se->got_destroy = 1;
+
2390 se->got_init = 0;
+
2391 if (se->op.destroy)
+
2392 se->op.destroy(se->userdata);
+
2393
+
2394 send_reply_ok(req, NULL, 0);
+
2395}
+
2396
+
2397static void list_del_nreq(struct fuse_notify_req *nreq)
+
2398{
+
2399 struct fuse_notify_req *prev = nreq->prev;
+
2400 struct fuse_notify_req *next = nreq->next;
+
2401 prev->next = next;
+
2402 next->prev = prev;
+
2403}
+
2404
+
2405static void list_add_nreq(struct fuse_notify_req *nreq,
+
2406 struct fuse_notify_req *next)
+
2407{
+
2408 struct fuse_notify_req *prev = next->prev;
+
2409 nreq->next = next;
+
2410 nreq->prev = prev;
+
2411 prev->next = nreq;
+
2412 next->prev = nreq;
+
2413}
+
2414
+
2415static void list_init_nreq(struct fuse_notify_req *nreq)
+
2416{
+
2417 nreq->next = nreq;
+
2418 nreq->prev = nreq;
+
2419}
+
2420
+
2421static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid,
+
2422 const void *inarg, const struct fuse_buf *buf)
+
2423{
+
2424 struct fuse_session *se = req->se;
+
2425 struct fuse_notify_req *nreq;
+
2426 struct fuse_notify_req *head;
+
2427
+
2428 pthread_mutex_lock(&se->lock);
+
2429 head = &se->notify_list;
+
2430 for (nreq = head->next; nreq != head; nreq = nreq->next) {
+
2431 if (nreq->unique == req->unique) {
+
2432 list_del_nreq(nreq);
+
2433 break;
+
2434 }
+
2435 }
+
2436 pthread_mutex_unlock(&se->lock);
+
2437
+
2438 if (nreq != head)
+
2439 nreq->reply(nreq, req, nodeid, inarg, buf);
+
2440}
+
2441
+
2442static int send_notify_iov(struct fuse_session *se, int notify_code,
+
2443 struct iovec *iov, int count)
+
2444{
+
2445 struct fuse_out_header out;
+
2446
+
2447 if (!se->got_init)
+
2448 return -ENOTCONN;
+
2449
+
2450 out.unique = 0;
+
2451 out.error = notify_code;
+
2452 iov[0].iov_base = &out;
+
2453 iov[0].iov_len = sizeof(struct fuse_out_header);
+
2454
+
2455 return fuse_send_msg(se, NULL, iov, count);
+
2456}
+
2457
+
+
2458int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
2459{
+
2460 if (ph != NULL) {
+
2461 struct fuse_notify_poll_wakeup_out outarg;
+
2462 struct iovec iov[2];
+
2463
+
2464 outarg.kh = ph->kh;
+
2465
+
2466 iov[1].iov_base = &outarg;
+
2467 iov[1].iov_len = sizeof(outarg);
+
2468
+
2469 return send_notify_iov(ph->se, FUSE_NOTIFY_POLL, iov, 2);
+
2470 } else {
+
2471 return 0;
+
2472 }
+
2473}
+
+
2474
+
+
2475int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
2476 off_t off, off_t len)
+
2477{
+
2478 struct fuse_notify_inval_inode_out outarg;
+
2479 struct iovec iov[2];
+
2480
+
2481 if (!se)
+
2482 return -EINVAL;
+
2483
+
2484 if (se->conn.proto_minor < 12)
+
2485 return -ENOSYS;
+
2486
+
2487 outarg.ino = ino;
+
2488 outarg.off = off;
+
2489 outarg.len = len;
+
2490
+
2491 iov[1].iov_base = &outarg;
+
2492 iov[1].iov_len = sizeof(outarg);
+
2493
+
2494 return send_notify_iov(se, FUSE_NOTIFY_INVAL_INODE, iov, 2);
+
2495}
+
+
2496
+
2516static int fuse_lowlevel_notify_entry(struct fuse_session *se, fuse_ino_t parent,
+
2517 const char *name, size_t namelen,
+
2518 enum fuse_notify_entry_flags flags)
+
2519{
+
2520 struct fuse_notify_inval_entry_out outarg;
+
2521 struct iovec iov[3];
+
2522
+
2523 if (!se)
+
2524 return -EINVAL;
+
2525
+
2526 if (se->conn.proto_minor < 12)
+
2527 return -ENOSYS;
+
2528
+
2529 outarg.parent = parent;
+
2530 outarg.namelen = namelen;
+
2531 outarg.flags = 0;
+
2532 if (flags & FUSE_LL_EXPIRE_ONLY)
+
2533 outarg.flags |= FUSE_EXPIRE_ONLY;
+
2534
+
2535 iov[1].iov_base = &outarg;
+
2536 iov[1].iov_len = sizeof(outarg);
+
2537 iov[2].iov_base = (void *)name;
+
2538 iov[2].iov_len = namelen + 1;
+
2539
+
2540 return send_notify_iov(se, FUSE_NOTIFY_INVAL_ENTRY, iov, 3);
+
2541}
+
2542
+
+
2543int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
2544 const char *name, size_t namelen)
+
2545{
+
2546 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_INVALIDATE);
+
2547}
+
+
2548
+
+
2549int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
2550 const char *name, size_t namelen)
+
2551{
+
2552 if (!se)
+
2553 return -EINVAL;
+
2554
+
2555 if (!(se->conn.capable_ext & FUSE_CAP_EXPIRE_ONLY))
+
2556 return -ENOSYS;
+
2557
+
2558 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_EXPIRE_ONLY);
+
2559}
+
+
2560
+
2561
+
+
2562int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
2563 fuse_ino_t parent, fuse_ino_t child,
+
2564 const char *name, size_t namelen)
+
2565{
+
2566 struct fuse_notify_delete_out outarg;
+
2567 struct iovec iov[3];
+
2568
+
2569 if (!se)
+
2570 return -EINVAL;
+
2571
+
2572 if (se->conn.proto_minor < 18)
+
2573 return -ENOSYS;
+
2574
+
2575 outarg.parent = parent;
+
2576 outarg.child = child;
+
2577 outarg.namelen = namelen;
+
2578 outarg.padding = 0;
+
2579
+
2580 iov[1].iov_base = &outarg;
+
2581 iov[1].iov_len = sizeof(outarg);
+
2582 iov[2].iov_base = (void *)name;
+
2583 iov[2].iov_len = namelen + 1;
+
2584
+
2585 return send_notify_iov(se, FUSE_NOTIFY_DELETE, iov, 3);
+
2586}
+
+
2587
+
+
2588int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
2589 off_t offset, struct fuse_bufvec *bufv,
+
2590 enum fuse_buf_copy_flags flags)
+
2591{
+
2592 struct fuse_out_header out;
+
2593 struct fuse_notify_store_out outarg;
+
2594 struct iovec iov[3];
+
2595 size_t size = fuse_buf_size(bufv);
+
2596 int res;
+
2597
+
2598 if (!se)
+
2599 return -EINVAL;
+
2600
+
2601 if (se->conn.proto_minor < 15)
+
2602 return -ENOSYS;
+
2603
+
2604 out.unique = 0;
+
2605 out.error = FUSE_NOTIFY_STORE;
+
2606
+
2607 outarg.nodeid = ino;
+
2608 outarg.offset = offset;
+
2609 outarg.size = size;
+
2610 outarg.padding = 0;
+
2611
+
2612 iov[0].iov_base = &out;
+
2613 iov[0].iov_len = sizeof(out);
+
2614 iov[1].iov_base = &outarg;
+
2615 iov[1].iov_len = sizeof(outarg);
+
2616
+
2617 res = fuse_send_data_iov(se, NULL, iov, 2, bufv, flags);
+
2618 if (res > 0)
+
2619 res = -res;
+
2620
+
2621 return res;
+
2622}
+
+
2623
+
2624struct fuse_retrieve_req {
+
2625 struct fuse_notify_req nreq;
+
2626 void *cookie;
+
2627};
+
2628
+
2629static void fuse_ll_retrieve_reply(struct fuse_notify_req *nreq,
+
2630 fuse_req_t req, fuse_ino_t ino,
+
2631 const void *inarg,
+
2632 const struct fuse_buf *ibuf)
+
2633{
+
2634 struct fuse_session *se = req->se;
+
2635 struct fuse_retrieve_req *rreq =
+
2636 container_of(nreq, struct fuse_retrieve_req, nreq);
+
2637 const struct fuse_notify_retrieve_in *arg = inarg;
+
2638 struct fuse_bufvec bufv = {
+
2639 .buf[0] = *ibuf,
+
2640 .count = 1,
+
2641 };
+
2642
+
2643 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
2644 bufv.buf[0].mem = PARAM(arg);
+
2645
+
2646 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
2647 sizeof(struct fuse_notify_retrieve_in);
+
2648
+
2649 if (bufv.buf[0].size < arg->size) {
+
2650 fuse_log(FUSE_LOG_ERR, "fuse: retrieve reply: buffer size too small\n");
+
2651 fuse_reply_none(req);
+
2652 goto out;
+
2653 }
+
2654 bufv.buf[0].size = arg->size;
+
2655
+
2656 if (se->op.retrieve_reply) {
+
2657 se->op.retrieve_reply(req, rreq->cookie, ino,
+
2658 arg->offset, &bufv);
+
2659 } else {
+
2660 fuse_reply_none(req);
+
2661 }
+
2662out:
+
2663 free(rreq);
+
2664 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
2665 fuse_ll_clear_pipe(se);
+
2666}
+
2667
+
+
2668int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
2669 size_t size, off_t offset, void *cookie)
+
2670{
+
2671 struct fuse_notify_retrieve_out outarg;
+
2672 struct iovec iov[2];
+
2673 struct fuse_retrieve_req *rreq;
+
2674 int err;
+
2675
+
2676 if (!se)
+
2677 return -EINVAL;
+
2678
+
2679 if (se->conn.proto_minor < 15)
+
2680 return -ENOSYS;
+
2681
+
2682 rreq = malloc(sizeof(*rreq));
+
2683 if (rreq == NULL)
+
2684 return -ENOMEM;
+
2685
+
2686 pthread_mutex_lock(&se->lock);
+
2687 rreq->cookie = cookie;
+
2688 rreq->nreq.unique = se->notify_ctr++;
+
2689 rreq->nreq.reply = fuse_ll_retrieve_reply;
+
2690 list_add_nreq(&rreq->nreq, &se->notify_list);
+
2691 pthread_mutex_unlock(&se->lock);
+
2692
+
2693 outarg.notify_unique = rreq->nreq.unique;
+
2694 outarg.nodeid = ino;
+
2695 outarg.offset = offset;
+
2696 outarg.size = size;
+
2697 outarg.padding = 0;
+
2698
+
2699 iov[1].iov_base = &outarg;
+
2700 iov[1].iov_len = sizeof(outarg);
+
2701
+
2702 err = send_notify_iov(se, FUSE_NOTIFY_RETRIEVE, iov, 2);
+
2703 if (err) {
+
2704 pthread_mutex_lock(&se->lock);
+
2705 list_del_nreq(&rreq->nreq);
+
2706 pthread_mutex_unlock(&se->lock);
+
2707 free(rreq);
+
2708 }
+
2709
+
2710 return err;
+
2711}
+
+
2712
+
+ +
2714{
+
2715 return req->se->userdata;
+
2716}
+
+
2717
+
+ +
2719{
+
2720 return &req->ctx;
+
2721}
+
+
2722
+
+ +
2724 void *data)
+
2725{
+
2726 pthread_mutex_lock(&req->lock);
+
2727 pthread_mutex_lock(&req->se->lock);
+
2728 req->u.ni.func = func;
+
2729 req->u.ni.data = data;
+
2730 pthread_mutex_unlock(&req->se->lock);
+
2731 if (req->interrupted && func)
+
2732 func(req, data);
+
2733 pthread_mutex_unlock(&req->lock);
+
2734}
+
+
2735
+
+ +
2737{
+
2738 int interrupted;
+
2739
+
2740 pthread_mutex_lock(&req->se->lock);
+
2741 interrupted = req->interrupted;
+
2742 pthread_mutex_unlock(&req->se->lock);
+
2743
+
2744 return interrupted;
+
2745}
+
+
2746
+
2747static struct {
+
2748 void (*func)(fuse_req_t, fuse_ino_t, const void *);
+
2749 const char *name;
+
2750} fuse_ll_ops[] = {
+
2751 [FUSE_LOOKUP] = { do_lookup, "LOOKUP" },
+
2752 [FUSE_FORGET] = { do_forget, "FORGET" },
+
2753 [FUSE_GETATTR] = { do_getattr, "GETATTR" },
+
2754 [FUSE_SETATTR] = { do_setattr, "SETATTR" },
+
2755 [FUSE_READLINK] = { do_readlink, "READLINK" },
+
2756 [FUSE_SYMLINK] = { do_symlink, "SYMLINK" },
+
2757 [FUSE_MKNOD] = { do_mknod, "MKNOD" },
+
2758 [FUSE_MKDIR] = { do_mkdir, "MKDIR" },
+
2759 [FUSE_UNLINK] = { do_unlink, "UNLINK" },
+
2760 [FUSE_RMDIR] = { do_rmdir, "RMDIR" },
+
2761 [FUSE_RENAME] = { do_rename, "RENAME" },
+
2762 [FUSE_LINK] = { do_link, "LINK" },
+
2763 [FUSE_OPEN] = { do_open, "OPEN" },
+
2764 [FUSE_READ] = { do_read, "READ" },
+
2765 [FUSE_WRITE] = { do_write, "WRITE" },
+
2766 [FUSE_STATFS] = { do_statfs, "STATFS" },
+
2767 [FUSE_RELEASE] = { do_release, "RELEASE" },
+
2768 [FUSE_FSYNC] = { do_fsync, "FSYNC" },
+
2769 [FUSE_SETXATTR] = { do_setxattr, "SETXATTR" },
+
2770 [FUSE_GETXATTR] = { do_getxattr, "GETXATTR" },
+
2771 [FUSE_LISTXATTR] = { do_listxattr, "LISTXATTR" },
+
2772 [FUSE_REMOVEXATTR] = { do_removexattr, "REMOVEXATTR" },
+
2773 [FUSE_FLUSH] = { do_flush, "FLUSH" },
+
2774 [FUSE_INIT] = { do_init, "INIT" },
+
2775 [FUSE_OPENDIR] = { do_opendir, "OPENDIR" },
+
2776 [FUSE_READDIR] = { do_readdir, "READDIR" },
+
2777 [FUSE_RELEASEDIR] = { do_releasedir, "RELEASEDIR" },
+
2778 [FUSE_FSYNCDIR] = { do_fsyncdir, "FSYNCDIR" },
+
2779 [FUSE_GETLK] = { do_getlk, "GETLK" },
+
2780 [FUSE_SETLK] = { do_setlk, "SETLK" },
+
2781 [FUSE_SETLKW] = { do_setlkw, "SETLKW" },
+
2782 [FUSE_ACCESS] = { do_access, "ACCESS" },
+
2783 [FUSE_CREATE] = { do_create, "CREATE" },
+
2784 [FUSE_TMPFILE] = { do_tmpfile, "TMPFILE" },
+
2785 [FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" },
+
2786 [FUSE_BMAP] = { do_bmap, "BMAP" },
+
2787 [FUSE_IOCTL] = { do_ioctl, "IOCTL" },
+
2788 [FUSE_POLL] = { do_poll, "POLL" },
+
2789 [FUSE_FALLOCATE] = { do_fallocate, "FALLOCATE" },
+
2790 [FUSE_DESTROY] = { do_destroy, "DESTROY" },
+
2791 [FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" },
+
2792 [FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" },
+
2793 [FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"},
+
2794 [FUSE_RENAME2] = { do_rename2, "RENAME2" },
+
2795 [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" },
+
2796 [FUSE_LSEEK] = { do_lseek, "LSEEK" },
+
2797 [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" },
+
2798};
+
2799
+
2800/*
+
2801 * For ABI compatibility we cannot allow higher values than CUSE_INIT.
+
2802 * Without ABI compatibility we could use the size of the array.
+
2803 * #define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
+
2804 */
+
2805#define FUSE_MAXOP (CUSE_INIT + 1)
+
2806
+
2807static const char *opname(enum fuse_opcode opcode)
+
2808{
+
2809 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
+
2810 return "???";
+
2811 else
+
2812 return fuse_ll_ops[opcode].name;
+
2813}
+
2814
+
2815static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst,
+
2816 struct fuse_bufvec *src)
+
2817{
+
2818 ssize_t res = fuse_buf_copy(dst, src, 0);
+
2819 if (res < 0) {
+
2820 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n", strerror(-res));
+
2821 return res;
+
2822 }
+
2823 if ((size_t)res < fuse_buf_size(dst)) {
+
2824 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: short read\n");
+
2825 return -1;
+
2826 }
+
2827 return 0;
+
2828}
+
2829
+
+
2830void fuse_session_process_buf(struct fuse_session *se,
+
2831 const struct fuse_buf *buf)
+
2832{
+
2833 fuse_session_process_buf_internal(se, buf, NULL);
+
2834}
+
+
2835
+
2836/* libfuse internal handler */
+
2837void fuse_session_process_buf_internal(struct fuse_session *se,
+
2838 const struct fuse_buf *buf, struct fuse_chan *ch)
+
2839{
+
2840 const size_t write_header_size = sizeof(struct fuse_in_header) +
+
2841 sizeof(struct fuse_write_in);
+
2842 struct fuse_bufvec bufv = { .buf[0] = *buf, .count = 1 };
+
2843 struct fuse_bufvec tmpbuf = FUSE_BUFVEC_INIT(write_header_size);
+
2844 struct fuse_in_header *in;
+
2845 const void *inarg;
+
2846 struct fuse_req *req;
+
2847 void *mbuf = NULL;
+
2848 int err;
+
2849 int res;
+
2850
+
2851 if (buf->flags & FUSE_BUF_IS_FD) {
+
2852 if (buf->size < tmpbuf.buf[0].size)
+
2853 tmpbuf.buf[0].size = buf->size;
+
2854
+
2855 mbuf = malloc(tmpbuf.buf[0].size);
+
2856 if (mbuf == NULL) {
+
2857 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate header\n");
+
2858 goto clear_pipe;
+
2859 }
+
2860 tmpbuf.buf[0].mem = mbuf;
+
2861
+
2862 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
2863 if (res < 0)
+
2864 goto clear_pipe;
+
2865
+
2866 in = mbuf;
+
2867 } else {
+
2868 in = buf->mem;
+
2869 }
+
2870
+
2871 if (se->debug) {
+
2872 fuse_log(FUSE_LOG_DEBUG,
+
2873 "unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n",
+
2874 (unsigned long long) in->unique,
+
2875 opname((enum fuse_opcode) in->opcode), in->opcode,
+
2876 (unsigned long long) in->nodeid, buf->size, in->pid);
+
2877 }
+
2878
+
2879 req = fuse_ll_alloc_req(se);
+
2880 if (req == NULL) {
+
2881 struct fuse_out_header out = {
+
2882 .unique = in->unique,
+
2883 .error = -ENOMEM,
+
2884 };
+
2885 struct iovec iov = {
+
2886 .iov_base = &out,
+
2887 .iov_len = sizeof(struct fuse_out_header),
+
2888 };
+
2889
+
2890 fuse_send_msg(se, ch, &iov, 1);
+
2891 goto clear_pipe;
+
2892 }
+
2893
+
2894 req->unique = in->unique;
+
2895 req->ctx.uid = in->uid;
+
2896 req->ctx.gid = in->gid;
+
2897 req->ctx.pid = in->pid;
+
2898 req->ch = ch ? fuse_chan_get(ch) : NULL;
+
2899
+
2900 err = EIO;
+
2901 if (!se->got_init) {
+
2902 enum fuse_opcode expected;
+
2903
+
2904 expected = se->cuse_data ? CUSE_INIT : FUSE_INIT;
+
2905 if (in->opcode != expected)
+
2906 goto reply_err;
+
2907 } else if (in->opcode == FUSE_INIT || in->opcode == CUSE_INIT)
+
2908 goto reply_err;
+
2909
+
2910 err = EACCES;
+
2911 /* Implement -o allow_root */
+
2912 if (se->deny_others && in->uid != se->owner && in->uid != 0 &&
+
2913 in->opcode != FUSE_INIT && in->opcode != FUSE_READ &&
+
2914 in->opcode != FUSE_WRITE && in->opcode != FUSE_FSYNC &&
+
2915 in->opcode != FUSE_RELEASE && in->opcode != FUSE_READDIR &&
+
2916 in->opcode != FUSE_FSYNCDIR && in->opcode != FUSE_RELEASEDIR &&
+
2917 in->opcode != FUSE_NOTIFY_REPLY &&
+
2918 in->opcode != FUSE_READDIRPLUS)
+
2919 goto reply_err;
+
2920
+
2921 err = ENOSYS;
+
2922 if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func)
+
2923 goto reply_err;
+
2924 /* Do not process interrupt request */
+
2925 if (se->conn.no_interrupt && in->opcode == FUSE_INTERRUPT) {
+
2926 if (se->debug)
+
2927 fuse_log(FUSE_LOG_DEBUG, "FUSE_INTERRUPT: reply to kernel to disable interrupt\n");
+
2928 goto reply_err;
+
2929 }
+
2930 if (!se->conn.no_interrupt && in->opcode != FUSE_INTERRUPT) {
+
2931 struct fuse_req *intr;
+
2932 pthread_mutex_lock(&se->lock);
+
2933 intr = check_interrupt(se, req);
+
2934 list_add_req(req, &se->list);
+
2935 pthread_mutex_unlock(&se->lock);
+
2936 if (intr)
+
2937 fuse_reply_err(intr, EAGAIN);
+
2938 }
+
2939
+
2940 if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size &&
+
2941 (in->opcode != FUSE_WRITE || !se->op.write_buf) &&
+
2942 in->opcode != FUSE_NOTIFY_REPLY) {
+
2943 void *newmbuf;
+
2944
+
2945 err = ENOMEM;
+
2946 newmbuf = realloc(mbuf, buf->size);
+
2947 if (newmbuf == NULL)
+
2948 goto reply_err;
+
2949 mbuf = newmbuf;
+
2950
+
2951 tmpbuf = FUSE_BUFVEC_INIT(buf->size - write_header_size);
+
2952 tmpbuf.buf[0].mem = (char *)mbuf + write_header_size;
+
2953
+
2954 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
2955 err = -res;
+
2956 if (res < 0)
+
2957 goto reply_err;
+
2958
+
2959 in = mbuf;
+
2960 }
+
2961
+
2962 inarg = (void *) &in[1];
+
2963 if (in->opcode == FUSE_WRITE && se->op.write_buf)
+
2964 do_write_buf(req, in->nodeid, inarg, buf);
+
2965 else if (in->opcode == FUSE_NOTIFY_REPLY)
+
2966 do_notify_reply(req, in->nodeid, inarg, buf);
+
2967 else
+
2968 fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg);
+
2969
+
2970out_free:
+
2971 free(mbuf);
+
2972 return;
+
2973
+
2974reply_err:
+
2975 fuse_reply_err(req, err);
+
2976clear_pipe:
+
2977 if (buf->flags & FUSE_BUF_IS_FD)
+
2978 fuse_ll_clear_pipe(se);
+
2979 goto out_free;
+
2980}
+
2981
+
2982#define LL_OPTION(n,o,v) \
+
2983 { n, offsetof(struct fuse_session, o), v }
+
2984
+
2985static const struct fuse_opt fuse_ll_opts[] = {
+
2986 LL_OPTION("debug", debug, 1),
+
2987 LL_OPTION("-d", debug, 1),
+
2988 LL_OPTION("--debug", debug, 1),
+
2989 LL_OPTION("allow_root", deny_others, 1),
+ +
2991};
+
2992
+
+ +
2994{
+
2995 printf("using FUSE kernel interface version %i.%i\n",
+
2996 FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION);
+
2997 fuse_mount_version();
+
2998}
+
+
2999
+
+ +
3001{
+
3002 /* These are not all options, but the ones that are
+
3003 potentially of interest to an end-user */
+
3004 printf(
+
3005" -o allow_other allow access by all users\n"
+
3006" -o allow_root allow access by root\n"
+
3007" -o auto_unmount auto unmount on process termination\n");
+
3008}
+
+
3009
+
+
3010void fuse_session_destroy(struct fuse_session *se)
+
3011{
+
3012 struct fuse_ll_pipe *llp;
+
3013
+
3014 if (se->got_init && !se->got_destroy) {
+
3015 if (se->op.destroy)
+
3016 se->op.destroy(se->userdata);
+
3017 }
+
3018 llp = pthread_getspecific(se->pipe_key);
+
3019 if (llp != NULL)
+
3020 fuse_ll_pipe_free(llp);
+
3021 pthread_key_delete(se->pipe_key);
+
3022 pthread_mutex_destroy(&se->lock);
+
3023 free(se->cuse_data);
+
3024 if (se->fd != -1)
+
3025 close(se->fd);
+
3026 if (se->io != NULL)
+
3027 free(se->io);
+
3028 destroy_mount_opts(se->mo);
+
3029 free(se);
+
3030}
+
+
3031
+
3032
+
3033static void fuse_ll_pipe_destructor(void *data)
+
3034{
+
3035 struct fuse_ll_pipe *llp = data;
+
3036 fuse_ll_pipe_free(llp);
+
3037}
+
3038
+
3039void fuse_buf_free(struct fuse_buf *buf)
+
3040{
+
3041 if (buf->mem == NULL)
+
3042 return;
+
3043
+
3044 size_t write_header_sz =
+
3045 sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in);
+
3046
+
3047 char *ptr = (char *)buf->mem - pagesize + write_header_sz;
+
3048 free(ptr);
+
3049 buf->mem = NULL;
+
3050}
+
3051
+
3052/*
+
3053 * This is used to allocate buffers that hold fuse requests
+
3054 */
+
3055static void *buf_alloc(size_t size, bool internal)
+
3056{
+
3057 /*
+
3058 * For libfuse internal caller add in alignment. That cannot be done
+
3059 * for an external caller, as it is not guaranteed that the external
+
3060 * caller frees the raw pointer.
+
3061 */
+
3062 if (internal) {
+
3063 size_t write_header_sz = sizeof(struct fuse_in_header) +
+
3064 sizeof(struct fuse_write_in);
+
3065 size_t new_size = ROUND_UP(size + write_header_sz, pagesize);
+
3066
+
3067 char *buf = aligned_alloc(pagesize, new_size);
+
3068 if (buf == NULL)
+
3069 return NULL;
+
3070
+
3071 buf += pagesize - write_header_sz;
+
3072
+
3073 return buf;
+
3074 } else {
+
3075 return malloc(size);
+
3076 }
+
3077}
+
3078
+
3079/*
+
3080 *@param internal true if called from libfuse internal code
+
3081 */
+
3082static int _fuse_session_receive_buf(struct fuse_session *se,
+
3083 struct fuse_buf *buf, struct fuse_chan *ch,
+
3084 bool internal)
+
3085{
+
3086 int err;
+
3087 ssize_t res;
+
3088 size_t bufsize;
+
3089#ifdef HAVE_SPLICE
+
3090 struct fuse_ll_pipe *llp;
+
3091 struct fuse_buf tmpbuf;
+
3092
+
3093pipe_retry:
+
3094 bufsize = se->bufsize;
+
3095
+
3096 if (se->conn.proto_minor < 14 ||
+
3097 !(se->conn.want_ext & FUSE_CAP_SPLICE_READ))
+
3098 goto fallback;
+
3099
+
3100 llp = fuse_ll_get_pipe(se);
+
3101 if (llp == NULL)
+
3102 goto fallback;
+
3103
+
3104 if (llp->size < bufsize) {
+
3105 if (llp->can_grow) {
+
3106 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, bufsize);
+
3107 if (res == -1) {
+
3108 llp->can_grow = 0;
+
3109 res = grow_pipe_to_max(llp->pipe[0]);
+
3110 if (res > 0)
+
3111 llp->size = res;
+
3112 goto fallback;
+
3113 }
+
3114 llp->size = res;
+
3115 }
+
3116 if (llp->size < bufsize)
+
3117 goto fallback;
+
3118 }
+
3119
+
3120 if (se->io != NULL && se->io->splice_receive != NULL) {
+
3121 res = se->io->splice_receive(ch ? ch->fd : se->fd, NULL,
+
3122 llp->pipe[1], NULL, bufsize, 0,
+
3123 se->userdata);
+
3124 } else {
+
3125 res = splice(ch ? ch->fd : se->fd, NULL, llp->pipe[1], NULL,
+
3126 bufsize, 0);
+
3127 }
+
3128 err = errno;
+
3129
+
3130 if (fuse_session_exited(se))
+
3131 return 0;
+
3132
+
3133 if (res == -1) {
+
3134 if (err == ENODEV) {
+
3135 /* Filesystem was unmounted, or connection was aborted
+
3136 via /sys/fs/fuse/connections */
+ +
3138 return 0;
+
3139 }
+
3140
+
3141 /* FUSE_INIT might have increased the required bufsize */
+
3142 if (err == EINVAL && bufsize < se->bufsize) {
+
3143 fuse_ll_clear_pipe(se);
+
3144 goto pipe_retry;
+
3145 }
+
3146
+
3147 if (err != EINTR && err != EAGAIN)
+
3148 perror("fuse: splice from device");
+
3149 return -err;
+
3150 }
+
3151
+
3152 if (res < sizeof(struct fuse_in_header)) {
+
3153 fuse_log(FUSE_LOG_ERR, "short splice from fuse device\n");
+
3154 return -EIO;
+
3155 }
+
3156
+
3157 tmpbuf = (struct fuse_buf){
+
3158 .size = res,
+
3159 .flags = FUSE_BUF_IS_FD,
+
3160 .fd = llp->pipe[0],
+
3161 };
+
3162
+
3163 /*
+
3164 * Don't bother with zero copy for small requests.
+
3165 * fuse_loop_mt() needs to check for FORGET so this more than
+
3166 * just an optimization.
+
3167 */
+
3168 if (res < sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) +
+
3169 pagesize) {
+
3170 struct fuse_bufvec src = { .buf[0] = tmpbuf, .count = 1 };
+
3171 struct fuse_bufvec dst = { .count = 1 };
+
3172
+
3173 if (!buf->mem) {
+
3174 buf->mem = buf_alloc(bufsize, internal);
+
3175 if (!buf->mem) {
+
3176 fuse_log(
+
3177 FUSE_LOG_ERR,
+
3178 "fuse: failed to allocate read buffer\n");
+
3179 return -ENOMEM;
+
3180 }
+
3181 buf->mem_size = bufsize;
+
3182 }
+
3183 buf->size = bufsize;
+
3184 buf->flags = 0;
+
3185 dst.buf[0] = *buf;
+
3186
+
3187 res = fuse_buf_copy(&dst, &src, 0);
+
3188 if (res < 0) {
+
3189 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n",
+
3190 strerror(-res));
+
3191 fuse_ll_clear_pipe(se);
+
3192 return res;
+
3193 }
+
3194 if (res < tmpbuf.size) {
+
3195 fuse_log(FUSE_LOG_ERR,
+
3196 "fuse: copy from pipe: short read\n");
+
3197 fuse_ll_clear_pipe(se);
+
3198 return -EIO;
+
3199 }
+
3200 assert(res == tmpbuf.size);
+
3201
+
3202 } else {
+
3203 /* Don't overwrite buf->mem, as that would cause a leak */
+
3204 buf->fd = tmpbuf.fd;
+
3205 buf->flags = tmpbuf.flags;
+
3206 }
+
3207 buf->size = tmpbuf.size;
+
3208
+
3209 return res;
+
3210
+
3211fallback:
+
3212#endif
+
3213 bufsize = internal ? buf->mem_size : se->bufsize;
+
3214 if (!buf->mem) {
+
3215 bufsize = se->bufsize; /* might have changed */
+
3216 buf->mem = buf_alloc(bufsize, internal);
+
3217 if (!buf->mem) {
+
3218 fuse_log(FUSE_LOG_ERR,
+
3219 "fuse: failed to allocate read buffer\n");
+
3220 return -ENOMEM;
+
3221 }
+
3222
+
3223 if (internal)
+
3224 buf->mem_size = bufsize;
+
3225 }
+
3226
+
3227restart:
+
3228 if (se->io != NULL) {
+
3229 /* se->io->read is never NULL if se->io is not NULL as
+
3230 specified by fuse_session_custom_io()*/
+
3231 res = se->io->read(ch ? ch->fd : se->fd, buf->mem, bufsize,
+
3232 se->userdata);
+
3233 } else {
+
3234 res = read(ch ? ch->fd : se->fd, buf->mem, bufsize);
+
3235 }
+
3236 err = errno;
+
3237
+
3238 if (fuse_session_exited(se))
+
3239 return 0;
+
3240 if (res == -1) {
+
3241 if (err == EINVAL && internal && se->bufsize > bufsize) {
+
3242 /* FUSE_INIT might have increased the required bufsize */
+
3243 bufsize = se->bufsize;
+
3244 void *newbuf = buf_alloc(bufsize, internal);
+
3245 if (!newbuf) {
+
3246 fuse_log(
+
3247 FUSE_LOG_ERR,
+
3248 "fuse: failed to (re)allocate read buffer\n");
+
3249 return -ENOMEM;
+
3250 }
+
3251 fuse_buf_free(buf);
+
3252 buf->mem = newbuf;
+
3253 buf->mem_size = bufsize;
+
3254 goto restart;
+
3255 }
+
3256
+
3257 /* ENOENT means the operation was interrupted, it's safe
+
3258 to restart */
+
3259 if (err == ENOENT)
+
3260 goto restart;
+
3261
+
3262 if (err == ENODEV) {
+
3263 /* Filesystem was unmounted, or connection was aborted
+
3264 via /sys/fs/fuse/connections */
+ +
3266 return 0;
+
3267 }
+
3268 /* Errors occurring during normal operation: EINTR (read
+
3269 interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem
+
3270 umounted) */
+
3271 if (err != EINTR && err != EAGAIN)
+
3272 perror("fuse: reading device");
+
3273 return -err;
+
3274 }
+
3275 if ((size_t)res < sizeof(struct fuse_in_header)) {
+
3276 fuse_log(FUSE_LOG_ERR, "short read on fuse device\n");
+
3277 return -EIO;
+
3278 }
+
3279
+
3280 buf->size = res;
+
3281
+
3282 return res;
+
3283}
+
3284
+
+
3285int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
3286{
+
3287 return _fuse_session_receive_buf(se, buf, NULL, false);
+
3288}
+
+
3289
+
3290/* libfuse internal handler */
+
3291int fuse_session_receive_buf_internal(struct fuse_session *se,
+
3292 struct fuse_buf *buf,
+
3293 struct fuse_chan *ch)
+
3294{
+
3295 /*
+
3296 * if run internally thread buffers are from libfuse - we can
+
3297 * reallocate them
+
3298 */
+
3299 if (unlikely(!se->got_init) && !se->buf_reallocable)
+
3300 se->buf_reallocable = true;
+
3301
+
3302 return _fuse_session_receive_buf(se, buf, ch, true);
+
3303}
+
3304
+
3305struct fuse_session *
+
3306fuse_session_new_versioned(struct fuse_args *args,
+
3307 const struct fuse_lowlevel_ops *op, size_t op_size,
+
3308 struct libfuse_version *version, void *userdata)
+
3309{
+
3310 int err;
+
3311 struct fuse_session *se;
+
3312 struct mount_opts *mo;
+
3313
+
3314 if (sizeof(struct fuse_lowlevel_ops) < op_size) {
+
3315 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
3316 op_size = sizeof(struct fuse_lowlevel_ops);
+
3317 }
+
3318
+
3319 if (args->argc == 0) {
+
3320 fuse_log(FUSE_LOG_ERR, "fuse: empty argv passed to fuse_session_new().\n");
+
3321 return NULL;
+
3322 }
+
3323
+
3324 se = (struct fuse_session *) calloc(1, sizeof(struct fuse_session));
+
3325 if (se == NULL) {
+
3326 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
3327 goto out1;
+
3328 }
+
3329 se->fd = -1;
+
3330 se->conn.max_write = FUSE_DEFAULT_MAX_PAGES_LIMIT * getpagesize();
+
3331 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
3332 se->conn.max_readahead = UINT_MAX;
+
3333
+
3334 /* Parse options */
+
3335 if(fuse_opt_parse(args, se, fuse_ll_opts, NULL) == -1)
+
3336 goto out2;
+
3337 if(se->deny_others) {
+
3338 /* Allowing access only by root is done by instructing
+
3339 * kernel to allow access by everyone, and then restricting
+
3340 * access to root and mountpoint owner in libfuse.
+
3341 */
+
3342 // We may be adding the option a second time, but
+
3343 // that doesn't hurt.
+
3344 if(fuse_opt_add_arg(args, "-oallow_other") == -1)
+
3345 goto out2;
+
3346 }
+
3347 mo = parse_mount_opts(args);
+
3348 if (mo == NULL)
+
3349 goto out3;
+
3350
+
3351 if(args->argc == 1 &&
+
3352 args->argv[0][0] == '-') {
+
3353 fuse_log(FUSE_LOG_ERR, "fuse: warning: argv[0] looks like an option, but "
+
3354 "will be ignored\n");
+
3355 } else if (args->argc != 1) {
+
3356 int i;
+
3357 fuse_log(FUSE_LOG_ERR, "fuse: unknown option(s): `");
+
3358 for(i = 1; i < args->argc-1; i++)
+
3359 fuse_log(FUSE_LOG_ERR, "%s ", args->argv[i]);
+
3360 fuse_log(FUSE_LOG_ERR, "%s'\n", args->argv[i]);
+
3361 goto out4;
+
3362 }
+
3363
+
3364 if (se->debug)
+
3365 fuse_log(FUSE_LOG_DEBUG, "FUSE library version: %s\n", PACKAGE_VERSION);
+
3366
+
3367 list_init_req(&se->list);
+
3368 list_init_req(&se->interrupts);
+
3369 list_init_nreq(&se->notify_list);
+
3370 se->notify_ctr = 1;
+
3371 pthread_mutex_init(&se->lock, NULL);
+
3372
+
3373 err = pthread_key_create(&se->pipe_key, fuse_ll_pipe_destructor);
+
3374 if (err) {
+
3375 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
3376 strerror(err));
+
3377 goto out5;
+
3378 }
+
3379
+
3380 memcpy(&se->op, op, op_size);
+
3381 se->owner = getuid();
+
3382 se->userdata = userdata;
+
3383
+
3384 se->mo = mo;
+
3385
+
3386 /* Fuse server application should pass the version it was compiled
+
3387 * against and pass it. If a libfuse version accidentally introduces an
+
3388 * ABI incompatibility, it might be possible to 'fix' that at run time,
+
3389 * by checking the version numbers.
+
3390 */
+
3391 se->version = *version;
+
3392
+
3393 return se;
+
3394
+
3395out5:
+
3396 pthread_mutex_destroy(&se->lock);
+
3397out4:
+
3398 fuse_opt_free_args(args);
+
3399out3:
+
3400 if (mo != NULL)
+
3401 destroy_mount_opts(mo);
+
3402out2:
+
3403 free(se);
+
3404out1:
+
3405 return NULL;
+
3406}
+
3407
+
3408struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
3409 const struct fuse_lowlevel_ops *op,
+
3410 size_t op_size, void *userdata);
+
3411struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
3412 const struct fuse_lowlevel_ops *op,
+
3413 size_t op_size,
+
3414 void *userdata)
+
3415{
+
3416 /* unknown version */
+
3417 struct libfuse_version version = { 0 };
+
3418
+
3419 return fuse_session_new_versioned(args, op, op_size, &version,
+
3420 userdata);
+
3421}
+
3422
+
3423FUSE_SYMVER("fuse_session_custom_io_317", "fuse_session_custom_io@@FUSE_3.17")
+
3424int fuse_session_custom_io_317(struct fuse_session *se,
+
3425 const struct fuse_custom_io *io, size_t op_size, int fd)
+
3426{
+
3427 if (sizeof(struct fuse_custom_io) < op_size) {
+
3428 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
3429 op_size = sizeof(struct fuse_custom_io);
+
3430 }
+
3431
+
3432 if (fd < 0) {
+
3433 fuse_log(FUSE_LOG_ERR, "Invalid file descriptor value %d passed to "
+
3434 "fuse_session_custom_io()\n", fd);
+
3435 return -EBADF;
+
3436 }
+
3437 if (io == NULL) {
+
3438 fuse_log(FUSE_LOG_ERR, "No custom IO passed to "
+
3439 "fuse_session_custom_io()\n");
+
3440 return -EINVAL;
+
3441 } else if (io->read == NULL || io->writev == NULL) {
+
3442 /* If the user provides their own file descriptor, we can't
+
3443 guarantee that the default behavior of the io operations made
+
3444 in libfuse will function properly. Therefore, we enforce the
+
3445 user to implement these io operations when using custom io. */
+
3446 fuse_log(FUSE_LOG_ERR, "io passed to fuse_session_custom_io() must "
+
3447 "implement both io->read() and io->writev\n");
+
3448 return -EINVAL;
+
3449 }
+
3450
+
3451 se->io = calloc(1, sizeof(struct fuse_custom_io));
+
3452 if (se->io == NULL) {
+
3453 fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for custom io. "
+
3454 "Error: %s\n", strerror(errno));
+
3455 return -errno;
+
3456 }
+
3457
+
3458 se->fd = fd;
+
3459 memcpy(se->io, io, op_size);
+
3460 return 0;
+
3461}
+
3462
+
3463int fuse_session_custom_io_30(struct fuse_session *se,
+
3464 const struct fuse_custom_io *io, int fd);
+
3465FUSE_SYMVER("fuse_session_custom_io_30", "fuse_session_custom_io@FUSE_3.0")
+
3466int fuse_session_custom_io_30(struct fuse_session *se,
+
3467 const struct fuse_custom_io *io, int fd)
+
3468{
+
3469 return fuse_session_custom_io_317(se, io,
+
3470 offsetof(struct fuse_custom_io, clone_fd), fd);
+
3471}
+
3472
+
+
3473int fuse_session_mount(struct fuse_session *se, const char *_mountpoint)
+
3474{
+
3475 int fd;
+
3476 char *mountpoint;
+
3477
+
3478 if (_mountpoint == NULL) {
+
3479 fuse_log(FUSE_LOG_ERR, "Invalid null-ptr mountpoint!\n");
+
3480 return -1;
+
3481 }
+
3482
+
3483 mountpoint = strdup(_mountpoint);
+
3484 if (mountpoint == NULL) {
+
3485 fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for mountpoint. Error: %s\n",
+
3486 strerror(errno));
+
3487 return -1;
+
3488 }
+
3489
+
3490 /*
+
3491 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
3492 * would ensue.
+
3493 */
+
3494 do {
+
3495 fd = open("/dev/null", O_RDWR);
+
3496 if (fd > 2)
+
3497 close(fd);
+
3498 } while (fd >= 0 && fd <= 2);
+
3499
+
3500 /*
+
3501 * To allow FUSE daemons to run without privileges, the caller may open
+
3502 * /dev/fuse before launching the file system and pass on the file
+
3503 * descriptor by specifying /dev/fd/N as the mount point. Note that the
+
3504 * parent process takes care of performing the mount in this case.
+
3505 */
+
3506 fd = fuse_mnt_parse_fuse_fd(mountpoint);
+
3507 if (fd != -1) {
+
3508 if (fcntl(fd, F_GETFD) == -1) {
+
3509 fuse_log(FUSE_LOG_ERR,
+
3510 "fuse: Invalid file descriptor /dev/fd/%u\n",
+
3511 fd);
+
3512 goto error_out;
+
3513 }
+
3514 se->fd = fd;
+
3515 return 0;
+
3516 }
+
3517
+
3518 /* Open channel */
+
3519 fd = fuse_kern_mount(mountpoint, se->mo);
+
3520 if (fd == -1)
+
3521 goto error_out;
+
3522 se->fd = fd;
+
3523
+
3524 /* Save mountpoint */
+
3525 se->mountpoint = mountpoint;
+
3526
+
3527 return 0;
+
3528
+
3529error_out:
+
3530 free(mountpoint);
+
3531 return -1;
+
3532}
+
+
3533
+
+
3534int fuse_session_fd(struct fuse_session *se)
+
3535{
+
3536 return se->fd;
+
3537}
+
+
3538
+
+
3539void fuse_session_unmount(struct fuse_session *se)
+
3540{
+
3541 if (se->mountpoint != NULL) {
+
3542 char *mountpoint = atomic_exchange(&se->mountpoint, NULL);
+
3543
+
3544 fuse_kern_unmount(mountpoint, se->fd);
+
3545 se->fd = -1;
+
3546 free(mountpoint);
+
3547 }
+
3548}
+
+
3549
+
3550#ifdef linux
+
3551int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
3552{
+
3553 char *buf;
+
3554 size_t bufsize = 1024;
+
3555 char path[128];
+
3556 int ret;
+
3557 int fd;
+
3558 unsigned long pid = req->ctx.pid;
+
3559 char *s;
+
3560
+
3561 sprintf(path, "/proc/%lu/task/%lu/status", pid, pid);
+
3562
+
3563retry:
+
3564 buf = malloc(bufsize);
+
3565 if (buf == NULL)
+
3566 return -ENOMEM;
+
3567
+
3568 ret = -EIO;
+
3569 fd = open(path, O_RDONLY);
+
3570 if (fd == -1)
+
3571 goto out_free;
+
3572
+
3573 ret = read(fd, buf, bufsize);
+
3574 close(fd);
+
3575 if (ret < 0) {
+
3576 ret = -EIO;
+
3577 goto out_free;
+
3578 }
+
3579
+
3580 if ((size_t)ret == bufsize) {
+
3581 free(buf);
+
3582 bufsize *= 4;
+
3583 goto retry;
+
3584 }
+
3585
+
3586 buf[ret] = '\0';
+
3587 ret = -EIO;
+
3588 s = strstr(buf, "\nGroups:");
+
3589 if (s == NULL)
+
3590 goto out_free;
+
3591
+
3592 s += 8;
+
3593 ret = 0;
+
3594 while (1) {
+
3595 char *end;
+
3596 unsigned long val = strtoul(s, &end, 0);
+
3597 if (end == s)
+
3598 break;
+
3599
+
3600 s = end;
+
3601 if (ret < size)
+
3602 list[ret] = val;
+
3603 ret++;
+
3604 }
+
3605
+
3606out_free:
+
3607 free(buf);
+
3608 return ret;
+
3609}
+
3610#else /* linux */
+
3611/*
+
3612 * This is currently not implemented on other than Linux...
+
3613 */
+
+
3614int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
3615{
+
3616 (void) req; (void) size; (void) list;
+
3617 return -ENOSYS;
+
3618}
+
+
3619#endif
+
3620
+
3621/* Prevent spurious data race warning - we don't care
+
3622 * about races for this flag */
+
3623__attribute__((no_sanitize_thread))
+
3624void fuse_session_exit(struct fuse_session *se)
+
3625{
+
3626 se->exited = 1;
+
3627}
+
3628
+
3629__attribute__((no_sanitize_thread))
+
3630void fuse_session_reset(struct fuse_session *se)
+
3631{
+
3632 se->exited = 0;
+
3633 se->error = 0;
+
3634}
+
3635
+
3636__attribute__((no_sanitize_thread))
+
3637int fuse_session_exited(struct fuse_session *se)
+
3638{
+
3639 return se->exited;
+
3640}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
@ FUSE_BUF_IS_FD
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
+
@ FUSE_BUF_IS_FD
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
struct fuse_req * fuse_req_t
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
uint64_t fuse_ino_t
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
enum fuse_buf_flags flags
+
size_t mem_size
+ +
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + + + +
uint64_t capable_ext
+
uint64_t want_ext
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:60
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t nonseekable
Definition fuse_common.h:78
+
int32_t backing_id
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t noflush
Definition fuse_common.h:93
+
uint32_t flush
Definition fuse_common.h:74
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2fuse__misc_8h_source.html b/doc/html/fuse-3_817_84_2lib_2fuse__misc_8h_source.html new file mode 100644 index 0000000..2d7a5ad --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2fuse__misc_8h_source.html @@ -0,0 +1,111 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/fuse_misc.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_misc.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include <pthread.h>
+
10
+
11/*
+
12 Versioned symbols cannot be used in some cases because it
+
13 - not supported on MacOSX (in MachO binary format)
+
14
+
15 Note: "@@" denotes the default symbol, "@" is binary a compat version.
+
16
+
17*/
+
18#ifdef LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS
+
19# if HAVE_SYMVER_ATTRIBUTE
+
20# define FUSE_SYMVER(sym1, sym2) __attribute__ ((symver (sym2)))
+
21# else
+
22# define FUSE_SYMVER(sym1, sym2) __asm__("\t.symver " sym1 "," sym2);
+
23# endif
+
24#else
+
25#define FUSE_SYMVER(sym1, sym2)
+
26#endif
+
27
+
28#ifdef HAVE_STRUCT_STAT_ST_ATIM
+
29/* Linux */
+
30#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec)
+
31#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec)
+
32#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec)
+
33#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atim.tv_nsec = (val)
+
34#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctim.tv_nsec = (val)
+
35#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtim.tv_nsec = (val)
+
36#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC)
+
37/* FreeBSD */
+
38#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimespec.tv_nsec)
+
39#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimespec.tv_nsec)
+
40#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimespec.tv_nsec)
+
41#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atimespec.tv_nsec = (val)
+
42#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctimespec.tv_nsec = (val)
+
43#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtimespec.tv_nsec = (val)
+
44#else
+
45#define ST_ATIM_NSEC(stbuf) 0
+
46#define ST_CTIM_NSEC(stbuf) 0
+
47#define ST_MTIM_NSEC(stbuf) 0
+
48#define ST_ATIM_NSEC_SET(stbuf, val) do { } while (0)
+
49#define ST_CTIM_NSEC_SET(stbuf, val) do { } while (0)
+
50#define ST_MTIM_NSEC_SET(stbuf, val) do { } while (0)
+
51#endif
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2fuse__opt_8c_source.html b/doc/html/fuse-3_817_84_2lib_2fuse__opt_8c_source.html new file mode 100644 index 0000000..05e5c1d --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2fuse__opt_8c_source.html @@ -0,0 +1,516 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/fuse_opt.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_opt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of option parsing routines (dealing with `struct
+
6 fuse_args`).
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#include "fuse_config.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15#include "fuse_misc.h"
+
16
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <assert.h>
+
21
+
22struct fuse_opt_context {
+
23 void *data;
+
24 const struct fuse_opt *opt;
+
25 fuse_opt_proc_t proc;
+
26 int argctr;
+
27 int argc;
+
28 char **argv;
+
29 struct fuse_args outargs;
+
30 char *opts;
+
31 int nonopt;
+
32};
+
33
+
+
34void fuse_opt_free_args(struct fuse_args *args)
+
35{
+
36 if (args) {
+
37 if (args->argv && args->allocated) {
+
38 int i;
+
39 for (i = 0; i < args->argc; i++)
+
40 free(args->argv[i]);
+
41 free(args->argv);
+
42 }
+
43 args->argc = 0;
+
44 args->argv = NULL;
+
45 args->allocated = 0;
+
46 }
+
47}
+
+
48
+
49static int alloc_failed(void)
+
50{
+
51 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
52 return -1;
+
53}
+
54
+
+
55int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
+
56{
+
57 char **newargv;
+
58 char *newarg;
+
59
+
60 assert(!args->argv || args->allocated);
+
61
+
62 newarg = strdup(arg);
+
63 if (!newarg)
+
64 return alloc_failed();
+
65
+
66 newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *));
+
67 if (!newargv) {
+
68 free(newarg);
+
69 return alloc_failed();
+
70 }
+
71
+
72 args->argv = newargv;
+
73 args->allocated = 1;
+
74 args->argv[args->argc++] = newarg;
+
75 args->argv[args->argc] = NULL;
+
76 return 0;
+
77}
+
+
78
+
79static int fuse_opt_insert_arg_common(struct fuse_args *args, int pos,
+
80 const char *arg)
+
81{
+
82 assert(pos <= args->argc);
+
83 if (fuse_opt_add_arg(args, arg) == -1)
+
84 return -1;
+
85
+
86 if (pos != args->argc - 1) {
+
87 char *newarg = args->argv[args->argc - 1];
+
88 memmove(&args->argv[pos + 1], &args->argv[pos],
+
89 sizeof(char *) * (args->argc - pos - 1));
+
90 args->argv[pos] = newarg;
+
91 }
+
92 return 0;
+
93}
+
94
+
+
95int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
+
96{
+
97 return fuse_opt_insert_arg_common(args, pos, arg);
+
98}
+
+
99
+
100static int next_arg(struct fuse_opt_context *ctx, const char *opt)
+
101{
+
102 if (ctx->argctr + 1 >= ctx->argc) {
+
103 fuse_log(FUSE_LOG_ERR, "fuse: missing argument after `%s'\n", opt);
+
104 return -1;
+
105 }
+
106 ctx->argctr++;
+
107 return 0;
+
108}
+
109
+
110static int add_arg(struct fuse_opt_context *ctx, const char *arg)
+
111{
+
112 return fuse_opt_add_arg(&ctx->outargs, arg);
+
113}
+
114
+
115static int add_opt_common(char **opts, const char *opt, int esc)
+
116{
+
117 unsigned oldlen = *opts ? strlen(*opts) : 0;
+
118 char *d = realloc(*opts, oldlen + 1 + strlen(opt) * 2 + 1);
+
119
+
120 if (!d)
+
121 return alloc_failed();
+
122
+
123 *opts = d;
+
124 if (oldlen) {
+
125 d += oldlen;
+
126 *d++ = ',';
+
127 }
+
128
+
129 for (; *opt; opt++) {
+
130 if (esc && (*opt == ',' || *opt == '\\'))
+
131 *d++ = '\\';
+
132 *d++ = *opt;
+
133 }
+
134 *d = '\0';
+
135
+
136 return 0;
+
137}
+
138
+
+
139int fuse_opt_add_opt(char **opts, const char *opt)
+
140{
+
141 return add_opt_common(opts, opt, 0);
+
142}
+
+
143
+
+
144int fuse_opt_add_opt_escaped(char **opts, const char *opt)
+
145{
+
146 return add_opt_common(opts, opt, 1);
+
147}
+
+
148
+
149static int add_opt(struct fuse_opt_context *ctx, const char *opt)
+
150{
+
151 return add_opt_common(&ctx->opts, opt, 1);
+
152}
+
153
+
154static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key,
+
155 int iso)
+
156{
+
157 if (key == FUSE_OPT_KEY_DISCARD)
+
158 return 0;
+
159
+
160 if (key != FUSE_OPT_KEY_KEEP && ctx->proc) {
+
161 int res = ctx->proc(ctx->data, arg, key, &ctx->outargs);
+
162 if (res == -1 || !res)
+
163 return res;
+
164 }
+
165 if (iso)
+
166 return add_opt(ctx, arg);
+
167 else
+
168 return add_arg(ctx, arg);
+
169}
+
170
+
171static int match_template(const char *t, const char *arg, unsigned *sepp)
+
172{
+
173 int arglen = strlen(arg);
+
174 const char *sep = strchr(t, '=');
+
175 sep = sep ? sep : strchr(t, ' ');
+
176 if (sep && (!sep[1] || sep[1] == '%')) {
+
177 int tlen = sep - t;
+
178 if (sep[0] == '=')
+
179 tlen ++;
+
180 if (arglen >= tlen && strncmp(arg, t, tlen) == 0) {
+
181 *sepp = sep - t;
+
182 return 1;
+
183 }
+
184 }
+
185 if (strcmp(t, arg) == 0) {
+
186 *sepp = 0;
+
187 return 1;
+
188 }
+
189 return 0;
+
190}
+
191
+
192static const struct fuse_opt *find_opt(const struct fuse_opt *opt,
+
193 const char *arg, unsigned *sepp)
+
194{
+
195 for (; opt && opt->templ; opt++)
+
196 if (match_template(opt->templ, arg, sepp))
+
197 return opt;
+
198 return NULL;
+
199}
+
200
+
201int fuse_opt_match(const struct fuse_opt *opts, const char *opt)
+
202{
+
203 unsigned dummy;
+
204 return find_opt(opts, opt, &dummy) ? 1 : 0;
+
205}
+
206
+
207static int process_opt_param(void *var, const char *format, const char *param,
+
208 const char *arg)
+
209{
+
210 assert(format[0] == '%');
+
211 if (format[1] == 's') {
+
212 char **s = var;
+
213 char *copy = strdup(param);
+
214 if (!copy)
+
215 return alloc_failed();
+
216
+
217 free(*s);
+
218 *s = copy;
+
219 } else {
+
220 if (sscanf(param, format, var) != 1) {
+
221 fuse_log(FUSE_LOG_ERR, "fuse: invalid parameter in option `%s'\n", arg);
+
222 return -1;
+
223 }
+
224 }
+
225 return 0;
+
226}
+
227
+
228static int process_opt(struct fuse_opt_context *ctx,
+
229 const struct fuse_opt *opt, unsigned sep,
+
230 const char *arg, int iso)
+
231{
+
232 if (opt->offset == -1U) {
+
233 if (call_proc(ctx, arg, opt->value, iso) == -1)
+
234 return -1;
+
235 } else {
+
236 void *var = (char *)ctx->data + opt->offset;
+
237 if (sep && opt->templ[sep + 1]) {
+
238 const char *param = arg + sep;
+
239 if (opt->templ[sep] == '=')
+
240 param ++;
+
241 if (process_opt_param(var, opt->templ + sep + 1,
+
242 param, arg) == -1)
+
243 return -1;
+
244 } else
+
245 *(int *)var = opt->value;
+
246 }
+
247 return 0;
+
248}
+
249
+
250static int process_opt_sep_arg(struct fuse_opt_context *ctx,
+
251 const struct fuse_opt *opt, unsigned sep,
+
252 const char *arg, int iso)
+
253{
+
254 int res;
+
255 char *newarg;
+
256 char *param;
+
257
+
258 if (next_arg(ctx, arg) == -1)
+
259 return -1;
+
260
+
261 param = ctx->argv[ctx->argctr];
+
262 newarg = malloc(sep + strlen(param) + 1);
+
263 if (!newarg)
+
264 return alloc_failed();
+
265
+
266 memcpy(newarg, arg, sep);
+
267 strcpy(newarg + sep, param);
+
268 res = process_opt(ctx, opt, sep, newarg, iso);
+
269 free(newarg);
+
270
+
271 return res;
+
272}
+
273
+
274static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso)
+
275{
+
276 unsigned sep;
+
277 const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep);
+
278 if (opt) {
+
279 for (; opt; opt = find_opt(opt + 1, arg, &sep)) {
+
280 int res;
+
281 if (sep && opt->templ[sep] == ' ' && !arg[sep])
+
282 res = process_opt_sep_arg(ctx, opt, sep, arg,
+
283 iso);
+
284 else
+
285 res = process_opt(ctx, opt, sep, arg, iso);
+
286 if (res == -1)
+
287 return -1;
+
288 }
+
289 return 0;
+
290 } else
+
291 return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso);
+
292}
+
293
+
294static int process_real_option_group(struct fuse_opt_context *ctx, char *opts)
+
295{
+
296 char *s = opts;
+
297 char *d = s;
+
298 int end = 0;
+
299
+
300 while (!end) {
+
301 if (*s == '\0')
+
302 end = 1;
+
303 if (*s == ',' || end) {
+
304 int res;
+
305
+
306 *d = '\0';
+
307 res = process_gopt(ctx, opts, 1);
+
308 if (res == -1)
+
309 return -1;
+
310 d = opts;
+
311 } else {
+
312 if (s[0] == '\\' && s[1] != '\0') {
+
313 s++;
+
314 if (s[0] >= '0' && s[0] <= '3' &&
+
315 s[1] >= '0' && s[1] <= '7' &&
+
316 s[2] >= '0' && s[2] <= '7') {
+
317 *d++ = (s[0] - '0') * 0100 +
+
318 (s[1] - '0') * 0010 +
+
319 (s[2] - '0');
+
320 s += 2;
+
321 } else {
+
322 *d++ = *s;
+
323 }
+
324 } else {
+
325 *d++ = *s;
+
326 }
+
327 }
+
328 s++;
+
329 }
+
330
+
331 return 0;
+
332}
+
333
+
334static int process_option_group(struct fuse_opt_context *ctx, const char *opts)
+
335{
+
336 int res;
+
337 char *copy = strdup(opts);
+
338
+
339 if (!copy) {
+
340 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
341 return -1;
+
342 }
+
343 res = process_real_option_group(ctx, copy);
+
344 free(copy);
+
345 return res;
+
346}
+
347
+
348static int process_one(struct fuse_opt_context *ctx, const char *arg)
+
349{
+
350 if (ctx->nonopt || arg[0] != '-')
+
351 return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0);
+
352 else if (arg[1] == 'o') {
+
353 if (arg[2])
+
354 return process_option_group(ctx, arg + 2);
+
355 else {
+
356 if (next_arg(ctx, arg) == -1)
+
357 return -1;
+
358
+
359 return process_option_group(ctx,
+
360 ctx->argv[ctx->argctr]);
+
361 }
+
362 } else if (arg[1] == '-' && !arg[2]) {
+
363 if (add_arg(ctx, arg) == -1)
+
364 return -1;
+
365 ctx->nonopt = ctx->outargs.argc;
+
366 return 0;
+
367 } else
+
368 return process_gopt(ctx, arg, 0);
+
369}
+
370
+
371static int opt_parse(struct fuse_opt_context *ctx)
+
372{
+
373 if (ctx->argc) {
+
374 if (add_arg(ctx, ctx->argv[0]) == -1)
+
375 return -1;
+
376 }
+
377
+
378 for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++)
+
379 if (process_one(ctx, ctx->argv[ctx->argctr]) == -1)
+
380 return -1;
+
381
+
382 if (ctx->opts) {
+
383 if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 ||
+
384 fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1)
+
385 return -1;
+
386 }
+
387
+
388 /* If option separator ("--") is the last argument, remove it */
+
389 if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc &&
+
390 strcmp(ctx->outargs.argv[ctx->outargs.argc - 1], "--") == 0) {
+
391 free(ctx->outargs.argv[ctx->outargs.argc - 1]);
+
392 ctx->outargs.argv[--ctx->outargs.argc] = NULL;
+
393 }
+
394
+
395 return 0;
+
396}
+
397
+
+
398int fuse_opt_parse(struct fuse_args *args, void *data,
+
399 const struct fuse_opt opts[], fuse_opt_proc_t proc)
+
400{
+
401 int res;
+
402 struct fuse_opt_context ctx = {
+
403 .data = data,
+
404 .opt = opts,
+
405 .proc = proc,
+
406 };
+
407
+
408 if (!args || !args->argv || !args->argc)
+
409 return 0;
+
410
+
411 ctx.argc = args->argc;
+
412 ctx.argv = args->argv;
+
413
+
414 res = opt_parse(&ctx);
+
415 if (res != -1) {
+
416 struct fuse_args tmp = *args;
+
417 *args = ctx.outargs;
+
418 ctx.outargs = tmp;
+
419 }
+
420 free(ctx.opts);
+
421 fuse_opt_free_args(&ctx.outargs);
+
422 return res;
+
423}
+
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
#define FUSE_OPT_KEY_OPT
Definition fuse_opt.h:129
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
unsigned long offset
Definition fuse_opt.h:85
+
const char * templ
Definition fuse_opt.h:79
+
int value
Definition fuse_opt.h:91
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2fuse__signals_8c_source.html b/doc/html/fuse-3_817_84_2lib_2fuse__signals_8c_source.html new file mode 100644 index 0000000..cb08d30 --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2fuse__signals_8c_source.html @@ -0,0 +1,276 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/fuse_signals.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_signals.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Utility functions for setting signal handlers.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14
+
15#include <stdio.h>
+
16#include <string.h>
+
17#include <signal.h>
+
18#include <stdlib.h>
+
19#include <errno.h>
+
20
+
21#ifdef HAVE_BACKTRACE
+
22#include <execinfo.h>
+
23#endif
+
24
+
25static int teardown_sigs[] = { SIGHUP, SIGINT, SIGTERM };
+
26static int ignore_sigs[] = { SIGPIPE};
+
27static int fail_sigs[] = { SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGSEGV };
+
28static struct fuse_session *fuse_instance;
+
29
+
30#ifdef HAVE_BACKTRACE
+
31#define BT_STACK_SZ (1024 * 1024)
+
32static void *backtrace_buffer[BT_STACK_SZ];
+
33#endif
+
34
+
35static void dump_stack(void)
+
36{
+
37#ifdef HAVE_BACKTRACE
+
38 char **strings;
+
39
+
40 int nptrs = backtrace(backtrace_buffer, BT_STACK_SZ);
+
41 strings = backtrace_symbols(backtrace_buffer, nptrs);
+
42
+
43 if (strings == NULL) {
+
44 fuse_log(FUSE_LOG_ERR, "Failed to get backtrace symbols: %s\n",
+
45 strerror(errno));
+
46 return;
+
47 }
+
48
+
49 for (int idx = 0; idx < nptrs; idx++)
+
50 fuse_log(FUSE_LOG_ERR, "%s\n", strings[idx]);
+
51
+
52 free(strings);
+
53#endif
+
54}
+
55
+
56static void exit_handler(int sig)
+
57{
+
58 if (fuse_instance == NULL)
+
59 return;
+
60
+
61 fuse_session_exit(fuse_instance);
+
62
+
63 if (sig < 0) {
+
64 fuse_log(FUSE_LOG_ERR,
+
65 "assertion error: signal value <= 0\n");
+
66 dump_stack();
+
67 abort();
+
68 fuse_instance->error = sig;
+
69 }
+
70
+
71 fuse_instance->error = sig;
+
72}
+
73
+
74static void exit_backtrace(int sig)
+
75{
+
76 if (fuse_instance == NULL)
+
77 return;
+
78
+
79 fuse_session_exit(fuse_instance);
+
80
+
81 fuse_remove_signal_handlers(fuse_instance);
+
82 fuse_log(FUSE_LOG_ERR, "Got signal: %d\n", sig);
+
83 dump_stack();
+
84 abort();
+
85}
+
86
+
87
+
88static void do_nothing(int sig)
+
89{
+
90 (void) sig;
+
91}
+
92
+
93static int set_one_signal_handler(int sig, void (*handler)(int), int remove)
+
94{
+
95 struct sigaction sa;
+
96 struct sigaction old_sa;
+
97
+
98 memset(&sa, 0, sizeof(struct sigaction));
+
99 sa.sa_handler = remove ? SIG_DFL : handler;
+
100 sigemptyset(&(sa.sa_mask));
+
101 sa.sa_flags = 0;
+
102
+
103 if (sigaction(sig, NULL, &old_sa) == -1) {
+
104 perror("fuse: cannot get old signal handler");
+
105 return -1;
+
106 }
+
107
+
108 if (old_sa.sa_handler == (remove ? handler : SIG_DFL) &&
+
109 sigaction(sig, &sa, NULL) == -1) {
+
110 perror("fuse: cannot set signal handler");
+
111 return -1;
+
112 }
+
113 return 0;
+
114}
+
115
+
116static int _fuse_set_signal_handlers(int signals[], int nr_signals,
+
117 void (*handler)(int))
+
118{
+
119 for (int idx = 0; idx < nr_signals; idx++) {
+
120 int signal = signals[idx];
+
121
+
122 /*
+
123 * If we used SIG_IGN instead of the do_nothing function,
+
124 * then we would be unable to tell if we set SIG_IGN (and
+
125 * thus should reset to SIG_DFL in fuse_remove_signal_handlers)
+
126 * or if it was already set to SIG_IGN (and should be left
+
127 * untouched.
+
128 */
+
129 if (set_one_signal_handler(signal, handler, 0) == -1) {
+
130 fuse_log(FUSE_LOG_ERR,
+
131 "Failed to install signal handler for sig %d\n",
+
132 signal);
+
133 return -1;
+
134 }
+
135 }
+
136
+
137 return 0;
+
138}
+
139
+
+
140int fuse_set_signal_handlers(struct fuse_session *se)
+
141{
+
142 size_t nr_signals;
+
143 int rc;
+
144
+
145 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
146 rc = _fuse_set_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
147 if (rc < 0)
+
148 return rc;
+
149
+
150 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
151 rc = _fuse_set_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
152 if (rc < 0)
+
153 return rc;
+
154
+
155 /*
+
156 * needs to be set independently if already set, as some applications
+
157 * may have multiple sessions and might rely on traditional behavior
+
158 * that the last session is used.
+
159 */
+
160 fuse_instance = se;
+
161
+
162 return 0;
+
163}
+
+
164
+
+
165int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
166{
+
167 size_t nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
168
+
169 int rc = _fuse_set_signal_handlers(fail_sigs, nr_signals,
+
170 exit_backtrace);
+
171 if (rc < 0)
+
172 return rc;
+
173
+
174 /* See fuse_set_signal_handlers, why set unconditionally */
+
175 fuse_instance = se;
+
176
+
177 return 0;
+
178}
+
+
179
+
180static void _fuse_remove_signal_handlers(int signals[], int nr_signals,
+
181 void (*handler)(int))
+
182{
+
183 for (int idx = 0; idx < nr_signals; idx++)
+
184 set_one_signal_handler(signals[idx], handler, 1);
+
185}
+
186
+
+
187void fuse_remove_signal_handlers(struct fuse_session *se)
+
188{
+
189 size_t nr_signals;
+
190
+
191 if (fuse_instance != se)
+
192 fuse_log(FUSE_LOG_ERR,
+
193 "fuse: fuse_remove_signal_handlers: unknown session\n");
+
194 else
+
195 fuse_instance = NULL;
+
196
+
197 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
198 _fuse_remove_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
199
+
200 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
201 _fuse_remove_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
202
+
203 nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
204 _fuse_remove_signal_handlers(fail_sigs, nr_signals, exit_backtrace);
+
205}
+
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2helper_8c_source.html b/doc/html/fuse-3_817_84_2lib_2helper_8c_source.html new file mode 100644 index 0000000..30590cd --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2helper_8c_source.html @@ -0,0 +1,620 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/helper.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
helper.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file COPYING.LIB.
+
11*/
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_i.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_opt.h"
+
17#include "fuse_lowlevel.h"
+
18#include "mount_util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <stddef.h>
+
23#include <unistd.h>
+
24#include <string.h>
+
25#include <limits.h>
+
26#include <errno.h>
+
27#include <sys/param.h>
+
28
+
29#define FUSE_HELPER_OPT(t, p) \
+
30 { t, offsetof(struct fuse_cmdline_opts, p), 1 }
+
31
+
32static const struct fuse_opt fuse_helper_opts[] = {
+
33 FUSE_HELPER_OPT("-h", show_help),
+
34 FUSE_HELPER_OPT("--help", show_help),
+
35 FUSE_HELPER_OPT("-V", show_version),
+
36 FUSE_HELPER_OPT("--version", show_version),
+
37 FUSE_HELPER_OPT("-d", debug),
+
38 FUSE_HELPER_OPT("debug", debug),
+
39 FUSE_HELPER_OPT("-d", foreground),
+
40 FUSE_HELPER_OPT("debug", foreground),
+ + +
43 FUSE_HELPER_OPT("-f", foreground),
+
44 FUSE_HELPER_OPT("-s", singlethread),
+
45 FUSE_HELPER_OPT("fsname=", nodefault_subtype),
+ +
47#ifndef __FreeBSD__
+
48 FUSE_HELPER_OPT("subtype=", nodefault_subtype),
+ +
50#endif
+
51 FUSE_HELPER_OPT("clone_fd", clone_fd),
+
52 FUSE_HELPER_OPT("max_idle_threads=%u", max_idle_threads),
+
53 FUSE_HELPER_OPT("max_threads=%u", max_threads),
+ +
55};
+
56
+
57struct fuse_conn_info_opts {
+
58 int atomic_o_trunc;
+
59 int no_remote_posix_lock;
+
60 int no_remote_flock;
+
61 int splice_write;
+
62 int splice_move;
+
63 int splice_read;
+
64 int no_splice_write;
+
65 int no_splice_move;
+
66 int no_splice_read;
+
67 int auto_inval_data;
+
68 int no_auto_inval_data;
+
69 int no_readdirplus;
+
70 int no_readdirplus_auto;
+
71 int async_dio;
+
72 int no_async_dio;
+
73 int writeback_cache;
+
74 int no_writeback_cache;
+
75 int async_read;
+
76 int sync_read;
+
77 unsigned max_write;
+
78 unsigned max_readahead;
+
79 unsigned max_background;
+
80 unsigned congestion_threshold;
+
81 unsigned time_gran;
+
82 int set_max_write;
+
83 int set_max_readahead;
+
84 int set_max_background;
+
85 int set_congestion_threshold;
+
86 int set_time_gran;
+
87};
+
88
+
89#define CONN_OPTION(t, p, v) \
+
90 { t, offsetof(struct fuse_conn_info_opts, p), v }
+
91static const struct fuse_opt conn_info_opt_spec[] = {
+
92 CONN_OPTION("max_write=%u", max_write, 0),
+
93 CONN_OPTION("max_write=", set_max_write, 1),
+
94 CONN_OPTION("max_readahead=%u", max_readahead, 0),
+
95 CONN_OPTION("max_readahead=", set_max_readahead, 1),
+
96 CONN_OPTION("max_background=%u", max_background, 0),
+
97 CONN_OPTION("max_background=", set_max_background, 1),
+
98 CONN_OPTION("congestion_threshold=%u", congestion_threshold, 0),
+
99 CONN_OPTION("congestion_threshold=", set_congestion_threshold, 1),
+
100 CONN_OPTION("sync_read", sync_read, 1),
+
101 CONN_OPTION("async_read", async_read, 1),
+
102 CONN_OPTION("atomic_o_trunc", atomic_o_trunc, 1),
+
103 CONN_OPTION("no_remote_lock", no_remote_posix_lock, 1),
+
104 CONN_OPTION("no_remote_lock", no_remote_flock, 1),
+
105 CONN_OPTION("no_remote_flock", no_remote_flock, 1),
+
106 CONN_OPTION("no_remote_posix_lock", no_remote_posix_lock, 1),
+
107 CONN_OPTION("splice_write", splice_write, 1),
+
108 CONN_OPTION("no_splice_write", no_splice_write, 1),
+
109 CONN_OPTION("splice_move", splice_move, 1),
+
110 CONN_OPTION("no_splice_move", no_splice_move, 1),
+
111 CONN_OPTION("splice_read", splice_read, 1),
+
112 CONN_OPTION("no_splice_read", no_splice_read, 1),
+
113 CONN_OPTION("auto_inval_data", auto_inval_data, 1),
+
114 CONN_OPTION("no_auto_inval_data", no_auto_inval_data, 1),
+
115 CONN_OPTION("readdirplus=no", no_readdirplus, 1),
+
116 CONN_OPTION("readdirplus=yes", no_readdirplus, 0),
+
117 CONN_OPTION("readdirplus=yes", no_readdirplus_auto, 1),
+
118 CONN_OPTION("readdirplus=auto", no_readdirplus, 0),
+
119 CONN_OPTION("readdirplus=auto", no_readdirplus_auto, 0),
+
120 CONN_OPTION("async_dio", async_dio, 1),
+
121 CONN_OPTION("no_async_dio", no_async_dio, 1),
+
122 CONN_OPTION("writeback_cache", writeback_cache, 1),
+
123 CONN_OPTION("no_writeback_cache", no_writeback_cache, 1),
+
124 CONN_OPTION("time_gran=%u", time_gran, 0),
+
125 CONN_OPTION("time_gran=", set_time_gran, 1),
+ +
127};
+
128
+
129
+
+ +
131{
+
132 printf(" -h --help print help\n"
+
133 " -V --version print version\n"
+
134 " -d -o debug enable debug output (implies -f)\n"
+
135 " -f foreground operation\n"
+
136 " -s disable multi-threaded operation\n"
+
137 " -o clone_fd use separate fuse device fd for each thread\n"
+
138 " (may improve performance)\n"
+
139 " -o max_idle_threads the maximum number of idle worker threads\n"
+
140 " allowed (default: -1)\n"
+
141 " -o max_threads the maximum number of worker threads\n"
+
142 " allowed (default: 10)\n");
+
143}
+
+
144
+
145static int fuse_helper_opt_proc(void *data, const char *arg, int key,
+
146 struct fuse_args *outargs)
+
147{
+
148 (void) outargs;
+
149 struct fuse_cmdline_opts *opts = data;
+
150
+
151 switch (key) {
+ +
153 if (!opts->mountpoint) {
+
154 if (fuse_mnt_parse_fuse_fd(arg) != -1) {
+
155 return fuse_opt_add_opt(&opts->mountpoint, arg);
+
156 }
+
157
+
158 char mountpoint[PATH_MAX] = "";
+
159 if (realpath(arg, mountpoint) == NULL) {
+
160 fuse_log(FUSE_LOG_ERR,
+
161 "fuse: bad mount point `%s': %s\n",
+
162 arg, strerror(errno));
+
163 return -1;
+
164 }
+
165 return fuse_opt_add_opt(&opts->mountpoint, mountpoint);
+
166 } else {
+
167 fuse_log(FUSE_LOG_ERR, "fuse: invalid argument `%s'\n", arg);
+
168 return -1;
+
169 }
+
170
+
171 default:
+
172 /* Pass through unknown options */
+
173 return 1;
+
174 }
+
175}
+
176
+
177/* Under FreeBSD, there is no subtype option so this
+
178 function actually sets the fsname */
+
179static int add_default_subtype(const char *progname, struct fuse_args *args)
+
180{
+
181 int res;
+
182 char *subtype_opt;
+
183
+
184 const char *basename = strrchr(progname, '/');
+
185 if (basename == NULL)
+
186 basename = progname;
+
187 else if (basename[1] != '\0')
+
188 basename++;
+
189
+
190 subtype_opt = (char *) malloc(strlen(basename) + 64);
+
191 if (subtype_opt == NULL) {
+
192 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
193 return -1;
+
194 }
+
195#ifdef __FreeBSD__
+
196 sprintf(subtype_opt, "-ofsname=%s", basename);
+
197#else
+
198 sprintf(subtype_opt, "-osubtype=%s", basename);
+
199#endif
+
200 res = fuse_opt_add_arg(args, subtype_opt);
+
201 free(subtype_opt);
+
202 return res;
+
203}
+
204
+
205int fuse_parse_cmdline_312(struct fuse_args *args,
+
206 struct fuse_cmdline_opts *opts);
+
207FUSE_SYMVER("fuse_parse_cmdline_312", "fuse_parse_cmdline@@FUSE_3.12")
+
208int fuse_parse_cmdline_312(struct fuse_args *args,
+
209 struct fuse_cmdline_opts *opts)
+
210{
+
211 memset(opts, 0, sizeof(struct fuse_cmdline_opts));
+
212
+
213 opts->max_idle_threads = UINT_MAX; /* new default in fuse version 3.12 */
+
214 opts->max_threads = 10;
+
215
+
216 if (fuse_opt_parse(args, opts, fuse_helper_opts,
+
217 fuse_helper_opt_proc) == -1)
+
218 return -1;
+
219
+
220 /* *Linux*: if neither -o subtype nor -o fsname are specified,
+
221 set subtype to program's basename.
+
222 *FreeBSD*: if fsname is not specified, set to program's
+
223 basename. */
+
224 if (!opts->nodefault_subtype)
+
225 if (add_default_subtype(args->argv[0], args) == -1)
+
226 return -1;
+
227
+
228 return 0;
+
229}
+
230
+
234int fuse_parse_cmdline_30(struct fuse_args *args,
+
235 struct fuse_cmdline_opts *opts);
+
236FUSE_SYMVER("fuse_parse_cmdline_30", "fuse_parse_cmdline@FUSE_3.0")
+
+ +
238 struct fuse_cmdline_opts *out_opts)
+
239{
+
240 struct fuse_cmdline_opts opts;
+
241
+
242 int rc = fuse_parse_cmdline_312(args, &opts);
+
243 if (rc == 0) {
+
244 /* copy up to the size of the old pre 3.12 struct */
+
245 memcpy(out_opts, &opts,
+
246 offsetof(struct fuse_cmdline_opts, max_idle_threads) +
+
247 sizeof(opts.max_idle_threads));
+
248 }
+
249
+
250 return rc;
+
251}
+
+
252
+
+
253int fuse_daemonize(int foreground)
+
254{
+
255 if (!foreground) {
+
256 int nullfd;
+
257 int waiter[2];
+
258 char completed;
+
259
+
260 if (pipe(waiter)) {
+
261 perror("fuse_daemonize: pipe");
+
262 return -1;
+
263 }
+
264
+
265 /*
+
266 * demonize current process by forking it and killing the
+
267 * parent. This makes current process as a child of 'init'.
+
268 */
+
269 switch(fork()) {
+
270 case -1:
+
271 perror("fuse_daemonize: fork");
+
272 return -1;
+
273 case 0:
+
274 break;
+
275 default:
+
276 (void) read(waiter[0], &completed, sizeof(completed));
+
277 _exit(0);
+
278 }
+
279
+
280 if (setsid() == -1) {
+
281 perror("fuse_daemonize: setsid");
+
282 return -1;
+
283 }
+
284
+
285 (void) chdir("/");
+
286
+
287 nullfd = open("/dev/null", O_RDWR, 0);
+
288 if (nullfd != -1) {
+
289 (void) dup2(nullfd, 0);
+
290 (void) dup2(nullfd, 1);
+
291 (void) dup2(nullfd, 2);
+
292 if (nullfd > 2)
+
293 close(nullfd);
+
294 }
+
295
+
296 /* Propagate completion of daemon initialization */
+
297 completed = 1;
+
298 (void) write(waiter[1], &completed, sizeof(completed));
+
299 close(waiter[0]);
+
300 close(waiter[1]);
+
301 } else {
+
302 (void) chdir("/");
+
303 }
+
304 return 0;
+
305}
+
+
306
+
+
307int fuse_main_real_versioned(int argc, char *argv[],
+
308 const struct fuse_operations *op, size_t op_size,
+
309 struct libfuse_version *version, void *user_data)
+
310{
+
311 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
312 struct fuse *fuse;
+
313 struct fuse_cmdline_opts opts;
+
314 int res;
+
315 struct fuse_loop_config *loop_config = NULL;
+
316
+
317 if (fuse_parse_cmdline(&args, &opts) != 0)
+
318 return 1;
+
319
+
320 if (opts.show_version) {
+
321 printf("FUSE library version %s\n", PACKAGE_VERSION);
+ +
323 res = 0;
+
324 goto out1;
+
325 }
+
326
+
327 if (opts.show_help) {
+
328 if(args.argv[0][0] != '\0')
+
329 printf("usage: %s [options] <mountpoint>\n\n",
+
330 args.argv[0]);
+
331 printf("FUSE options:\n");
+ +
333 fuse_lib_help(&args);
+
334 res = 0;
+
335 goto out1;
+
336 }
+
337
+
338 if (!opts.show_help &&
+
339 !opts.mountpoint) {
+
340 fuse_log(FUSE_LOG_ERR, "error: no mountpoint specified\n");
+
341 res = 2;
+
342 goto out1;
+
343 }
+
344
+
345 struct fuse *_fuse_new_31(struct fuse_args *args,
+
346 const struct fuse_operations *op, size_t op_size,
+
347 struct libfuse_version *version,
+
348 void *user_data);
+
349 fuse = _fuse_new_31(&args, op, op_size, version, user_data);
+
350 if (fuse == NULL) {
+
351 res = 3;
+
352 goto out1;
+
353 }
+
354
+
355 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
356 res = 4;
+
357 goto out2;
+
358 }
+
359
+
360 if (fuse_daemonize(opts.foreground) != 0) {
+
361 res = 5;
+
362 goto out3;
+
363 }
+
364
+
365 struct fuse_session *se = fuse_get_session(fuse);
+
366 if (fuse_set_signal_handlers(se) != 0) {
+
367 res = 6;
+
368 goto out3;
+
369 }
+
370
+
371 if (opts.singlethread)
+
372 res = fuse_loop(fuse);
+
373 else {
+
374 loop_config = fuse_loop_cfg_create();
+
375 if (loop_config == NULL) {
+
376 res = 7;
+
377 goto out3;
+
378 }
+
379
+
380 fuse_loop_cfg_set_clone_fd(loop_config, opts.clone_fd);
+
381
+
382 fuse_loop_cfg_set_idle_threads(loop_config, opts.max_idle_threads);
+
383 fuse_loop_cfg_set_max_threads(loop_config, opts.max_threads);
+
384 res = fuse_loop_mt(fuse, loop_config);
+
385 }
+
386 if (res)
+
387 res = 8;
+
388
+ +
390out3:
+
391 fuse_unmount(fuse);
+
392out2:
+
393 fuse_destroy(fuse);
+
394out1:
+
395 fuse_loop_cfg_destroy(loop_config);
+
396 free(opts.mountpoint);
+
397 fuse_opt_free_args(&args);
+
398 return res;
+
399}
+
+
400
+
401/* Not symboled, as not part of the official API */
+
402int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
403 size_t op_size, void *user_data);
+
404int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
405 size_t op_size, void *user_data)
+
406{
+
407 struct libfuse_version version = { 0 };
+
408 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
409 user_data);
+
410}
+
411
+
+
412void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
413 struct fuse_conn_info *conn)
+
414{
+
415 if(opts->set_max_write)
+
416 conn->max_write = opts->max_write;
+
417 if(opts->set_max_background)
+
418 conn->max_background = opts->max_background;
+
419 if(opts->set_congestion_threshold)
+
420 conn->congestion_threshold = opts->congestion_threshold;
+
421 if(opts->set_time_gran)
+
422 conn->time_gran = opts->time_gran;
+
423 if(opts->set_max_readahead)
+
424 conn->max_readahead = opts->max_readahead;
+
425
+
426#define LL_ENABLE(cond, cap) \
+
427 do { \
+
428 if (cond) \
+
429 fuse_set_feature_flag(conn, cap); \
+
430 } while (0)
+
431
+
432#define LL_DISABLE(cond, cap) \
+
433 do { \
+
434 if (cond) \
+
435 fuse_unset_feature_flag(conn, cap); \
+
436 } while (0)
+
437
+
438 LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ);
+
439 LL_DISABLE(opts->no_splice_read, FUSE_CAP_SPLICE_READ);
+
440
+
441 LL_ENABLE(opts->splice_write, FUSE_CAP_SPLICE_WRITE);
+
442 LL_DISABLE(opts->no_splice_write, FUSE_CAP_SPLICE_WRITE);
+
443
+
444 LL_ENABLE(opts->splice_move, FUSE_CAP_SPLICE_MOVE);
+
445 LL_DISABLE(opts->no_splice_move, FUSE_CAP_SPLICE_MOVE);
+
446
+
447 LL_ENABLE(opts->auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
448 LL_DISABLE(opts->no_auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
449
+
450 LL_DISABLE(opts->no_readdirplus, FUSE_CAP_READDIRPLUS);
+
451 LL_DISABLE(opts->no_readdirplus_auto, FUSE_CAP_READDIRPLUS_AUTO);
+
452
+
453 LL_ENABLE(opts->async_dio, FUSE_CAP_ASYNC_DIO);
+
454 LL_DISABLE(opts->no_async_dio, FUSE_CAP_ASYNC_DIO);
+
455
+
456 LL_ENABLE(opts->writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
457 LL_DISABLE(opts->no_writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
458
+
459 LL_ENABLE(opts->async_read, FUSE_CAP_ASYNC_READ);
+
460 LL_DISABLE(opts->sync_read, FUSE_CAP_ASYNC_READ);
+
461
+
462 LL_DISABLE(opts->no_remote_posix_lock, FUSE_CAP_POSIX_LOCKS);
+
463 LL_DISABLE(opts->no_remote_flock, FUSE_CAP_FLOCK_LOCKS);
+
464}
+
+
465
+
+
466struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args)
+
467{
+
468 struct fuse_conn_info_opts *opts;
+
469
+
470 opts = calloc(1, sizeof(struct fuse_conn_info_opts));
+
471 if(opts == NULL) {
+
472 fuse_log(FUSE_LOG_ERR, "calloc failed\n");
+
473 return NULL;
+
474 }
+
475 if(fuse_opt_parse(args, opts, conn_info_opt_spec, NULL) == -1) {
+
476 free(opts);
+
477 return NULL;
+
478 }
+
479 return opts;
+
480}
+
+
481
+
+
482int fuse_open_channel(const char *mountpoint, const char* options)
+
483{
+
484 struct mount_opts *opts = NULL;
+
485 int fd = -1;
+
486 const char *argv[] = { "", "-o", options };
+
487 int argc = sizeof(argv) / sizeof(argv[0]);
+
488 struct fuse_args args = FUSE_ARGS_INIT(argc, (char**) argv);
+
489
+
490 opts = parse_mount_opts(&args);
+
491 if (opts == NULL)
+
492 return -1;
+
493
+
494 fd = fuse_kern_mount(mountpoint, opts);
+
495 destroy_mount_opts(opts);
+
496
+
497 return fd;
+
498}
+
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5197
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5146
+
int fuse_main_real_versioned(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
Definition helper.c:307
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:482
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5202
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:466
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_READDIRPLUS
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:412
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t time_gran
+
uint32_t congestion_threshold
+
uint32_t max_write
+
uint32_t max_readahead
+
uint32_t max_background
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2modules_2iconv_8c_source.html b/doc/html/fuse-3_817_84_2lib_2modules_2iconv_8c_source.html new file mode 100644 index 0000000..4a4967d --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2modules_2iconv_8c_source.html @@ -0,0 +1,816 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/modules/iconv.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
iconv.c
+
+
+
1/*
+
2 fuse iconv module: file name charset conversion
+
3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include <fuse_config.h>
+
10
+
11#include <fuse.h>
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <stddef.h>
+
15#include <string.h>
+
16#include <errno.h>
+
17#include <iconv.h>
+
18#include <pthread.h>
+
19#include <locale.h>
+
20#include <langinfo.h>
+
21
+
22struct iconv {
+
23 struct fuse_fs *next;
+
24 pthread_mutex_t lock;
+
25 char *from_code;
+
26 char *to_code;
+
27 iconv_t tofs;
+
28 iconv_t fromfs;
+
29};
+
30
+
31struct iconv_dh {
+
32 struct iconv *ic;
+
33 void *prev_buf;
+
34 fuse_fill_dir_t prev_filler;
+
35};
+
36
+
37static struct iconv *iconv_get(void)
+
38{
+ +
40}
+
41
+
42static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp,
+
43 int fromfs)
+
44{
+
45 size_t pathlen;
+
46 size_t newpathlen;
+
47 char *newpath;
+
48 size_t plen;
+
49 char *p;
+
50 size_t res;
+
51 int err;
+
52
+
53 if (path == NULL) {
+
54 *newpathp = NULL;
+
55 return 0;
+
56 }
+
57
+
58 pathlen = strlen(path);
+
59 newpathlen = pathlen * 4;
+
60 newpath = malloc(newpathlen + 1);
+
61 if (!newpath)
+
62 return -ENOMEM;
+
63
+
64 plen = newpathlen;
+
65 p = newpath;
+
66 pthread_mutex_lock(&ic->lock);
+
67 do {
+
68 res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path,
+
69 &pathlen, &p, &plen);
+
70 if (res == (size_t) -1) {
+
71 char *tmp;
+
72 size_t inc;
+
73
+
74 err = -EILSEQ;
+
75 if (errno != E2BIG)
+
76 goto err;
+
77
+
78 inc = (pathlen + 1) * 4;
+
79 newpathlen += inc;
+
80 int dp = p - newpath;
+
81 tmp = realloc(newpath, newpathlen + 1);
+
82 err = -ENOMEM;
+
83 if (!tmp)
+
84 goto err;
+
85
+
86 p = tmp + dp;
+
87 plen += inc;
+
88 newpath = tmp;
+
89 }
+
90 } while (res == (size_t) -1);
+
91 pthread_mutex_unlock(&ic->lock);
+
92 *p = '\0';
+
93 *newpathp = newpath;
+
94 return 0;
+
95
+
96err:
+
97 iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL);
+
98 pthread_mutex_unlock(&ic->lock);
+
99 free(newpath);
+
100 return err;
+
101}
+
102
+
103static int iconv_getattr(const char *path, struct stat *stbuf,
+
104 struct fuse_file_info *fi)
+
105{
+
106 struct iconv *ic = iconv_get();
+
107 char *newpath;
+
108 int err = iconv_convpath(ic, path, &newpath, 0);
+
109 if (!err) {
+
110 err = fuse_fs_getattr(ic->next, newpath, stbuf, fi);
+
111 free(newpath);
+
112 }
+
113 return err;
+
114}
+
115
+
116static int iconv_access(const char *path, int mask)
+
117{
+
118 struct iconv *ic = iconv_get();
+
119 char *newpath;
+
120 int err = iconv_convpath(ic, path, &newpath, 0);
+
121 if (!err) {
+
122 err = fuse_fs_access(ic->next, newpath, mask);
+
123 free(newpath);
+
124 }
+
125 return err;
+
126}
+
127
+
128static int iconv_readlink(const char *path, char *buf, size_t size)
+
129{
+
130 struct iconv *ic = iconv_get();
+
131 char *newpath;
+
132 int err = iconv_convpath(ic, path, &newpath, 0);
+
133 if (!err) {
+
134 err = fuse_fs_readlink(ic->next, newpath, buf, size);
+
135 if (!err) {
+
136 char *newlink;
+
137 err = iconv_convpath(ic, buf, &newlink, 1);
+
138 if (!err) {
+
139 strncpy(buf, newlink, size - 1);
+
140 buf[size - 1] = '\0';
+
141 free(newlink);
+
142 }
+
143 }
+
144 free(newpath);
+
145 }
+
146 return err;
+
147}
+
148
+
149static int iconv_opendir(const char *path, struct fuse_file_info *fi)
+
150{
+
151 struct iconv *ic = iconv_get();
+
152 char *newpath;
+
153 int err = iconv_convpath(ic, path, &newpath, 0);
+
154 if (!err) {
+
155 err = fuse_fs_opendir(ic->next, newpath, fi);
+
156 free(newpath);
+
157 }
+
158 return err;
+
159}
+
160
+
161static int iconv_dir_fill(void *buf, const char *name,
+
162 const struct stat *stbuf, off_t off,
+
163 enum fuse_fill_dir_flags flags)
+
164{
+
165 struct iconv_dh *dh = buf;
+
166 char *newname;
+
167 int res = 0;
+
168 if (iconv_convpath(dh->ic, name, &newname, 1) == 0) {
+
169 res = dh->prev_filler(dh->prev_buf, newname, stbuf, off, flags);
+
170 free(newname);
+
171 }
+
172 return res;
+
173}
+
174
+
175static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
176 off_t offset, struct fuse_file_info *fi,
+
177 enum fuse_readdir_flags flags)
+
178{
+
179 struct iconv *ic = iconv_get();
+
180 char *newpath;
+
181 int err = iconv_convpath(ic, path, &newpath, 0);
+
182 if (!err) {
+
183 struct iconv_dh dh;
+
184 dh.ic = ic;
+
185 dh.prev_buf = buf;
+
186 dh.prev_filler = filler;
+
187 err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill,
+
188 offset, fi, flags);
+
189 free(newpath);
+
190 }
+
191 return err;
+
192}
+
193
+
194static int iconv_releasedir(const char *path, struct fuse_file_info *fi)
+
195{
+
196 struct iconv *ic = iconv_get();
+
197 char *newpath;
+
198 int err = iconv_convpath(ic, path, &newpath, 0);
+
199 if (!err) {
+
200 err = fuse_fs_releasedir(ic->next, newpath, fi);
+
201 free(newpath);
+
202 }
+
203 return err;
+
204}
+
205
+
206static int iconv_mknod(const char *path, mode_t mode, dev_t rdev)
+
207{
+
208 struct iconv *ic = iconv_get();
+
209 char *newpath;
+
210 int err = iconv_convpath(ic, path, &newpath, 0);
+
211 if (!err) {
+
212 err = fuse_fs_mknod(ic->next, newpath, mode, rdev);
+
213 free(newpath);
+
214 }
+
215 return err;
+
216}
+
217
+
218static int iconv_mkdir(const char *path, mode_t mode)
+
219{
+
220 struct iconv *ic = iconv_get();
+
221 char *newpath;
+
222 int err = iconv_convpath(ic, path, &newpath, 0);
+
223 if (!err) {
+
224 err = fuse_fs_mkdir(ic->next, newpath, mode);
+
225 free(newpath);
+
226 }
+
227 return err;
+
228}
+
229
+
230static int iconv_unlink(const char *path)
+
231{
+
232 struct iconv *ic = iconv_get();
+
233 char *newpath;
+
234 int err = iconv_convpath(ic, path, &newpath, 0);
+
235 if (!err) {
+
236 err = fuse_fs_unlink(ic->next, newpath);
+
237 free(newpath);
+
238 }
+
239 return err;
+
240}
+
241
+
242static int iconv_rmdir(const char *path)
+
243{
+
244 struct iconv *ic = iconv_get();
+
245 char *newpath;
+
246 int err = iconv_convpath(ic, path, &newpath, 0);
+
247 if (!err) {
+
248 err = fuse_fs_rmdir(ic->next, newpath);
+
249 free(newpath);
+
250 }
+
251 return err;
+
252}
+
253
+
254static int iconv_symlink(const char *from, const char *to)
+
255{
+
256 struct iconv *ic = iconv_get();
+
257 char *newfrom;
+
258 char *newto;
+
259 int err = iconv_convpath(ic, from, &newfrom, 0);
+
260 if (!err) {
+
261 err = iconv_convpath(ic, to, &newto, 0);
+
262 if (!err) {
+
263 err = fuse_fs_symlink(ic->next, newfrom, newto);
+
264 free(newto);
+
265 }
+
266 free(newfrom);
+
267 }
+
268 return err;
+
269}
+
270
+
271static int iconv_rename(const char *from, const char *to, unsigned int flags)
+
272{
+
273 struct iconv *ic = iconv_get();
+
274 char *newfrom;
+
275 char *newto;
+
276 int err = iconv_convpath(ic, from, &newfrom, 0);
+
277 if (!err) {
+
278 err = iconv_convpath(ic, to, &newto, 0);
+
279 if (!err) {
+
280 err = fuse_fs_rename(ic->next, newfrom, newto, flags);
+
281 free(newto);
+
282 }
+
283 free(newfrom);
+
284 }
+
285 return err;
+
286}
+
287
+
288static int iconv_link(const char *from, const char *to)
+
289{
+
290 struct iconv *ic = iconv_get();
+
291 char *newfrom;
+
292 char *newto;
+
293 int err = iconv_convpath(ic, from, &newfrom, 0);
+
294 if (!err) {
+
295 err = iconv_convpath(ic, to, &newto, 0);
+
296 if (!err) {
+
297 err = fuse_fs_link(ic->next, newfrom, newto);
+
298 free(newto);
+
299 }
+
300 free(newfrom);
+
301 }
+
302 return err;
+
303}
+
304
+
305static int iconv_chmod(const char *path, mode_t mode,
+
306 struct fuse_file_info *fi)
+
307{
+
308 struct iconv *ic = iconv_get();
+
309 char *newpath;
+
310 int err = iconv_convpath(ic, path, &newpath, 0);
+
311 if (!err) {
+
312 err = fuse_fs_chmod(ic->next, newpath, mode, fi);
+
313 free(newpath);
+
314 }
+
315 return err;
+
316}
+
317
+
318static int iconv_chown(const char *path, uid_t uid, gid_t gid,
+
319 struct fuse_file_info *fi)
+
320{
+
321 struct iconv *ic = iconv_get();
+
322 char *newpath;
+
323 int err = iconv_convpath(ic, path, &newpath, 0);
+
324 if (!err) {
+
325 err = fuse_fs_chown(ic->next, newpath, uid, gid, fi);
+
326 free(newpath);
+
327 }
+
328 return err;
+
329}
+
330
+
331static int iconv_truncate(const char *path, off_t size,
+
332 struct fuse_file_info *fi)
+
333{
+
334 struct iconv *ic = iconv_get();
+
335 char *newpath;
+
336 int err = iconv_convpath(ic, path, &newpath, 0);
+
337 if (!err) {
+
338 err = fuse_fs_truncate(ic->next, newpath, size, fi);
+
339 free(newpath);
+
340 }
+
341 return err;
+
342}
+
343
+
344static int iconv_utimens(const char *path, const struct timespec ts[2],
+
345 struct fuse_file_info *fi)
+
346{
+
347 struct iconv *ic = iconv_get();
+
348 char *newpath;
+
349 int err = iconv_convpath(ic, path, &newpath, 0);
+
350 if (!err) {
+
351 err = fuse_fs_utimens(ic->next, newpath, ts, fi);
+
352 free(newpath);
+
353 }
+
354 return err;
+
355}
+
356
+
357static int iconv_create(const char *path, mode_t mode,
+
358 struct fuse_file_info *fi)
+
359{
+
360 struct iconv *ic = iconv_get();
+
361 char *newpath;
+
362 int err = iconv_convpath(ic, path, &newpath, 0);
+
363 if (!err) {
+
364 err = fuse_fs_create(ic->next, newpath, mode, fi);
+
365 free(newpath);
+
366 }
+
367 return err;
+
368}
+
369
+
370static int iconv_open_file(const char *path, struct fuse_file_info *fi)
+
371{
+
372 struct iconv *ic = iconv_get();
+
373 char *newpath;
+
374 int err = iconv_convpath(ic, path, &newpath, 0);
+
375 if (!err) {
+
376 err = fuse_fs_open(ic->next, newpath, fi);
+
377 free(newpath);
+
378 }
+
379 return err;
+
380}
+
381
+
382static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp,
+
383 size_t size, off_t offset, struct fuse_file_info *fi)
+
384{
+
385 struct iconv *ic = iconv_get();
+
386 char *newpath;
+
387 int err = iconv_convpath(ic, path, &newpath, 0);
+
388 if (!err) {
+
389 err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi);
+
390 free(newpath);
+
391 }
+
392 return err;
+
393}
+
394
+
395static int iconv_write_buf(const char *path, struct fuse_bufvec *buf,
+
396 off_t offset, struct fuse_file_info *fi)
+
397{
+
398 struct iconv *ic = iconv_get();
+
399 char *newpath;
+
400 int err = iconv_convpath(ic, path, &newpath, 0);
+
401 if (!err) {
+
402 err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi);
+
403 free(newpath);
+
404 }
+
405 return err;
+
406}
+
407
+
408static int iconv_statfs(const char *path, struct statvfs *stbuf)
+
409{
+
410 struct iconv *ic = iconv_get();
+
411 char *newpath;
+
412 int err = iconv_convpath(ic, path, &newpath, 0);
+
413 if (!err) {
+
414 err = fuse_fs_statfs(ic->next, newpath, stbuf);
+
415 free(newpath);
+
416 }
+
417 return err;
+
418}
+
419
+
420static int iconv_flush(const char *path, struct fuse_file_info *fi)
+
421{
+
422 struct iconv *ic = iconv_get();
+
423 char *newpath;
+
424 int err = iconv_convpath(ic, path, &newpath, 0);
+
425 if (!err) {
+
426 err = fuse_fs_flush(ic->next, newpath, fi);
+
427 free(newpath);
+
428 }
+
429 return err;
+
430}
+
431
+
432static int iconv_release(const char *path, struct fuse_file_info *fi)
+
433{
+
434 struct iconv *ic = iconv_get();
+
435 char *newpath;
+
436 int err = iconv_convpath(ic, path, &newpath, 0);
+
437 if (!err) {
+
438 err = fuse_fs_release(ic->next, newpath, fi);
+
439 free(newpath);
+
440 }
+
441 return err;
+
442}
+
443
+
444static int iconv_fsync(const char *path, int isdatasync,
+
445 struct fuse_file_info *fi)
+
446{
+
447 struct iconv *ic = iconv_get();
+
448 char *newpath;
+
449 int err = iconv_convpath(ic, path, &newpath, 0);
+
450 if (!err) {
+
451 err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi);
+
452 free(newpath);
+
453 }
+
454 return err;
+
455}
+
456
+
457static int iconv_fsyncdir(const char *path, int isdatasync,
+
458 struct fuse_file_info *fi)
+
459{
+
460 struct iconv *ic = iconv_get();
+
461 char *newpath;
+
462 int err = iconv_convpath(ic, path, &newpath, 0);
+
463 if (!err) {
+
464 err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi);
+
465 free(newpath);
+
466 }
+
467 return err;
+
468}
+
469
+
470static int iconv_setxattr(const char *path, const char *name,
+
471 const char *value, size_t size, int flags)
+
472{
+
473 struct iconv *ic = iconv_get();
+
474 char *newpath;
+
475 int err = iconv_convpath(ic, path, &newpath, 0);
+
476 if (!err) {
+
477 err = fuse_fs_setxattr(ic->next, newpath, name, value, size,
+
478 flags);
+
479 free(newpath);
+
480 }
+
481 return err;
+
482}
+
483
+
484static int iconv_getxattr(const char *path, const char *name, char *value,
+
485 size_t size)
+
486{
+
487 struct iconv *ic = iconv_get();
+
488 char *newpath;
+
489 int err = iconv_convpath(ic, path, &newpath, 0);
+
490 if (!err) {
+
491 err = fuse_fs_getxattr(ic->next, newpath, name, value, size);
+
492 free(newpath);
+
493 }
+
494 return err;
+
495}
+
496
+
497static int iconv_listxattr(const char *path, char *list, size_t size)
+
498{
+
499 struct iconv *ic = iconv_get();
+
500 char *newpath;
+
501 int err = iconv_convpath(ic, path, &newpath, 0);
+
502 if (!err) {
+
503 err = fuse_fs_listxattr(ic->next, newpath, list, size);
+
504 free(newpath);
+
505 }
+
506 return err;
+
507}
+
508
+
509static int iconv_removexattr(const char *path, const char *name)
+
510{
+
511 struct iconv *ic = iconv_get();
+
512 char *newpath;
+
513 int err = iconv_convpath(ic, path, &newpath, 0);
+
514 if (!err) {
+
515 err = fuse_fs_removexattr(ic->next, newpath, name);
+
516 free(newpath);
+
517 }
+
518 return err;
+
519}
+
520
+
521static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
522 struct flock *lock)
+
523{
+
524 struct iconv *ic = iconv_get();
+
525 char *newpath;
+
526 int err = iconv_convpath(ic, path, &newpath, 0);
+
527 if (!err) {
+
528 err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock);
+
529 free(newpath);
+
530 }
+
531 return err;
+
532}
+
533
+
534static int iconv_flock(const char *path, struct fuse_file_info *fi, int op)
+
535{
+
536 struct iconv *ic = iconv_get();
+
537 char *newpath;
+
538 int err = iconv_convpath(ic, path, &newpath, 0);
+
539 if (!err) {
+
540 err = fuse_fs_flock(ic->next, newpath, fi, op);
+
541 free(newpath);
+
542 }
+
543 return err;
+
544}
+
545
+
546static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx)
+
547{
+
548 struct iconv *ic = iconv_get();
+
549 char *newpath;
+
550 int err = iconv_convpath(ic, path, &newpath, 0);
+
551 if (!err) {
+
552 err = fuse_fs_bmap(ic->next, newpath, blocksize, idx);
+
553 free(newpath);
+
554 }
+
555 return err;
+
556}
+
557
+
558static off_t iconv_lseek(const char *path, off_t off, int whence,
+
559 struct fuse_file_info *fi)
+
560{
+
561 struct iconv *ic = iconv_get();
+
562 char *newpath;
+
563 int res = iconv_convpath(ic, path, &newpath, 0);
+
564 if (!res) {
+
565 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
+
566 free(newpath);
+
567 }
+
568 return res;
+
569}
+
570
+
571static void *iconv_init(struct fuse_conn_info *conn,
+
572 struct fuse_config *cfg)
+
573{
+
574 struct iconv *ic = iconv_get();
+
575 fuse_fs_init(ic->next, conn, cfg);
+
576 /* Don't touch cfg->nullpath_ok, we can work with
+
577 either */
+
578 return ic;
+
579}
+
580
+
581static void iconv_destroy(void *data)
+
582{
+
583 struct iconv *ic = data;
+
584 fuse_fs_destroy(ic->next);
+
585 iconv_close(ic->tofs);
+
586 iconv_close(ic->fromfs);
+
587 pthread_mutex_destroy(&ic->lock);
+
588 free(ic->from_code);
+
589 free(ic->to_code);
+
590 free(ic);
+
591}
+
592
+
593static const struct fuse_operations iconv_oper = {
+
594 .destroy = iconv_destroy,
+
595 .init = iconv_init,
+
596 .getattr = iconv_getattr,
+
597 .access = iconv_access,
+
598 .readlink = iconv_readlink,
+
599 .opendir = iconv_opendir,
+
600 .readdir = iconv_readdir,
+
601 .releasedir = iconv_releasedir,
+
602 .mknod = iconv_mknod,
+
603 .mkdir = iconv_mkdir,
+
604 .symlink = iconv_symlink,
+
605 .unlink = iconv_unlink,
+
606 .rmdir = iconv_rmdir,
+
607 .rename = iconv_rename,
+
608 .link = iconv_link,
+
609 .chmod = iconv_chmod,
+
610 .chown = iconv_chown,
+
611 .truncate = iconv_truncate,
+
612 .utimens = iconv_utimens,
+
613 .create = iconv_create,
+
614 .open = iconv_open_file,
+
615 .read_buf = iconv_read_buf,
+
616 .write_buf = iconv_write_buf,
+
617 .statfs = iconv_statfs,
+
618 .flush = iconv_flush,
+
619 .release = iconv_release,
+
620 .fsync = iconv_fsync,
+
621 .fsyncdir = iconv_fsyncdir,
+
622 .setxattr = iconv_setxattr,
+
623 .getxattr = iconv_getxattr,
+
624 .listxattr = iconv_listxattr,
+
625 .removexattr = iconv_removexattr,
+
626 .lock = iconv_lock,
+
627 .flock = iconv_flock,
+
628 .bmap = iconv_bmap,
+
629 .lseek = iconv_lseek,
+
630};
+
631
+
632static const struct fuse_opt iconv_opts[] = {
+
633 FUSE_OPT_KEY("-h", 0),
+
634 FUSE_OPT_KEY("--help", 0),
+
635 { "from_code=%s", offsetof(struct iconv, from_code), 0 },
+
636 { "to_code=%s", offsetof(struct iconv, to_code), 1 },
+ +
638};
+
639
+
640static void iconv_help(void)
+
641{
+
642 char *charmap;
+
643 const char *old = setlocale(LC_CTYPE, "");
+
644
+
645 charmap = strdup(nl_langinfo(CODESET));
+
646 if (old)
+
647 setlocale(LC_CTYPE, old);
+
648 else
+
649 perror("setlocale");
+
650
+
651 printf(
+
652" -o from_code=CHARSET original encoding of file names (default: UTF-8)\n"
+
653" -o to_code=CHARSET new encoding of the file names (default: %s)\n",
+
654 charmap);
+
655 free(charmap);
+
656}
+
657
+
658static int iconv_opt_proc(void *data, const char *arg, int key,
+
659 struct fuse_args *outargs)
+
660{
+
661 (void) data; (void) arg; (void) outargs;
+
662
+
663 if (!key) {
+
664 iconv_help();
+
665 return -1;
+
666 }
+
667
+
668 return 1;
+
669}
+
670
+
671static struct fuse_fs *iconv_new(struct fuse_args *args,
+
672 struct fuse_fs *next[])
+
673{
+
674 struct fuse_fs *fs;
+
675 struct iconv *ic;
+
676 const char *old = NULL;
+
677 const char *from;
+
678 const char *to;
+
679
+
680 ic = calloc(1, sizeof(struct iconv));
+
681 if (ic == NULL) {
+
682 fuse_log(FUSE_LOG_ERR, "fuse-iconv: memory allocation failed\n");
+
683 return NULL;
+
684 }
+
685
+
686 if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1)
+
687 goto out_free;
+
688
+
689 if (!next[0] || next[1]) {
+
690 fuse_log(FUSE_LOG_ERR, "fuse-iconv: exactly one next filesystem required\n");
+
691 goto out_free;
+
692 }
+
693
+
694 from = ic->from_code ? ic->from_code : "UTF-8";
+
695 to = ic->to_code ? ic->to_code : "";
+
696 /* FIXME: detect charset equivalence? */
+
697 if (!to[0])
+
698 old = setlocale(LC_CTYPE, "");
+
699 ic->tofs = iconv_open(from, to);
+
700 if (ic->tofs == (iconv_t) -1) {
+
701 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
702 to, from);
+
703 goto out_free;
+
704 }
+
705 ic->fromfs = iconv_open(to, from);
+
706 if (ic->tofs == (iconv_t) -1) {
+
707 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
708 from, to);
+
709 goto out_iconv_close_to;
+
710 }
+
711 if (old) {
+
712 setlocale(LC_CTYPE, old);
+
713 old = NULL;
+
714 }
+
715
+
716 ic->next = next[0];
+
717 fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic);
+
718 if (!fs)
+
719 goto out_iconv_close_from;
+
720
+
721 return fs;
+
722
+
723out_iconv_close_from:
+
724 iconv_close(ic->fromfs);
+
725out_iconv_close_to:
+
726 iconv_close(ic->tofs);
+
727out_free:
+
728 free(ic->from_code);
+
729 free(ic->to_code);
+
730 free(ic);
+
731 if (old) {
+
732 setlocale(LC_CTYPE, old);
+
733 }
+
734 return NULL;
+
735}
+
736
+
737FUSE_REGISTER_MODULE(iconv, iconv_new);
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
fuse_fill_dir_flags
Definition fuse.h:58
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_REGISTER_MODULE(name_, factory_)
Definition fuse.h:1395
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + + +
void * private_data
Definition fuse.h:874
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+ +
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2modules_2subdir_8c_source.html b/doc/html/fuse-3_817_84_2lib_2modules_2subdir_8c_source.html new file mode 100644 index 0000000..e6726b4 --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2modules_2subdir_8c_source.html @@ -0,0 +1,767 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/modules/subdir.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
subdir.c
+
+
+
1/*
+
2 fuse subdir module: offset paths with a base directory
+
3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include <fuse_config.h>
+
10
+
11#include <fuse.h>
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <stddef.h>
+
15#include <string.h>
+
16#include <errno.h>
+
17
+
18struct subdir {
+
19 char *base;
+
20 size_t baselen;
+
21 int rellinks;
+
22 struct fuse_fs *next;
+
23};
+
24
+
25static struct subdir *subdir_get(void)
+
26{
+ +
28}
+
29
+
30static int subdir_addpath(struct subdir *d, const char *path, char **newpathp)
+
31{
+
32 char *newpath = NULL;
+
33
+
34 if (path != NULL) {
+
35 unsigned newlen = d->baselen + strlen(path);
+
36
+
37 newpath = malloc(newlen + 2);
+
38 if (!newpath)
+
39 return -ENOMEM;
+
40
+
41 if (path[0] == '/')
+
42 path++;
+
43 strcpy(newpath, d->base);
+
44 strcpy(newpath + d->baselen, path);
+
45 if (!newpath[0])
+
46 strcpy(newpath, ".");
+
47 }
+
48 *newpathp = newpath;
+
49
+
50 return 0;
+
51}
+
52
+
53static int subdir_getattr(const char *path, struct stat *stbuf,
+
54 struct fuse_file_info *fi)
+
55{
+
56 struct subdir *d = subdir_get();
+
57 char *newpath;
+
58 int err = subdir_addpath(d, path, &newpath);
+
59 if (!err) {
+
60 err = fuse_fs_getattr(d->next, newpath, stbuf, fi);
+
61 free(newpath);
+
62 }
+
63 return err;
+
64}
+
65
+
66static int subdir_access(const char *path, int mask)
+
67{
+
68 struct subdir *d = subdir_get();
+
69 char *newpath;
+
70 int err = subdir_addpath(d, path, &newpath);
+
71 if (!err) {
+
72 err = fuse_fs_access(d->next, newpath, mask);
+
73 free(newpath);
+
74 }
+
75 return err;
+
76}
+
77
+
78
+
79static int count_components(const char *p)
+
80{
+
81 int ctr;
+
82
+
83 for (; *p == '/'; p++);
+
84 for (ctr = 0; *p; ctr++) {
+
85 for (; *p && *p != '/'; p++);
+
86 for (; *p == '/'; p++);
+
87 }
+
88 return ctr;
+
89}
+
90
+
91static void strip_common(const char **sp, const char **tp)
+
92{
+
93 const char *s = *sp;
+
94 const char *t = *tp;
+
95 do {
+
96 for (; *s == '/'; s++);
+
97 for (; *t == '/'; t++);
+
98 *tp = t;
+
99 *sp = s;
+
100 for (; *s == *t && *s && *s != '/'; s++, t++);
+
101 } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t));
+
102}
+
103
+
104static void transform_symlink(struct subdir *d, const char *path,
+
105 char *buf, size_t size)
+
106{
+
107 const char *l = buf;
+
108 size_t llen;
+
109 char *s;
+
110 int dotdots;
+
111 int i;
+
112
+
113 if (l[0] != '/' || d->base[0] != '/')
+
114 return;
+
115
+
116 strip_common(&l, &path);
+
117 if (l - buf < (long) d->baselen)
+
118 return;
+
119
+
120 dotdots = count_components(path);
+
121 if (!dotdots)
+
122 return;
+
123 dotdots--;
+
124
+
125 llen = strlen(l);
+
126 if (dotdots * 3 + llen + 2 > size)
+
127 return;
+
128
+
129 s = buf + dotdots * 3;
+
130 if (llen)
+
131 memmove(s, l, llen + 1);
+
132 else if (!dotdots)
+
133 strcpy(s, ".");
+
134 else
+
135 *s = '\0';
+
136
+
137 for (s = buf, i = 0; i < dotdots; i++, s += 3)
+
138 memcpy(s, "../", 3);
+
139}
+
140
+
141
+
142static int subdir_readlink(const char *path, char *buf, size_t size)
+
143{
+
144 struct subdir *d = subdir_get();
+
145 char *newpath;
+
146 int err = subdir_addpath(d, path, &newpath);
+
147 if (!err) {
+
148 err = fuse_fs_readlink(d->next, newpath, buf, size);
+
149 if (!err && d->rellinks)
+
150 transform_symlink(d, newpath, buf, size);
+
151 free(newpath);
+
152 }
+
153 return err;
+
154}
+
155
+
156static int subdir_opendir(const char *path, struct fuse_file_info *fi)
+
157{
+
158 struct subdir *d = subdir_get();
+
159 char *newpath;
+
160 int err = subdir_addpath(d, path, &newpath);
+
161 if (!err) {
+
162 err = fuse_fs_opendir(d->next, newpath, fi);
+
163 free(newpath);
+
164 }
+
165 return err;
+
166}
+
167
+
168static int subdir_readdir(const char *path, void *buf,
+
169 fuse_fill_dir_t filler, off_t offset,
+
170 struct fuse_file_info *fi,
+
171 enum fuse_readdir_flags flags)
+
172{
+
173 struct subdir *d = subdir_get();
+
174 char *newpath;
+
175 int err = subdir_addpath(d, path, &newpath);
+
176 if (!err) {
+
177 err = fuse_fs_readdir(d->next, newpath, buf, filler, offset,
+
178 fi, flags);
+
179 free(newpath);
+
180 }
+
181 return err;
+
182}
+
183
+
184static int subdir_releasedir(const char *path, struct fuse_file_info *fi)
+
185{
+
186 struct subdir *d = subdir_get();
+
187 char *newpath;
+
188 int err = subdir_addpath(d, path, &newpath);
+
189 if (!err) {
+
190 err = fuse_fs_releasedir(d->next, newpath, fi);
+
191 free(newpath);
+
192 }
+
193 return err;
+
194}
+
195
+
196static int subdir_mknod(const char *path, mode_t mode, dev_t rdev)
+
197{
+
198 struct subdir *d = subdir_get();
+
199 char *newpath;
+
200 int err = subdir_addpath(d, path, &newpath);
+
201 if (!err) {
+
202 err = fuse_fs_mknod(d->next, newpath, mode, rdev);
+
203 free(newpath);
+
204 }
+
205 return err;
+
206}
+
207
+
208static int subdir_mkdir(const char *path, mode_t mode)
+
209{
+
210 struct subdir *d = subdir_get();
+
211 char *newpath;
+
212 int err = subdir_addpath(d, path, &newpath);
+
213 if (!err) {
+
214 err = fuse_fs_mkdir(d->next, newpath, mode);
+
215 free(newpath);
+
216 }
+
217 return err;
+
218}
+
219
+
220static int subdir_unlink(const char *path)
+
221{
+
222 struct subdir *d = subdir_get();
+
223 char *newpath;
+
224 int err = subdir_addpath(d, path, &newpath);
+
225 if (!err) {
+
226 err = fuse_fs_unlink(d->next, newpath);
+
227 free(newpath);
+
228 }
+
229 return err;
+
230}
+
231
+
232static int subdir_rmdir(const char *path)
+
233{
+
234 struct subdir *d = subdir_get();
+
235 char *newpath;
+
236 int err = subdir_addpath(d, path, &newpath);
+
237 if (!err) {
+
238 err = fuse_fs_rmdir(d->next, newpath);
+
239 free(newpath);
+
240 }
+
241 return err;
+
242}
+
243
+
244static int subdir_symlink(const char *from, const char *path)
+
245{
+
246 struct subdir *d = subdir_get();
+
247 char *newpath;
+
248 int err = subdir_addpath(d, path, &newpath);
+
249 if (!err) {
+
250 err = fuse_fs_symlink(d->next, from, newpath);
+
251 free(newpath);
+
252 }
+
253 return err;
+
254}
+
255
+
256static int subdir_rename(const char *from, const char *to, unsigned int flags)
+
257{
+
258 struct subdir *d = subdir_get();
+
259 char *newfrom;
+
260 char *newto;
+
261 int err = subdir_addpath(d, from, &newfrom);
+
262 if (!err) {
+
263 err = subdir_addpath(d, to, &newto);
+
264 if (!err) {
+
265 err = fuse_fs_rename(d->next, newfrom, newto, flags);
+
266 free(newto);
+
267 }
+
268 free(newfrom);
+
269 }
+
270 return err;
+
271}
+
272
+
273static int subdir_link(const char *from, const char *to)
+
274{
+
275 struct subdir *d = subdir_get();
+
276 char *newfrom;
+
277 char *newto;
+
278 int err = subdir_addpath(d, from, &newfrom);
+
279 if (!err) {
+
280 err = subdir_addpath(d, to, &newto);
+
281 if (!err) {
+
282 err = fuse_fs_link(d->next, newfrom, newto);
+
283 free(newto);
+
284 }
+
285 free(newfrom);
+
286 }
+
287 return err;
+
288}
+
289
+
290static int subdir_chmod(const char *path, mode_t mode,
+
291 struct fuse_file_info *fi)
+
292{
+
293 struct subdir *d = subdir_get();
+
294 char *newpath;
+
295 int err = subdir_addpath(d, path, &newpath);
+
296 if (!err) {
+
297 err = fuse_fs_chmod(d->next, newpath, mode, fi);
+
298 free(newpath);
+
299 }
+
300 return err;
+
301}
+
302
+
303static int subdir_chown(const char *path, uid_t uid, gid_t gid,
+
304 struct fuse_file_info *fi)
+
305{
+
306 struct subdir *d = subdir_get();
+
307 char *newpath;
+
308 int err = subdir_addpath(d, path, &newpath);
+
309 if (!err) {
+
310 err = fuse_fs_chown(d->next, newpath, uid, gid, fi);
+
311 free(newpath);
+
312 }
+
313 return err;
+
314}
+
315
+
316static int subdir_truncate(const char *path, off_t size,
+
317 struct fuse_file_info *fi)
+
318{
+
319 struct subdir *d = subdir_get();
+
320 char *newpath;
+
321 int err = subdir_addpath(d, path, &newpath);
+
322 if (!err) {
+
323 err = fuse_fs_truncate(d->next, newpath, size, fi);
+
324 free(newpath);
+
325 }
+
326 return err;
+
327}
+
328
+
329static int subdir_utimens(const char *path, const struct timespec ts[2],
+
330 struct fuse_file_info *fi)
+
331{
+
332 struct subdir *d = subdir_get();
+
333 char *newpath;
+
334 int err = subdir_addpath(d, path, &newpath);
+
335 if (!err) {
+
336 err = fuse_fs_utimens(d->next, newpath, ts, fi);
+
337 free(newpath);
+
338 }
+
339 return err;
+
340}
+
341
+
342static int subdir_create(const char *path, mode_t mode,
+
343 struct fuse_file_info *fi)
+
344{
+
345 struct subdir *d = subdir_get();
+
346 char *newpath;
+
347 int err = subdir_addpath(d, path, &newpath);
+
348 if (!err) {
+
349 err = fuse_fs_create(d->next, newpath, mode, fi);
+
350 free(newpath);
+
351 }
+
352 return err;
+
353}
+
354
+
355static int subdir_open(const char *path, struct fuse_file_info *fi)
+
356{
+
357 struct subdir *d = subdir_get();
+
358 char *newpath;
+
359 int err = subdir_addpath(d, path, &newpath);
+
360 if (!err) {
+
361 err = fuse_fs_open(d->next, newpath, fi);
+
362 free(newpath);
+
363 }
+
364 return err;
+
365}
+
366
+
367static int subdir_read_buf(const char *path, struct fuse_bufvec **bufp,
+
368 size_t size, off_t offset, struct fuse_file_info *fi)
+
369{
+
370 struct subdir *d = subdir_get();
+
371 char *newpath;
+
372 int err = subdir_addpath(d, path, &newpath);
+
373 if (!err) {
+
374 err = fuse_fs_read_buf(d->next, newpath, bufp, size, offset, fi);
+
375 free(newpath);
+
376 }
+
377 return err;
+
378}
+
379
+
380static int subdir_write_buf(const char *path, struct fuse_bufvec *buf,
+
381 off_t offset, struct fuse_file_info *fi)
+
382{
+
383 struct subdir *d = subdir_get();
+
384 char *newpath;
+
385 int err = subdir_addpath(d, path, &newpath);
+
386 if (!err) {
+
387 err = fuse_fs_write_buf(d->next, newpath, buf, offset, fi);
+
388 free(newpath);
+
389 }
+
390 return err;
+
391}
+
392
+
393static int subdir_statfs(const char *path, struct statvfs *stbuf)
+
394{
+
395 struct subdir *d = subdir_get();
+
396 char *newpath;
+
397 int err = subdir_addpath(d, path, &newpath);
+
398 if (!err) {
+
399 err = fuse_fs_statfs(d->next, newpath, stbuf);
+
400 free(newpath);
+
401 }
+
402 return err;
+
403}
+
404
+
405static int subdir_flush(const char *path, struct fuse_file_info *fi)
+
406{
+
407 struct subdir *d = subdir_get();
+
408 char *newpath;
+
409 int err = subdir_addpath(d, path, &newpath);
+
410 if (!err) {
+
411 err = fuse_fs_flush(d->next, newpath, fi);
+
412 free(newpath);
+
413 }
+
414 return err;
+
415}
+
416
+
417static int subdir_release(const char *path, struct fuse_file_info *fi)
+
418{
+
419 struct subdir *d = subdir_get();
+
420 char *newpath;
+
421 int err = subdir_addpath(d, path, &newpath);
+
422 if (!err) {
+
423 err = fuse_fs_release(d->next, newpath, fi);
+
424 free(newpath);
+
425 }
+
426 return err;
+
427}
+
428
+
429static int subdir_fsync(const char *path, int isdatasync,
+
430 struct fuse_file_info *fi)
+
431{
+
432 struct subdir *d = subdir_get();
+
433 char *newpath;
+
434 int err = subdir_addpath(d, path, &newpath);
+
435 if (!err) {
+
436 err = fuse_fs_fsync(d->next, newpath, isdatasync, fi);
+
437 free(newpath);
+
438 }
+
439 return err;
+
440}
+
441
+
442static int subdir_fsyncdir(const char *path, int isdatasync,
+
443 struct fuse_file_info *fi)
+
444{
+
445 struct subdir *d = subdir_get();
+
446 char *newpath;
+
447 int err = subdir_addpath(d, path, &newpath);
+
448 if (!err) {
+
449 err = fuse_fs_fsyncdir(d->next, newpath, isdatasync, fi);
+
450 free(newpath);
+
451 }
+
452 return err;
+
453}
+
454
+
455static int subdir_setxattr(const char *path, const char *name,
+
456 const char *value, size_t size, int flags)
+
457{
+
458 struct subdir *d = subdir_get();
+
459 char *newpath;
+
460 int err = subdir_addpath(d, path, &newpath);
+
461 if (!err) {
+
462 err = fuse_fs_setxattr(d->next, newpath, name, value, size,
+
463 flags);
+
464 free(newpath);
+
465 }
+
466 return err;
+
467}
+
468
+
469static int subdir_getxattr(const char *path, const char *name, char *value,
+
470 size_t size)
+
471{
+
472 struct subdir *d = subdir_get();
+
473 char *newpath;
+
474 int err = subdir_addpath(d, path, &newpath);
+
475 if (!err) {
+
476 err = fuse_fs_getxattr(d->next, newpath, name, value, size);
+
477 free(newpath);
+
478 }
+
479 return err;
+
480}
+
481
+
482static int subdir_listxattr(const char *path, char *list, size_t size)
+
483{
+
484 struct subdir *d = subdir_get();
+
485 char *newpath;
+
486 int err = subdir_addpath(d, path, &newpath);
+
487 if (!err) {
+
488 err = fuse_fs_listxattr(d->next, newpath, list, size);
+
489 free(newpath);
+
490 }
+
491 return err;
+
492}
+
493
+
494static int subdir_removexattr(const char *path, const char *name)
+
495{
+
496 struct subdir *d = subdir_get();
+
497 char *newpath;
+
498 int err = subdir_addpath(d, path, &newpath);
+
499 if (!err) {
+
500 err = fuse_fs_removexattr(d->next, newpath, name);
+
501 free(newpath);
+
502 }
+
503 return err;
+
504}
+
505
+
506static int subdir_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
507 struct flock *lock)
+
508{
+
509 struct subdir *d = subdir_get();
+
510 char *newpath;
+
511 int err = subdir_addpath(d, path, &newpath);
+
512 if (!err) {
+
513 err = fuse_fs_lock(d->next, newpath, fi, cmd, lock);
+
514 free(newpath);
+
515 }
+
516 return err;
+
517}
+
518
+
519static int subdir_flock(const char *path, struct fuse_file_info *fi, int op)
+
520{
+
521 struct subdir *d = subdir_get();
+
522 char *newpath;
+
523 int err = subdir_addpath(d, path, &newpath);
+
524 if (!err) {
+
525 err = fuse_fs_flock(d->next, newpath, fi, op);
+
526 free(newpath);
+
527 }
+
528 return err;
+
529}
+
530
+
531static int subdir_bmap(const char *path, size_t blocksize, uint64_t *idx)
+
532{
+
533 struct subdir *d = subdir_get();
+
534 char *newpath;
+
535 int err = subdir_addpath(d, path, &newpath);
+
536 if (!err) {
+
537 err = fuse_fs_bmap(d->next, newpath, blocksize, idx);
+
538 free(newpath);
+
539 }
+
540 return err;
+
541}
+
542
+
543static off_t subdir_lseek(const char *path, off_t off, int whence,
+
544 struct fuse_file_info *fi)
+
545{
+
546 struct subdir *ic = subdir_get();
+
547 char *newpath;
+
548 int res = subdir_addpath(ic, path, &newpath);
+
549 if (!res) {
+
550 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
+
551 free(newpath);
+
552 }
+
553 return res;
+
554}
+
555
+
556static void *subdir_init(struct fuse_conn_info *conn,
+
557 struct fuse_config *cfg)
+
558{
+
559 struct subdir *d = subdir_get();
+
560 fuse_fs_init(d->next, conn, cfg);
+
561 /* Don't touch cfg->nullpath_ok, we can work with
+
562 either */
+
563 return d;
+
564}
+
565
+
566static void subdir_destroy(void *data)
+
567{
+
568 struct subdir *d = data;
+
569 fuse_fs_destroy(d->next);
+
570 free(d->base);
+
571 free(d);
+
572}
+
573
+
574static const struct fuse_operations subdir_oper = {
+
575 .destroy = subdir_destroy,
+
576 .init = subdir_init,
+
577 .getattr = subdir_getattr,
+
578 .access = subdir_access,
+
579 .readlink = subdir_readlink,
+
580 .opendir = subdir_opendir,
+
581 .readdir = subdir_readdir,
+
582 .releasedir = subdir_releasedir,
+
583 .mknod = subdir_mknod,
+
584 .mkdir = subdir_mkdir,
+
585 .symlink = subdir_symlink,
+
586 .unlink = subdir_unlink,
+
587 .rmdir = subdir_rmdir,
+
588 .rename = subdir_rename,
+
589 .link = subdir_link,
+
590 .chmod = subdir_chmod,
+
591 .chown = subdir_chown,
+
592 .truncate = subdir_truncate,
+
593 .utimens = subdir_utimens,
+
594 .create = subdir_create,
+
595 .open = subdir_open,
+
596 .read_buf = subdir_read_buf,
+
597 .write_buf = subdir_write_buf,
+
598 .statfs = subdir_statfs,
+
599 .flush = subdir_flush,
+
600 .release = subdir_release,
+
601 .fsync = subdir_fsync,
+
602 .fsyncdir = subdir_fsyncdir,
+
603 .setxattr = subdir_setxattr,
+
604 .getxattr = subdir_getxattr,
+
605 .listxattr = subdir_listxattr,
+
606 .removexattr = subdir_removexattr,
+
607 .lock = subdir_lock,
+
608 .flock = subdir_flock,
+
609 .bmap = subdir_bmap,
+
610 .lseek = subdir_lseek,
+
611};
+
612
+
613static const struct fuse_opt subdir_opts[] = {
+
614 FUSE_OPT_KEY("-h", 0),
+
615 FUSE_OPT_KEY("--help", 0),
+
616 { "subdir=%s", offsetof(struct subdir, base), 0 },
+
617 { "rellinks", offsetof(struct subdir, rellinks), 1 },
+
618 { "norellinks", offsetof(struct subdir, rellinks), 0 },
+ +
620};
+
621
+
622static void subdir_help(void)
+
623{
+
624 printf(
+
625" -o subdir=DIR prepend this directory to all paths (mandatory)\n"
+
626" -o [no]rellinks transform absolute symlinks to relative\n");
+
627}
+
628
+
629static int subdir_opt_proc(void *data, const char *arg, int key,
+
630 struct fuse_args *outargs)
+
631{
+
632 (void) data; (void) arg; (void) outargs;
+
633
+
634 if (!key) {
+
635 subdir_help();
+
636 return -1;
+
637 }
+
638
+
639 return 1;
+
640}
+
641
+
642static struct fuse_fs *subdir_new(struct fuse_args *args,
+
643 struct fuse_fs *next[])
+
644{
+
645 struct fuse_fs *fs;
+
646 struct subdir *d;
+
647
+
648 d = calloc(1, sizeof(struct subdir));
+
649 if (d == NULL) {
+
650 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
+
651 return NULL;
+
652 }
+
653
+
654 if (fuse_opt_parse(args, d, subdir_opts, subdir_opt_proc) == -1)
+
655 goto out_free;
+
656
+
657 if (!next[0] || next[1]) {
+
658 fuse_log(FUSE_LOG_ERR, "fuse-subdir: exactly one next filesystem required\n");
+
659 goto out_free;
+
660 }
+
661
+
662 if (!d->base) {
+
663 fuse_log(FUSE_LOG_ERR, "fuse-subdir: missing 'subdir' option\n");
+
664 goto out_free;
+
665 }
+
666
+
667 if (d->base[0] && d->base[strlen(d->base)-1] != '/') {
+
668 char *tmp = realloc(d->base, strlen(d->base) + 2);
+
669 if (!tmp) {
+
670 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
+
671 goto out_free;
+
672 }
+
673 d->base = tmp;
+
674 strcat(d->base, "/");
+
675 }
+
676 d->baselen = strlen(d->base);
+
677 d->next = next[0];
+
678 fs = fuse_fs_new(&subdir_oper, sizeof(subdir_oper), d);
+
679 if (!fs)
+
680 goto out_free;
+
681 return fs;
+
682
+
683out_free:
+
684 free(d->base);
+
685 free(d);
+
686 return NULL;
+
687}
+
688
+
689FUSE_REGISTER_MODULE(subdir, subdir_new);
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_REGISTER_MODULE(name_, factory_)
Definition fuse.h:1395
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + + +
void * private_data
Definition fuse.h:874
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+ +
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2mount_8c_source.html b/doc/html/fuse-3_817_84_2lib_2mount_8c_source.html new file mode 100644 index 0000000..8ff6612 --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2mount_8c_source.html @@ -0,0 +1,792 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/mount.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Architecture specific file system mounting (Linux).
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11/* For environ */
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_misc.h"
+
17#include "fuse_opt.h"
+
18#include "mount_util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <unistd.h>
+
23#include <stddef.h>
+
24#include <string.h>
+
25#include <fcntl.h>
+
26#include <errno.h>
+
27#include <poll.h>
+
28#include <spawn.h>
+
29#include <sys/socket.h>
+
30#include <sys/un.h>
+
31#include <sys/wait.h>
+
32
+
33#include "fuse_mount_compat.h"
+
34
+
35#ifdef __NetBSD__
+
36#include <perfuse.h>
+
37
+
38#define MS_RDONLY MNT_RDONLY
+
39#define MS_NOSUID MNT_NOSUID
+
40#define MS_NODEV MNT_NODEV
+
41#define MS_NOEXEC MNT_NOEXEC
+
42#define MS_SYNCHRONOUS MNT_SYNCHRONOUS
+
43#define MS_NOATIME MNT_NOATIME
+
44#define MS_NOSYMFOLLOW MNT_NOSYMFOLLOW
+
45
+
46#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
+
47#endif
+
48
+
49#define FUSERMOUNT_PROG "fusermount3"
+
50#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
+
51#define FUSE_COMMFD2_ENV "_FUSE_COMMFD2"
+
52
+
53#ifndef MS_DIRSYNC
+
54#define MS_DIRSYNC 128
+
55#endif
+
56
+
57enum {
+
58 KEY_KERN_FLAG,
+
59 KEY_KERN_OPT,
+
60 KEY_FUSERMOUNT_OPT,
+
61 KEY_SUBTYPE_OPT,
+
62 KEY_MTAB_OPT,
+
63 KEY_ALLOW_OTHER,
+
64 KEY_RO,
+
65};
+
66
+
67struct mount_opts {
+
68 int allow_other;
+
69 int flags;
+
70 int auto_unmount;
+
71 int blkdev;
+
72 char *fsname;
+
73 char *subtype;
+
74 char *subtype_opt;
+
75 char *mtab_opts;
+
76 char *fusermount_opts;
+
77 char *kernel_opts;
+
78 unsigned max_read;
+
79};
+
80
+
81#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 }
+
82
+
83static const struct fuse_opt fuse_mount_opts[] = {
+
84 FUSE_MOUNT_OPT("allow_other", allow_other),
+
85 FUSE_MOUNT_OPT("blkdev", blkdev),
+
86 FUSE_MOUNT_OPT("auto_unmount", auto_unmount),
+
87 FUSE_MOUNT_OPT("fsname=%s", fsname),
+
88 FUSE_MOUNT_OPT("max_read=%u", max_read),
+
89 FUSE_MOUNT_OPT("subtype=%s", subtype),
+
90 FUSE_OPT_KEY("allow_other", KEY_KERN_OPT),
+
91 FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT),
+
92 FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT),
+
93 FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT),
+
94 FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT),
+
95 FUSE_OPT_KEY("blksize=", KEY_KERN_OPT),
+
96 FUSE_OPT_KEY("default_permissions", KEY_KERN_OPT),
+
97 FUSE_OPT_KEY("context=", KEY_KERN_OPT),
+
98 FUSE_OPT_KEY("fscontext=", KEY_KERN_OPT),
+
99 FUSE_OPT_KEY("defcontext=", KEY_KERN_OPT),
+
100 FUSE_OPT_KEY("rootcontext=", KEY_KERN_OPT),
+
101 FUSE_OPT_KEY("max_read=", KEY_KERN_OPT),
+
102 FUSE_OPT_KEY("user=", KEY_MTAB_OPT),
+
103 FUSE_OPT_KEY("-n", KEY_MTAB_OPT),
+
104 FUSE_OPT_KEY("-r", KEY_RO),
+
105 FUSE_OPT_KEY("ro", KEY_KERN_FLAG),
+
106 FUSE_OPT_KEY("rw", KEY_KERN_FLAG),
+
107 FUSE_OPT_KEY("suid", KEY_KERN_FLAG),
+
108 FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG),
+
109 FUSE_OPT_KEY("dev", KEY_KERN_FLAG),
+
110 FUSE_OPT_KEY("nodev", KEY_KERN_FLAG),
+
111 FUSE_OPT_KEY("exec", KEY_KERN_FLAG),
+
112 FUSE_OPT_KEY("noexec", KEY_KERN_FLAG),
+
113 FUSE_OPT_KEY("async", KEY_KERN_FLAG),
+
114 FUSE_OPT_KEY("sync", KEY_KERN_FLAG),
+
115 FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG),
+
116 FUSE_OPT_KEY("noatime", KEY_KERN_FLAG),
+
117 FUSE_OPT_KEY("nodiratime", KEY_KERN_FLAG),
+
118 FUSE_OPT_KEY("nostrictatime", KEY_KERN_FLAG),
+
119 FUSE_OPT_KEY("symfollow", KEY_KERN_FLAG),
+
120 FUSE_OPT_KEY("nosymfollow", KEY_KERN_FLAG),
+ +
122};
+
123
+
124/*
+
125 * Running fusermount by calling 'posix_spawn'
+
126 *
+
127 * @param out_pid might be NULL
+
128 */
+
129static int fusermount_posix_spawn(posix_spawn_file_actions_t *action,
+
130 char const * const argv[], pid_t *out_pid)
+
131{
+
132 const char *full_path = FUSERMOUNT_DIR "/" FUSERMOUNT_PROG;
+
133 pid_t pid;
+
134
+
135 /* See man 7 environ for the global environ pointer */
+
136
+
137 /* first try the install path */
+
138 int status = posix_spawn(&pid, full_path, action, NULL,
+
139 (char * const *) argv, environ);
+
140 if (status != 0) {
+
141 /* if that fails, try a system install */
+
142 status = posix_spawnp(&pid, FUSERMOUNT_PROG, action, NULL,
+
143 (char * const *) argv, environ);
+
144 }
+
145
+
146 if (status != 0) {
+
147 fuse_log(FUSE_LOG_ERR,
+
148 "On calling fusermount posix_spawn failed: %s\n",
+
149 strerror(status));
+
150 return -status;
+
151 }
+
152
+
153 if (out_pid)
+
154 *out_pid = pid;
+
155 else
+
156 waitpid(pid, NULL, 0); /* FIXME: check exit code and return error if any */
+
157
+
158 return 0;
+
159}
+
160
+
161void fuse_mount_version(void)
+
162{
+
163 char const *const argv[] = {FUSERMOUNT_PROG, "--version", NULL};
+
164 int status = fusermount_posix_spawn(NULL, argv, NULL);
+
165
+
166 if(status != 0)
+
167 fuse_log(FUSE_LOG_ERR, "Running '%s --version' failed",
+
168 FUSERMOUNT_PROG);
+
169}
+
170
+
171struct mount_flags {
+
172 const char *opt;
+
173 unsigned long flag;
+
174 int on;
+
175};
+
176
+
177static const struct mount_flags mount_flags[] = {
+
178 {"rw", MS_RDONLY, 0},
+
179 {"ro", MS_RDONLY, 1},
+
180 {"suid", MS_NOSUID, 0},
+
181 {"nosuid", MS_NOSUID, 1},
+
182 {"dev", MS_NODEV, 0},
+
183 {"nodev", MS_NODEV, 1},
+
184 {"exec", MS_NOEXEC, 0},
+
185 {"noexec", MS_NOEXEC, 1},
+
186 {"async", MS_SYNCHRONOUS, 0},
+
187 {"sync", MS_SYNCHRONOUS, 1},
+
188 {"noatime", MS_NOATIME, 1},
+
189 {"nodiratime", MS_NODIRATIME, 1},
+
190 {"norelatime", MS_RELATIME, 0},
+
191 {"nostrictatime", MS_STRICTATIME, 0},
+
192 {"symfollow", MS_NOSYMFOLLOW, 0},
+
193 {"nosymfollow", MS_NOSYMFOLLOW, 1},
+
194#ifndef __NetBSD__
+
195 {"dirsync", MS_DIRSYNC, 1},
+
196#endif
+
197 {NULL, 0, 0}
+
198};
+
199
+
200unsigned get_max_read(struct mount_opts *o)
+
201{
+
202 return o->max_read;
+
203}
+
204
+
205static void set_mount_flag(const char *s, int *flags)
+
206{
+
207 int i;
+
208
+
209 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
210 const char *opt = mount_flags[i].opt;
+
211 if (strcmp(opt, s) == 0) {
+
212 if (mount_flags[i].on)
+
213 *flags |= mount_flags[i].flag;
+
214 else
+
215 *flags &= ~mount_flags[i].flag;
+
216 return;
+
217 }
+
218 }
+
219 fuse_log(FUSE_LOG_ERR, "fuse: internal error, can't find mount flag\n");
+
220 abort();
+
221}
+
222
+
223static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+
224 struct fuse_args *outargs)
+
225{
+
226 (void) outargs;
+
227 struct mount_opts *mo = data;
+
228
+
229 switch (key) {
+
230 case KEY_RO:
+
231 arg = "ro";
+
232 /* fall through */
+
233 case KEY_KERN_FLAG:
+
234 set_mount_flag(arg, &mo->flags);
+
235 return 0;
+
236
+
237 case KEY_KERN_OPT:
+
238 return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
239
+
240 case KEY_FUSERMOUNT_OPT:
+
241 return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg);
+
242
+
243 case KEY_SUBTYPE_OPT:
+
244 return fuse_opt_add_opt(&mo->subtype_opt, arg);
+
245
+
246 case KEY_MTAB_OPT:
+
247 return fuse_opt_add_opt(&mo->mtab_opts, arg);
+
248
+
249 /* Third party options like 'x-gvfs-notrash' */
+
250 case FUSE_OPT_KEY_OPT:
+
251 return (strncmp("x-", arg, 2) == 0) ?
+
252 fuse_opt_add_opt(&mo->mtab_opts, arg) :
+
253 1;
+
254 }
+
255
+
256 /* Pass through unknown options */
+
257 return 1;
+
258}
+
259
+
260/* return value:
+
261 * >= 0 => fd
+
262 * -1 => error
+
263 */
+
264static int receive_fd(int fd)
+
265{
+
266 struct msghdr msg;
+
267 struct iovec iov;
+
268 char buf[1];
+
269 int rv;
+
270 size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)];
+
271 struct cmsghdr *cmsg;
+
272
+
273 iov.iov_base = buf;
+
274 iov.iov_len = 1;
+
275
+
276 memset(&msg, 0, sizeof(msg));
+
277 msg.msg_name = 0;
+
278 msg.msg_namelen = 0;
+
279 msg.msg_iov = &iov;
+
280 msg.msg_iovlen = 1;
+
281 /* old BSD implementations should use msg_accrights instead of
+
282 * msg_control; the interface is different. */
+
283 msg.msg_control = ccmsg;
+
284 msg.msg_controllen = sizeof(ccmsg);
+
285
+
286 while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR);
+
287 if (rv == -1) {
+
288 fuse_log(FUSE_LOG_ERR, "recvmsg failed: %s", strerror(errno));
+
289 return -1;
+
290 }
+
291 if(!rv) {
+
292 /* EOF */
+
293 return -1;
+
294 }
+
295
+
296 cmsg = CMSG_FIRSTHDR(&msg);
+
297 if (cmsg->cmsg_type != SCM_RIGHTS) {
+
298 fuse_log(FUSE_LOG_ERR, "got control message of unknown type %d\n",
+
299 cmsg->cmsg_type);
+
300 return -1;
+
301 }
+
302 return *(int*)CMSG_DATA(cmsg);
+
303}
+
304
+
305void fuse_kern_unmount(const char *mountpoint, int fd)
+
306{
+
307 int res;
+
308
+
309 if (fd != -1) {
+
310 struct pollfd pfd;
+
311
+
312 pfd.fd = fd;
+
313 pfd.events = 0;
+
314 res = poll(&pfd, 1, 0);
+
315
+
316 /* Need to close file descriptor, otherwise synchronous umount
+
317 would recurse into filesystem, and deadlock.
+
318
+
319 Caller expects fuse_kern_unmount to close the fd, so close it
+
320 anyway. */
+
321 close(fd);
+
322
+
323 /* If file poll returns POLLERR on the device file descriptor,
+
324 then the filesystem is already unmounted or the connection
+
325 was severed via /sys/fs/fuse/connections/NNN/abort */
+
326 if (res == 1 && (pfd.revents & POLLERR))
+
327 return;
+
328 }
+
329
+
330 if (geteuid() == 0) {
+
331 fuse_mnt_umount("fuse", mountpoint, mountpoint, 1);
+
332 return;
+
333 }
+
334
+
335 res = umount2(mountpoint, 2);
+
336 if (res == 0)
+
337 return;
+
338
+
339 char const * const argv[] =
+
340 { FUSERMOUNT_PROG, "--unmount", "--quiet", "--lazy",
+
341 "--", mountpoint, NULL };
+
342 int status = fusermount_posix_spawn(NULL, argv, NULL);
+
343 if(status != 0) {
+
344 fuse_log(FUSE_LOG_ERR, "Spawning %s to unmount failed: %s",
+
345 FUSERMOUNT_PROG, strerror(-status));
+
346 return;
+
347 }
+
348}
+
349
+
350static int setup_auto_unmount(const char *mountpoint, int quiet)
+
351{
+
352 int fds[2];
+
353 pid_t pid;
+
354 int res;
+
355
+
356 if (!mountpoint) {
+
357 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
358 return -1;
+
359 }
+
360
+
361 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+
362 if(res == -1) {
+
363 fuse_log(FUSE_LOG_ERR, "Setting up auto-unmountsocketpair() failed",
+
364 strerror(errno));
+
365 return -1;
+
366 }
+
367
+
368 char arg_fd_entry[30];
+
369 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
+
370 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
+
371 /*
+
372 * This helps to identify the FD hold by parent process.
+
373 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
+
374 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
+
375 * One potential use case is to satisfy FD-Leak checks.
+
376 */
+
377 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
+
378 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
+
379
+
380 char const *const argv[] = {
+
381 FUSERMOUNT_PROG,
+
382 "--auto-unmount",
+
383 "--",
+
384 mountpoint,
+
385 NULL,
+
386 };
+
387
+
388 // TODO: add error handling for all manipulations of action.
+
389 posix_spawn_file_actions_t action;
+
390 posix_spawn_file_actions_init(&action);
+
391
+
392 if (quiet) {
+
393 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
+
394 posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
+
395 }
+
396 posix_spawn_file_actions_addclose(&action, fds[1]);
+
397
+
398 /*
+
399 * auto-umount runs in the background - it is not waiting for the
+
400 * process
+
401 */
+
402 int status = fusermount_posix_spawn(&action, argv, &pid);
+
403
+
404 posix_spawn_file_actions_destroy(&action);
+
405
+
406 if(status != 0) {
+
407 close(fds[0]);
+
408 close(fds[1]);
+
409 fuse_log(FUSE_LOG_ERR, "fuse: Setting up auto-unmount failed (spawn): %s",
+
410 strerror(-status));
+
411 return -1;
+
412 }
+
413 // passed to child now, so can close here.
+
414 close(fds[0]);
+
415
+
416 // Now fusermount3 will only exit when fds[1] closes automatically when our
+
417 // process exits.
+
418 return 0;
+
419 // Note: fds[1] is leakend and doesn't get FD_CLOEXEC
+
420}
+
421
+
422static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
+
423 const char *opts, int quiet)
+
424{
+
425 int fds[2];
+
426 pid_t pid;
+
427 int res;
+
428
+
429 if (!mountpoint) {
+
430 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
431 return -1;
+
432 }
+
433
+
434 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+
435 if(res == -1) {
+
436 fuse_log(FUSE_LOG_ERR, "Running %s: socketpair() failed: %s\n",
+
437 FUSERMOUNT_PROG, strerror(errno));
+
438 return -1;
+
439 }
+
440
+
441 char arg_fd_entry[30];
+
442 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
+
443 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
+
444 /*
+
445 * This helps to identify the FD hold by parent process.
+
446 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
+
447 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
+
448 * One potential use case is to satisfy FD-Leak checks.
+
449 */
+
450 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
+
451 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
+
452
+
453 char const *const argv[] = {
+
454 FUSERMOUNT_PROG,
+
455 "-o", opts ? opts : "",
+
456 "--",
+
457 mountpoint,
+
458 NULL,
+
459 };
+
460
+
461
+
462 posix_spawn_file_actions_t action;
+
463 posix_spawn_file_actions_init(&action);
+
464
+
465 if (quiet) {
+
466 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
+
467 posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
+
468 }
+
469 posix_spawn_file_actions_addclose(&action, fds[1]);
+
470
+
471 int status = fusermount_posix_spawn(&action, argv, &pid);
+
472
+
473 posix_spawn_file_actions_destroy(&action);
+
474
+
475 if(status != 0) {
+
476 close(fds[0]);
+
477 close(fds[1]);
+
478 fuse_log(FUSE_LOG_ERR, "posix_spawn(p)() for %s failed: %s",
+
479 FUSERMOUNT_PROG, strerror(-status));
+
480 return -1;
+
481 }
+
482
+
483 // passed to child now, so can close here.
+
484 close(fds[0]);
+
485
+
486 int fd = receive_fd(fds[1]);
+
487
+
488 if (!mo->auto_unmount) {
+
489 /* with auto_unmount option fusermount3 will not exit until
+
490 this socket is closed */
+
491 close(fds[1]);
+
492 waitpid(pid, NULL, 0); /* bury zombie */
+
493 }
+
494
+
495 if (fd >= 0)
+
496 fcntl(fd, F_SETFD, FD_CLOEXEC);
+
497
+
498 return fd;
+
499}
+
500
+
501#ifndef O_CLOEXEC
+
502#define O_CLOEXEC 0
+
503#endif
+
504
+
505static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
+
506 const char *mnt_opts)
+
507{
+
508 char tmp[128];
+
509 const char *devname = "/dev/fuse";
+
510 char *source = NULL;
+
511 char *type = NULL;
+
512 struct stat stbuf;
+
513 int fd;
+
514 int res;
+
515
+
516 if (!mnt) {
+
517 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
518 return -1;
+
519 }
+
520
+
521 res = stat(mnt, &stbuf);
+
522 if (res == -1) {
+
523 fuse_log(FUSE_LOG_ERR, "fuse: failed to access mountpoint %s: %s\n",
+
524 mnt, strerror(errno));
+
525 return -1;
+
526 }
+
527
+
528 fd = open(devname, O_RDWR | O_CLOEXEC);
+
529 if (fd == -1) {
+
530 if (errno == ENODEV || errno == ENOENT)
+
531 fuse_log(FUSE_LOG_ERR, "fuse: device not found, try 'modprobe fuse' first\n");
+
532 else
+
533 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n",
+
534 devname, strerror(errno));
+
535 return -1;
+
536 }
+
537 if (!O_CLOEXEC)
+
538 fcntl(fd, F_SETFD, FD_CLOEXEC);
+
539
+
540 snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+
541 fd, stbuf.st_mode & S_IFMT, getuid(), getgid());
+
542
+
543 res = fuse_opt_add_opt(&mo->kernel_opts, tmp);
+
544 if (res == -1)
+
545 goto out_close;
+
546
+
547 source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
+
548 (mo->subtype ? strlen(mo->subtype) : 0) +
+
549 strlen(devname) + 32);
+
550
+
551 type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
+
552 if (!type || !source) {
+
553 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate memory\n");
+
554 goto out_close;
+
555 }
+
556
+
557 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+
558 if (mo->subtype) {
+
559 strcat(type, ".");
+
560 strcat(type, mo->subtype);
+
561 }
+
562 strcpy(source,
+
563 mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
+
564
+
565 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+
566 if (res == -1 && errno == ENODEV && mo->subtype) {
+
567 /* Probably missing subtype support */
+
568 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+
569 if (mo->fsname) {
+
570 if (!mo->blkdev)
+
571 sprintf(source, "%s#%s", mo->subtype,
+
572 mo->fsname);
+
573 } else {
+
574 strcpy(source, type);
+
575 }
+
576 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+
577 }
+
578 if (res == -1) {
+
579 /*
+
580 * Maybe kernel doesn't support unprivileged mounts, in this
+
581 * case try falling back to fusermount3
+
582 */
+
583 if (errno == EPERM) {
+
584 res = -2;
+
585 } else {
+
586 int errno_save = errno;
+
587 if (mo->blkdev && errno == ENODEV &&
+
588 !fuse_mnt_check_fuseblk())
+
589 fuse_log(FUSE_LOG_ERR,
+
590 "fuse: 'fuseblk' support missing\n");
+
591 else
+
592 fuse_log(FUSE_LOG_ERR, "fuse: mount failed: %s\n",
+
593 strerror(errno_save));
+
594 }
+
595
+
596 goto out_close;
+
597 }
+
598
+
599#ifndef IGNORE_MTAB
+
600 if (geteuid() == 0) {
+
601 char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
+
602 res = -1;
+
603 if (!newmnt)
+
604 goto out_umount;
+
605
+
606 res = fuse_mnt_add_mount("fuse", source, newmnt, type,
+
607 mnt_opts);
+
608 free(newmnt);
+
609 if (res == -1)
+
610 goto out_umount;
+
611 }
+
612#endif /* IGNORE_MTAB */
+
613 free(type);
+
614 free(source);
+
615
+
616 return fd;
+
617
+
618out_umount:
+
619 umount2(mnt, 2); /* lazy umount */
+
620out_close:
+
621 free(type);
+
622 free(source);
+
623 close(fd);
+
624 return res;
+
625}
+
626
+
627static int get_mnt_flag_opts(char **mnt_optsp, int flags)
+
628{
+
629 int i;
+
630
+
631 if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1)
+
632 return -1;
+
633
+
634 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
635 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+
636 fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1)
+
637 return -1;
+
638 }
+
639 return 0;
+
640}
+
641
+
642struct mount_opts *parse_mount_opts(struct fuse_args *args)
+
643{
+
644 struct mount_opts *mo;
+
645
+
646 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
+
647 if (mo == NULL)
+
648 return NULL;
+
649
+
650 memset(mo, 0, sizeof(struct mount_opts));
+
651 mo->flags = MS_NOSUID | MS_NODEV;
+
652
+
653 if (args &&
+
654 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+
655 goto err_out;
+
656
+
657 return mo;
+
658
+
659err_out:
+
660 destroy_mount_opts(mo);
+
661 return NULL;
+
662}
+
663
+
664void destroy_mount_opts(struct mount_opts *mo)
+
665{
+
666 free(mo->fsname);
+
667 free(mo->subtype);
+
668 free(mo->fusermount_opts);
+
669 free(mo->subtype_opt);
+
670 free(mo->kernel_opts);
+
671 free(mo->mtab_opts);
+
672 free(mo);
+
673}
+
674
+
675
+
676int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
+
677{
+
678 int res = -1;
+
679 char *mnt_opts = NULL;
+
680
+
681 res = -1;
+
682 if (get_mnt_flag_opts(&mnt_opts, mo->flags) == -1)
+
683 goto out;
+
684 if (mo->kernel_opts && fuse_opt_add_opt(&mnt_opts, mo->kernel_opts) == -1)
+
685 goto out;
+
686 if (mo->mtab_opts && fuse_opt_add_opt(&mnt_opts, mo->mtab_opts) == -1)
+
687 goto out;
+
688
+
689 res = fuse_mount_sys(mountpoint, mo, mnt_opts);
+
690 if (res >= 0 && mo->auto_unmount) {
+
691 if(0 > setup_auto_unmount(mountpoint, 0)) {
+
692 // Something went wrong, let's umount like in fuse_mount_sys.
+
693 umount2(mountpoint, MNT_DETACH); /* lazy umount */
+
694 res = -1;
+
695 }
+
696 } else if (res == -2) {
+
697 if (mo->fusermount_opts &&
+
698 fuse_opt_add_opt(&mnt_opts, mo->fusermount_opts) == -1)
+
699 goto out;
+
700
+
701 if (mo->subtype) {
+
702 char *tmp_opts = NULL;
+
703
+
704 res = -1;
+
705 if (fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1 ||
+
706 fuse_opt_add_opt(&tmp_opts, mo->subtype_opt) == -1) {
+
707 free(tmp_opts);
+
708 goto out;
+
709 }
+
710
+
711 res = fuse_mount_fusermount(mountpoint, mo, tmp_opts, 1);
+
712 free(tmp_opts);
+
713 if (res == -1)
+
714 res = fuse_mount_fusermount(mountpoint, mo,
+
715 mnt_opts, 0);
+
716 } else {
+
717 res = fuse_mount_fusermount(mountpoint, mo, mnt_opts, 0);
+
718 }
+
719 }
+
720out:
+
721 free(mnt_opts);
+
722 return res;
+
723}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
#define FUSE_OPT_KEY_OPT
Definition fuse_opt.h:129
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2mount__bsd_8c_source.html b/doc/html/fuse-3_817_84_2lib_2mount__bsd_8c_source.html new file mode 100644 index 0000000..c3a406c --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2mount__bsd_8c_source.html @@ -0,0 +1,333 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/mount_bsd.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_bsd.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2005-2008 Csaba Henk <csaba.henk@creo.hu>
+
4
+
5 Architecture specific file system mounting (FreeBSD).
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_i.h"
+
13#include "fuse_misc.h"
+
14#include "fuse_opt.h"
+
15#include "util.h"
+
16
+
17#include <sys/param.h>
+
18#include "fuse_mount_compat.h"
+
19
+
20#include <sys/wait.h>
+
21#include <stdio.h>
+
22#include <stdlib.h>
+
23#include <unistd.h>
+
24#include <stddef.h>
+
25#include <fcntl.h>
+
26#include <errno.h>
+
27#include <string.h>
+
28
+
29#define FUSERMOUNT_PROG "mount_fusefs"
+
30#define FUSE_DEV_TRUNK "/dev/fuse"
+
31
+
32enum {
+
33 KEY_RO,
+
34 KEY_KERN
+
35};
+
36
+
37struct mount_opts {
+
38 int allow_other;
+
39 char *kernel_opts;
+
40 unsigned max_read;
+
41};
+
42
+
43#define FUSE_DUAL_OPT_KEY(templ, key) \
+
44 FUSE_OPT_KEY(templ, key), FUSE_OPT_KEY("no" templ, key)
+
45
+
46static const struct fuse_opt fuse_mount_opts[] = {
+
47 { "allow_other", offsetof(struct mount_opts, allow_other), 1 },
+
48 { "max_read=%u", offsetof(struct mount_opts, max_read), 1 },
+
49 FUSE_OPT_KEY("-r", KEY_RO),
+
50 /* standard FreeBSD mount options */
+
51 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
+
52 FUSE_DUAL_OPT_KEY("async", KEY_KERN),
+
53 FUSE_DUAL_OPT_KEY("atime", KEY_KERN),
+
54 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
+
55 FUSE_DUAL_OPT_KEY("exec", KEY_KERN),
+
56 FUSE_DUAL_OPT_KEY("suid", KEY_KERN),
+
57 FUSE_DUAL_OPT_KEY("symfollow", KEY_KERN),
+
58 FUSE_DUAL_OPT_KEY("rdonly", KEY_KERN),
+
59 FUSE_DUAL_OPT_KEY("sync", KEY_KERN),
+
60 FUSE_DUAL_OPT_KEY("union", KEY_KERN),
+
61 FUSE_DUAL_OPT_KEY("userquota", KEY_KERN),
+
62 FUSE_DUAL_OPT_KEY("groupquota", KEY_KERN),
+
63 FUSE_DUAL_OPT_KEY("clusterr", KEY_KERN),
+
64 FUSE_DUAL_OPT_KEY("clusterw", KEY_KERN),
+
65 FUSE_DUAL_OPT_KEY("suiddir", KEY_KERN),
+
66 FUSE_DUAL_OPT_KEY("snapshot", KEY_KERN),
+
67 FUSE_DUAL_OPT_KEY("multilabel", KEY_KERN),
+
68 FUSE_DUAL_OPT_KEY("acls", KEY_KERN),
+
69 FUSE_DUAL_OPT_KEY("force", KEY_KERN),
+
70 FUSE_DUAL_OPT_KEY("update", KEY_KERN),
+
71 FUSE_DUAL_OPT_KEY("ro", KEY_KERN),
+
72 FUSE_DUAL_OPT_KEY("rw", KEY_KERN),
+
73 FUSE_DUAL_OPT_KEY("auto", KEY_KERN),
+
74 FUSE_DUAL_OPT_KEY("automounted", KEY_KERN),
+
75 /* options supported under both Linux and FBSD */
+
76 FUSE_DUAL_OPT_KEY("allow_other", KEY_KERN),
+
77 FUSE_DUAL_OPT_KEY("default_permissions",KEY_KERN),
+
78 FUSE_OPT_KEY("max_read=", KEY_KERN),
+
79 FUSE_OPT_KEY("subtype=", KEY_KERN),
+
80 /* FBSD FUSE specific mount options */
+
81 FUSE_DUAL_OPT_KEY("private", KEY_KERN),
+
82 FUSE_DUAL_OPT_KEY("neglect_shares", KEY_KERN),
+
83 FUSE_DUAL_OPT_KEY("push_symlinks_in", KEY_KERN),
+
84#if __FreeBSD_version >= 1200519
+
85 FUSE_DUAL_OPT_KEY("intr", KEY_KERN),
+
86#endif
+
87 /* stock FBSD mountopt parsing routine lets anything be negated... */
+
88 /*
+
89 * Linux specific mount options, but let just the mount util
+
90 * handle them
+
91 */
+
92 FUSE_OPT_KEY("fsname=", KEY_KERN),
+ +
94};
+
95
+
96void fuse_mount_version(void)
+
97{
+
98 system(FUSERMOUNT_PROG " --version");
+
99}
+
100
+
101unsigned get_max_read(struct mount_opts *o)
+
102{
+
103 return o->max_read;
+
104}
+
105
+
106static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+
107 struct fuse_args *outargs)
+
108{
+
109 (void) outargs;
+
110 struct mount_opts *mo = data;
+
111
+
112 switch (key) {
+
113 case KEY_RO:
+
114 arg = "ro";
+
115 /* fall through */
+
116
+
117 case KEY_KERN:
+
118 return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
119 }
+
120
+
121 /* Pass through unknown options */
+
122 return 1;
+
123}
+
124
+
125void fuse_kern_unmount(const char *mountpoint, int fd)
+
126{
+
127 if (close(fd) < 0)
+
128 fuse_log(FUSE_LOG_ERR, "closing FD %d failed: %s", fd, strerror(errno));
+
129 if (unmount(mountpoint, MNT_FORCE) < 0)
+
130 fuse_log(FUSE_LOG_ERR, "unmounting %s failed: %s",
+
131 mountpoint, strerror(errno));
+
132}
+
133
+
134static int fuse_mount_core(const char *mountpoint, const char *opts)
+
135{
+
136 const char *mountprog = FUSERMOUNT_PROG;
+
137 long fd;
+
138 char *fdnam, *dev;
+
139 pid_t pid, cpid;
+
140 int status;
+
141 int err;
+
142
+
143 fdnam = getenv("FUSE_DEV_FD");
+
144
+
145 if (fdnam) {
+
146 err = libfuse_strtol(fdnam, &fd);
+
147 if (err || fd < 0) {
+
148 fuse_log(FUSE_LOG_ERR, "invalid value given in FUSE_DEV_FD\n");
+
149 return -1;
+
150 }
+
151
+
152 goto mount;
+
153 }
+
154
+
155 dev = getenv("FUSE_DEV_NAME");
+
156
+
157 if (! dev)
+
158 dev = (char *)FUSE_DEV_TRUNK;
+
159
+
160 if ((fd = open(dev, O_RDWR)) < 0) {
+
161 perror("fuse: failed to open fuse device");
+
162 return -1;
+
163 }
+
164
+
165mount:
+
166 if (getenv("FUSE_NO_MOUNT") || ! mountpoint)
+
167 goto out;
+
168
+
169 pid = fork();
+
170 cpid = pid;
+
171
+
172 if (pid == -1) {
+
173 perror("fuse: fork() failed");
+
174 close(fd);
+
175 return -1;
+
176 }
+
177
+
178 if (pid == 0) {
+
179 pid = fork();
+
180
+
181 if (pid == -1) {
+
182 perror("fuse: fork() failed");
+
183 close(fd);
+
184 _exit(EXIT_FAILURE);
+
185 }
+
186
+
187 if (pid == 0) {
+
188 const char *argv[32];
+
189 int a = 0;
+
190 int ret = -1;
+
191
+
192 if (! fdnam)
+
193 {
+
194 ret = asprintf(&fdnam, "%ld", fd);
+
195 if(ret == -1)
+
196 {
+
197 perror("fuse: failed to assemble mount arguments");
+
198 close(fd);
+
199 _exit(EXIT_FAILURE);
+
200 }
+
201 }
+
202
+
203 argv[a++] = mountprog;
+
204 if (opts) {
+
205 argv[a++] = "-o";
+
206 argv[a++] = opts;
+
207 }
+
208 argv[a++] = fdnam;
+
209 argv[a++] = mountpoint;
+
210 argv[a++] = NULL;
+
211 execvp(mountprog, (char **) argv);
+
212 perror("fuse: failed to exec mount program");
+
213 free(fdnam);
+
214 _exit(EXIT_FAILURE);
+
215 }
+
216
+
217 _exit(EXIT_SUCCESS);
+
218 }
+
219
+
220 if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) {
+
221 perror("fuse: failed to mount file system");
+
222 if (close(fd) < 0)
+
223 perror("fuse: closing FD");
+
224 return -1;
+
225 }
+
226
+
227out:
+
228 return fd;
+
229}
+
230
+
231struct mount_opts *parse_mount_opts(struct fuse_args *args)
+
232{
+
233 struct mount_opts *mo;
+
234
+
235 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
+
236 if (mo == NULL)
+
237 return NULL;
+
238
+
239 memset(mo, 0, sizeof(struct mount_opts));
+
240
+
241 if (args &&
+
242 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+
243 goto err_out;
+
244
+
245 return mo;
+
246
+
247err_out:
+
248 destroy_mount_opts(mo);
+
249 return NULL;
+
250}
+
251
+
252void destroy_mount_opts(struct mount_opts *mo)
+
253{
+
254 free(mo->kernel_opts);
+
255 free(mo);
+
256}
+
257
+
258int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
+
259{
+
260 /* mount util should not try to spawn the daemon */
+
261 setenv("MOUNT_FUSEFS_SAFE", "1", 1);
+
262 /* to notify the mount util it's called from lib */
+
263 setenv("MOUNT_FUSEFS_CALL_BY_LIB", "1", 1);
+
264
+
265 return fuse_mount_core(mountpoint, mo->kernel_opts);
+
266}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2mount__util_8c_source.html b/doc/html/fuse-3_817_84_2lib_2mount__util_8c_source.html new file mode 100644 index 0000000..b3f9c6e --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2mount__util_8c_source.html @@ -0,0 +1,437 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/mount_util.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_util.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Architecture-independent mounting code.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "mount_util.h"
+
13
+
14#include <stdio.h>
+
15#include <unistd.h>
+
16#include <stdlib.h>
+
17#include <string.h>
+
18#include <signal.h>
+
19#include <dirent.h>
+
20#include <errno.h>
+
21#include <fcntl.h>
+
22#include <limits.h>
+
23#include <paths.h>
+
24#if !defined( __NetBSD__) && !defined(__FreeBSD__) && !defined(__DragonFly__) && !defined(__ANDROID__)
+
25#include <mntent.h>
+
26#else
+
27#define IGNORE_MTAB
+
28#endif
+
29#include <sys/stat.h>
+
30#include <sys/wait.h>
+
31
+
32#include "fuse_mount_compat.h"
+
33
+
34#include <sys/param.h>
+
35
+
36#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
+
37#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0)
+
38#endif
+
39
+
40#ifdef IGNORE_MTAB
+
41#define mtab_needs_update(mnt) 0
+
42#else
+
43static int mtab_needs_update(const char *mnt)
+
44{
+
45 int res;
+
46 struct stat stbuf;
+
47
+
48 /* If mtab is within new mount, don't touch it */
+
49 if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 &&
+
50 _PATH_MOUNTED[strlen(mnt)] == '/')
+
51 return 0;
+
52
+
53 /*
+
54 * Skip mtab update if /etc/mtab:
+
55 *
+
56 * - doesn't exist,
+
57 * - is on a read-only filesystem.
+
58 */
+
59 res = lstat(_PATH_MOUNTED, &stbuf);
+
60 if (res == -1) {
+
61 if (errno == ENOENT)
+
62 return 0;
+
63 } else {
+
64 uid_t ruid;
+
65 int err;
+
66
+
67 ruid = getuid();
+
68 if (ruid != 0)
+
69 setreuid(0, -1);
+
70
+
71 res = access(_PATH_MOUNTED, W_OK);
+
72 err = (res == -1) ? errno : 0;
+
73 if (ruid != 0)
+
74 setreuid(ruid, -1);
+
75
+
76 if (err == EROFS)
+
77 return 0;
+
78
+
79 res = access("/run/mount/utab", F_OK);
+
80 if (res == -1)
+
81 return 0;
+
82 }
+
83
+
84 return 1;
+
85}
+
86#endif /* IGNORE_MTAB */
+
87
+
88static int add_mount(const char *progname, const char *fsname,
+
89 const char *mnt, const char *type, const char *opts)
+
90{
+
91 int res;
+
92 int status;
+
93 sigset_t blockmask;
+
94 sigset_t oldmask;
+
95
+
96 sigemptyset(&blockmask);
+
97 sigaddset(&blockmask, SIGCHLD);
+
98 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
99 if (res == -1) {
+
100 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
101 return -1;
+
102 }
+
103
+
104 res = fork();
+
105 if (res == -1) {
+
106 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
107 goto out_restore;
+
108 }
+
109 if (res == 0) {
+
110 char *env = NULL;
+
111
+
112 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
113
+
114 if(setuid(geteuid()) == -1) {
+
115 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
116 res = -1;
+
117 goto out_restore;
+
118 }
+
119
+
120 execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
+
121 "-f", "-t", type, "-o", opts, fsname, mnt, NULL, &env);
+
122 fprintf(stderr, "%s: failed to execute /bin/mount: %s\n",
+
123 progname, strerror(errno));
+
124 exit(1);
+
125 }
+
126 res = waitpid(res, &status, 0);
+
127 if (res == -1)
+
128 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
129
+
130 if (status != 0)
+
131 res = -1;
+
132
+
133 out_restore:
+
134 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
135
+
136 return res;
+
137}
+
138
+
139int fuse_mnt_add_mount(const char *progname, const char *fsname,
+
140 const char *mnt, const char *type, const char *opts)
+
141{
+
142 if (!mtab_needs_update(mnt))
+
143 return 0;
+
144
+
145 return add_mount(progname, fsname, mnt, type, opts);
+
146}
+
147
+
148static int exec_umount(const char *progname, const char *rel_mnt, int lazy)
+
149{
+
150 int res;
+
151 int status;
+
152 sigset_t blockmask;
+
153 sigset_t oldmask;
+
154
+
155 sigemptyset(&blockmask);
+
156 sigaddset(&blockmask, SIGCHLD);
+
157 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
158 if (res == -1) {
+
159 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
160 return -1;
+
161 }
+
162
+
163 res = fork();
+
164 if (res == -1) {
+
165 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
166 goto out_restore;
+
167 }
+
168 if (res == 0) {
+
169 char *env = NULL;
+
170
+
171 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
172
+
173 if(setuid(geteuid()) == -1) {
+
174 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
175 res = -1;
+
176 goto out_restore;
+
177 }
+
178
+
179 if (lazy) {
+
180 execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+
181 "-l", NULL, &env);
+
182 } else {
+
183 execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+
184 NULL, &env);
+
185 }
+
186 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+
187 progname, strerror(errno));
+
188 exit(1);
+
189 }
+
190 res = waitpid(res, &status, 0);
+
191 if (res == -1)
+
192 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
193
+
194 if (status != 0) {
+
195 res = -1;
+
196 }
+
197
+
198 out_restore:
+
199 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
200 return res;
+
201
+
202}
+
203
+
204int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+
205 const char *rel_mnt, int lazy)
+
206{
+
207 int res;
+
208
+
209 if (!mtab_needs_update(abs_mnt)) {
+
210 res = umount2(rel_mnt, lazy ? 2 : 0);
+
211 if (res == -1)
+
212 fprintf(stderr, "%s: failed to unmount %s: %s\n",
+
213 progname, abs_mnt, strerror(errno));
+
214 return res;
+
215 }
+
216
+
217 return exec_umount(progname, rel_mnt, lazy);
+
218}
+
219
+
220static int remove_mount(const char *progname, const char *mnt)
+
221{
+
222 int res;
+
223 int status;
+
224 sigset_t blockmask;
+
225 sigset_t oldmask;
+
226
+
227 sigemptyset(&blockmask);
+
228 sigaddset(&blockmask, SIGCHLD);
+
229 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
230 if (res == -1) {
+
231 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
232 return -1;
+
233 }
+
234
+
235 res = fork();
+
236 if (res == -1) {
+
237 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
238 goto out_restore;
+
239 }
+
240 if (res == 0) {
+
241 char *env = NULL;
+
242
+
243 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
244
+
245 if(setuid(geteuid()) == -1) {
+
246 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
247 res = -1;
+
248 goto out_restore;
+
249 }
+
250
+
251 execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i",
+
252 "--fake", mnt, NULL, &env);
+
253 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+
254 progname, strerror(errno));
+
255 exit(1);
+
256 }
+
257 res = waitpid(res, &status, 0);
+
258 if (res == -1)
+
259 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
260
+
261 if (status != 0)
+
262 res = -1;
+
263
+
264 out_restore:
+
265 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
266 return res;
+
267}
+
268
+
269int fuse_mnt_remove_mount(const char *progname, const char *mnt)
+
270{
+
271 if (!mtab_needs_update(mnt))
+
272 return 0;
+
273
+
274 return remove_mount(progname, mnt);
+
275}
+
276
+
277char *fuse_mnt_resolve_path(const char *progname, const char *orig)
+
278{
+
279 char buf[PATH_MAX];
+
280 char *copy;
+
281 char *dst;
+
282 char *end;
+
283 char *lastcomp;
+
284 const char *toresolv;
+
285
+
286 if (!orig[0]) {
+
287 fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname,
+
288 orig);
+
289 return NULL;
+
290 }
+
291
+
292 copy = strdup(orig);
+
293 if (copy == NULL) {
+
294 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
295 return NULL;
+
296 }
+
297
+
298 toresolv = copy;
+
299 lastcomp = NULL;
+
300 for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --);
+
301 if (end[0] != '/') {
+
302 char *tmp;
+
303 end[1] = '\0';
+
304 tmp = strrchr(copy, '/');
+
305 if (tmp == NULL) {
+
306 lastcomp = copy;
+
307 toresolv = ".";
+
308 } else {
+
309 lastcomp = tmp + 1;
+
310 if (tmp == copy)
+
311 toresolv = "/";
+
312 }
+
313 if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) {
+
314 lastcomp = NULL;
+
315 toresolv = copy;
+
316 }
+
317 else if (tmp)
+
318 tmp[0] = '\0';
+
319 }
+
320 if (realpath(toresolv, buf) == NULL) {
+
321 fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig,
+
322 strerror(errno));
+
323 free(copy);
+
324 return NULL;
+
325 }
+
326 if (lastcomp == NULL)
+
327 dst = strdup(buf);
+
328 else {
+
329 dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1);
+
330 if (dst) {
+
331 unsigned buflen = strlen(buf);
+
332 if (buflen && buf[buflen-1] == '/')
+
333 sprintf(dst, "%s%s", buf, lastcomp);
+
334 else
+
335 sprintf(dst, "%s/%s", buf, lastcomp);
+
336 }
+
337 }
+
338 free(copy);
+
339 if (dst == NULL)
+
340 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
341 return dst;
+
342}
+
343
+
344int fuse_mnt_check_fuseblk(void)
+
345{
+
346 char buf[256];
+
347 FILE *f = fopen("/proc/filesystems", "r");
+
348 if (!f)
+
349 return 1;
+
350
+
351 while (fgets(buf, sizeof(buf), f))
+
352 if (strstr(buf, "fuseblk\n")) {
+
353 fclose(f);
+
354 return 1;
+
355 }
+
356
+
357 fclose(f);
+
358 return 0;
+
359}
+
360
+
361int fuse_mnt_parse_fuse_fd(const char *mountpoint)
+
362{
+
363 int fd = -1;
+
364 int len = 0;
+
365
+
366 if (mountpoint == NULL) {
+
367 fprintf(stderr, "Invalid null-ptr mount-point!\n");
+
368 return -1;
+
369 }
+
370
+
371 if (sscanf(mountpoint, "/dev/fd/%u%n", &fd, &len) == 1 &&
+
372 len == strlen(mountpoint)) {
+
373 return fd;
+
374 }
+
375
+
376 return -1;
+
377}
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2mount__util_8h_source.html b/doc/html/fuse-3_817_84_2lib_2mount__util_8h_source.html new file mode 100644 index 0000000..41ae396 --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2mount__util_8h_source.html @@ -0,0 +1,78 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/mount_util.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_util.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#include <sys/types.h>
+
10
+
11int fuse_mnt_add_mount(const char *progname, const char *fsname,
+
12 const char *mnt, const char *type, const char *opts);
+
13int fuse_mnt_remove_mount(const char *progname, const char *mnt);
+
14int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+
15 const char *rel_mnt, int lazy);
+
16char *fuse_mnt_resolve_path(const char *progname, const char *orig);
+
17int fuse_mnt_check_fuseblk(void);
+
18int fuse_mnt_parse_fuse_fd(const char *mountpoint);
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2util_8c_source.html b/doc/html/fuse-3_817_84_2lib_2util_8c_source.html new file mode 100644 index 0000000..413dabc --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2util_8c_source.html @@ -0,0 +1,95 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/util.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
util.c
+
+
+
1#include <stdlib.h>
+
2#include <errno.h>
+
3
+
4#ifndef FUSE_USE_VERSION
+
5#define FUSE_USE_VERSION (FUSE_MAKE_VERSION(3, 18))
+
6#endif
+
7
+
8#include "util.h"
+
9#include "fuse_log.h"
+
10#include "fuse_lowlevel.h"
+
11#include <stdio.h>
+
12
+
13int libfuse_strtol(const char *str, long *res)
+
14{
+
15 char *endptr;
+
16 int base = 10;
+
17 long val;
+
18
+
19 errno = 0;
+
20
+
21 if (!str)
+
22 return -EINVAL;
+
23
+
24 val = strtol(str, &endptr, base);
+
25
+
26 if (errno)
+
27 return -errno;
+
28
+
29 if (endptr == str || *endptr != '\0')
+
30 return -EINVAL;
+
31
+
32 *res = val;
+
33 return 0;
+
34}
+
35
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2lib_2util_8h_source.html b/doc/html/fuse-3_817_84_2lib_2util_8h_source.html new file mode 100644 index 0000000..31d3a57 --- /dev/null +++ b/doc/html/fuse-3_817_84_2lib_2util_8h_source.html @@ -0,0 +1,97 @@ + + + + + + + +libfuse: fuse-3.17.4/lib/util.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
util.h
+
+
+
1#ifndef FUSE_UTIL_H_
+
2#define FUSE_UTIL_H_
+
3
+
4#include <stdint.h>
+
5#include <stdbool.h>
+
6
+
7#define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1))
+
8
+
9#define likely(x) __builtin_expect(!!(x), 1)
+
10#define unlikely(x) __builtin_expect(!!(x), 0)
+
11
+
12struct fuse_conn_info;
+
13
+
14int libfuse_strtol(const char *str, long *res);
+
15
+
19static inline uint32_t fuse_lower_32_bits(uint64_t nr)
+
20{
+
21 return (uint32_t)(nr & 0xffffffff);
+
22}
+
23
+
27static inline uint64_t fuse_higher_32_bits(uint64_t nr)
+
28{
+
29 return nr & ~0xffffffffULL;
+
30}
+
31
+
32#ifndef FUSE_VAR_UNUSED
+
33#define FUSE_VAR_UNUSED(var) (__attribute__((unused)) var)
+
34#endif
+
35
+
36#define container_of(ptr, type, member) \
+
37 ({ \
+
38 unsigned long __mptr = (unsigned long)(ptr); \
+
39 ((type *)(__mptr - offsetof(type, member))); \
+
40 })
+
41
+
42#endif
+ +
+ + + + diff --git a/doc/html/fuse-3_817_84_2test_2hello_8c.html b/doc/html/fuse-3_817_84_2test_2hello_8c.html new file mode 100644 index 0000000..0eb7a1f --- /dev/null +++ b/doc/html/fuse-3_817_84_2test_2hello_8c.html @@ -0,0 +1,265 @@ + + + + + + + +libfuse: fuse-3.17.4/test/hello.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using high-level API

+

Compile with:

gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <stddef.h>
+
#include <assert.h>
+
+
/*
+
* Command line options
+
*
+
* We can't set default values for the char* fields here because
+
* fuse_opt_parse would attempt to free() them when the user specifies
+
* different values on the command line.
+
*/
+
static struct options {
+
const char *filename;
+
const char *contents;
+
int show_help;
+
} options;
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--name=%s", filename),
+
OPTION("--contents=%s", contents),
+
OPTION("-h", show_help),
+
OPTION("--help", show_help),
+ +
};
+
+
static void *hello_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->kernel_cache = 1;
+
+
/* Test setting flags the old way */
+ + +
+
return NULL;
+
}
+
+
static int hello_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res = 0;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
} else if (strcmp(path+1, options.filename) == 0) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(options.contents);
+
} else
+
res = -ENOENT;
+
+
return res;
+
}
+
+
static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int hello_open(const char *path, struct fuse_file_info *fi)
+
{
+
if (strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
+
return 0;
+
}
+
+
static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
size_t len;
+
(void) fi;
+
if(strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
len = strlen(options.contents);
+
if (offset < len) {
+
if (offset + size > len)
+
size = len - offset;
+
memcpy(buf, options.contents + offset, size);
+
} else
+
size = 0;
+
+
return size;
+
}
+
+
static const struct fuse_operations hello_oper = {
+
.init = hello_init,
+
.getattr = hello_getattr,
+
.readdir = hello_readdir,
+
.open = hello_open,
+
.read = hello_read,
+
};
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --name=<s> Name of the \"hello\" file\n"
+
" (default: \"hello\")\n"
+
" --contents=<s> Contents \"hello\" file\n"
+
" (default \"Hello, World!\\n\")\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[])
+
{
+
int ret;
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
+
/* Set defaults -- we have to use strdup so that
+
fuse_opt_parse can free the defaults if other
+
values are specified */
+
options.filename = strdup("hello");
+
options.contents = strdup("Hello World!\n");
+
+
/* Parse options */
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
/* When --help is specified, first print our own file-system
+
specific help text, then signal fuse_main to show
+
additional help (by adding `--help` to the options again)
+
without usage: line (by setting argv[0] to the empty
+
string) */
+
if (options.show_help) {
+
show_help(argv[0]);
+
assert(fuse_opt_add_arg(&args, "--help") == 0);
+
args.argv[0][0] = '\0';
+
}
+
+
ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+ +
return ret;
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_ASYNC_READ
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file hello.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_84_2test_2hello_8c_source.html b/doc/html/fuse-3_817_84_2test_2hello_8c_source.html new file mode 100644 index 0000000..e73d4d3 --- /dev/null +++ b/doc/html/fuse-3_817_84_2test_2hello_8c_source.html @@ -0,0 +1,250 @@ + + + + + + + +libfuse: fuse-3.17.4/test/hello.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello.c
+
+
+Go to the documentation of this file.
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 * Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 *
+
5 * This program can be distributed under the terms of the GNU GPLv2.
+
6 * See the file COPYING.
+
7 */
+
8
+
21#define FUSE_USE_VERSION 31
+
22
+
23#include <fuse.h>
+
24#include <stdio.h>
+
25#include <string.h>
+
26#include <errno.h>
+
27#include <fcntl.h>
+
28#include <stddef.h>
+
29#include <assert.h>
+
30
+
31/*
+
32 * Command line options
+
33 *
+
34 * We can't set default values for the char* fields here because
+
35 * fuse_opt_parse would attempt to free() them when the user specifies
+
36 * different values on the command line.
+
37 */
+
38static struct options {
+
39 const char *filename;
+
40 const char *contents;
+
41 int show_help;
+
42} options;
+
43
+
44#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
45static const struct fuse_opt option_spec[] = {
+
46 OPTION("--name=%s", filename), OPTION("--contents=%s", contents),
+
47 OPTION("-h", show_help), OPTION("--help", show_help), FUSE_OPT_END
+
48};
+
49
+
50static void *hello_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
51{
+
52 (void)conn;
+
53 cfg->kernel_cache = 1;
+
54
+
55 /* Test setting flags the old way */
+ +
57 conn->want &= ~FUSE_CAP_ASYNC_READ;
+
58
+
59 return NULL;
+
60}
+
61
+
62static int hello_getattr(const char *path, struct stat *stbuf,
+
63 struct fuse_file_info *fi)
+
64{
+
65 (void)fi;
+
66 int res = 0;
+
67
+
68 memset(stbuf, 0, sizeof(struct stat));
+
69 if (strcmp(path, "/") == 0) {
+
70 stbuf->st_mode = S_IFDIR | 0755;
+
71 stbuf->st_nlink = 2;
+
72 } else if (strcmp(path + 1, options.filename) == 0) {
+
73 stbuf->st_mode = S_IFREG | 0444;
+
74 stbuf->st_nlink = 1;
+
75 stbuf->st_size = strlen(options.contents);
+
76 } else
+
77 res = -ENOENT;
+
78
+
79 return res;
+
80}
+
81
+
82static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
83 off_t offset, struct fuse_file_info *fi,
+
84 enum fuse_readdir_flags flags)
+
85{
+
86 (void)offset;
+
87 (void)fi;
+
88 (void)flags;
+
89
+
90 if (strcmp(path, "/") != 0)
+
91 return -ENOENT;
+
92
+
93 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
94 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
95 filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
96
+
97 return 0;
+
98}
+
99
+
100static int hello_open(const char *path, struct fuse_file_info *fi)
+
101{
+
102 if (strcmp(path + 1, options.filename) != 0)
+
103 return -ENOENT;
+
104
+
105 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
106 return -EACCES;
+
107
+
108 return 0;
+
109}
+
110
+
111static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
112 struct fuse_file_info *fi)
+
113{
+
114 size_t len;
+
115 (void)fi;
+
116 if (strcmp(path + 1, options.filename) != 0)
+
117 return -ENOENT;
+
118
+
119 len = strlen(options.contents);
+
120 if (offset < len) {
+
121 if (offset + size > len)
+
122 size = len - offset;
+
123 memcpy(buf, options.contents + offset, size);
+
124 } else
+
125 size = 0;
+
126
+
127 return size;
+
128}
+
129
+
130static const struct fuse_operations hello_oper = {
+
131 .init = hello_init,
+
132 .getattr = hello_getattr,
+
133 .readdir = hello_readdir,
+
134 .open = hello_open,
+
135 .read = hello_read,
+
136};
+
137
+
138static void show_help(const char *progname)
+
139{
+
140 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
141 printf("File-system specific options:\n"
+
142 " --name=<s> Name of the \"hello\" file\n"
+
143 " (default: \"hello\")\n"
+
144 " --contents=<s> Contents \"hello\" file\n"
+
145 " (default \"Hello, World!\\n\")\n"
+
146 "\n");
+
147}
+
148
+
149int main(int argc, char *argv[])
+
150{
+
151 int ret;
+
152 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
153
+
154 /* Set defaults -- we have to use strdup so that
+
155 * fuse_opt_parse can free the defaults if other
+
156 * values are specified
+
157 */
+
158 options.filename = strdup("hello");
+
159 options.contents = strdup("Hello World!\n");
+
160
+
161 /* Parse options */
+
162 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
163 return 1;
+
164
+
165 /* When --help is specified, first print our own file-system
+
166 * specific help text, then signal fuse_main to show
+
167 * additional help (by adding `--help` to the options again)
+
168 * without usage: line (by setting argv[0] to the empty
+
169 * string)
+
170 */
+
171 if (options.show_help) {
+
172 show_help(argv[0]);
+
173 assert(fuse_opt_add_arg(&args, "--help") == 0);
+
174 args.argv[0][0] = '\0';
+
175 }
+
176
+
177 ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+
178 fuse_opt_free_args(&args);
+
179 return ret;
+
180}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_CAP_ASYNC_READ
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2test_2readdir__inode_8c_source.html b/doc/html/fuse-3_817_84_2test_2readdir__inode_8c_source.html new file mode 100644 index 0000000..642eb16 --- /dev/null +++ b/doc/html/fuse-3_817_84_2test_2readdir__inode_8c_source.html @@ -0,0 +1,117 @@ + + + + + + + +libfuse: fuse-3.17.4/test/readdir_inode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
readdir_inode.c
+
+
+
1/*
+
2 * Prints each directory entry, its inode and d_type as returned by 'readdir'.
+
3 * Skips '.' and '..' because readdir is not required to return them and
+
4 * some of our examples don't. However if they are returned, their d_type
+
5 * should be valid.
+
6 */
+
7
+
8#include <stdio.h>
+
9#include <string.h>
+
10#include <sys/types.h>
+
11#include <dirent.h>
+
12#include <errno.h>
+
13
+
14int main(int argc, char* argv[])
+
15{
+
16 DIR* dirp;
+
17 struct dirent* dent;
+
18
+
19 if (argc != 2) {
+
20 fprintf(stderr, "Usage: readdir_inode dir\n");
+
21 return 1;
+
22 }
+
23
+
24 dirp = opendir(argv[1]);
+
25 if (dirp == NULL) {
+
26 perror("failed to open directory");
+
27 return 2;
+
28 }
+
29
+
30 errno = 0;
+
31 dent = readdir(dirp);
+
32 while (dent != NULL) {
+
33 if (strcmp(dent->d_name, ".") != 0 && strcmp(dent->d_name, "..") != 0) {
+
34 printf("%llu %d %s\n", (unsigned long long)dent->d_ino,
+
35 (int)dent->d_type, dent->d_name);
+
36 if ((long long)dent->d_ino < 0)
+
37 fprintf(stderr,"%s : bad d_ino %llu\n",
+
38 dent->d_name, (unsigned long long)dent->d_ino);
+
39 if ((dent->d_type < 1) || (dent->d_type > 15))
+
40 fprintf(stderr,"%s : bad d_type %d\n",
+
41 dent->d_name, (int)dent->d_type);
+
42 } else {
+
43 if (dent->d_type != DT_DIR)
+
44 fprintf(stderr,"%s : bad d_type %d\n",
+
45 dent->d_name, (int)dent->d_type);
+
46 }
+
47 dent = readdir(dirp);
+
48 }
+
49 if (errno != 0) {
+
50 perror("failed to read directory entry");
+
51 return 3;
+
52 }
+
53
+
54 closedir(dirp);
+
55
+
56 return 0;
+
57}
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2test_2release__unlink__race_8c_source.html b/doc/html/fuse-3_817_84_2test_2release__unlink__race_8c_source.html new file mode 100644 index 0000000..8774a64 --- /dev/null +++ b/doc/html/fuse-3_817_84_2test_2release__unlink__race_8c_source.html @@ -0,0 +1,183 @@ + + + + + + + +libfuse: fuse-3.17.4/test/release_unlink_race.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
release_unlink_race.c
+
+
+
1/*
+
2 This program can be distributed under the terms of the GNU GPLv2.
+
3 See the file COPYING.
+
4*/
+
5
+
6#define FUSE_USE_VERSION 31
+
7
+
8#define _GNU_SOURCE
+
9
+
10#include <fuse.h>
+
11
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <unistd.h>
+
15#include <errno.h>
+
16
+
17static void *xmp_init(struct fuse_conn_info *conn,
+
18 struct fuse_config *cfg)
+
19{
+
20 (void) conn;
+
21
+
22 cfg->use_ino = 1;
+
23 cfg->nullpath_ok = 1;
+
24 cfg->entry_timeout = 0;
+
25 cfg->attr_timeout = 0;
+
26 cfg->negative_timeout = 0;
+
27
+
28 return NULL;
+
29}
+
30
+
31static int xmp_getattr(const char *path, struct stat *stbuf,
+
32 struct fuse_file_info *fi)
+
33{
+
34 int res;
+
35
+
36 (void) path;
+
37
+
38 if(fi)
+
39 res = fstat(fi->fh, stbuf);
+
40 else
+
41 res = lstat(path, stbuf);
+
42 if (res == -1)
+
43 return -errno;
+
44
+
45 return 0;
+
46}
+
47
+
48static int xmp_unlink(const char *path)
+
49{
+
50 int res;
+
51
+
52 res = unlink(path);
+
53 if (res == -1)
+
54 return -errno;
+
55
+
56 return 0;
+
57}
+
58
+
59static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
60{
+
61 int res;
+
62
+
63 if (flags)
+
64 return -EINVAL;
+
65
+
66 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
+
67
+
68 res = rename(from, to);
+
69 if (res == -1)
+
70 return -errno;
+
71
+
72 return 0;
+
73}
+
74
+
75static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
76{
+
77 int fd;
+
78
+
79 fd = open(path, fi->flags, mode);
+
80 if (fd == -1)
+
81 return -errno;
+
82
+
83 fi->fh = fd;
+
84 return 0;
+
85}
+
86
+
87static int xmp_release(const char *path, struct fuse_file_info *fi)
+
88{
+
89 (void) path;
+
90
+
91 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
+
92
+
93 close(fi->fh);
+
94
+
95 return 0;
+
96}
+
97
+
98static const struct fuse_operations xmp_oper = {
+
99 .init = xmp_init,
+
100 .getattr = xmp_getattr,
+
101 .unlink = xmp_unlink,
+
102 .rename = xmp_rename,
+
103 .create = xmp_create,
+
104 .release = xmp_release,
+
105};
+
106
+
107int main(int argc, char *argv[])
+
108{
+
109 umask(0);
+
110 return fuse_main(argc, argv, &xmp_oper, NULL);
+
111}
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2test_2stracedecode_8c_source.html b/doc/html/fuse-3_817_84_2test_2stracedecode_8c_source.html new file mode 100644 index 0000000..fcab4b8 --- /dev/null +++ b/doc/html/fuse-3_817_84_2test_2stracedecode_8c_source.html @@ -0,0 +1,259 @@ + + + + + + + +libfuse: fuse-3.17.4/test/stracedecode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
stracedecode.c
+
+
+
1#include <stdio.h>
+
2#include <string.h>
+
3#include "fuse_kernel.h"
+
4
+
5static struct {
+
6 const char *name;
+
7} fuse_ll_ops[] = {
+
8 [FUSE_LOOKUP] = { "LOOKUP" },
+
9 [FUSE_FORGET] = { "FORGET" },
+
10 [FUSE_GETATTR] = { "GETATTR" },
+
11 [FUSE_SETATTR] = { "SETATTR" },
+
12 [FUSE_READLINK] = { "READLINK" },
+
13 [FUSE_SYMLINK] = { "SYMLINK" },
+
14 [FUSE_MKNOD] = { "MKNOD" },
+
15 [FUSE_MKDIR] = { "MKDIR" },
+
16 [FUSE_UNLINK] = { "UNLINK" },
+
17 [FUSE_RMDIR] = { "RMDIR" },
+
18 [FUSE_RENAME] = { "RENAME" },
+
19 [FUSE_LINK] = { "LINK" },
+
20 [FUSE_OPEN] = { "OPEN" },
+
21 [FUSE_READ] = { "READ" },
+
22 [FUSE_WRITE] = { "WRITE" },
+
23 [FUSE_STATFS] = { "STATFS" },
+
24 [FUSE_RELEASE] = { "RELEASE" },
+
25 [FUSE_FSYNC] = { "FSYNC" },
+
26 [FUSE_SETXATTR] = { "SETXATTR" },
+
27 [FUSE_GETXATTR] = { "GETXATTR" },
+
28 [FUSE_LISTXATTR] = { "LISTXATTR" },
+
29 [FUSE_REMOVEXATTR] = { "REMOVEXATTR" },
+
30 [FUSE_FLUSH] = { "FLUSH" },
+
31 [FUSE_INIT] = { "INIT" },
+
32 [FUSE_OPENDIR] = { "OPENDIR" },
+
33 [FUSE_READDIR] = { "READDIR" },
+
34 [FUSE_RELEASEDIR] = { "RELEASEDIR" },
+
35 [FUSE_FSYNCDIR] = { "FSYNCDIR" },
+
36 [FUSE_GETLK] = { "GETLK" },
+
37 [FUSE_SETLK] = { "SETLK" },
+
38 [FUSE_SETLKW] = { "SETLKW" },
+
39 [FUSE_ACCESS] = { "ACCESS" },
+
40 [FUSE_CREATE] = { "CREATE" },
+
41 [FUSE_TMPFILE] = { "TMPFILE" },
+
42 [FUSE_INTERRUPT] = { "INTERRUPT" },
+
43 [FUSE_BMAP] = { "BMAP" },
+
44 [FUSE_DESTROY] = { "DESTROY" },
+
45 [FUSE_READDIRPLUS] = { "READDIRPLUS" },
+
46};
+
47
+
48#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
+
49
+
50static const char *opname(enum fuse_opcode opcode)
+
51{
+
52 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
+
53 return "???";
+
54 else
+
55 return fuse_ll_ops[opcode].name;
+
56}
+
57
+
58
+
59static void process_buf(int dir, char *buf, int len)
+
60{
+
61 static unsigned long long prevuniq = -1;
+
62 static int prevopcode;
+
63
+
64 if (!dir) {
+
65 struct fuse_in_header *in = (struct fuse_in_header *) buf;
+
66 buf += sizeof(struct fuse_in_header);
+
67
+
68 printf("unique: %llu, opcode: %s (%i), nodeid: %lu, len: %i, insize: %i\n",
+
69 (unsigned long long) in->unique,
+
70 opname((enum fuse_opcode) in->opcode), in->opcode,
+
71 (unsigned long) in->nodeid, in->len, len);
+
72
+
73 switch (in->opcode) {
+
74 case FUSE_READ: {
+
75 struct fuse_read_in *arg = (struct fuse_read_in *) buf;
+
76 printf("-READ fh:%llu off:%llu siz:%u rfl:%u own:%llu fl:%u\n",
+
77 arg->fh, arg->offset, arg->size, arg->read_flags,
+
78 arg->lock_owner, arg->flags);
+
79 break;
+
80 }
+
81 case FUSE_WRITE: {
+
82 struct fuse_write_in *arg = (struct fuse_write_in *) buf;
+
83 printf("-WRITE fh:%llu off:%llu siz:%u wfl:%u own:%llu fl:%u\n",
+
84 arg->fh, arg->offset, arg->size, arg->write_flags,
+
85 arg->lock_owner, arg->flags);
+
86 break;
+
87 }
+
88 }
+
89 prevuniq = in->unique;
+
90 prevopcode = in->opcode;
+
91 } else {
+
92 struct fuse_out_header *out = (struct fuse_out_header *) buf;
+
93 buf += sizeof(struct fuse_out_header);
+
94
+
95 printf(" unique: %llu, error: %i (%s), len: %i, outsize: %i\n",
+
96 (unsigned long long) out->unique, out->error,
+
97 strerror(-out->error), out->len, len);
+
98
+
99 if (out->unique == prevuniq) {
+
100 switch (prevopcode) {
+
101 case FUSE_GETATTR: {
+
102 struct fuse_attr_out *arg = (struct fuse_attr_out *) buf;
+
103 printf("+ATTR v:%llu.%09u i:%llu s:%llu b:%llu\n",
+
104 arg->attr_valid, arg->attr_valid_nsec,
+
105 arg->attr.ino, arg->attr.size, arg->attr.blocks);
+
106 break;
+
107 }
+
108 case FUSE_LOOKUP: {
+
109 struct fuse_entry_out *arg = (struct fuse_entry_out *) buf;
+
110 printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n",
+
111 arg->nodeid, arg->attr_valid, arg->attr_valid_nsec,
+
112 arg->attr.ino, arg->attr.size, arg->attr.blocks);
+
113 break;
+
114 }
+
115 }
+
116 }
+
117 }
+
118
+
119}
+
120
+
121int main(void)
+
122{
+
123 FILE *in = stdin;
+
124 while (1) {
+
125 int dir;
+
126 int res;
+
127 char buf[1048576];
+
128 unsigned len = 0;
+
129
+
130 memset(buf, 0, sizeof(buf));
+
131 while (1) {
+
132 char str[32];
+
133
+
134 res = fscanf(in, "%30s", str);
+
135 if (res != 1 && feof(in))
+
136 return 0;
+
137
+
138 if (res == 0)
+
139 continue;
+
140
+
141 if (strncmp(str, "read(", 5) == 0) {
+
142 dir = 0;
+
143 break;
+
144 } else if (strncmp(str, "writev(", 7) == 0) {
+
145 dir = 1;
+
146 break;
+
147 }
+
148 }
+
149
+
150 while (1) {
+
151 int c = getc(in);
+
152 if (c == '"') {
+
153 while (1) {
+
154 int val;
+
155
+
156 c = getc(in);
+
157 if (c == EOF) {
+
158 fprintf(stderr, "eof in string\n");
+
159 break;
+
160 }
+
161 if (c == '\n') {
+
162 fprintf(stderr, "eol in string\n");
+
163 break;
+
164 }
+
165 if (c == '"')
+
166 break;
+
167 if (c != '\\') {
+
168 val = c;
+
169 } else {
+
170 c = getc(in);
+
171 switch (c) {
+
172 case 'n': val = '\n'; break;
+
173 case 'r': val = '\r'; break;
+
174 case 't': val = '\t'; break;
+
175 case '"': val = '"'; break;
+
176 case '\\': val = '\\'; break;
+
177 case 'x':
+
178 res = scanf("%x", &val);
+
179 if (res != 1) {
+
180 fprintf(stderr, "parse error\n");
+
181 continue;
+
182 }
+
183 break;
+
184 default:
+
185 fprintf(stderr, "unknown sequence: '\\%c'\n", c);
+
186 continue;
+
187 }
+
188 }
+
189 buf[len++] = val;
+
190 }
+
191 }
+
192 if (c == '\n')
+
193 break;
+
194 }
+
195 process_buf(dir, buf, len);
+
196 memset(buf, 0, len);
+
197 len = 0;
+
198 }
+
199}
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2test_2test__abi_8c_source.html b/doc/html/fuse-3_817_84_2test_2test__abi_8c_source.html new file mode 100644 index 0000000..f779677 --- /dev/null +++ b/doc/html/fuse-3_817_84_2test_2test__abi_8c_source.html @@ -0,0 +1,80 @@ + + + + + + + +libfuse: fuse-3.17.4/test/test_abi.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_abi.c
+
+
+
1#define FUSE_USE_VERSION 30
+
2
+
3#include "fuse.h"
+
4
+
5#include <stdio.h>
+
6#include <stdlib.h>
+
7
+
8int main(void)
+
9{
+
10 if (sizeof(struct fuse_file_info) != 64) {
+
11 fprintf(stderr, "struct fuse_file_info size mismatch\n");
+
12 exit(1);
+
13 }
+
14 if (sizeof(struct fuse_conn_info) != 128) {
+
15 fprintf(stderr, "struct fuse_conn_info size mismatch\n");
+
16 exit(1);
+
17 }
+
18}
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_84_2test_2test__setattr_8c_source.html b/doc/html/fuse-3_817_84_2test_2test__setattr_8c_source.html new file mode 100644 index 0000000..2643634 --- /dev/null +++ b/doc/html/fuse-3_817_84_2test_2test__setattr_8c_source.html @@ -0,0 +1,272 @@ + + + + + + + +libfuse: fuse-3.17.4/test/test_setattr.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_setattr.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
9
+
10#define FUSE_USE_VERSION 30
+
11
+
12/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
+
13#include <fuse.h>
+
14
+
15#include <fuse_config.h>
+
16#include <fuse_lowlevel.h>
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <errno.h>
+
21#include <fcntl.h>
+
22#include <assert.h>
+
23#include <stddef.h>
+
24#include <unistd.h>
+
25#include <pthread.h>
+
26
+
27#ifndef __linux__
+
28#include <limits.h>
+
29#else
+
30#include <linux/limits.h>
+
31#endif
+
32
+
33#define FILE_INO 2
+
34#define FILE_NAME "truncate_me"
+
35
+
36static int got_fh;
+
37static mode_t file_mode = S_IFREG | 0644;
+
38
+
39static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
40 stbuf->st_ino = ino;
+
41 if (ino == FUSE_ROOT_ID) {
+
42 stbuf->st_mode = S_IFDIR | 0755;
+
43 stbuf->st_nlink = 1;
+
44 }
+
45
+
46 else if (ino == FILE_INO) {
+
47 stbuf->st_mode = file_mode;
+
48 stbuf->st_nlink = 1;
+
49 stbuf->st_size = 0;
+
50 }
+
51
+
52 else
+
53 return -1;
+
54
+
55 return 0;
+
56}
+
57
+
58static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
59 const char *name) {
+
60 struct fuse_entry_param e;
+
61 memset(&e, 0, sizeof(e));
+
62
+
63 if (parent != FUSE_ROOT_ID)
+
64 goto err_out;
+
65 else if (strcmp(name, FILE_NAME) == 0)
+
66 e.ino = FILE_INO;
+
67 else
+
68 goto err_out;
+
69
+
70 if (tfs_stat(e.ino, &e.attr) != 0)
+
71 goto err_out;
+
72 fuse_reply_entry(req, &e);
+
73 return;
+
74
+
75err_out:
+
76 fuse_reply_err(req, ENOENT);
+
77}
+
78
+
79static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
80 struct fuse_file_info *fi) {
+
81 struct stat stbuf;
+
82
+
83 (void) fi;
+
84
+
85 memset(&stbuf, 0, sizeof(stbuf));
+
86 if (tfs_stat(ino, &stbuf) != 0)
+
87 fuse_reply_err(req, ENOENT);
+
88 else
+
89 fuse_reply_attr(req, &stbuf, 5);
+
90}
+
91
+
92static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
93 struct fuse_file_info *fi) {
+
94 if (ino == FUSE_ROOT_ID)
+
95 fuse_reply_err(req, EISDIR);
+
96 else {
+
97 assert(ino == FILE_INO);
+
98 fi->fh = FILE_INO;
+
99 fuse_reply_open(req, fi);
+
100 }
+
101}
+
102
+
103static void tfs_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
104 int to_set, struct fuse_file_info *fi) {
+
105 if(ino != FILE_INO ||
+
106 !(to_set & FUSE_SET_ATTR_MODE)) {
+
107 fuse_reply_err(req, EINVAL);
+
108 return;
+
109 }
+
110
+
111 if(fi == NULL)
+
112 fprintf(stderr, "setattr with fi == NULL\n");
+
113 else if (fi->fh != FILE_INO)
+
114 fprintf(stderr, "setattr with wrong fi->fh\n");
+
115 else {
+
116 fprintf(stderr, "setattr ok\n");
+
117 got_fh = 1;
+
118 file_mode = attr->st_mode;
+
119 }
+
120
+
121 tfs_getattr(req, ino, fi);
+
122}
+
123
+
124static struct fuse_lowlevel_ops tfs_oper = {
+
125 .lookup = tfs_lookup,
+
126 .getattr = tfs_getattr,
+
127 .open = tfs_open,
+
128 .setattr = tfs_setattr,
+
129};
+
130
+
131static void* run_fs(void *data) {
+
132 struct fuse_session *se = (struct fuse_session*) data;
+
133 assert(fuse_session_loop(se) == 0);
+
134 return NULL;
+
135}
+
136
+
137static void test_fs(char *mountpoint) {
+
138 char fname[PATH_MAX];
+
139 int fd;
+
140
+
141 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
+
142 mountpoint) > 0);
+
143 fd = open(fname, O_WRONLY);
+
144 if (fd == -1) {
+
145 perror(fname);
+
146 assert(0);
+
147 }
+
148
+
149 assert(fchmod(fd, 0600) == 0);
+
150 close(fd);
+
151}
+
152
+
153int main(int argc, char *argv[]) {
+
154 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
155 struct fuse_session *se;
+
156 struct fuse_cmdline_opts fuse_opts;
+
157 pthread_t fs_thread;
+
158
+
159 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
+
160#ifndef __FreeBSD__
+
161 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
+
162#endif
+
163 se = fuse_session_new(&args, &tfs_oper,
+
164 sizeof(tfs_oper), NULL);
+
165 assert (se != NULL);
+
166 assert(fuse_set_signal_handlers(se) == 0);
+
167 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
+
168
+
169 /* Start file-system thread */
+
170 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
+
171
+
172 /* Do test */
+
173 test_fs(fuse_opts.mountpoint);
+
174
+
175 /* Stop file system */
+
176 assert(pthread_cancel(fs_thread) == 0);
+
177
+ +
179 assert(got_fh == 1);
+ + +
182
+
183 printf("Test completed successfully.\n");
+
184 return 0;
+
185}
+
186
+
187
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
uint64_t fuse_ino_t
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
+
fuse_ino_t ino
+ + + +
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2test_2test__syscalls_8c_source.html b/doc/html/fuse-3_817_84_2test_2test__syscalls_8c_source.html new file mode 100644 index 0000000..cbeb7ca --- /dev/null +++ b/doc/html/fuse-3_817_84_2test_2test__syscalls_8c_source.html @@ -0,0 +1,2258 @@ + + + + + + + +libfuse: fuse-3.17.4/test/test_syscalls.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_syscalls.c
+
+
+
1#define _GNU_SOURCE
+
2#include "fuse_config.h"
+
3
+
4#include <stdio.h>
+
5#include <stdlib.h>
+
6#include <stdarg.h>
+
7#include <string.h>
+
8#include <unistd.h>
+
9#include <fcntl.h>
+
10#include <dirent.h>
+
11#include <utime.h>
+
12#include <errno.h>
+
13#include <assert.h>
+
14#include <sys/socket.h>
+
15#include <sys/types.h>
+
16#include <sys/stat.h>
+
17#include <sys/un.h>
+
18
+
19#ifndef ALLPERMS
+
20# define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)/* 07777 */
+
21#endif
+
22
+
23
+
24static const char *basepath;
+
25static const char *basepath_r;
+
26static char testfile[1024];
+
27static char testfile2[1024];
+
28static char testdir[1024];
+
29static char testdir2[1024];
+
30static char testsock[1024];
+
31static char subfile[1280];
+
32
+
33static char testfile_r[1024];
+
34static char testfile2_r[1024];
+
35static char testdir_r[1024];
+
36static char testdir2_r[1024];
+
37static char subfile_r[1280];
+
38
+
39static char testname[256];
+
40static char testdata[] = "abcdefghijklmnopqrstuvwxyz";
+
41static char testdata2[] = "1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./";
+
42static const char *testdir_files[] = { "f1", "f2", NULL};
+
43static long seekdir_offsets[4];
+
44static char zerodata[4096];
+
45static int testdatalen = sizeof(testdata) - 1;
+
46static int testdata2len = sizeof(testdata2) - 1;
+
47static unsigned int testnum = 0;
+
48static unsigned int select_test = 0;
+
49static unsigned int skip_test = 0;
+
50static unsigned int unlinked_test = 0;
+
51
+
52#define MAX_ENTRIES 1024
+
53#define MAX_TESTS 100
+
54
+
55static struct test {
+
56 int fd;
+
57 struct stat stat;
+
58} tests[MAX_TESTS];
+
59
+
60static void test_perror(const char *func, const char *msg)
+
61{
+
62 fprintf(stderr, "%s %s() - %s: %s\n", testname, func, msg,
+
63 strerror(errno));
+
64}
+
65
+
66static void test_error(const char *func, const char *msg, ...)
+
67 __attribute__ ((format (printf, 2, 3)));
+
68
+
69static void __start_test(const char *fmt, ...)
+
70 __attribute__ ((format (printf, 1, 2)));
+
71
+
72static void test_error(const char *func, const char *msg, ...)
+
73{
+
74 va_list ap;
+
75 fprintf(stderr, "%s %s() - ", testname, func);
+
76 va_start(ap, msg);
+
77 vfprintf(stderr, msg, ap);
+
78 va_end(ap);
+
79 fprintf(stderr, "\n");
+
80}
+
81
+
82static int is_dot_or_dotdot(const char *name) {
+
83 return name[0] == '.' &&
+
84 (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
+
85}
+
86
+
87static void success(void)
+
88{
+
89 fprintf(stderr, "%s OK\n", testname);
+
90}
+
91
+
92#define this_test (&tests[testnum-1])
+
93#define next_test (&tests[testnum])
+
94
+
95static void __start_test(const char *fmt, ...)
+
96{
+
97 unsigned int n;
+
98 va_list ap;
+
99 n = sprintf(testname, "%3i [", testnum);
+
100 va_start(ap, fmt);
+
101 n += vsprintf(testname + n, fmt, ap);
+
102 va_end(ap);
+
103 sprintf(testname + n, "]");
+
104 // Use dedicated testfile per test
+
105 sprintf(testfile, "%s/testfile.%d", basepath, testnum);
+
106 sprintf(testfile_r, "%s/testfile.%d", basepath_r, testnum);
+
107 if (testnum > MAX_TESTS) {
+
108 fprintf(stderr, "%s - too many tests\n", testname);
+
109 exit(1);
+
110 }
+
111 this_test->fd = -1;
+
112}
+
113
+
114#define start_test(msg, args...) { \
+
115 testnum++; \
+
116 if ((select_test && testnum != select_test) || \
+
117 (testnum == skip_test)) { \
+
118 return 0; \
+
119 } \
+
120 __start_test(msg, ##args); \
+
121}
+
122
+
123#define PERROR(msg) test_perror(__FUNCTION__, msg)
+
124#define ERROR(msg, args...) test_error(__FUNCTION__, msg, ##args)
+
125
+
126#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
127
+
128static int st_check_size(struct stat *st, int len)
+
129{
+
130 if (st->st_size != len) {
+
131 ERROR("length %u instead of %u", (int) st->st_size,
+
132 (int) len);
+
133 return -1;
+
134 }
+
135 return 0;
+
136}
+
137
+
138static int check_size(const char *path, int len)
+
139{
+
140 struct stat stbuf;
+
141 int res = stat(path, &stbuf);
+
142 if (res == -1) {
+
143 PERROR("stat");
+
144 return -1;
+
145 }
+
146 return st_check_size(&stbuf, len);
+
147}
+
148
+
149static int check_testfile_size(const char *path, int len)
+
150{
+
151 this_test->stat.st_size = len;
+
152 return check_size(path, len);
+
153}
+
154
+
155static int st_check_type(struct stat *st, mode_t type)
+
156{
+
157 if ((st->st_mode & S_IFMT) != type) {
+
158 ERROR("type 0%o instead of 0%o", st->st_mode & S_IFMT, type);
+
159 return -1;
+
160 }
+
161 return 0;
+
162}
+
163
+
164static int check_type(const char *path, mode_t type)
+
165{
+
166 struct stat stbuf;
+
167 int res = lstat(path, &stbuf);
+
168 if (res == -1) {
+
169 PERROR("lstat");
+
170 return -1;
+
171 }
+
172 return st_check_type(&stbuf, type);
+
173}
+
174
+
175static int st_check_mode(struct stat *st, mode_t mode)
+
176{
+
177 if ((st->st_mode & ALLPERMS) != mode) {
+
178 ERROR("mode 0%o instead of 0%o", st->st_mode & ALLPERMS,
+
179 mode);
+
180 return -1;
+
181 }
+
182 return 0;
+
183}
+
184
+
185static int check_mode(const char *path, mode_t mode)
+
186{
+
187 struct stat stbuf;
+
188 int res = lstat(path, &stbuf);
+
189 if (res == -1) {
+
190 PERROR("lstat");
+
191 return -1;
+
192 }
+
193 return st_check_mode(&stbuf, mode);
+
194}
+
195
+
196static int check_testfile_mode(const char *path, mode_t mode)
+
197{
+
198 this_test->stat.st_mode &= ~ALLPERMS;
+
199 this_test->stat.st_mode |= mode;
+
200 return check_mode(path, mode);
+
201}
+
202
+
203static int check_times(const char *path, time_t atime, time_t mtime)
+
204{
+
205 int err = 0;
+
206 struct stat stbuf;
+
207 int res = lstat(path, &stbuf);
+
208 if (res == -1) {
+
209 PERROR("lstat");
+
210 return -1;
+
211 }
+
212 if (stbuf.st_atime != atime) {
+
213 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
+
214 err--;
+
215 }
+
216 if (stbuf.st_mtime != mtime) {
+
217 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
+
218 err--;
+
219 }
+
220 if (err)
+
221 return -1;
+
222
+
223 return 0;
+
224}
+
225
+
226#if 0
+
227static int fcheck_times(int fd, time_t atime, time_t mtime)
+
228{
+
229 int err = 0;
+
230 struct stat stbuf;
+
231 int res = fstat(fd, &stbuf);
+
232 if (res == -1) {
+
233 PERROR("fstat");
+
234 return -1;
+
235 }
+
236 if (stbuf.st_atime != atime) {
+
237 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
+
238 err--;
+
239 }
+
240 if (stbuf.st_mtime != mtime) {
+
241 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
+
242 err--;
+
243 }
+
244 if (err)
+
245 return -1;
+
246
+
247 return 0;
+
248}
+
249#endif
+
250
+
251static int st_check_nlink(struct stat *st, nlink_t nlink)
+
252{
+
253 if (st->st_nlink != nlink) {
+
254 ERROR("nlink %li instead of %li", (long) st->st_nlink,
+
255 (long) nlink);
+
256 return -1;
+
257 }
+
258 return 0;
+
259}
+
260
+
261static int check_nlink(const char *path, nlink_t nlink)
+
262{
+
263 struct stat stbuf;
+
264 int res = lstat(path, &stbuf);
+
265 if (res == -1) {
+
266 PERROR("lstat");
+
267 return -1;
+
268 }
+
269 return st_check_nlink(&stbuf, nlink);
+
270}
+
271
+
272static int fcheck_stat(int fd, int flags, struct stat *st)
+
273{
+
274 struct stat stbuf;
+
275 int res = fstat(fd, &stbuf);
+
276 if (res == -1) {
+
277 if (flags & O_PATH) {
+
278 // With O_PATH fd, the server does not have to keep
+
279 // the inode alive so FUSE inode may be stale or bad
+
280 if (errno == ESTALE || errno == EIO ||
+
281 errno == ENOENT || errno == EBADF)
+
282 return 0;
+
283 }
+
284 PERROR("fstat");
+
285 return -1;
+
286 }
+
287
+
288 int err = 0;
+
289 err += st_check_type(&stbuf, st->st_mode & S_IFMT);
+
290 err += st_check_mode(&stbuf, st->st_mode & ALLPERMS);
+
291 err += st_check_size(&stbuf, st->st_size);
+
292 err += st_check_nlink(&stbuf, st->st_nlink);
+
293
+
294 return err;
+
295}
+
296
+
297static int check_nonexist(const char *path)
+
298{
+
299 struct stat stbuf;
+
300 int res = lstat(path, &stbuf);
+
301 if (res == 0) {
+
302 ERROR("file should not exist");
+
303 return -1;
+
304 }
+
305 if (errno != ENOENT) {
+
306 ERROR("file should not exist: %s", strerror(errno));
+
307 return -1;
+
308 }
+
309 return 0;
+
310}
+
311
+
312static int check_buffer(const char *buf, const char *data, unsigned len)
+
313{
+
314 if (memcmp(buf, data, len) != 0) {
+
315 ERROR("data mismatch");
+
316 return -1;
+
317 }
+
318 return 0;
+
319}
+
320
+
321static int check_data(const char *path, const char *data, int offset,
+
322 unsigned len)
+
323{
+
324 char buf[4096];
+
325 int res;
+
326 int fd = open(path, O_RDONLY);
+
327 if (fd == -1) {
+
328 PERROR("open");
+
329 return -1;
+
330 }
+
331 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
+
332 PERROR("lseek");
+
333 close(fd);
+
334 return -1;
+
335 }
+
336 while (len) {
+
337 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
+
338 res = read(fd, buf, rdlen);
+
339 if (res == -1) {
+
340 PERROR("read");
+
341 close(fd);
+
342 return -1;
+
343 }
+
344 if (res != rdlen) {
+
345 ERROR("short read: %u instead of %u", res, rdlen);
+
346 close(fd);
+
347 return -1;
+
348 }
+
349 if (check_buffer(buf, data, rdlen) != 0) {
+
350 close(fd);
+
351 return -1;
+
352 }
+
353 data += rdlen;
+
354 len -= rdlen;
+
355 }
+
356 res = close(fd);
+
357 if (res == -1) {
+
358 PERROR("close");
+
359 return -1;
+
360 }
+
361 return 0;
+
362}
+
363
+
364static int fcheck_data(int fd, const char *data, int offset,
+
365 unsigned len)
+
366{
+
367 char buf[4096];
+
368 int res;
+
369 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
+
370 PERROR("lseek");
+
371 return -1;
+
372 }
+
373 while (len) {
+
374 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
+
375 res = read(fd, buf, rdlen);
+
376 if (res == -1) {
+
377 PERROR("read");
+
378 return -1;
+
379 }
+
380 if (res != rdlen) {
+
381 ERROR("short read: %u instead of %u", res, rdlen);
+
382 return -1;
+
383 }
+
384 if (check_buffer(buf, data, rdlen) != 0) {
+
385 return -1;
+
386 }
+
387 data += rdlen;
+
388 len -= rdlen;
+
389 }
+
390 return 0;
+
391}
+
392
+
393static int check_dir_contents(const char *path, const char **contents)
+
394{
+
395 int i;
+
396 int res;
+
397 int err = 0;
+
398 int found[MAX_ENTRIES];
+
399 const char *cont[MAX_ENTRIES];
+
400 DIR *dp;
+
401
+
402 for (i = 0; contents[i]; i++) {
+
403 assert(i < MAX_ENTRIES - 3);
+
404 found[i] = 0;
+
405 cont[i] = contents[i];
+
406 }
+
407 cont[i] = NULL;
+
408
+
409 dp = opendir(path);
+
410 if (dp == NULL) {
+
411 PERROR("opendir");
+
412 return -1;
+
413 }
+
414 memset(found, 0, sizeof(found));
+
415 while(1) {
+
416 struct dirent *de;
+
417 errno = 0;
+
418 de = readdir(dp);
+
419 if (de == NULL) {
+
420 if (errno) {
+
421 PERROR("readdir");
+
422 closedir(dp);
+
423 return -1;
+
424 }
+
425 break;
+
426 }
+
427 if (is_dot_or_dotdot(de->d_name))
+
428 continue;
+
429 for (i = 0; cont[i] != NULL; i++) {
+
430 assert(i < MAX_ENTRIES);
+
431 if (strcmp(cont[i], de->d_name) == 0) {
+
432 if (found[i]) {
+
433 ERROR("duplicate entry <%s>",
+
434 de->d_name);
+
435 err--;
+
436 } else
+
437 found[i] = 1;
+
438 break;
+
439 }
+
440 }
+
441 if (!cont[i]) {
+
442 ERROR("unexpected entry <%s>", de->d_name);
+
443 err --;
+
444 }
+
445 }
+
446 for (i = 0; cont[i] != NULL; i++) {
+
447 if (!found[i]) {
+
448 ERROR("missing entry <%s>", cont[i]);
+
449 err--;
+
450 }
+
451 }
+
452 res = closedir(dp);
+
453 if (res == -1) {
+
454 PERROR("closedir");
+
455 return -1;
+
456 }
+
457 if (err)
+
458 return -1;
+
459
+
460 return 0;
+
461}
+
462
+
463static int create_file(const char *path, const char *data, int len)
+
464{
+
465 int res;
+
466 int fd;
+
467
+
468 unlink(path);
+
469 fd = creat(path, 0644);
+
470 if (fd == -1) {
+
471 PERROR("creat");
+
472 return -1;
+
473 }
+
474 if (len) {
+
475 res = write(fd, data, len);
+
476 if (res == -1) {
+
477 PERROR("write");
+
478 close(fd);
+
479 return -1;
+
480 }
+
481 if (res != len) {
+
482 ERROR("write is short: %u instead of %u", res, len);
+
483 close(fd);
+
484 return -1;
+
485 }
+
486 }
+
487 res = close(fd);
+
488 if (res == -1) {
+
489 PERROR("close");
+
490 return -1;
+
491 }
+
492 res = check_type(path, S_IFREG);
+
493 if (res == -1)
+
494 return -1;
+
495 res = check_mode(path, 0644);
+
496 if (res == -1)
+
497 return -1;
+
498 res = check_nlink(path, 1);
+
499 if (res == -1)
+
500 return -1;
+
501 res = check_size(path, len);
+
502 if (res == -1)
+
503 return -1;
+
504
+
505 if (len) {
+
506 res = check_data(path, data, 0, len);
+
507 if (res == -1)
+
508 return -1;
+
509 }
+
510
+
511 return 0;
+
512}
+
513
+
514static int create_path_fd(const char *path, const char *data, int len)
+
515{
+
516 int path_fd;
+
517 int res;
+
518
+
519 res = create_file(path, data, len);
+
520 if (res == -1)
+
521 return -1;
+
522
+
523 path_fd = open(path, O_PATH);
+
524 if (path_fd == -1)
+
525 PERROR("open(O_PATH)");
+
526
+
527 return path_fd;
+
528}
+
529
+
530// Can be called once per test
+
531static int create_testfile(const char *path, const char *data, int len)
+
532{
+
533 struct test *t = this_test;
+
534 struct stat *st = &t->stat;
+
535 int res, fd;
+
536
+
537 if (t->fd > 0) {
+
538 ERROR("testfile already created");
+
539 return -1;
+
540 }
+
541
+
542 fd = create_path_fd(path, data, len);
+
543 if (fd == -1)
+
544 return -1;
+
545
+
546 t->fd = fd;
+
547
+
548 res = fstat(fd, st);
+
549 if (res == -1) {
+
550 PERROR("fstat");
+
551 return -1;
+
552 }
+
553
+
554 return 0;
+
555}
+
556
+
557static int check_unlinked_testfile(int fd)
+
558{
+
559 struct stat *st = &this_test->stat;
+
560
+
561 st->st_nlink = 0;
+
562 return fcheck_stat(fd, O_PATH, st);
+
563}
+
564
+
565// Check recorded testfiles after all tests completed
+
566static int check_unlinked_testfiles(void)
+
567{
+
568 int fd;
+
569 int res, err = 0;
+
570 int num = testnum;
+
571
+
572 if (!unlinked_test)
+
573 return 0;
+
574
+
575 testnum = 0;
+
576 while (testnum < num) {
+
577 fd = next_test->fd;
+
578 start_test("check_unlinked_testfile");
+
579 if (fd == -1)
+
580 continue;
+
581
+
582 err += check_unlinked_testfile(fd);
+
583 res = close(fd);
+
584 if (res == -1) {
+
585 PERROR("close(test_fd)");
+
586 err--;
+
587 }
+
588 }
+
589
+
590 if (err) {
+
591 fprintf(stderr, "%i unlinked testfile checks failed\n", -err);
+
592 return 1;
+
593 }
+
594
+
595 return err;
+
596}
+
597
+
598static int cleanup_dir(const char *path, const char **dir_files, int quiet)
+
599{
+
600 int i;
+
601 int err = 0;
+
602
+
603 for (i = 0; dir_files[i]; i++) {
+
604 int res;
+
605 char fpath[1280];
+
606 sprintf(fpath, "%s/%s", path, dir_files[i]);
+
607 res = unlink(fpath);
+
608 if (res == -1 && !quiet) {
+
609 PERROR("unlink");
+
610 err --;
+
611 }
+
612 }
+
613 if (err)
+
614 return -1;
+
615
+
616 return 0;
+
617}
+
618
+
619static int create_dir(const char *path, const char **dir_files)
+
620{
+
621 int res;
+
622 int i;
+
623
+
624 rmdir(path);
+
625 res = mkdir(path, 0755);
+
626 if (res == -1) {
+
627 PERROR("mkdir");
+
628 return -1;
+
629 }
+
630 res = check_type(path, S_IFDIR);
+
631 if (res == -1)
+
632 return -1;
+
633 res = check_mode(path, 0755);
+
634 if (res == -1)
+
635 return -1;
+
636
+
637 for (i = 0; dir_files[i]; i++) {
+
638 char fpath[1280];
+
639 sprintf(fpath, "%s/%s", path, dir_files[i]);
+
640 res = create_file(fpath, "", 0);
+
641 if (res == -1) {
+
642 cleanup_dir(path, dir_files, 1);
+
643 return -1;
+
644 }
+
645 }
+
646 res = check_dir_contents(path, dir_files);
+
647 if (res == -1) {
+
648 cleanup_dir(path, dir_files, 1);
+
649 return -1;
+
650 }
+
651
+
652 return 0;
+
653}
+
654
+
655static int test_truncate(int len)
+
656{
+
657 const char *data = testdata;
+
658 int datalen = testdatalen;
+
659 int res;
+
660
+
661 start_test("truncate(%u)", (int) len);
+
662 res = create_testfile(testfile, data, datalen);
+
663 if (res == -1)
+
664 return -1;
+
665
+
666 res = truncate(testfile, len);
+
667 if (res == -1) {
+
668 PERROR("truncate");
+
669 return -1;
+
670 }
+
671 res = check_testfile_size(testfile, len);
+
672 if (res == -1)
+
673 return -1;
+
674
+
675 if (len > 0) {
+
676 if (len <= datalen) {
+
677 res = check_data(testfile, data, 0, len);
+
678 if (res == -1)
+
679 return -1;
+
680 } else {
+
681 res = check_data(testfile, data, 0, datalen);
+
682 if (res == -1)
+
683 return -1;
+
684 res = check_data(testfile, zerodata, datalen,
+
685 len - datalen);
+
686 if (res == -1)
+
687 return -1;
+
688 }
+
689 }
+
690 res = unlink(testfile);
+
691 if (res == -1) {
+
692 PERROR("unlink");
+
693 return -1;
+
694 }
+
695 res = check_nonexist(testfile);
+
696 if (res == -1)
+
697 return -1;
+
698
+
699 success();
+
700 return 0;
+
701}
+
702
+
703static int test_ftruncate(int len, int mode)
+
704{
+
705 const char *data = testdata;
+
706 int datalen = testdatalen;
+
707 int res;
+
708 int fd;
+
709
+
710 start_test("ftruncate(%u) mode: 0%03o", len, mode);
+
711 res = create_testfile(testfile, data, datalen);
+
712 if (res == -1)
+
713 return -1;
+
714
+
715 fd = open(testfile, O_WRONLY);
+
716 if (fd == -1) {
+
717 PERROR("open");
+
718 return -1;
+
719 }
+
720
+
721 res = fchmod(fd, mode);
+
722 if (res == -1) {
+
723 PERROR("fchmod");
+
724 close(fd);
+
725 return -1;
+
726 }
+
727 res = check_testfile_mode(testfile, mode);
+
728 if (res == -1) {
+
729 close(fd);
+
730 return -1;
+
731 }
+
732 res = ftruncate(fd, len);
+
733 if (res == -1) {
+
734 PERROR("ftruncate");
+
735 close(fd);
+
736 return -1;
+
737 }
+
738 close(fd);
+
739 res = check_testfile_size(testfile, len);
+
740 if (res == -1)
+
741 return -1;
+
742
+
743 if (len > 0) {
+
744 if (len <= datalen) {
+
745 res = check_data(testfile, data, 0, len);
+
746 if (res == -1)
+
747 return -1;
+
748 } else {
+
749 res = check_data(testfile, data, 0, datalen);
+
750 if (res == -1)
+
751 return -1;
+
752 res = check_data(testfile, zerodata, datalen,
+
753 len - datalen);
+
754 if (res == -1)
+
755 return -1;
+
756 }
+
757 }
+
758 res = unlink(testfile);
+
759 if (res == -1) {
+
760 PERROR("unlink");
+
761 return -1;
+
762 }
+
763 res = check_nonexist(testfile);
+
764 if (res == -1)
+
765 return -1;
+
766
+
767 success();
+
768 return 0;
+
769}
+
770
+
771static int test_seekdir(void)
+
772{
+
773 int i;
+
774 int res;
+
775 DIR *dp;
+
776 struct dirent *de = NULL;
+
777
+
778 start_test("seekdir");
+
779 res = create_dir(testdir, testdir_files);
+
780 if (res == -1)
+
781 return res;
+
782
+
783 dp = opendir(testdir);
+
784 if (dp == NULL) {
+
785 PERROR("opendir");
+
786 return -1;
+
787 }
+
788
+
789 /* Remember dir offsets */
+
790 for (i = 0; i < ARRAY_SIZE(seekdir_offsets); i++) {
+
791 seekdir_offsets[i] = telldir(dp);
+
792 errno = 0;
+
793 de = readdir(dp);
+
794 if (de == NULL) {
+
795 if (errno) {
+
796 PERROR("readdir");
+
797 goto fail;
+
798 }
+
799 break;
+
800 }
+
801 }
+
802
+
803 /* Walk until the end of directory */
+
804 while (de)
+
805 de = readdir(dp);
+
806
+
807 /* Start from the last valid dir offset and seek backwards */
+
808 for (i--; i >= 0; i--) {
+
809 seekdir(dp, seekdir_offsets[i]);
+
810 de = readdir(dp);
+
811 if (de == NULL) {
+
812 ERROR("Unexpected end of directory after seekdir()");
+
813 goto fail;
+
814 }
+
815 }
+
816
+
817 closedir(dp);
+
818 res = cleanup_dir(testdir, testdir_files, 0);
+
819 if (!res)
+
820 success();
+
821 return res;
+
822fail:
+
823 closedir(dp);
+
824 cleanup_dir(testdir, testdir_files, 1);
+
825 return -1;
+
826}
+
827
+
828#ifdef HAVE_COPY_FILE_RANGE
+
829static int test_copy_file_range(void)
+
830{
+
831 const char *data = testdata;
+
832 int datalen = testdatalen;
+
833 int err = 0;
+
834 int res;
+
835 int fd_in, fd_out;
+
836 off_t pos_in = 0, pos_out = 0;
+
837
+
838 start_test("copy_file_range");
+
839 unlink(testfile);
+
840 fd_in = open(testfile, O_CREAT | O_RDWR, 0644);
+
841 if (fd_in == -1) {
+
842 PERROR("creat");
+
843 return -1;
+
844 }
+
845 res = write(fd_in, data, datalen);
+
846 if (res == -1) {
+
847 PERROR("write");
+
848 close(fd_in);
+
849 return -1;
+
850 }
+
851 if (res != datalen) {
+
852 ERROR("write is short: %u instead of %u", res, datalen);
+
853 close(fd_in);
+
854 return -1;
+
855 }
+
856
+
857 unlink(testfile2);
+
858 fd_out = creat(testfile2, 0644);
+
859 if (fd_out == -1) {
+
860 PERROR("creat");
+
861 close(fd_in);
+
862 return -1;
+
863 }
+
864 res = copy_file_range(fd_in, &pos_in, fd_out, &pos_out, datalen, 0);
+
865 if (res == -1) {
+
866 PERROR("copy_file_range");
+
867 close(fd_in);
+
868 close(fd_out);
+
869 return -1;
+
870 }
+
871 if (res != datalen) {
+
872 ERROR("copy is short: %u instead of %u", res, datalen);
+
873 close(fd_in);
+
874 close(fd_out);
+
875 return -1;
+
876 }
+
877
+
878 res = close(fd_in);
+
879 if (res == -1) {
+
880 PERROR("close");
+
881 close(fd_out);
+
882 return -1;
+
883 }
+
884 res = close(fd_out);
+
885 if (res == -1) {
+
886 PERROR("close");
+
887 return -1;
+
888 }
+
889
+
890 err = check_data(testfile2, data, 0, datalen);
+
891
+
892 res = unlink(testfile);
+
893 if (res == -1) {
+
894 PERROR("unlink");
+
895 return -1;
+
896 }
+
897 res = check_nonexist(testfile);
+
898 if (res == -1)
+
899 return -1;
+
900 if (err)
+
901 return -1;
+
902
+
903 res = unlink(testfile2);
+
904 if (res == -1) {
+
905 PERROR("unlink");
+
906 return -1;
+
907 }
+
908 res = check_nonexist(testfile2);
+
909 if (res == -1)
+
910 return -1;
+
911 if (err)
+
912 return -1;
+
913
+
914 success();
+
915 return 0;
+
916}
+
917#else
+
918static int test_copy_file_range(void)
+
919{
+
920 return 0;
+
921}
+
922#endif
+
923
+
924static int test_utime(void)
+
925{
+
926 struct utimbuf utm;
+
927 time_t atime = 987631200;
+
928 time_t mtime = 123116400;
+
929 int res;
+
930
+
931 start_test("utime");
+
932 res = create_testfile(testfile, NULL, 0);
+
933 if (res == -1)
+
934 return -1;
+
935
+
936 utm.actime = atime;
+
937 utm.modtime = mtime;
+
938 res = utime(testfile, &utm);
+
939 if (res == -1) {
+
940 PERROR("utime");
+
941 return -1;
+
942 }
+
943 res = check_times(testfile, atime, mtime);
+
944 if (res == -1) {
+
945 return -1;
+
946 }
+
947 res = unlink(testfile);
+
948 if (res == -1) {
+
949 PERROR("unlink");
+
950 return -1;
+
951 }
+
952 res = check_nonexist(testfile);
+
953 if (res == -1)
+
954 return -1;
+
955
+
956 success();
+
957 return 0;
+
958}
+
959
+
960static int test_create(void)
+
961{
+
962 const char *data = testdata;
+
963 int datalen = testdatalen;
+
964 int err = 0;
+
965 int res;
+
966 int fd;
+
967
+
968 start_test("create");
+
969 unlink(testfile);
+
970 fd = creat(testfile, 0644);
+
971 if (fd == -1) {
+
972 PERROR("creat");
+
973 return -1;
+
974 }
+
975 res = write(fd, data, datalen);
+
976 if (res == -1) {
+
977 PERROR("write");
+
978 close(fd);
+
979 return -1;
+
980 }
+
981 if (res != datalen) {
+
982 ERROR("write is short: %u instead of %u", res, datalen);
+
983 close(fd);
+
984 return -1;
+
985 }
+
986 res = close(fd);
+
987 if (res == -1) {
+
988 PERROR("close");
+
989 return -1;
+
990 }
+
991 res = check_type(testfile, S_IFREG);
+
992 if (res == -1)
+
993 return -1;
+
994 err += check_mode(testfile, 0644);
+
995 err += check_nlink(testfile, 1);
+
996 err += check_size(testfile, datalen);
+
997 err += check_data(testfile, data, 0, datalen);
+
998 res = unlink(testfile);
+
999 if (res == -1) {
+
1000 PERROR("unlink");
+
1001 return -1;
+
1002 }
+
1003 res = check_nonexist(testfile);
+
1004 if (res == -1)
+
1005 return -1;
+
1006 if (err)
+
1007 return -1;
+
1008
+
1009 success();
+
1010 return 0;
+
1011}
+
1012
+
1013static int test_create_unlink(void)
+
1014{
+
1015 const char *data = testdata;
+
1016 int datalen = testdatalen;
+
1017 int err = 0;
+
1018 int res;
+
1019 int fd;
+
1020
+
1021 start_test("create+unlink");
+
1022 unlink(testfile);
+
1023 fd = open(testfile, O_CREAT | O_RDWR | O_TRUNC, 0644);
+
1024 if (fd == -1) {
+
1025 PERROR("creat");
+
1026 return -1;
+
1027 }
+
1028 res = unlink(testfile);
+
1029 if (res == -1) {
+
1030 PERROR("unlink");
+
1031 close(fd);
+
1032 return -1;
+
1033 }
+
1034 res = check_nonexist(testfile);
+
1035 if (res == -1) {
+
1036 close(fd);
+
1037 return -1;
+
1038 }
+
1039 res = write(fd, data, datalen);
+
1040 if (res == -1) {
+
1041 PERROR("write");
+
1042 close(fd);
+
1043 return -1;
+
1044 }
+
1045 if (res != datalen) {
+
1046 ERROR("write is short: %u instead of %u", res, datalen);
+
1047 close(fd);
+
1048 return -1;
+
1049 }
+
1050 struct stat st = {
+
1051 .st_mode = S_IFREG | 0644,
+
1052 .st_size = datalen,
+
1053 };
+
1054 err = fcheck_stat(fd, O_RDWR, &st);
+
1055 err += fcheck_data(fd, data, 0, datalen);
+
1056 res = close(fd);
+
1057 if (res == -1) {
+
1058 PERROR("close");
+
1059 err--;
+
1060 }
+
1061 if (err)
+
1062 return -1;
+
1063
+
1064 success();
+
1065 return 0;
+
1066}
+
1067
+
1068static int test_mknod(void)
+
1069{
+
1070 int err = 0;
+
1071 int res;
+
1072
+
1073 start_test("mknod");
+
1074 unlink(testfile);
+
1075 res = mknod(testfile, 0644, 0);
+
1076 if (res == -1) {
+
1077 PERROR("mknod");
+
1078 return -1;
+
1079 }
+
1080 res = check_type(testfile, S_IFREG);
+
1081 if (res == -1)
+
1082 return -1;
+
1083 err += check_mode(testfile, 0644);
+
1084 err += check_nlink(testfile, 1);
+
1085 err += check_size(testfile, 0);
+
1086 res = unlink(testfile);
+
1087 if (res == -1) {
+
1088 PERROR("unlink");
+
1089 return -1;
+
1090 }
+
1091 res = check_nonexist(testfile);
+
1092 if (res == -1)
+
1093 return -1;
+
1094 if (err)
+
1095 return -1;
+
1096
+
1097 success();
+
1098 return 0;
+
1099}
+
1100
+
1101#define test_open(exist, flags, mode) do_test_open(exist, flags, #flags, mode)
+
1102
+
1103static int do_test_open(int exist, int flags, const char *flags_str, int mode)
+
1104{
+
1105 char buf[4096];
+
1106 const char *data = testdata;
+
1107 int datalen = testdatalen;
+
1108 unsigned currlen = 0;
+
1109 int err = 0;
+
1110 int res;
+
1111 int fd;
+
1112 off_t off;
+
1113
+
1114 start_test("open(%s, %s, 0%03o)", exist ? "+" : "-", flags_str, mode);
+
1115 unlink(testfile);
+
1116 if (exist) {
+
1117 res = create_file(testfile_r, testdata2, testdata2len);
+
1118 if (res == -1)
+
1119 return -1;
+
1120
+
1121 currlen = testdata2len;
+
1122 }
+
1123
+
1124 fd = open(testfile, flags, mode);
+
1125 if ((flags & O_CREAT) && (flags & O_EXCL) && exist) {
+
1126 if (fd != -1) {
+
1127 ERROR("open should have failed");
+
1128 close(fd);
+
1129 return -1;
+
1130 } else if (errno == EEXIST)
+
1131 goto succ;
+
1132 }
+
1133 if (!(flags & O_CREAT) && !exist) {
+
1134 if (fd != -1) {
+
1135 ERROR("open should have failed");
+
1136 close(fd);
+
1137 return -1;
+
1138 } else if (errno == ENOENT)
+
1139 goto succ;
+
1140 }
+
1141 if (fd == -1) {
+
1142 PERROR("open");
+
1143 return -1;
+
1144 }
+
1145
+
1146 if (flags & O_TRUNC)
+
1147 currlen = 0;
+
1148
+
1149 err += check_type(testfile, S_IFREG);
+
1150 if (exist)
+
1151 err += check_mode(testfile, 0644);
+
1152 else
+
1153 err += check_mode(testfile, mode);
+
1154 err += check_nlink(testfile, 1);
+
1155 err += check_size(testfile, currlen);
+
1156 if (exist && !(flags & O_TRUNC) && (mode & S_IRUSR))
+
1157 err += check_data(testfile, testdata2, 0, testdata2len);
+
1158
+
1159 res = write(fd, data, datalen);
+
1160 if ((flags & O_ACCMODE) != O_RDONLY) {
+
1161 if (res == -1) {
+
1162 PERROR("write");
+
1163 err --;
+
1164 } else if (res != datalen) {
+
1165 ERROR("write is short: %u instead of %u", res, datalen);
+
1166 err --;
+
1167 } else {
+
1168 if (datalen > (int) currlen)
+
1169 currlen = datalen;
+
1170
+
1171 err += check_size(testfile, currlen);
+
1172
+
1173 if (mode & S_IRUSR) {
+
1174 err += check_data(testfile, data, 0, datalen);
+
1175 if (exist && !(flags & O_TRUNC) &&
+
1176 testdata2len > datalen)
+
1177 err += check_data(testfile,
+
1178 testdata2 + datalen,
+
1179 datalen,
+
1180 testdata2len - datalen);
+
1181 }
+
1182 }
+
1183 } else {
+
1184 if (res != -1) {
+
1185 ERROR("write should have failed");
+
1186 err --;
+
1187 } else if (errno != EBADF) {
+
1188 PERROR("write");
+
1189 err --;
+
1190 }
+
1191 }
+
1192 off = lseek(fd, SEEK_SET, 0);
+
1193 if (off == (off_t) -1) {
+
1194 PERROR("lseek");
+
1195 err--;
+
1196 } else if (off != 0) {
+
1197 ERROR("offset should have returned 0");
+
1198 err --;
+
1199 }
+
1200 res = read(fd, buf, sizeof(buf));
+
1201 if ((flags & O_ACCMODE) != O_WRONLY) {
+
1202 if (res == -1) {
+
1203 PERROR("read");
+
1204 err--;
+
1205 } else {
+
1206 int readsize =
+
1207 currlen < sizeof(buf) ? currlen : sizeof(buf);
+
1208 if (res != readsize) {
+
1209 ERROR("read is short: %i instead of %u",
+
1210 res, readsize);
+
1211 err--;
+
1212 } else {
+
1213 if ((flags & O_ACCMODE) != O_RDONLY) {
+
1214 err += check_buffer(buf, data, datalen);
+
1215 if (exist && !(flags & O_TRUNC) &&
+
1216 testdata2len > datalen)
+
1217 err += check_buffer(buf + datalen,
+
1218 testdata2 + datalen,
+
1219 testdata2len - datalen);
+
1220 } else if (exist)
+
1221 err += check_buffer(buf, testdata2,
+
1222 testdata2len);
+
1223 }
+
1224 }
+
1225 } else {
+
1226 if (res != -1) {
+
1227 ERROR("read should have failed");
+
1228 err --;
+
1229 } else if (errno != EBADF) {
+
1230 PERROR("read");
+
1231 err --;
+
1232 }
+
1233 }
+
1234
+
1235 res = close(fd);
+
1236 if (res == -1) {
+
1237 PERROR("close");
+
1238 return -1;
+
1239 }
+
1240 res = unlink(testfile);
+
1241 if (res == -1) {
+
1242 PERROR("unlink");
+
1243 return -1;
+
1244 }
+
1245 res = check_nonexist(testfile);
+
1246 if (res == -1)
+
1247 return -1;
+
1248 res = check_nonexist(testfile_r);
+
1249 if (res == -1)
+
1250 return -1;
+
1251 if (err)
+
1252 return -1;
+
1253
+
1254succ:
+
1255 success();
+
1256 return 0;
+
1257}
+
1258
+
1259#define test_open_acc(flags, mode, err) \
+
1260 do_test_open_acc(flags, #flags, mode, err)
+
1261
+
1262static int do_test_open_acc(int flags, const char *flags_str, int mode, int err)
+
1263{
+
1264 const char *data = testdata;
+
1265 int datalen = testdatalen;
+
1266 int res;
+
1267 int fd;
+
1268
+
1269 start_test("open_acc(%s) mode: 0%03o message: '%s'", flags_str, mode,
+
1270 strerror(err));
+
1271 unlink(testfile);
+
1272 res = create_testfile(testfile, data, datalen);
+
1273 if (res == -1)
+
1274 return -1;
+
1275
+
1276 res = chmod(testfile, mode);
+
1277 if (res == -1) {
+
1278 PERROR("chmod");
+
1279 return -1;
+
1280 }
+
1281
+
1282 res = check_testfile_mode(testfile, mode);
+
1283 if (res == -1)
+
1284 return -1;
+
1285
+
1286 fd = open(testfile, flags);
+
1287 if (fd == -1) {
+
1288 if (err != errno) {
+
1289 PERROR("open");
+
1290 return -1;
+
1291 }
+
1292 } else {
+
1293 if (err) {
+
1294 ERROR("open should have failed");
+
1295 close(fd);
+
1296 return -1;
+
1297 }
+
1298 close(fd);
+
1299 }
+
1300
+
1301 res = unlink(testfile);
+
1302 if (res == -1) {
+
1303 PERROR("unlink");
+
1304 return -1;
+
1305 }
+
1306 res = check_nonexist(testfile);
+
1307 if (res == -1)
+
1308 return -1;
+
1309 res = check_nonexist(testfile_r);
+
1310 if (res == -1)
+
1311 return -1;
+
1312
+
1313 success();
+
1314 return 0;
+
1315}
+
1316
+
1317static int test_symlink(void)
+
1318{
+
1319 char buf[1024];
+
1320 const char *data = testdata;
+
1321 int datalen = testdatalen;
+
1322 int linklen = strlen(testfile);
+
1323 int err = 0;
+
1324 int res;
+
1325
+
1326 start_test("symlink");
+
1327 res = create_testfile(testfile, data, datalen);
+
1328 if (res == -1)
+
1329 return -1;
+
1330
+
1331 unlink(testfile2);
+
1332 res = symlink(testfile, testfile2);
+
1333 if (res == -1) {
+
1334 PERROR("symlink");
+
1335 return -1;
+
1336 }
+
1337 res = check_type(testfile2, S_IFLNK);
+
1338 if (res == -1)
+
1339 return -1;
+
1340 err += check_mode(testfile2, 0777);
+
1341 err += check_nlink(testfile2, 1);
+
1342 res = readlink(testfile2, buf, sizeof(buf));
+
1343 if (res == -1) {
+
1344 PERROR("readlink");
+
1345 err--;
+
1346 }
+
1347 if (res != linklen) {
+
1348 ERROR("short readlink: %u instead of %u", res, linklen);
+
1349 err--;
+
1350 }
+
1351 if (memcmp(buf, testfile, linklen) != 0) {
+
1352 ERROR("link mismatch");
+
1353 err--;
+
1354 }
+
1355 err += check_size(testfile2, datalen);
+
1356 err += check_data(testfile2, data, 0, datalen);
+
1357 res = unlink(testfile2);
+
1358 if (res == -1) {
+
1359 PERROR("unlink");
+
1360 return -1;
+
1361 }
+
1362 res = check_nonexist(testfile2);
+
1363 if (res == -1)
+
1364 return -1;
+
1365 if (err)
+
1366 return -1;
+
1367
+
1368 res = unlink(testfile);
+
1369 if (res == -1) {
+
1370 PERROR("unlink");
+
1371 return -1;
+
1372 }
+
1373 res = check_nonexist(testfile);
+
1374 if (res == -1)
+
1375 return -1;
+
1376
+
1377 success();
+
1378 return 0;
+
1379}
+
1380
+
1381static int test_link(void)
+
1382{
+
1383 const char *data = testdata;
+
1384 int datalen = testdatalen;
+
1385 int err = 0;
+
1386 int res;
+
1387
+
1388 start_test("link");
+
1389 res = create_testfile(testfile, data, datalen);
+
1390 if (res == -1)
+
1391 return -1;
+
1392
+
1393 unlink(testfile2);
+
1394 res = link(testfile, testfile2);
+
1395 if (res == -1) {
+
1396 PERROR("link");
+
1397 return -1;
+
1398 }
+
1399 res = check_type(testfile2, S_IFREG);
+
1400 if (res == -1)
+
1401 return -1;
+
1402 err += check_mode(testfile2, 0644);
+
1403 err += check_nlink(testfile2, 2);
+
1404 err += check_size(testfile2, datalen);
+
1405 err += check_data(testfile2, data, 0, datalen);
+
1406 res = unlink(testfile);
+
1407 if (res == -1) {
+
1408 PERROR("unlink");
+
1409 return -1;
+
1410 }
+
1411 res = check_nonexist(testfile);
+
1412 if (res == -1)
+
1413 return -1;
+
1414
+
1415 err += check_nlink(testfile2, 1);
+
1416 res = unlink(testfile2);
+
1417 if (res == -1) {
+
1418 PERROR("unlink");
+
1419 return -1;
+
1420 }
+
1421 res = check_nonexist(testfile2);
+
1422 if (res == -1)
+
1423 return -1;
+
1424 if (err)
+
1425 return -1;
+
1426
+
1427 success();
+
1428 return 0;
+
1429}
+
1430
+
1431static int test_link2(void)
+
1432{
+
1433 const char *data = testdata;
+
1434 int datalen = testdatalen;
+
1435 int err = 0;
+
1436 int res;
+
1437
+
1438 start_test("link-unlink-link");
+
1439 res = create_testfile(testfile, data, datalen);
+
1440 if (res == -1)
+
1441 return -1;
+
1442
+
1443 unlink(testfile2);
+
1444 res = link(testfile, testfile2);
+
1445 if (res == -1) {
+
1446 PERROR("link");
+
1447 return -1;
+
1448 }
+
1449 res = unlink(testfile);
+
1450 if (res == -1) {
+
1451 PERROR("unlink");
+
1452 return -1;
+
1453 }
+
1454 res = check_nonexist(testfile);
+
1455 if (res == -1)
+
1456 return -1;
+
1457 res = link(testfile2, testfile);
+
1458 if (res == -1) {
+
1459 PERROR("link");
+
1460 }
+
1461 res = check_type(testfile, S_IFREG);
+
1462 if (res == -1)
+
1463 return -1;
+
1464 err += check_mode(testfile, 0644);
+
1465 err += check_nlink(testfile, 2);
+
1466 err += check_size(testfile, datalen);
+
1467 err += check_data(testfile, data, 0, datalen);
+
1468
+
1469 res = unlink(testfile2);
+
1470 if (res == -1) {
+
1471 PERROR("unlink");
+
1472 return -1;
+
1473 }
+
1474 err += check_nlink(testfile, 1);
+
1475 res = unlink(testfile);
+
1476 if (res == -1) {
+
1477 PERROR("unlink");
+
1478 return -1;
+
1479 }
+
1480 res = check_nonexist(testfile);
+
1481 if (res == -1)
+
1482 return -1;
+
1483 if (err)
+
1484 return -1;
+
1485
+
1486 success();
+
1487 return 0;
+
1488}
+
1489
+
1490static int test_rename_file(void)
+
1491{
+
1492 const char *data = testdata;
+
1493 int datalen = testdatalen;
+
1494 int err = 0;
+
1495 int res;
+
1496
+
1497 start_test("rename file");
+
1498 res = create_testfile(testfile, data, datalen);
+
1499 if (res == -1)
+
1500 return -1;
+
1501
+
1502 unlink(testfile2);
+
1503 res = rename(testfile, testfile2);
+
1504 if (res == -1) {
+
1505 PERROR("rename");
+
1506 return -1;
+
1507 }
+
1508 res = check_nonexist(testfile);
+
1509 if (res == -1)
+
1510 return -1;
+
1511 res = check_type(testfile2, S_IFREG);
+
1512 if (res == -1)
+
1513 return -1;
+
1514 err += check_mode(testfile2, 0644);
+
1515 err += check_nlink(testfile2, 1);
+
1516 err += check_size(testfile2, datalen);
+
1517 err += check_data(testfile2, data, 0, datalen);
+
1518 res = unlink(testfile2);
+
1519 if (res == -1) {
+
1520 PERROR("unlink");
+
1521 return -1;
+
1522 }
+
1523 res = check_nonexist(testfile2);
+
1524 if (res == -1)
+
1525 return -1;
+
1526 if (err)
+
1527 return -1;
+
1528
+
1529 success();
+
1530 return 0;
+
1531}
+
1532
+
1533static int test_rename_dir(void)
+
1534{
+
1535 int err = 0;
+
1536 int res;
+
1537
+
1538 start_test("rename dir");
+
1539 res = create_dir(testdir, testdir_files);
+
1540 if (res == -1)
+
1541 return -1;
+
1542
+
1543 rmdir(testdir2);
+
1544 res = rename(testdir, testdir2);
+
1545 if (res == -1) {
+
1546 PERROR("rename");
+
1547 cleanup_dir(testdir, testdir_files, 1);
+
1548 return -1;
+
1549 }
+
1550 res = check_nonexist(testdir);
+
1551 if (res == -1) {
+
1552 cleanup_dir(testdir, testdir_files, 1);
+
1553 return -1;
+
1554 }
+
1555 res = check_type(testdir2, S_IFDIR);
+
1556 if (res == -1) {
+
1557 cleanup_dir(testdir2, testdir_files, 1);
+
1558 return -1;
+
1559 }
+
1560 err += check_mode(testdir2, 0755);
+
1561 err += check_dir_contents(testdir2, testdir_files);
+
1562 err += cleanup_dir(testdir2, testdir_files, 0);
+
1563 res = rmdir(testdir2);
+
1564 if (res == -1) {
+
1565 PERROR("rmdir");
+
1566 return -1;
+
1567 }
+
1568 res = check_nonexist(testdir2);
+
1569 if (res == -1)
+
1570 return -1;
+
1571 if (err)
+
1572 return -1;
+
1573
+
1574 success();
+
1575 return 0;
+
1576}
+
1577
+
1578static int test_rename_dir_loop(void)
+
1579{
+
1580#define PATH(p) (snprintf(path, sizeof path, "%s/%s", testdir, p), path)
+
1581#define PATH2(p) (snprintf(path2, sizeof path2, "%s/%s", testdir, p), path2)
+
1582
+
1583 char path[1280], path2[1280];
+
1584 int err = 0;
+
1585 int res;
+
1586
+
1587 start_test("rename dir loop");
+
1588
+
1589 res = create_dir(testdir, testdir_files);
+
1590 if (res == -1)
+
1591 return -1;
+
1592
+
1593 res = mkdir(PATH("a"), 0755);
+
1594 if (res == -1) {
+
1595 PERROR("mkdir");
+
1596 goto fail;
+
1597 }
+
1598
+
1599 res = rename(PATH("a"), PATH2("a"));
+
1600 if (res == -1) {
+
1601 PERROR("rename");
+
1602 goto fail;
+
1603 }
+
1604
+
1605 errno = 0;
+
1606 res = rename(PATH("a"), PATH2("a/b"));
+
1607 if (res == 0 || errno != EINVAL) {
+
1608 PERROR("rename");
+
1609 goto fail;
+
1610 }
+
1611
+
1612 res = mkdir(PATH("a/b"), 0755);
+
1613 if (res == -1) {
+
1614 PERROR("mkdir");
+
1615 goto fail;
+
1616 }
+
1617
+
1618 res = mkdir(PATH("a/b/c"), 0755);
+
1619 if (res == -1) {
+
1620 PERROR("mkdir");
+
1621 goto fail;
+
1622 }
+
1623
+
1624 errno = 0;
+
1625 res = rename(PATH("a"), PATH2("a/b/c"));
+
1626 if (res == 0 || errno != EINVAL) {
+
1627 PERROR("rename");
+
1628 goto fail;
+
1629 }
+
1630
+
1631 errno = 0;
+
1632 res = rename(PATH("a"), PATH2("a/b/c/a"));
+
1633 if (res == 0 || errno != EINVAL) {
+
1634 PERROR("rename");
+
1635 goto fail;
+
1636 }
+
1637
+
1638 errno = 0;
+
1639 res = rename(PATH("a/b/c"), PATH2("a"));
+
1640 if (res == 0 || errno != ENOTEMPTY) {
+
1641 PERROR("rename");
+
1642 goto fail;
+
1643 }
+
1644
+
1645 res = open(PATH("a/foo"), O_CREAT, 0644);
+
1646 if (res == -1) {
+
1647 PERROR("open");
+
1648 goto fail;
+
1649 }
+
1650 close(res);
+
1651
+
1652 res = rename(PATH("a/foo"), PATH2("a/bar"));
+
1653 if (res == -1) {
+
1654 PERROR("rename");
+
1655 goto fail;
+
1656 }
+
1657
+
1658 res = rename(PATH("a/bar"), PATH2("a/foo"));
+
1659 if (res == -1) {
+
1660 PERROR("rename");
+
1661 goto fail;
+
1662 }
+
1663
+
1664 res = rename(PATH("a/foo"), PATH2("a/b/bar"));
+
1665 if (res == -1) {
+
1666 PERROR("rename");
+
1667 goto fail;
+
1668 }
+
1669
+
1670 res = rename(PATH("a/b/bar"), PATH2("a/foo"));
+
1671 if (res == -1) {
+
1672 PERROR("rename");
+
1673 goto fail;
+
1674 }
+
1675
+
1676 res = rename(PATH("a/foo"), PATH2("a/b/c/bar"));
+
1677 if (res == -1) {
+
1678 PERROR("rename");
+
1679 goto fail;
+
1680 }
+
1681
+
1682 res = rename(PATH("a/b/c/bar"), PATH2("a/foo"));
+
1683 if (res == -1) {
+
1684 PERROR("rename");
+
1685 goto fail;
+
1686 }
+
1687
+
1688 res = open(PATH("a/bar"), O_CREAT, 0644);
+
1689 if (res == -1) {
+
1690 PERROR("open");
+
1691 goto fail;
+
1692 }
+
1693 close(res);
+
1694
+
1695 res = rename(PATH("a/foo"), PATH2("a/bar"));
+
1696 if (res == -1) {
+
1697 PERROR("rename");
+
1698 goto fail;
+
1699 }
+
1700
+
1701 unlink(PATH("a/bar"));
+
1702
+
1703 res = rename(PATH("a/b"), PATH2("a/d"));
+
1704 if (res == -1) {
+
1705 PERROR("rename");
+
1706 goto fail;
+
1707 }
+
1708
+
1709 res = rename(PATH("a/d"), PATH2("a/b"));
+
1710 if (res == -1) {
+
1711 PERROR("rename");
+
1712 goto fail;
+
1713 }
+
1714
+
1715 res = mkdir(PATH("a/d"), 0755);
+
1716 if (res == -1) {
+
1717 PERROR("mkdir");
+
1718 goto fail;
+
1719 }
+
1720
+
1721 res = rename(PATH("a/b"), PATH2("a/d"));
+
1722 if (res == -1) {
+
1723 PERROR("rename");
+
1724 goto fail;
+
1725 }
+
1726
+
1727 res = rename(PATH("a/d"), PATH2("a/b"));
+
1728 if (res == -1) {
+
1729 PERROR("rename");
+
1730 goto fail;
+
1731 }
+
1732
+
1733 res = mkdir(PATH("a/d"), 0755);
+
1734 if (res == -1) {
+
1735 PERROR("mkdir");
+
1736 goto fail;
+
1737 }
+
1738
+
1739 res = mkdir(PATH("a/d/e"), 0755);
+
1740 if (res == -1) {
+
1741 PERROR("mkdir");
+
1742 goto fail;
+
1743 }
+
1744
+
1745 errno = 0;
+
1746 res = rename(PATH("a/b"), PATH2("a/d"));
+
1747 if (res == 0 || (errno != ENOTEMPTY && errno != EEXIST)) {
+
1748 PERROR("rename");
+
1749 goto fail;
+
1750 }
+
1751
+
1752 rmdir(PATH("a/d/e"));
+
1753 rmdir(PATH("a/d"));
+
1754
+
1755 rmdir(PATH("a/b/c"));
+
1756 rmdir(PATH("a/b"));
+
1757 rmdir(PATH("a"));
+
1758
+
1759 err += cleanup_dir(testdir, testdir_files, 0);
+
1760 res = rmdir(testdir);
+
1761 if (res == -1) {
+
1762 PERROR("rmdir");
+
1763 goto fail;
+
1764 }
+
1765 res = check_nonexist(testdir);
+
1766 if (res == -1)
+
1767 return -1;
+
1768 if (err)
+
1769 return -1;
+
1770
+
1771 success();
+
1772 return 0;
+
1773
+
1774fail:
+
1775 unlink(PATH("a/bar"));
+
1776
+
1777 rmdir(PATH("a/d/e"));
+
1778 rmdir(PATH("a/d"));
+
1779
+
1780 rmdir(PATH("a/b/c"));
+
1781 rmdir(PATH("a/b"));
+
1782 rmdir(PATH("a"));
+
1783
+
1784 cleanup_dir(testdir, testdir_files, 1);
+
1785 rmdir(testdir);
+
1786
+
1787 return -1;
+
1788
+
1789#undef PATH2
+
1790#undef PATH
+
1791}
+
1792
+
1793static int test_mkfifo(void)
+
1794{
+
1795 int res;
+
1796 int err = 0;
+
1797
+
1798 start_test("mkfifo");
+
1799 unlink(testfile);
+
1800 res = mkfifo(testfile, 0644);
+
1801 if (res == -1) {
+
1802 PERROR("mkfifo");
+
1803 return -1;
+
1804 }
+
1805 res = check_type(testfile, S_IFIFO);
+
1806 if (res == -1)
+
1807 return -1;
+
1808 err += check_mode(testfile, 0644);
+
1809 err += check_nlink(testfile, 1);
+
1810 res = unlink(testfile);
+
1811 if (res == -1) {
+
1812 PERROR("unlink");
+
1813 return -1;
+
1814 }
+
1815 res = check_nonexist(testfile);
+
1816 if (res == -1)
+
1817 return -1;
+
1818 if (err)
+
1819 return -1;
+
1820
+
1821 success();
+
1822 return 0;
+
1823}
+
1824
+
1825static int test_mkdir(void)
+
1826{
+
1827 int res;
+
1828 int err = 0;
+
1829 const char *dir_contents[] = {NULL};
+
1830
+
1831 start_test("mkdir");
+
1832 rmdir(testdir);
+
1833 res = mkdir(testdir, 0755);
+
1834 if (res == -1) {
+
1835 PERROR("mkdir");
+
1836 return -1;
+
1837 }
+
1838 res = check_type(testdir, S_IFDIR);
+
1839 if (res == -1)
+
1840 return -1;
+
1841 err += check_mode(testdir, 0755);
+
1842 /* Some file systems (like btrfs) don't track link
+
1843 count for directories */
+
1844 //err += check_nlink(testdir, 2);
+
1845 err += check_dir_contents(testdir, dir_contents);
+
1846 res = rmdir(testdir);
+
1847 if (res == -1) {
+
1848 PERROR("rmdir");
+
1849 return -1;
+
1850 }
+
1851 res = check_nonexist(testdir);
+
1852 if (res == -1)
+
1853 return -1;
+
1854 if (err)
+
1855 return -1;
+
1856
+
1857 success();
+
1858 return 0;
+
1859}
+
1860
+
1861static int test_socket(void)
+
1862{
+
1863 struct sockaddr_un su;
+
1864 int fd;
+
1865 int res;
+
1866 int err = 0;
+
1867 const size_t test_sock_len = strlen(testsock) + 1;
+
1868
+
1869 start_test("socket");
+
1870 if (test_sock_len > sizeof(su.sun_path)) {
+
1871 fprintf(stderr, "Need to shorten mount point by %zu chars\n",
+
1872 strlen(testsock) + 1 - sizeof(su.sun_path));
+
1873 return -1;
+
1874 }
+
1875 unlink(testsock);
+
1876 fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
1877 if (fd < 0) {
+
1878 PERROR("socket");
+
1879 return -1;
+
1880 }
+
1881 su.sun_family = AF_UNIX;
+
1882
+
1883 strncpy(su.sun_path, testsock, test_sock_len);
+
1884 su.sun_path[sizeof(su.sun_path) - 1] = '\0';
+
1885 res = bind(fd, (struct sockaddr*)&su, sizeof(su));
+
1886 if (res == -1) {
+
1887 PERROR("bind");
+
1888 return -1;
+
1889 }
+
1890
+
1891 res = check_type(testsock, S_IFSOCK);
+
1892 if (res == -1) {
+
1893 close(fd);
+
1894 return -1;
+
1895 }
+
1896 err += check_nlink(testsock, 1);
+
1897 close(fd);
+
1898 res = unlink(testsock);
+
1899 if (res == -1) {
+
1900 PERROR("unlink");
+
1901 return -1;
+
1902 }
+
1903 res = check_nonexist(testsock);
+
1904 if (res == -1)
+
1905 return -1;
+
1906 if (err)
+
1907 return -1;
+
1908
+
1909 success();
+
1910 return 0;
+
1911}
+
1912
+
1913#define test_create_ro_dir(flags) \
+
1914 do_test_create_ro_dir(flags, #flags)
+
1915
+
1916static int do_test_create_ro_dir(int flags, const char *flags_str)
+
1917{
+
1918 int res;
+
1919 int err = 0;
+
1920 int fd;
+
1921
+
1922 start_test("open(%s) in read-only directory", flags_str);
+
1923 rmdir(testdir);
+
1924 res = mkdir(testdir, 0555);
+
1925 if (res == -1) {
+
1926 PERROR("mkdir");
+
1927 return -1;
+
1928 }
+
1929 fd = open(subfile, flags, 0644);
+
1930 if (fd != -1) {
+
1931 close(fd);
+
1932 unlink(subfile);
+
1933 ERROR("open should have failed");
+
1934 err--;
+
1935 } else {
+
1936 res = check_nonexist(subfile);
+
1937 if (res == -1)
+
1938 err--;
+
1939 }
+
1940 unlink(subfile);
+
1941 res = rmdir(testdir);
+
1942 if (res == -1) {
+
1943 PERROR("rmdir");
+
1944 return -1;
+
1945 }
+
1946 res = check_nonexist(testdir);
+
1947 if (res == -1)
+
1948 return -1;
+
1949 if (err)
+
1950 return -1;
+
1951
+
1952 success();
+
1953 return 0;
+
1954}
+
1955
+
1956#ifndef __FreeBSD__
+
1957/* this tests open with O_TMPFILE
+
1958 note that this will only work with the fuse low level api
+
1959 you will get ENOTSUP with the high level api */
+
1960static int test_create_tmpfile(void)
+
1961{
+
1962 rmdir(testdir);
+
1963 int res = mkdir(testdir, 0777);
+
1964 if (res)
+
1965 return -1;
+
1966
+
1967 start_test("create tmpfile");
+
1968
+
1969 int fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
+
1970 if(fd == -1) {
+
1971 if (errno == ENOTSUP) {
+
1972 /* don't bother if we're working on an old kernel
+
1973 or on the high level API */
+
1974 return 0;
+
1975 }
+
1976
+
1977 PERROR("open O_TMPFILE | O_RDWR");
+
1978 return -1;
+
1979 }
+
1980 close(fd);
+
1981
+
1982 fd = open(testdir, O_TMPFILE | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
+
1983 if(fd == -1){
+
1984 PERROR("open with O_TMPFILE | O_WRONLY | O_EXCL");
+
1985 return -1;
+
1986 };
+
1987 close(fd);
+
1988
+
1989 fd = open(testdir, O_TMPFILE | O_RDONLY, S_IRUSR);
+
1990 if (fd != -1) {
+
1991 ERROR("open with O_TMPFILE | O_RDONLY succeeded");
+
1992 return -1;
+
1993 }
+
1994
+
1995 success();
+
1996 return 0;
+
1997}
+
1998
+
1999static int test_create_and_link_tmpfile(void)
+
2000{
+
2001 /* skip this test for now since the github runner will fail in the linkat call below */
+
2002 return 0;
+
2003
+
2004 rmdir(testdir);
+
2005 unlink(testfile);
+
2006
+
2007 int res = mkdir(testdir, 0777);
+
2008 if (res)
+
2009 return -1;
+
2010
+
2011 start_test("create and link tmpfile");
+
2012
+
2013 int fd = open(testdir, O_TMPFILE | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR);
+
2014 if(fd == -1) {
+
2015 if (errno == ENOTSUP) {
+
2016 /* don't bother if we're working on an old kernel
+
2017 or on the high level API */
+
2018 return 0;
+
2019 }
+
2020 PERROR("open with O_TMPFILE | O_RDWR | O_EXCL");
+
2021 return -1;
+
2022 }
+
2023
+
2024 if (!linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
+
2025 ERROR("linkat succeeded on a tmpfile opened with O_EXCL");
+
2026 return -1;
+
2027 }
+
2028 close(fd);
+
2029
+
2030 fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
+
2031 if(fd == -1) {
+
2032 PERROR("open O_TMPFILE");
+
2033 return -1;
+
2034 }
+
2035
+
2036 if (check_nonexist(testfile)) {
+
2037 return -1;
+
2038 }
+
2039
+
2040 if (linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
+
2041 PERROR("linkat tempfile");
+
2042 return -1;
+
2043 }
+
2044 close(fd);
+
2045
+
2046 if (check_nlink(testfile, 1)) {
+
2047 return -1;
+
2048 }
+
2049 unlink(testfile);
+
2050
+
2051 success();
+
2052 return 0;
+
2053}
+
2054#endif
+
2055
+
2056int main(int argc, char *argv[])
+
2057{
+
2058 int err = 0;
+
2059 int a;
+
2060 int is_root;
+
2061
+
2062 umask(0);
+
2063 if (argc < 2 || argc > 4) {
+
2064 fprintf(stderr, "usage: %s testdir [:realdir] [[-]test#] [-u]\n", argv[0]);
+
2065 return 1;
+
2066 }
+
2067 basepath = argv[1];
+
2068 basepath_r = basepath;
+
2069 for (a = 2; a < argc; a++) {
+
2070 char *endptr;
+
2071 char *arg = argv[a];
+
2072 if (arg[0] == ':') {
+
2073 basepath_r = arg + 1;
+
2074 } else {
+
2075 if (arg[0] == '-') {
+
2076 arg++;
+
2077 if (arg[0] == 'u') {
+
2078 unlinked_test = 1;
+
2079 endptr = arg + 1;
+
2080 } else {
+
2081 skip_test = strtoul(arg, &endptr, 10);
+
2082 }
+
2083 } else {
+
2084 select_test = strtoul(arg, &endptr, 10);
+
2085 }
+
2086 if (arg[0] == '\0' || *endptr != '\0') {
+
2087 fprintf(stderr, "invalid option: '%s'\n", argv[a]);
+
2088 return 1;
+
2089 }
+
2090 }
+
2091 }
+
2092 assert(strlen(basepath) < 512);
+
2093 assert(strlen(basepath_r) < 512);
+
2094 if (basepath[0] != '/') {
+
2095 fprintf(stderr, "testdir must be an absolute path\n");
+
2096 return 1;
+
2097 }
+
2098
+
2099 sprintf(testfile, "%s/testfile", basepath);
+
2100 sprintf(testfile2, "%s/testfile2", basepath);
+
2101 sprintf(testdir, "%s/testdir", basepath);
+
2102 sprintf(testdir2, "%s/testdir2", basepath);
+
2103 sprintf(subfile, "%s/subfile", testdir2);
+
2104 sprintf(testsock, "%s/testsock", basepath);
+
2105
+
2106 sprintf(testfile_r, "%s/testfile", basepath_r);
+
2107 sprintf(testfile2_r, "%s/testfile2", basepath_r);
+
2108 sprintf(testdir_r, "%s/testdir", basepath_r);
+
2109 sprintf(testdir2_r, "%s/testdir2", basepath_r);
+
2110 sprintf(subfile_r, "%s/subfile", testdir2_r);
+
2111
+
2112 is_root = (geteuid() == 0);
+
2113
+
2114 err += test_create();
+
2115 err += test_create_unlink();
+
2116 err += test_symlink();
+
2117 err += test_link();
+
2118 err += test_link2();
+
2119 err += test_mknod();
+
2120 err += test_mkfifo();
+
2121 err += test_mkdir();
+
2122 err += test_rename_file();
+
2123 err += test_rename_dir();
+
2124 err += test_rename_dir_loop();
+
2125 err += test_seekdir();
+
2126 err += test_socket();
+
2127 err += test_utime();
+
2128 err += test_truncate(0);
+
2129 err += test_truncate(testdatalen / 2);
+
2130 err += test_truncate(testdatalen);
+
2131 err += test_truncate(testdatalen + 100);
+
2132 err += test_ftruncate(0, 0600);
+
2133 err += test_ftruncate(testdatalen / 2, 0600);
+
2134 err += test_ftruncate(testdatalen, 0600);
+
2135 err += test_ftruncate(testdatalen + 100, 0600);
+
2136 err += test_ftruncate(0, 0400);
+
2137 err += test_ftruncate(0, 0200);
+
2138 err += test_ftruncate(0, 0000);
+
2139 err += test_open(0, O_RDONLY, 0);
+
2140 err += test_open(1, O_RDONLY, 0);
+
2141 err += test_open(1, O_RDWR, 0);
+
2142 err += test_open(1, O_WRONLY, 0);
+
2143 err += test_open(0, O_RDWR | O_CREAT, 0600);
+
2144 err += test_open(1, O_RDWR | O_CREAT, 0600);
+
2145 err += test_open(0, O_RDWR | O_CREAT | O_TRUNC, 0600);
+
2146 err += test_open(1, O_RDWR | O_CREAT | O_TRUNC, 0600);
+
2147 err += test_open(0, O_RDONLY | O_CREAT, 0600);
+
2148 err += test_open(0, O_RDONLY | O_CREAT, 0400);
+
2149 err += test_open(0, O_RDONLY | O_CREAT, 0200);
+
2150 err += test_open(0, O_RDONLY | O_CREAT, 0000);
+
2151 err += test_open(0, O_WRONLY | O_CREAT, 0600);
+
2152 err += test_open(0, O_WRONLY | O_CREAT, 0400);
+
2153 err += test_open(0, O_WRONLY | O_CREAT, 0200);
+
2154 err += test_open(0, O_WRONLY | O_CREAT, 0000);
+
2155 err += test_open(0, O_RDWR | O_CREAT, 0400);
+
2156 err += test_open(0, O_RDWR | O_CREAT, 0200);
+
2157 err += test_open(0, O_RDWR | O_CREAT, 0000);
+
2158 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0600);
+
2159 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0600);
+
2160 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0000);
+
2161 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0000);
+
2162 err += test_open_acc(O_RDONLY, 0600, 0);
+
2163 err += test_open_acc(O_WRONLY, 0600, 0);
+
2164 err += test_open_acc(O_RDWR, 0600, 0);
+
2165 err += test_open_acc(O_RDONLY, 0400, 0);
+
2166 err += test_open_acc(O_WRONLY, 0200, 0);
+
2167 if(!is_root) {
+
2168 err += test_open_acc(O_RDONLY | O_TRUNC, 0400, EACCES);
+
2169 err += test_open_acc(O_WRONLY, 0400, EACCES);
+
2170 err += test_open_acc(O_RDWR, 0400, EACCES);
+
2171 err += test_open_acc(O_RDONLY, 0200, EACCES);
+
2172 err += test_open_acc(O_RDWR, 0200, EACCES);
+
2173 err += test_open_acc(O_RDONLY, 0000, EACCES);
+
2174 err += test_open_acc(O_WRONLY, 0000, EACCES);
+
2175 err += test_open_acc(O_RDWR, 0000, EACCES);
+
2176 }
+
2177 err += test_create_ro_dir(O_CREAT);
+
2178 err += test_create_ro_dir(O_CREAT | O_EXCL);
+
2179 err += test_create_ro_dir(O_CREAT | O_WRONLY);
+
2180 err += test_create_ro_dir(O_CREAT | O_TRUNC);
+
2181 err += test_copy_file_range();
+
2182#ifndef __FreeBSD__
+
2183 err += test_create_tmpfile();
+
2184 err += test_create_and_link_tmpfile();
+
2185#endif
+
2186
+
2187 unlink(testfile2);
+
2188 unlink(testsock);
+
2189 rmdir(testdir);
+
2190 rmdir(testdir2);
+
2191
+
2192 if (err) {
+
2193 fprintf(stderr, "%i tests failed\n", -err);
+
2194 return 1;
+
2195 }
+
2196
+
2197 return check_unlinked_testfiles();
+
2198}
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2test_2test__want__conversion_8c_source.html b/doc/html/fuse-3_817_84_2test_2test__want__conversion_8c_source.html new file mode 100644 index 0000000..00c8125 --- /dev/null +++ b/doc/html/fuse-3_817_84_2test_2test__want__conversion_8c_source.html @@ -0,0 +1,264 @@ + + + + + + + +libfuse: fuse-3.17.4/test/test_want_conversion.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_want_conversion.c
+
+
+
1#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17)
+
2
+
3#include "util.h"
+
4#include "fuse_i.h"
+
5#include "fuse_lowlevel.h"
+
6#include <stdio.h>
+
7#include <assert.h>
+
8#include <inttypes.h>
+
9#include <stdbool.h>
+
10#include <err.h>
+
11
+
12static void print_conn_info(const char *prefix, struct fuse_conn_info *conn)
+
13{
+
14 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
15
+
16 printf("%s: want=0x%" PRIx32 " want_ext=0x%" PRIx64
+
17 " want_default=0x%" PRIx32 " want_ext_default=0x%" PRIx64 "\n",
+
18 prefix, conn->want, conn->want_ext, se->conn_want,
+
19 se->conn_want_ext);
+
20}
+
21
+
22static void application_init_old_style(struct fuse_conn_info *conn)
+
23{
+
24 /* Simulate application init the old style */
+ +
26 conn->want &= ~FUSE_CAP_SPLICE_READ;
+
27
+
28 /*
+
29 * Also use new style API, as that might happen through
+
30 * fuse_apply_conn_info_opts()
+
31 */
+ +
33}
+
34
+
35static void application_init_new_style(struct fuse_conn_info *conn)
+
36{
+
37 /* Simulate application init the new style */
+ + + +
41}
+
42
+
43static void test_fuse_fs_init(struct fuse_conn_info *conn, bool new_style)
+
44{
+
45 /* High-level init */
+ +
47
+
48 if (new_style)
+
49 application_init_new_style(conn);
+
50 else
+
51 application_init_old_style(conn);
+
52}
+
53
+
54static void test_do_init(struct fuse_conn_info *conn, bool new_style)
+
55{
+
56 /* Initial setup */
+ + + + +
61 conn->capable = fuse_lower_32_bits(conn->capable_ext);
+
62
+ + + +
66
+
67 print_conn_info("Initial state", conn);
+
68
+
69 int rc;
+
70
+
71 test_fuse_fs_init(conn, new_style);
+
72 print_conn_info("After init", conn);
+
73
+ +
75 assert(rc == 0);
+
76
+
77 /* Verify all expected flags are set */
+
78 assert(!(conn->want_ext & FUSE_CAP_SPLICE_READ));
+
79 assert(conn->want_ext & FUSE_CAP_SPLICE_WRITE);
+
80 assert(conn->want_ext & FUSE_CAP_SPLICE_MOVE);
+
81 assert(conn->want_ext & FUSE_CAP_EXPORT_SUPPORT);
+
82 assert(conn->want_ext & FUSE_CAP_ASYNC_READ);
+
83 assert(conn->want_ext & FUSE_CAP_IOCTL_DIR);
+
84
+
85 /* Verify no other flags are set */
+
86 assert(conn->want_ext ==
+ + + +
90
+
91 print_conn_info("After init", conn);
+
92}
+
93
+
94static void test_want_conversion_basic(void)
+
95{
+
96 const struct fuse_lowlevel_ops ops = { 0 };
+
97 struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+
98 struct fuse_session *se;
+
99 struct fuse_conn_info *conn;
+
100
+
101 /* Add the program name to arg[0] */
+
102 if (fuse_opt_add_arg(&args, "test_signals")) {
+
103 fprintf(stderr, "Failed to add argument\n");
+
104 errx(1, "Failed to add argument");
+
105 }
+
106
+
107
+
108 se = fuse_session_new(&args, &ops, sizeof(ops), NULL);
+
109 assert(se);
+
110 conn = &se->conn;
+
111 printf("\nTesting basic want conversion, old style:\n");
+
112 test_do_init(conn, false);
+ +
114
+
115 se = fuse_session_new(&args, &ops, sizeof(ops), NULL);
+
116 assert(se);
+
117 conn = &se->conn;
+
118 printf("\nTesting basic want conversion, new style:\n");
+
119 test_do_init(conn, true);
+
120 print_conn_info("After init", conn);
+ +
122
+
123 fuse_opt_free_args(&args);
+
124
+
125}
+
126
+
127static void test_want_conversion_conflict(void)
+
128{
+
129 struct fuse_conn_info conn = { 0 };
+
130 int rc;
+
131
+
132 printf("\nTesting want conversion conflict:\n");
+
133
+
134 /* Test conflicting values */
+
135 /* Initialize like fuse_lowlevel.c does */
+ + + +
139 conn.capable = fuse_lower_32_bits(conn.capable_ext);
+
140 conn.want_ext = conn.capable_ext;
+
141 conn.want = fuse_lower_32_bits(conn.want_ext);
+
142 print_conn_info("Test conflict initial", &conn);
+
143
+
144 /* Simulate application init modifying capabilities */
+
145 conn.want_ext |= FUSE_CAP_ATOMIC_O_TRUNC; /* Add new capability */
+
146 conn.want &= ~FUSE_CAP_SPLICE_READ; /* Remove a capability */
+
147
+ +
149 assert(rc == -EINVAL);
+
150 print_conn_info("Test conflict after", &conn);
+
151
+
152 printf("Want conversion conflict test passed\n");
+
153}
+
154
+
155static void test_want_conversion_high_bits(void)
+
156{
+
157 struct fuse_conn_info conn = { 0 };
+
158 int rc;
+
159
+
160 printf("\nTesting want conversion high bits preservation:\n");
+
161
+
162 /* Test high bits preservation */
+
163 conn.want_ext = (1ULL << 33) | FUSE_CAP_ASYNC_READ;
+
164 conn.want = fuse_lower_32_bits(conn.want_ext);
+
165 print_conn_info("Test high bits initial", &conn);
+
166
+ +
168 assert(rc == 0);
+
169 assert(conn.want_ext == ((1ULL << 33) | FUSE_CAP_ASYNC_READ));
+
170 print_conn_info("Test high bits after", &conn);
+
171
+
172 printf("Want conversion high bits test passed\n");
+
173}
+
174
+
175int main(void)
+
176{
+
177 test_want_conversion_basic();
+
178 test_want_conversion_conflict();
+
179 test_want_conversion_high_bits();
+
180 return 0;
+
181}
+
#define FUSE_CAP_IOCTL_DIR
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + + +
uint64_t capable_ext
+
uint32_t capable
+
uint64_t want_ext
+ +
+ + + + diff --git a/doc/html/fuse-3_817_84_2test_2test__write__cache_8c_source.html b/doc/html/fuse-3_817_84_2test_2test__write__cache_8c_source.html new file mode 100644 index 0000000..3b6292b --- /dev/null +++ b/doc/html/fuse-3_817_84_2test_2test__write__cache_8c_source.html @@ -0,0 +1,404 @@ + + + + + + + +libfuse: fuse-3.17.4/test/test_write_cache.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_write_cache.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
9
+
10#define FUSE_USE_VERSION 30
+
11
+
12/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
+
13#include <fuse.h>
+
14
+
15#include <fuse_config.h>
+
16#include <fuse_lowlevel.h>
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <errno.h>
+
21#include <fcntl.h>
+
22#include <assert.h>
+
23#include <stddef.h>
+
24#include <unistd.h>
+
25#include <sys/stat.h>
+
26#include <pthread.h>
+
27#include <stdatomic.h>
+
28
+
29#ifndef __linux__
+
30#include <limits.h>
+
31#else
+
32#include <linux/limits.h>
+
33#endif
+
34
+
35#define FILE_INO 2
+
36#define FILE_NAME "write_me"
+
37
+
38/* Command line parsing */
+
39struct options {
+
40 int writeback;
+
41 int data_size;
+
42 int delay_ms;
+
43} options = {
+
44 .writeback = 0,
+
45 .data_size = 2048,
+
46 .delay_ms = 0,
+
47};
+
48
+
49#define WRITE_SYSCALLS 64
+
50
+
51#define OPTION(t, p) \
+
52 { t, offsetof(struct options, p), 1 }
+
53static const struct fuse_opt option_spec[] = {
+
54 OPTION("writeback_cache", writeback),
+
55 OPTION("--data-size=%d", data_size),
+
56 OPTION("--delay_ms=%d", delay_ms),
+ +
58};
+
59static int got_write;
+
60static atomic_int write_cnt;
+
61
+
62pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+
63pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
64static int write_start, write_done;
+
65
+
66static void tfs_init (void *userdata, struct fuse_conn_info *conn)
+
67{
+
68 (void) userdata;
+
69
+
70 if(options.writeback) {
+ + +
73 }
+
74}
+
75
+
76static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
77 stbuf->st_ino = ino;
+
78 if (ino == FUSE_ROOT_ID) {
+
79 stbuf->st_mode = S_IFDIR | 0755;
+
80 stbuf->st_nlink = 1;
+
81 }
+
82
+
83 else if (ino == FILE_INO) {
+
84 stbuf->st_mode = S_IFREG | 0222;
+
85 stbuf->st_nlink = 1;
+
86 stbuf->st_size = 0;
+
87 }
+
88
+
89 else
+
90 return -1;
+
91
+
92 return 0;
+
93}
+
94
+
95static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
96 const char *name) {
+
97 struct fuse_entry_param e;
+
98 memset(&e, 0, sizeof(e));
+
99
+
100 if (parent != FUSE_ROOT_ID)
+
101 goto err_out;
+
102 else if (strcmp(name, FILE_NAME) == 0)
+
103 e.ino = FILE_INO;
+
104 else
+
105 goto err_out;
+
106
+
107 if (tfs_stat(e.ino, &e.attr) != 0)
+
108 goto err_out;
+
109 fuse_reply_entry(req, &e);
+
110 return;
+
111
+
112err_out:
+
113 fuse_reply_err(req, ENOENT);
+
114}
+
115
+
116static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
117 struct fuse_file_info *fi) {
+
118 struct stat stbuf;
+
119
+
120 (void) fi;
+
121
+
122 memset(&stbuf, 0, sizeof(stbuf));
+
123 if (tfs_stat(ino, &stbuf) != 0)
+
124 fuse_reply_err(req, ENOENT);
+
125 else
+
126 fuse_reply_attr(req, &stbuf, 5);
+
127}
+
128
+
129static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
130 struct fuse_file_info *fi) {
+
131 if (ino == FUSE_ROOT_ID)
+
132 fuse_reply_err(req, EISDIR);
+
133 else {
+
134 assert(ino == FILE_INO);
+
135 /* Test close(rofd) does not block waiting for pending writes */
+
136 fi->noflush = !options.writeback && options.delay_ms &&
+
137 (fi->flags & O_ACCMODE) == O_RDONLY;
+
138 fuse_reply_open(req, fi);
+
139 }
+
140}
+
141
+
142static void tfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+
143 size_t size, off_t off, struct fuse_file_info *fi) {
+
144 (void) fi; (void) buf; (void) off;
+
145 size_t expected;
+
146
+
147 assert(ino == FILE_INO);
+
148 expected = options.data_size;
+
149 if(options.writeback)
+
150 expected *= 2;
+
151
+
152 write_cnt++;
+
153
+
154 if(size != expected && !options.writeback)
+
155 fprintf(stderr, "ERROR: Expected %zd bytes, got %zd\n!",
+
156 expected, size);
+
157 else
+
158 got_write = 1;
+
159
+
160 /* Simulate waiting for pending writes */
+
161 if (options.delay_ms) {
+
162 pthread_mutex_lock(&lock);
+
163 write_start = 1;
+
164 pthread_cond_signal(&cond);
+
165 pthread_mutex_unlock(&lock);
+
166
+
167 usleep(options.delay_ms * 1000);
+
168
+
169 pthread_mutex_lock(&lock);
+
170 write_done = 1;
+
171 pthread_cond_signal(&cond);
+
172 pthread_mutex_unlock(&lock);
+
173 }
+
174
+
175 fuse_reply_write(req, size);
+
176}
+
177
+
178static struct fuse_lowlevel_ops tfs_oper = {
+
179 .init = tfs_init,
+
180 .lookup = tfs_lookup,
+
181 .getattr = tfs_getattr,
+
182 .open = tfs_open,
+
183 .write = tfs_write,
+
184};
+
185
+
186static void* close_rofd(void *data) {
+
187 int rofd = (int)(long) data;
+
188
+
189 /* Wait for first write to start */
+
190 pthread_mutex_lock(&lock);
+
191 while (!write_start && !write_done)
+
192 pthread_cond_wait(&cond, &lock);
+
193 pthread_mutex_unlock(&lock);
+
194
+
195 close(rofd);
+
196 printf("rofd closed. write_start: %d write_done: %d\n", write_start, write_done);
+
197
+
198 /* First write should not have been completed */
+
199 if (write_done)
+
200 fprintf(stderr, "ERROR: close(rofd) blocked on write!\n");
+
201
+
202 return NULL;
+
203}
+
204
+
205static void* run_fs(void *data) {
+
206 struct fuse_session *se = (struct fuse_session*) data;
+
207 assert(fuse_session_loop(se) == 0);
+
208 return NULL;
+
209}
+
210
+
211static void test_fs(char *mountpoint) {
+
212 char fname[PATH_MAX];
+
213 char *buf;
+
214 const size_t iosize = options.data_size;
+
215 const size_t dsize = options.data_size * WRITE_SYSCALLS;
+
216 int fd, rofd;
+
217 pthread_t rofd_thread;
+
218 off_t off = 0;
+
219
+
220 buf = malloc(dsize);
+
221 assert(buf != NULL);
+
222 assert((fd = open("/dev/urandom", O_RDONLY)) != -1);
+
223 assert(read(fd, buf, dsize) == dsize);
+
224 close(fd);
+
225
+
226 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
+
227 mountpoint) > 0);
+
228 fd = open(fname, O_WRONLY);
+
229 if (fd == -1) {
+
230 perror(fname);
+
231 assert(0);
+
232 }
+
233
+
234 if (options.delay_ms) {
+
235 /* Verify that close(rofd) does not block waiting for pending writes */
+
236 rofd = open(fname, O_RDONLY);
+
237 assert(pthread_create(&rofd_thread, NULL, close_rofd, (void *)(long)rofd) == 0);
+
238 /* Give close_rofd time to start */
+
239 usleep(options.delay_ms * 1000);
+
240 }
+
241
+
242 for (int cnt = 0; cnt < WRITE_SYSCALLS; cnt++) {
+
243 assert(pwrite(fd, buf + off, iosize, off) == iosize);
+
244 off += iosize;
+
245 assert(off <= dsize);
+
246 }
+
247 free(buf);
+
248 close(fd);
+
249
+
250 if (options.delay_ms) {
+
251 printf("rwfd closed. write_start: %d write_done: %d\n", write_start, write_done);
+
252 assert(pthread_join(rofd_thread, NULL) == 0);
+
253 }
+
254}
+
255
+
256int main(int argc, char *argv[]) {
+
257 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
258 struct fuse_session *se;
+
259 struct fuse_cmdline_opts fuse_opts;
+
260 pthread_t fs_thread;
+
261
+
262 assert(fuse_opt_parse(&args, &options, option_spec, NULL) == 0);
+
263 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
+
264#ifndef __FreeBSD__
+
265 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
+
266#endif
+
267 se = fuse_session_new(&args, &tfs_oper,
+
268 sizeof(tfs_oper), NULL);
+
269 fuse_opt_free_args(&args);
+
270 assert (se != NULL);
+
271 assert(fuse_set_signal_handlers(se) == 0);
+
272 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
+
273
+
274 /* Start file-system thread */
+
275 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
+
276
+
277 /* Write test data */
+
278 test_fs(fuse_opts.mountpoint);
+
279 free(fuse_opts.mountpoint);
+
280
+
281 /* Stop file system */
+ + +
284 assert(pthread_join(fs_thread, NULL) == 0);
+
285
+
286 assert(got_write == 1);
+
287
+
288 /*
+
289 * when writeback cache is enabled, kernel side can merge requests, but
+
290 * memory pressure, system 'sync' might trigger data flushes before - flush
+
291 * might happen in between write syscalls - merging subpage writes into
+
292 * a single page and pages into large fuse requests might or might not work.
+
293 * Though we can expect that that at least some (but maybe all) write
+
294 * system calls can be merged.
+
295 */
+
296 if (options.writeback)
+
297 assert(write_cnt < WRITE_SYSCALLS);
+
298 else
+
299 assert(write_cnt == WRITE_SYSCALLS);
+
300
+ + +
303
+
304 printf("Test completed successfully.\n");
+
305 return 0;
+
306}
+
307
+
308
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_WRITEBACK_CACHE
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
uint64_t fuse_ino_t
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
+
fuse_ino_t ino
+ +
uint32_t noflush
Definition fuse_common.h:93
+ + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_84_2test_2wrong__command_8c_source.html b/doc/html/fuse-3_817_84_2test_2wrong__command_8c_source.html new file mode 100644 index 0000000..1b0976d --- /dev/null +++ b/doc/html/fuse-3_817_84_2test_2wrong__command_8c_source.html @@ -0,0 +1,76 @@ + + + + + + + +libfuse: fuse-3.17.4/test/wrong_command.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
wrong_command.c
+
+
+
1#include <stdio.h>
+
2
+
3int main(void) {
+
4#ifdef MESON_IS_SUBPROJECT
+
5 fprintf(stderr, "libfuse tests were skipped because it's a meson subproject.\n"
+
6 "If you wish to run them try:\n"
+
7 "'cd <srcdir>/subprojects/libfuse && meson . build && cd build && python3 -m pytest test/' instead");
+
8 return 77; /* report as a skipped test */
+
9#else
+
10 fprintf(stderr, "\x1B[31m\e[1m"
+
11 "This is not the command you are looking for.\n"
+
12 "You probably want to run 'python3 -m pytest test/' instead"
+
13 "\e[0m\n");
+
14 return 1;
+
15#endif
+
16}
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2util_2fusermount_8c_source.html b/doc/html/fuse-3_817_84_2util_2fusermount_8c_source.html new file mode 100644 index 0000000..2872069 --- /dev/null +++ b/doc/html/fuse-3_817_84_2util_2fusermount_8c_source.html @@ -0,0 +1,1796 @@ + + + + + + + +libfuse: fuse-3.17.4/util/fusermount.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fusermount.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8/* This program does the mounting and unmounting of FUSE filesystems */
+
9
+
10#define _GNU_SOURCE /* for clone,strchrnul and close_range */
+
11#include "fuse_config.h"
+
12#include "mount_util.h"
+
13#include "util.h"
+
14
+
15#include <stdio.h>
+
16#include <stdlib.h>
+
17#include <string.h>
+
18#include <ctype.h>
+
19#include <unistd.h>
+
20#include <getopt.h>
+
21#include <errno.h>
+
22#include <fcntl.h>
+
23#include <pwd.h>
+
24#include <paths.h>
+
25#include <mntent.h>
+
26#include <sys/wait.h>
+
27#include <sys/stat.h>
+
28#include <sys/param.h>
+
29
+
30#include "fuse_mount_compat.h"
+
31
+
32#include <sys/fsuid.h>
+
33#include <sys/socket.h>
+
34#include <sys/utsname.h>
+
35#include <sched.h>
+
36#include <stdbool.h>
+
37#include <sys/vfs.h>
+
38
+
39#ifdef HAVE_CLOSE_RANGE
+
40#include <linux/close_range.h>
+
41#endif
+
42
+
43#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
+
44
+
45#define FUSE_DEV "/dev/fuse"
+
46
+
47static const char *progname;
+
48
+
49static int user_allow_other = 0;
+
50static int mount_max = 1000;
+
51
+
52static int auto_unmount = 0;
+
53
+
54#ifdef GETMNTENT_NEEDS_UNESCAPING
+
55// Older versions of musl libc don't unescape entries in /etc/mtab
+
56
+
57// unescapes octal sequences like \040 in-place
+
58// That's ok, because unescaping can not extend the length of the string.
+
59static void unescape(char *buf) {
+
60 char *src = buf;
+
61 char *dest = buf;
+
62 while (1) {
+
63 char *next_src = strchrnul(src, '\\');
+
64 int offset = next_src - src;
+
65 memmove(dest, src, offset);
+
66 src = next_src;
+
67 dest += offset;
+
68
+
69 if(*src == '\0') {
+
70 *dest = *src;
+
71 return;
+
72 }
+
73 src++;
+
74
+
75 if('0' <= src[0] && src[0] < '2' &&
+
76 '0' <= src[1] && src[1] < '8' &&
+
77 '0' <= src[2] && src[2] < '8') {
+
78 *dest++ = (src[0] - '0') << 6
+
79 | (src[1] - '0') << 3
+
80 | (src[2] - '0') << 0;
+
81 src += 3;
+
82 } else if (src[0] == '\\') {
+
83 *dest++ = '\\';
+
84 src += 1;
+
85 } else {
+
86 *dest++ = '\\';
+
87 }
+
88 }
+
89}
+
90
+
91static struct mntent *GETMNTENT(FILE *stream)
+
92{
+
93 struct mntent *entp = getmntent(stream);
+
94 if(entp != NULL) {
+
95 unescape(entp->mnt_fsname);
+
96 unescape(entp->mnt_dir);
+
97 unescape(entp->mnt_type);
+
98 unescape(entp->mnt_opts);
+
99 }
+
100 return entp;
+
101}
+
102#else
+
103#define GETMNTENT getmntent
+
104#endif // GETMNTENT_NEEDS_UNESCAPING
+
105
+
106/*
+
107 * Take a ',' separated option string and extract "x-" options
+
108 */
+
109static int extract_x_options(const char *original, char **non_x_opts,
+
110 char **x_opts)
+
111{
+
112 size_t orig_len;
+
113 const char *opt, *opt_end;
+
114
+
115 orig_len = strlen(original) + 1;
+
116
+
117 *non_x_opts = calloc(1, orig_len);
+
118 *x_opts = calloc(1, orig_len);
+
119
+
120 size_t non_x_opts_len = orig_len;
+
121 size_t x_opts_len = orig_len;
+
122
+
123 if (*non_x_opts == NULL || *x_opts == NULL) {
+
124 fprintf(stderr, "%s: Failed to allocate %zuB.\n",
+
125 __func__, orig_len);
+
126 return -ENOMEM;
+
127 }
+
128
+
129 for (opt = original; opt < original + orig_len; opt = opt_end + 1) {
+
130 char *opt_buf;
+
131
+
132 opt_end = strchr(opt, ',');
+
133 if (opt_end == NULL)
+
134 opt_end = original + orig_len;
+
135
+
136 size_t opt_len = opt_end - opt;
+
137 size_t opt_len_left = orig_len - (opt - original);
+
138 size_t buf_len;
+
139 bool is_x_opts;
+
140
+
141 if (strncmp(opt, "x-", MIN(2, opt_len_left)) == 0) {
+
142 buf_len = x_opts_len;
+
143 is_x_opts = true;
+
144 opt_buf = *x_opts;
+
145 } else {
+
146 buf_len = non_x_opts_len;
+
147 is_x_opts = false;
+
148 opt_buf = *non_x_opts;
+
149 }
+
150
+
151 if (buf_len < orig_len) {
+
152 strncat(opt_buf, ",", 2);
+
153 buf_len -= 1;
+
154 }
+
155
+
156 /* omits ',' */
+
157 if ((ssize_t)(buf_len - opt_len) < 0) {
+
158 /* This would be a bug */
+
159 fprintf(stderr, "%s: no buf space left in copy, orig='%s'\n",
+
160 __func__, original);
+
161 return -EIO;
+
162 }
+
163
+
164 strncat(opt_buf, opt, opt_end - opt);
+
165 buf_len -= opt_len;
+
166
+
167 if (is_x_opts)
+
168 x_opts_len = buf_len;
+
169 else
+
170 non_x_opts_len = buf_len;
+
171 }
+
172
+
173 return 0;
+
174}
+
175
+
176static const char *get_user_name(void)
+
177{
+
178 struct passwd *pw = getpwuid(getuid());
+
179 if (pw != NULL && pw->pw_name != NULL)
+
180 return pw->pw_name;
+
181 else {
+
182 fprintf(stderr, "%s: could not determine username\n", progname);
+
183 return NULL;
+
184 }
+
185}
+
186
+
187static uid_t oldfsuid;
+
188static gid_t oldfsgid;
+
189
+
190static void drop_privs(void)
+
191{
+
192 if (getuid() != 0) {
+
193 oldfsuid = setfsuid(getuid());
+
194 oldfsgid = setfsgid(getgid());
+
195 }
+
196}
+
197
+
198static void restore_privs(void)
+
199{
+
200 if (getuid() != 0) {
+
201 setfsuid(oldfsuid);
+
202 setfsgid(oldfsgid);
+
203 }
+
204}
+
205
+
206#ifndef IGNORE_MTAB
+
207/*
+
208 * Make sure that /etc/mtab is checked and updated atomically
+
209 */
+
210static int lock_umount(void)
+
211{
+
212 const char *mtab_lock = _PATH_MOUNTED ".fuselock";
+
213 int mtablock;
+
214 int res;
+
215 struct stat mtab_stat;
+
216
+
217 /* /etc/mtab could be a symlink to /proc/mounts */
+
218 if (lstat(_PATH_MOUNTED, &mtab_stat) == 0 && S_ISLNK(mtab_stat.st_mode))
+
219 return -1;
+
220
+
221 mtablock = open(mtab_lock, O_RDWR | O_CREAT, 0600);
+
222 if (mtablock == -1) {
+
223 fprintf(stderr, "%s: unable to open fuse lock file: %s\n",
+
224 progname, strerror(errno));
+
225 return -1;
+
226 }
+
227 res = lockf(mtablock, F_LOCK, 0);
+
228 if (res < 0) {
+
229 fprintf(stderr, "%s: error getting lock: %s\n", progname,
+
230 strerror(errno));
+
231 close(mtablock);
+
232 return -1;
+
233 }
+
234
+
235 return mtablock;
+
236}
+
237
+
238static void unlock_umount(int mtablock)
+
239{
+
240 if (mtablock >= 0) {
+
241 int res;
+
242
+
243 res = lockf(mtablock, F_ULOCK, 0);
+
244 if (res < 0) {
+
245 fprintf(stderr, "%s: error releasing lock: %s\n",
+
246 progname, strerror(errno));
+
247 }
+
248 close(mtablock);
+
249 }
+
250}
+
251
+
252static int add_mount(const char *source, const char *mnt, const char *type,
+
253 const char *opts)
+
254{
+
255 return fuse_mnt_add_mount(progname, source, mnt, type, opts);
+
256}
+
257
+
258static int may_unmount(const char *mnt, int quiet)
+
259{
+
260 struct mntent *entp;
+
261 FILE *fp;
+
262 const char *user = NULL;
+
263 char uidstr[32];
+
264 unsigned uidlen = 0;
+
265 int found;
+
266 const char *mtab = _PATH_MOUNTED;
+
267
+
268 user = get_user_name();
+
269 if (user == NULL)
+
270 return -1;
+
271
+
272 fp = setmntent(mtab, "r");
+
273 if (fp == NULL) {
+
274 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
+
275 strerror(errno));
+
276 return -1;
+
277 }
+
278
+
279 uidlen = sprintf(uidstr, "%u", getuid());
+
280
+
281 found = 0;
+
282 while ((entp = GETMNTENT(fp)) != NULL) {
+
283 if (!found && strcmp(entp->mnt_dir, mnt) == 0 &&
+
284 (strcmp(entp->mnt_type, "fuse") == 0 ||
+
285 strcmp(entp->mnt_type, "fuseblk") == 0 ||
+
286 strncmp(entp->mnt_type, "fuse.", 5) == 0 ||
+
287 strncmp(entp->mnt_type, "fuseblk.", 8) == 0)) {
+
288 char *p = strstr(entp->mnt_opts, "user=");
+
289 if (p &&
+
290 (p == entp->mnt_opts || *(p-1) == ',') &&
+
291 strcmp(p + 5, user) == 0) {
+
292 found = 1;
+
293 break;
+
294 }
+
295 /* /etc/mtab is a link pointing to
+
296 /proc/mounts: */
+
297 else if ((p =
+
298 strstr(entp->mnt_opts, "user_id=")) &&
+
299 (p == entp->mnt_opts ||
+
300 *(p-1) == ',') &&
+
301 strncmp(p + 8, uidstr, uidlen) == 0 &&
+
302 (*(p+8+uidlen) == ',' ||
+
303 *(p+8+uidlen) == '\0')) {
+
304 found = 1;
+
305 break;
+
306 }
+
307 }
+
308 }
+
309 endmntent(fp);
+
310
+
311 if (!found) {
+
312 if (!quiet)
+
313 fprintf(stderr,
+
314 "%s: entry for %s not found in %s\n",
+
315 progname, mnt, mtab);
+
316 return -1;
+
317 }
+
318
+
319 return 0;
+
320}
+
321#endif
+
322
+
323/*
+
324 * Check whether the file specified in "fusermount3 -u" is really a
+
325 * mountpoint and not a symlink. This is necessary otherwise the user
+
326 * could move the mountpoint away and replace it with a symlink
+
327 * pointing to an arbitrary mount, thereby tricking fusermount3 into
+
328 * unmounting that (umount(2) will follow symlinks).
+
329 *
+
330 * This is the child process running in a separate mount namespace, so
+
331 * we don't mess with the global namespace and if the process is
+
332 * killed for any reason, mounts are automatically cleaned up.
+
333 *
+
334 * First make sure nothing is propagated back into the parent
+
335 * namespace by marking all mounts "private".
+
336 *
+
337 * Then bind mount parent onto a stable base where the user can't move
+
338 * it around.
+
339 *
+
340 * Finally check /proc/mounts for an entry matching the requested
+
341 * mountpoint. If it's found then we are OK, and the user can't move
+
342 * it around within the parent directory as rename() will return
+
343 * EBUSY. Be careful to ignore any mounts that existed before the
+
344 * bind.
+
345 */
+
346static int check_is_mount_child(void *p)
+
347{
+
348 const char **a = p;
+
349 const char *last = a[0];
+
350 const char *mnt = a[1];
+
351 const char *type = a[2];
+
352 int res;
+
353 const char *procmounts = "/proc/mounts";
+
354 int found;
+
355 FILE *fp;
+
356 struct mntent *entp;
+
357 int count;
+
358
+
359 res = mount("", "/", "", MS_PRIVATE | MS_REC, NULL);
+
360 if (res == -1) {
+
361 fprintf(stderr, "%s: failed to mark mounts private: %s\n",
+
362 progname, strerror(errno));
+
363 return 1;
+
364 }
+
365
+
366 fp = setmntent(procmounts, "r");
+
367 if (fp == NULL) {
+
368 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
+
369 procmounts, strerror(errno));
+
370 return 1;
+
371 }
+
372
+
373 count = 0;
+
374 while (GETMNTENT(fp) != NULL)
+
375 count++;
+
376 endmntent(fp);
+
377
+
378 fp = setmntent(procmounts, "r");
+
379 if (fp == NULL) {
+
380 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
+
381 procmounts, strerror(errno));
+
382 return 1;
+
383 }
+
384
+
385 res = mount(".", "/", "", MS_BIND | MS_REC, NULL);
+
386 if (res == -1) {
+
387 fprintf(stderr, "%s: failed to bind parent to /: %s\n",
+
388 progname, strerror(errno));
+
389 return 1;
+
390 }
+
391
+
392 found = 0;
+
393 while ((entp = GETMNTENT(fp)) != NULL) {
+
394 if (count > 0) {
+
395 count--;
+
396 continue;
+
397 }
+
398 if (entp->mnt_dir[0] == '/' &&
+
399 strcmp(entp->mnt_dir + 1, last) == 0 &&
+
400 (!type || strcmp(entp->mnt_type, type) == 0)) {
+
401 found = 1;
+
402 break;
+
403 }
+
404 }
+
405 endmntent(fp);
+
406
+
407 if (!found) {
+
408 fprintf(stderr, "%s: %s not mounted\n", progname, mnt);
+
409 return 1;
+
410 }
+
411
+
412 return 0;
+
413}
+
414
+
415static pid_t clone_newns(void *a)
+
416{
+
417 char buf[131072];
+
418 char *stack = buf + (sizeof(buf) / 2 - ((size_t) buf & 15));
+
419
+
420#ifdef __ia64__
+
421 extern int __clone2(int (*fn)(void *),
+
422 void *child_stack_base, size_t stack_size,
+
423 int flags, void *arg, pid_t *ptid,
+
424 void *tls, pid_t *ctid);
+
425
+
426 return __clone2(check_is_mount_child, stack, sizeof(buf) / 2,
+
427 CLONE_NEWNS, a, NULL, NULL, NULL);
+
428#else
+
429 return clone(check_is_mount_child, stack, CLONE_NEWNS, a);
+
430#endif
+
431}
+
432
+
433static int check_is_mount(const char *last, const char *mnt, const char *type)
+
434{
+
435 pid_t pid, p;
+
436 int status;
+
437 const char *a[3] = { last, mnt, type };
+
438
+
439 pid = clone_newns((void *) a);
+
440 if (pid == (pid_t) -1) {
+
441 fprintf(stderr, "%s: failed to clone namespace: %s\n",
+
442 progname, strerror(errno));
+
443 return -1;
+
444 }
+
445 p = waitpid(pid, &status, __WCLONE);
+
446 if (p == (pid_t) -1) {
+
447 fprintf(stderr, "%s: waitpid failed: %s\n",
+
448 progname, strerror(errno));
+
449 return -1;
+
450 }
+
451 if (!WIFEXITED(status)) {
+
452 fprintf(stderr, "%s: child terminated abnormally (status %i)\n",
+
453 progname, status);
+
454 return -1;
+
455 }
+
456 if (WEXITSTATUS(status) != 0)
+
457 return -1;
+
458
+
459 return 0;
+
460}
+
461
+
462static int chdir_to_parent(char *copy, const char **lastp)
+
463{
+
464 char *tmp;
+
465 const char *parent;
+
466 char buf[65536];
+
467 int res;
+
468
+
469 tmp = strrchr(copy, '/');
+
470 if (tmp == NULL || tmp[1] == '\0') {
+
471 fprintf(stderr, "%s: internal error: invalid abs path: <%s>\n",
+
472 progname, copy);
+
473 return -1;
+
474 }
+
475 if (tmp != copy) {
+
476 *tmp = '\0';
+
477 parent = copy;
+
478 *lastp = tmp + 1;
+
479 } else if (tmp[1] != '\0') {
+
480 *lastp = tmp + 1;
+
481 parent = "/";
+
482 } else {
+
483 *lastp = ".";
+
484 parent = "/";
+
485 }
+
486
+
487 res = chdir(parent);
+
488 if (res == -1) {
+
489 fprintf(stderr, "%s: failed to chdir to %s: %s\n",
+
490 progname, parent, strerror(errno));
+
491 return -1;
+
492 }
+
493
+
494 if (getcwd(buf, sizeof(buf)) == NULL) {
+
495 fprintf(stderr, "%s: failed to obtain current directory: %s\n",
+
496 progname, strerror(errno));
+
497 return -1;
+
498 }
+
499 if (strcmp(buf, parent) != 0) {
+
500 fprintf(stderr, "%s: mountpoint moved (%s -> %s)\n", progname,
+
501 parent, buf);
+
502 return -1;
+
503
+
504 }
+
505
+
506 return 0;
+
507}
+
508
+
509#ifndef IGNORE_MTAB
+
510static int unmount_fuse_locked(const char *mnt, int quiet, int lazy)
+
511{
+
512 int res;
+
513 char *copy;
+
514 const char *last;
+
515 int umount_flags = (lazy ? UMOUNT_DETACH : 0) | UMOUNT_NOFOLLOW;
+
516
+
517 if (getuid() != 0) {
+
518 res = may_unmount(mnt, quiet);
+
519 if (res == -1)
+
520 return -1;
+
521 }
+
522
+
523 copy = strdup(mnt);
+
524 if (copy == NULL) {
+
525 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
526 return -1;
+
527 }
+
528
+
529 drop_privs();
+
530 res = chdir_to_parent(copy, &last);
+
531 if (res == -1) {
+
532 restore_privs();
+
533 goto out;
+
534 }
+
535
+
536 res = umount2(last, umount_flags);
+
537 restore_privs();
+
538 if (res == -1 && !quiet) {
+
539 fprintf(stderr, "%s: failed to unmount %s: %s\n",
+
540 progname, mnt, strerror(errno));
+
541 }
+
542
+
543out:
+
544 free(copy);
+
545 if (res == -1)
+
546 return -1;
+
547
+
548 res = chdir("/");
+
549 if (res == -1) {
+
550 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
551 return -1;
+
552 }
+
553
+
554 return fuse_mnt_remove_mount(progname, mnt);
+
555}
+
556
+
557static int unmount_fuse(const char *mnt, int quiet, int lazy)
+
558{
+
559 int res;
+
560 int mtablock = lock_umount();
+
561
+
562 res = unmount_fuse_locked(mnt, quiet, lazy);
+
563 unlock_umount(mtablock);
+
564
+
565 return res;
+
566}
+
567
+
568static int count_fuse_fs(void)
+
569{
+
570 struct mntent *entp;
+
571 int count = 0;
+
572 const char *mtab = _PATH_MOUNTED;
+
573 FILE *fp = setmntent(mtab, "r");
+
574 if (fp == NULL) {
+
575 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
+
576 strerror(errno));
+
577 return -1;
+
578 }
+
579 while ((entp = GETMNTENT(fp)) != NULL) {
+
580 if (strcmp(entp->mnt_type, "fuse") == 0 ||
+
581 strncmp(entp->mnt_type, "fuse.", 5) == 0)
+
582 count ++;
+
583 }
+
584 endmntent(fp);
+
585 return count;
+
586}
+
587
+
588
+
589#else /* IGNORE_MTAB */
+
590static int count_fuse_fs(void)
+
591{
+
592 return 0;
+
593}
+
594
+
595static int add_mount(const char *source, const char *mnt, const char *type,
+
596 const char *opts)
+
597{
+
598 (void) source;
+
599 (void) mnt;
+
600 (void) type;
+
601 (void) opts;
+
602 return 0;
+
603}
+
604
+
605static int unmount_fuse(const char *mnt, int quiet, int lazy)
+
606{
+
607 (void) quiet;
+
608 return fuse_mnt_umount(progname, mnt, mnt, lazy);
+
609}
+
610#endif /* IGNORE_MTAB */
+
611
+
612static void strip_line(char *line)
+
613{
+
614 char *s = strchr(line, '#');
+
615 if (s != NULL)
+
616 s[0] = '\0';
+
617 for (s = line + strlen(line) - 1;
+
618 s >= line && isspace((unsigned char) *s); s--);
+
619 s[1] = '\0';
+
620 for (s = line; isspace((unsigned char) *s); s++);
+
621 if (s != line)
+
622 memmove(line, s, strlen(s)+1);
+
623}
+
624
+
625static void parse_line(char *line, int linenum)
+
626{
+
627 int tmp;
+
628 if (strcmp(line, "user_allow_other") == 0)
+
629 user_allow_other = 1;
+
630 else if (sscanf(line, "mount_max = %i", &tmp) == 1)
+
631 mount_max = tmp;
+
632 else if(line[0])
+
633 fprintf(stderr,
+
634 "%s: unknown parameter in %s at line %i: '%s'\n",
+
635 progname, FUSE_CONF, linenum, line);
+
636}
+
637
+
638static void read_conf(void)
+
639{
+
640 FILE *fp = fopen(FUSE_CONF, "r");
+
641 if (fp != NULL) {
+
642 int linenum = 1;
+
643 char line[256];
+
644 int isnewline = 1;
+
645 while (fgets(line, sizeof(line), fp) != NULL) {
+
646 if (isnewline) {
+
647 if (line[strlen(line)-1] == '\n') {
+
648 strip_line(line);
+
649 parse_line(line, linenum);
+
650 } else {
+
651 isnewline = 0;
+
652 }
+
653 } else if(line[strlen(line)-1] == '\n') {
+
654 fprintf(stderr, "%s: reading %s: line %i too long\n", progname, FUSE_CONF, linenum);
+
655
+
656 isnewline = 1;
+
657 }
+
658 if (isnewline)
+
659 linenum ++;
+
660 }
+
661 if (!isnewline) {
+
662 fprintf(stderr, "%s: reading %s: missing newline at end of file\n", progname, FUSE_CONF);
+
663
+
664 }
+
665 if (ferror(fp)) {
+
666 fprintf(stderr, "%s: reading %s: read failed\n", progname, FUSE_CONF);
+
667 exit(1);
+
668 }
+
669 fclose(fp);
+
670 } else if (errno != ENOENT) {
+
671 bool fatal = (errno != EACCES && errno != ELOOP &&
+
672 errno != ENAMETOOLONG && errno != ENOTDIR &&
+
673 errno != EOVERFLOW);
+
674 fprintf(stderr, "%s: failed to open %s: %s\n",
+
675 progname, FUSE_CONF, strerror(errno));
+
676 if (fatal)
+
677 exit(1);
+
678 }
+
679}
+
680
+
681static int begins_with(const char *s, const char *beg)
+
682{
+
683 if (strncmp(s, beg, strlen(beg)) == 0)
+
684 return 1;
+
685 else
+
686 return 0;
+
687}
+
688
+
689struct mount_flags {
+
690 const char *opt;
+
691 unsigned long flag;
+
692 int on;
+
693 int safe;
+
694};
+
695
+
696static struct mount_flags mount_flags[] = {
+
697 {"rw", MS_RDONLY, 0, 1},
+
698 {"ro", MS_RDONLY, 1, 1},
+
699 {"suid", MS_NOSUID, 0, 0},
+
700 {"nosuid", MS_NOSUID, 1, 1},
+
701 {"dev", MS_NODEV, 0, 0},
+
702 {"nodev", MS_NODEV, 1, 1},
+
703 {"exec", MS_NOEXEC, 0, 1},
+
704 {"noexec", MS_NOEXEC, 1, 1},
+
705 {"async", MS_SYNCHRONOUS, 0, 1},
+
706 {"sync", MS_SYNCHRONOUS, 1, 1},
+
707 {"atime", MS_NOATIME, 0, 1},
+
708 {"noatime", MS_NOATIME, 1, 1},
+
709 {"diratime", MS_NODIRATIME, 0, 1},
+
710 {"nodiratime", MS_NODIRATIME, 1, 1},
+
711 {"lazytime", MS_LAZYTIME, 1, 1},
+
712 {"nolazytime", MS_LAZYTIME, 0, 1},
+
713 {"relatime", MS_RELATIME, 1, 1},
+
714 {"norelatime", MS_RELATIME, 0, 1},
+
715 {"strictatime", MS_STRICTATIME, 1, 1},
+
716 {"nostrictatime", MS_STRICTATIME, 0, 1},
+
717 {"dirsync", MS_DIRSYNC, 1, 1},
+
718 {"symfollow", MS_NOSYMFOLLOW, 0, 1},
+
719 {"nosymfollow", MS_NOSYMFOLLOW, 1, 1},
+
720 {NULL, 0, 0, 0}
+
721};
+
722
+
723static int find_mount_flag(const char *s, unsigned len, int *on, int *flag)
+
724{
+
725 int i;
+
726
+
727 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
728 const char *opt = mount_flags[i].opt;
+
729 if (strlen(opt) == len && strncmp(opt, s, len) == 0) {
+
730 *on = mount_flags[i].on;
+
731 *flag = mount_flags[i].flag;
+
732 if (!mount_flags[i].safe && getuid() != 0) {
+
733 *flag = 0;
+
734 fprintf(stderr,
+
735 "%s: unsafe option %s ignored\n",
+
736 progname, opt);
+
737 }
+
738 return 1;
+
739 }
+
740 }
+
741 return 0;
+
742}
+
743
+
744static int add_option(char **optsp, const char *opt, unsigned expand)
+
745{
+
746 char *newopts;
+
747 if (*optsp == NULL)
+
748 newopts = strdup(opt);
+
749 else {
+
750 unsigned oldsize = strlen(*optsp);
+
751 unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1;
+
752 newopts = (char *) realloc(*optsp, newsize);
+
753 if (newopts)
+
754 sprintf(newopts + oldsize, ",%s", opt);
+
755 }
+
756 if (newopts == NULL) {
+
757 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
758 return -1;
+
759 }
+
760 *optsp = newopts;
+
761 return 0;
+
762}
+
763
+
764static int get_mnt_opts(int flags, char *opts, char **mnt_optsp)
+
765{
+
766 int i;
+
767 int l;
+
768
+
769 if (!(flags & MS_RDONLY) && add_option(mnt_optsp, "rw", 0) == -1)
+
770 return -1;
+
771
+
772 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
773 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+
774 add_option(mnt_optsp, mount_flags[i].opt, 0) == -1)
+
775 return -1;
+
776 }
+
777
+
778 if (add_option(mnt_optsp, opts, 0) == -1)
+
779 return -1;
+
780 /* remove comma from end of opts*/
+
781 l = strlen(*mnt_optsp);
+
782 if ((*mnt_optsp)[l-1] == ',')
+
783 (*mnt_optsp)[l-1] = '\0';
+
784 if (getuid() != 0) {
+
785 const char *user = get_user_name();
+
786 if (user == NULL)
+
787 return -1;
+
788
+
789 if (add_option(mnt_optsp, "user=", strlen(user)) == -1)
+
790 return -1;
+
791 strcat(*mnt_optsp, user);
+
792 }
+
793 return 0;
+
794}
+
795
+
796static int opt_eq(const char *s, unsigned len, const char *opt)
+
797{
+
798 if(strlen(opt) == len && strncmp(s, opt, len) == 0)
+
799 return 1;
+
800 else
+
801 return 0;
+
802}
+
803
+
804static int get_string_opt(const char *s, unsigned len, const char *opt,
+
805 char **val)
+
806{
+
807 int i;
+
808 unsigned opt_len = strlen(opt);
+
809 char *d;
+
810
+
811 if (*val)
+
812 free(*val);
+
813 *val = (char *) malloc(len - opt_len + 1);
+
814 if (!*val) {
+
815 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
816 return 0;
+
817 }
+
818
+
819 d = *val;
+
820 s += opt_len;
+
821 len -= opt_len;
+
822 for (i = 0; i < len; i++) {
+
823 if (s[i] == '\\' && i + 1 < len)
+
824 i++;
+
825 *d++ = s[i];
+
826 }
+
827 *d = '\0';
+
828 return 1;
+
829}
+
830
+
831/* The kernel silently truncates the "data" argument to PAGE_SIZE-1 characters.
+
832 * This can be dangerous if it e.g. truncates the option "group_id=1000" to
+
833 * "group_id=1".
+
834 * This wrapper detects this case and bails out with an error.
+
835 */
+
836static int mount_notrunc(const char *source, const char *target,
+
837 const char *filesystemtype, unsigned long mountflags,
+
838 const char *data) {
+
839 if (strlen(data) > sysconf(_SC_PAGESIZE) - 1) {
+
840 fprintf(stderr, "%s: mount options too long\n", progname);
+
841 errno = EINVAL;
+
842 return -1;
+
843 }
+
844 return mount(source, target, filesystemtype, mountflags, data);
+
845}
+
846
+
847
+
848static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
+
849 int fd, const char *opts, const char *dev, char **sourcep,
+
850 char **mnt_optsp)
+
851{
+
852 int res;
+
853 int flags = MS_NOSUID | MS_NODEV;
+
854 char *optbuf;
+
855 char *mnt_opts = NULL;
+
856 const char *s;
+
857 char *d;
+
858 char *fsname = NULL;
+
859 char *subtype = NULL;
+
860 char *source = NULL;
+
861 char *type = NULL;
+
862 int blkdev = 0;
+
863
+
864 optbuf = (char *) malloc(strlen(opts) + 128);
+
865 if (!optbuf) {
+
866 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
867 return -1;
+
868 }
+
869
+
870 for (s = opts, d = optbuf; *s;) {
+
871 unsigned len;
+
872 const char *fsname_str = "fsname=";
+
873 const char *subtype_str = "subtype=";
+
874 bool escape_ok = begins_with(s, fsname_str) ||
+
875 begins_with(s, subtype_str);
+
876 for (len = 0; s[len]; len++) {
+
877 if (escape_ok && s[len] == '\\' && s[len + 1])
+
878 len++;
+
879 else if (s[len] == ',')
+
880 break;
+
881 }
+
882 if (begins_with(s, fsname_str)) {
+
883 if (!get_string_opt(s, len, fsname_str, &fsname))
+
884 goto err;
+
885 } else if (begins_with(s, subtype_str)) {
+
886 if (!get_string_opt(s, len, subtype_str, &subtype))
+
887 goto err;
+
888 } else if (opt_eq(s, len, "blkdev")) {
+
889 if (getuid() != 0) {
+
890 fprintf(stderr,
+
891 "%s: option blkdev is privileged\n",
+
892 progname);
+
893 goto err;
+
894 }
+
895 blkdev = 1;
+
896 } else if (opt_eq(s, len, "auto_unmount")) {
+
897 auto_unmount = 1;
+
898 } else if (!opt_eq(s, len, "nonempty") &&
+
899 !begins_with(s, "fd=") &&
+
900 !begins_with(s, "rootmode=") &&
+
901 !begins_with(s, "user_id=") &&
+
902 !begins_with(s, "group_id=")) {
+
903 int on;
+
904 int flag;
+
905 int skip_option = 0;
+
906 if (opt_eq(s, len, "large_read")) {
+
907 struct utsname utsname;
+
908 unsigned kmaj, kmin;
+
909 res = uname(&utsname);
+
910 if (res == 0 &&
+
911 sscanf(utsname.release, "%u.%u",
+
912 &kmaj, &kmin) == 2 &&
+
913 (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
+
914 fprintf(stderr, "%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n", progname, kmaj, kmin);
+
915 skip_option = 1;
+
916 }
+
917 }
+
918 if (getuid() != 0 && !user_allow_other &&
+
919 (opt_eq(s, len, "allow_other") ||
+
920 opt_eq(s, len, "allow_root"))) {
+
921 fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n", progname, len, s, FUSE_CONF);
+
922 goto err;
+
923 }
+
924 if (!skip_option) {
+
925 if (find_mount_flag(s, len, &on, &flag)) {
+
926 if (on)
+
927 flags |= flag;
+
928 else
+
929 flags &= ~flag;
+
930 } else if (opt_eq(s, len, "default_permissions") ||
+
931 opt_eq(s, len, "allow_other") ||
+
932 begins_with(s, "max_read=") ||
+
933 begins_with(s, "blksize=")) {
+
934 memcpy(d, s, len);
+
935 d += len;
+
936 *d++ = ',';
+
937 } else {
+
938 fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, s);
+
939 exit(1);
+
940 }
+
941 }
+
942 }
+
943 s += len;
+
944 if (*s)
+
945 s++;
+
946 }
+
947 *d = '\0';
+
948 res = get_mnt_opts(flags, optbuf, &mnt_opts);
+
949 if (res == -1)
+
950 goto err;
+
951
+
952 sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+
953 fd, rootmode, getuid(), getgid());
+
954
+
955 source = malloc((fsname ? strlen(fsname) : 0) +
+
956 (subtype ? strlen(subtype) : 0) + strlen(dev) + 32);
+
957
+
958 type = malloc((subtype ? strlen(subtype) : 0) + 32);
+
959 if (!type || !source) {
+
960 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
961 goto err;
+
962 }
+
963
+
964 if (subtype)
+
965 sprintf(type, "%s.%s", blkdev ? "fuseblk" : "fuse", subtype);
+
966 else
+
967 strcpy(type, blkdev ? "fuseblk" : "fuse");
+
968
+
969 if (fsname)
+
970 strcpy(source, fsname);
+
971 else
+
972 strcpy(source, subtype ? subtype : dev);
+
973
+
974 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
975 if (res == -1 && errno == ENODEV && subtype) {
+
976 /* Probably missing subtype support */
+
977 strcpy(type, blkdev ? "fuseblk" : "fuse");
+
978 if (fsname) {
+
979 if (!blkdev)
+
980 sprintf(source, "%s#%s", subtype, fsname);
+
981 } else {
+
982 strcpy(source, type);
+
983 }
+
984
+
985 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
986 }
+
987 if (res == -1 && errno == EINVAL) {
+
988 /* It could be an old version not supporting group_id */
+
989 sprintf(d, "fd=%i,rootmode=%o,user_id=%u",
+
990 fd, rootmode, getuid());
+
991 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
992 }
+
993 if (res == -1) {
+
994 int errno_save = errno;
+
995 if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
+
996 fprintf(stderr, "%s: 'fuseblk' support missing\n",
+
997 progname);
+
998 else
+
999 fprintf(stderr, "%s: mount failed: %s\n", progname,
+
1000 strerror(errno_save));
+
1001 goto err;
+
1002 }
+
1003 *sourcep = source;
+
1004 *typep = type;
+
1005 *mnt_optsp = mnt_opts;
+
1006 free(fsname);
+
1007 free(optbuf);
+
1008
+
1009 return 0;
+
1010
+
1011err:
+
1012 free(fsname);
+
1013 free(subtype);
+
1014 free(source);
+
1015 free(type);
+
1016 free(mnt_opts);
+
1017 free(optbuf);
+
1018 return -1;
+
1019}
+
1020
+
1021static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)
+
1022{
+
1023 int res;
+
1024 const char *mnt = *mntp;
+
1025 const char *origmnt = mnt;
+
1026 struct statfs fs_buf;
+
1027 size_t i;
+
1028
+
1029 res = lstat(mnt, stbuf);
+
1030 if (res == -1) {
+
1031 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
+
1032 progname, mnt, strerror(errno));
+
1033 return -1;
+
1034 }
+
1035
+
1036 /* No permission checking is done for root */
+
1037 if (getuid() == 0)
+
1038 return 0;
+
1039
+
1040 if (S_ISDIR(stbuf->st_mode)) {
+
1041 res = chdir(mnt);
+
1042 if (res == -1) {
+
1043 fprintf(stderr,
+
1044 "%s: failed to chdir to mountpoint: %s\n",
+
1045 progname, strerror(errno));
+
1046 return -1;
+
1047 }
+
1048 mnt = *mntp = ".";
+
1049 res = lstat(mnt, stbuf);
+
1050 if (res == -1) {
+
1051 fprintf(stderr,
+
1052 "%s: failed to access mountpoint %s: %s\n",
+
1053 progname, origmnt, strerror(errno));
+
1054 return -1;
+
1055 }
+
1056
+
1057 if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) {
+
1058 fprintf(stderr, "%s: mountpoint %s not owned by user\n",
+
1059 progname, origmnt);
+
1060 return -1;
+
1061 }
+
1062
+
1063 res = access(mnt, W_OK);
+
1064 if (res == -1) {
+
1065 fprintf(stderr, "%s: user has no write access to mountpoint %s\n",
+
1066 progname, origmnt);
+
1067 return -1;
+
1068 }
+
1069 } else if (S_ISREG(stbuf->st_mode)) {
+
1070 static char procfile[256];
+
1071 *mountpoint_fd = open(mnt, O_WRONLY);
+
1072 if (*mountpoint_fd == -1) {
+
1073 fprintf(stderr, "%s: failed to open %s: %s\n",
+
1074 progname, mnt, strerror(errno));
+
1075 return -1;
+
1076 }
+
1077 res = fstat(*mountpoint_fd, stbuf);
+
1078 if (res == -1) {
+
1079 fprintf(stderr,
+
1080 "%s: failed to access mountpoint %s: %s\n",
+
1081 progname, mnt, strerror(errno));
+
1082 return -1;
+
1083 }
+
1084 if (!S_ISREG(stbuf->st_mode)) {
+
1085 fprintf(stderr,
+
1086 "%s: mountpoint %s is no longer a regular file\n",
+
1087 progname, mnt);
+
1088 return -1;
+
1089 }
+
1090
+
1091 sprintf(procfile, "/proc/self/fd/%i", *mountpoint_fd);
+
1092 *mntp = procfile;
+
1093 } else {
+
1094 fprintf(stderr,
+
1095 "%s: mountpoint %s is not a directory or a regular file\n",
+
1096 progname, mnt);
+
1097 return -1;
+
1098 }
+
1099
+
1100 /* Do not permit mounting over anything in procfs - it has a couple
+
1101 * places to which we have "write access" without being supposed to be
+
1102 * able to just put anything we want there.
+
1103 * Luckily, without allow_other, we can't get other users to actually
+
1104 * use any fake information we try to put there anyway.
+
1105 * Use a whitelist to be safe. */
+
1106 if (statfs(*mntp, &fs_buf)) {
+
1107 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
+
1108 progname, mnt, strerror(errno));
+
1109 return -1;
+
1110 }
+
1111
+
1112 /* Define permitted filesystems for the mount target. This was
+
1113 * originally the same list as used by the ecryptfs mount helper
+
1114 * (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225)
+
1115 * but got expanded as we found more filesystems that needed to be
+
1116 * overlaid. */
+
1117 typeof(fs_buf.f_type) f_type_whitelist[] = {
+
1118 0x61756673 /* AUFS_SUPER_MAGIC */,
+
1119 0x00000187 /* AUTOFS_SUPER_MAGIC */,
+
1120 0xCA451A4E /* BCACHEFS_STATFS_MAGIC */,
+
1121 0x9123683E /* BTRFS_SUPER_MAGIC */,
+
1122 0x00C36400 /* CEPH_SUPER_MAGIC */,
+
1123 0xFF534D42 /* CIFS_MAGIC_NUMBER */,
+
1124 0x0000F15F /* ECRYPTFS_SUPER_MAGIC */,
+
1125 0X2011BAB0 /* EXFAT_SUPER_MAGIC */,
+
1126 0x0000EF53 /* EXT[234]_SUPER_MAGIC */,
+
1127 0xF2F52010 /* F2FS_SUPER_MAGIC */,
+
1128 0x65735546 /* FUSE_SUPER_MAGIC */,
+
1129 0x01161970 /* GFS2_MAGIC */,
+
1130 0x47504653 /* GPFS_SUPER_MAGIC */,
+
1131 0x0000482b /* HFSPLUS_SUPER_MAGIC */,
+
1132 0x000072B6 /* JFFS2_SUPER_MAGIC */,
+
1133 0x3153464A /* JFS_SUPER_MAGIC */,
+
1134 0x0BD00BD0 /* LL_SUPER_MAGIC */,
+
1135 0X00004D44 /* MSDOS_SUPER_MAGIC */,
+
1136 0x0000564C /* NCP_SUPER_MAGIC */,
+
1137 0x00006969 /* NFS_SUPER_MAGIC */,
+
1138 0x00003434 /* NILFS_SUPER_MAGIC */,
+
1139 0x5346544E /* NTFS_SB_MAGIC */,
+
1140 0x7366746E /* NTFS3_SUPER_MAGIC */,
+
1141 0x5346414f /* OPENAFS_SUPER_MAGIC */,
+
1142 0x794C7630 /* OVERLAYFS_SUPER_MAGIC */,
+
1143 0xAAD7AAEA /* PANFS_SUPER_MAGIC */,
+
1144 0x52654973 /* REISERFS_SUPER_MAGIC */,
+
1145 0xFE534D42 /* SMB2_SUPER_MAGIC */,
+
1146 0x73717368 /* SQUASHFS_MAGIC */,
+
1147 0x01021994 /* TMPFS_MAGIC */,
+
1148 0x24051905 /* UBIFS_SUPER_MAGIC */,
+
1149#if __SIZEOF_LONG__ > 4
+
1150 0x736675005346544e /* UFSD */,
+
1151#endif
+
1152 0x58465342 /* XFS_SB_MAGIC */,
+
1153 0x2FC12FC1 /* ZFS_SUPER_MAGIC */,
+
1154 0x858458f6 /* RAMFS_MAGIC */,
+
1155 };
+
1156 for (i = 0; i < sizeof(f_type_whitelist)/sizeof(f_type_whitelist[0]); i++) {
+
1157 if (f_type_whitelist[i] == fs_buf.f_type)
+
1158 return 0;
+
1159 }
+
1160
+
1161 fprintf(stderr, "%s: mounting over filesystem type %#010lx is forbidden\n",
+
1162 progname, (unsigned long)fs_buf.f_type);
+
1163 return -1;
+
1164}
+
1165
+
1166static int try_open(const char *dev, char **devp, int silent)
+
1167{
+
1168 int fd = open(dev, O_RDWR);
+
1169 if (fd != -1) {
+
1170 *devp = strdup(dev);
+
1171 if (*devp == NULL) {
+
1172 fprintf(stderr, "%s: failed to allocate memory\n",
+
1173 progname);
+
1174 close(fd);
+
1175 fd = -1;
+
1176 }
+
1177 } else if (errno == ENODEV ||
+
1178 errno == ENOENT)/* check for ENOENT too, for the udev case */
+
1179 return -2;
+
1180 else if (!silent) {
+
1181 fprintf(stderr, "%s: failed to open %s: %s\n", progname, dev,
+
1182 strerror(errno));
+
1183 }
+
1184 return fd;
+
1185}
+
1186
+
1187static int try_open_fuse_device(char **devp)
+
1188{
+
1189 int fd;
+
1190
+
1191 drop_privs();
+
1192 fd = try_open(FUSE_DEV, devp, 0);
+
1193 restore_privs();
+
1194 return fd;
+
1195}
+
1196
+
1197static int open_fuse_device(char **devp)
+
1198{
+
1199 int fd = try_open_fuse_device(devp);
+
1200 if (fd >= -1)
+
1201 return fd;
+
1202
+
1203 fprintf(stderr,
+
1204 "%s: fuse device not found, try 'modprobe fuse' first\n",
+
1205 progname);
+
1206
+
1207 return -1;
+
1208}
+
1209
+
1210
+
1211static int mount_fuse(const char *mnt, const char *opts, const char **type)
+
1212{
+
1213 int res;
+
1214 int fd;
+
1215 char *dev;
+
1216 struct stat stbuf;
+
1217 char *source = NULL;
+
1218 char *mnt_opts = NULL;
+
1219 const char *real_mnt = mnt;
+
1220 int mountpoint_fd = -1;
+
1221 char *do_mount_opts = NULL;
+
1222 char *x_opts = NULL;
+
1223
+
1224 fd = open_fuse_device(&dev);
+
1225 if (fd == -1)
+
1226 return -1;
+
1227
+
1228 drop_privs();
+
1229 read_conf();
+
1230
+
1231 if (getuid() != 0 && mount_max != -1) {
+
1232 int mount_count = count_fuse_fs();
+
1233 if (mount_count >= mount_max) {
+
1234 fprintf(stderr, "%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n", progname, FUSE_CONF);
+
1235 goto fail_close_fd;
+
1236 }
+
1237 }
+
1238
+
1239 // Extract any options starting with "x-"
+
1240 res= extract_x_options(opts, &do_mount_opts, &x_opts);
+
1241 if (res)
+
1242 goto fail_close_fd;
+
1243
+
1244 res = check_perm(&real_mnt, &stbuf, &mountpoint_fd);
+
1245 restore_privs();
+
1246 if (res != -1)
+
1247 res = do_mount(real_mnt, type, stbuf.st_mode & S_IFMT,
+
1248 fd, do_mount_opts, dev, &source, &mnt_opts);
+
1249
+
1250 if (mountpoint_fd != -1)
+
1251 close(mountpoint_fd);
+
1252
+
1253 if (res == -1)
+
1254 goto fail_close_fd;
+
1255
+
1256 res = chdir("/");
+
1257 if (res == -1) {
+
1258 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1259 goto fail_close_fd;
+
1260 }
+
1261
+
1262 if (geteuid() == 0) {
+
1263 if (x_opts && strlen(x_opts) > 0) {
+
1264 /*
+
1265 * Add back the options starting with "x-" to opts from
+
1266 * do_mount. +2 for ',' and '\0'
+
1267 */
+
1268 size_t mnt_opts_len = strlen(mnt_opts);
+
1269 size_t x_mnt_opts_len = mnt_opts_len+
+
1270 strlen(x_opts) + 2;
+
1271 char *x_mnt_opts = calloc(1, x_mnt_opts_len);
+
1272
+
1273 if (mnt_opts_len) {
+
1274 strcpy(x_mnt_opts, mnt_opts);
+
1275 strncat(x_mnt_opts, ",", 2);
+
1276 }
+
1277
+
1278 strncat(x_mnt_opts, x_opts,
+
1279 x_mnt_opts_len - mnt_opts_len - 2);
+
1280
+
1281 free(mnt_opts);
+
1282 mnt_opts = x_mnt_opts;
+
1283 }
+
1284
+
1285 res = add_mount(source, mnt, *type, mnt_opts);
+
1286 if (res == -1) {
+
1287 /* Can't clean up mount in a non-racy way */
+
1288 goto fail_close_fd;
+
1289 }
+
1290 }
+
1291
+
1292out_free:
+
1293 free(source);
+
1294 free(mnt_opts);
+
1295 free(dev);
+
1296 free(x_opts);
+
1297 free(do_mount_opts);
+
1298
+
1299 return fd;
+
1300
+
1301fail_close_fd:
+
1302 close(fd);
+
1303 fd = -1;
+
1304 goto out_free;
+
1305}
+
1306
+
1307static int send_fd(int sock_fd, int fd)
+
1308{
+
1309 int retval;
+
1310 struct msghdr msg;
+
1311 struct cmsghdr *p_cmsg;
+
1312 struct iovec vec;
+
1313 size_t cmsgbuf[CMSG_SPACE(sizeof(fd)) / sizeof(size_t)];
+
1314 int *p_fds;
+
1315 char sendchar = 0;
+
1316
+
1317 msg.msg_control = cmsgbuf;
+
1318 msg.msg_controllen = sizeof(cmsgbuf);
+
1319 p_cmsg = CMSG_FIRSTHDR(&msg);
+
1320 p_cmsg->cmsg_level = SOL_SOCKET;
+
1321 p_cmsg->cmsg_type = SCM_RIGHTS;
+
1322 p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+
1323 p_fds = (int *) CMSG_DATA(p_cmsg);
+
1324 *p_fds = fd;
+
1325 msg.msg_controllen = p_cmsg->cmsg_len;
+
1326 msg.msg_name = NULL;
+
1327 msg.msg_namelen = 0;
+
1328 msg.msg_iov = &vec;
+
1329 msg.msg_iovlen = 1;
+
1330 msg.msg_flags = 0;
+
1331 /* "To pass file descriptors or credentials you need to send/read at
+
1332 * least one byte" (man 7 unix) */
+
1333 vec.iov_base = &sendchar;
+
1334 vec.iov_len = sizeof(sendchar);
+
1335 while ((retval = sendmsg(sock_fd, &msg, 0)) == -1 && errno == EINTR);
+
1336 if (retval != 1) {
+
1337 perror("sending file descriptor");
+
1338 return -1;
+
1339 }
+
1340 return 0;
+
1341}
+
1342
+
1343/* Helper for should_auto_unmount
+
1344 *
+
1345 * fusermount typically has the s-bit set - initial open of `mnt` was as root
+
1346 * and got EACCESS as 'allow_other' was not specified.
+
1347 * Try opening `mnt` again with uid and guid of the calling process.
+
1348 */
+
1349static int recheck_ENOTCONN_as_owner(const char *mnt)
+
1350{
+
1351 int pid = fork();
+
1352 if(pid == -1) {
+
1353 perror("fuse: recheck_ENOTCONN_as_owner can't fork");
+
1354 _exit(EXIT_FAILURE);
+
1355 } else if(pid == 0) {
+
1356 uid_t uid = getuid();
+
1357 gid_t gid = getgid();
+
1358 if(setresgid(gid, gid, gid) == -1) {
+
1359 perror("fuse: can't set resgid");
+
1360 _exit(EXIT_FAILURE);
+
1361 }
+
1362 if(setresuid(uid, uid, uid) == -1) {
+
1363 perror("fuse: can't set resuid");
+
1364 _exit(EXIT_FAILURE);
+
1365 }
+
1366
+
1367 int fd = open(mnt, O_RDONLY);
+
1368 if(fd == -1 && errno == ENOTCONN)
+
1369 _exit(EXIT_SUCCESS);
+
1370 else
+
1371 _exit(EXIT_FAILURE);
+
1372 } else {
+
1373 int status;
+
1374 int res = waitpid(pid, &status, 0);
+
1375 if (res == -1) {
+
1376 perror("fuse: waiting for child failed");
+
1377 _exit(EXIT_FAILURE);
+
1378 }
+
1379 return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS;
+
1380 }
+
1381}
+
1382
+
1383/* The parent fuse process has died: decide whether to auto_unmount.
+
1384 *
+
1385 * In the normal case (umount or fusermount -u), the filesystem
+
1386 * has already been unmounted. If we simply unmount again we can
+
1387 * cause problems with stacked mounts (e.g. autofs).
+
1388 *
+
1389 * So we unmount here only in abnormal case where fuse process has
+
1390 * died without unmount happening. To detect this, we first look in
+
1391 * the mount table to make sure the mountpoint is still mounted and
+
1392 * has proper type. If so, we then see if opening the mount dir is
+
1393 * returning 'Transport endpoint is not connected'.
+
1394 *
+
1395 * The order of these is important, because if autofs is in use,
+
1396 * opening the dir to check for ENOTCONN will cause a new mount
+
1397 * in the normal case where filesystem has been unmounted cleanly.
+
1398 */
+
1399static int should_auto_unmount(const char *mnt, const char *type)
+
1400{
+
1401 char *copy;
+
1402 const char *last;
+
1403 int result = 0;
+
1404 int fd;
+
1405
+
1406 copy = strdup(mnt);
+
1407 if (copy == NULL) {
+
1408 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
1409 return 0;
+
1410 }
+
1411
+
1412 if (chdir_to_parent(copy, &last) == -1)
+
1413 goto out;
+
1414 if (check_is_mount(last, mnt, type) == -1)
+
1415 goto out;
+
1416
+
1417 fd = open(mnt, O_RDONLY);
+
1418
+
1419 if (fd != -1) {
+
1420 close(fd);
+
1421 } else {
+
1422 switch(errno) {
+
1423 case ENOTCONN:
+
1424 result = 1;
+
1425 break;
+
1426 case EACCES:
+
1427 result = recheck_ENOTCONN_as_owner(mnt);
+
1428 break;
+
1429 default:
+
1430 result = 0;
+
1431 break;
+
1432 }
+
1433 }
+
1434out:
+
1435 free(copy);
+
1436 return result;
+
1437}
+
1438
+
1439static void usage(void)
+
1440{
+
1441 printf("%s: [options] mountpoint\n"
+
1442 "Options:\n"
+
1443 " -h print help\n"
+
1444 " -V print version\n"
+
1445 " -o opt[,opt...] mount options\n"
+
1446 " -u unmount\n"
+
1447 " -q quiet\n"
+
1448 " -z lazy unmount\n",
+
1449 progname);
+
1450 exit(1);
+
1451}
+
1452
+
1453static void show_version(void)
+
1454{
+
1455 printf("fusermount3 version: %s\n", PACKAGE_VERSION);
+
1456 exit(0);
+
1457}
+
1458
+
1459static void close_range_loop(int min_fd, int max_fd, int cfd)
+
1460{
+
1461 for (int fd = min_fd; fd <= max_fd; fd++)
+
1462 if (fd != cfd)
+
1463 close(fd);
+
1464}
+
1465
+
1466/*
+
1467 * Close all inherited fds that are not needed
+
1468 * Ideally these wouldn't come up at all, applications should better
+
1469 * use FD_CLOEXEC / O_CLOEXEC
+
1470 */
+
1471static int close_inherited_fds(int cfd)
+
1472{
+
1473 int rc = -1;
+
1474 int nullfd;
+
1475
+
1476 /* We can't even report an error */
+
1477 if (cfd <= STDERR_FILENO)
+
1478 return -EINVAL;
+
1479
+
1480#ifdef HAVE_CLOSE_RANGE
+
1481 if (cfd < STDERR_FILENO + 2) {
+
1482 close_range_loop(STDERR_FILENO + 1, cfd - 1, cfd);
+
1483 } else {
+
1484 rc = close_range(STDERR_FILENO + 1, cfd - 1, 0);
+
1485 if (rc < 0)
+
1486 goto fallback;
+
1487 }
+
1488
+
1489 /* Close high range */
+
1490 rc = close_range(cfd + 1, ~0U, 0);
+
1491#else
+
1492 goto fallback; /* make use of fallback to avoid compiler warnings */
+
1493#endif
+
1494
+
1495fallback:
+
1496 if (rc < 0) {
+
1497 int max_fd = sysconf(_SC_OPEN_MAX) - 1;
+
1498
+
1499 close_range_loop(STDERR_FILENO + 1, max_fd, cfd);
+
1500 }
+
1501
+
1502 nullfd = open("/dev/null", O_RDWR);
+
1503 if (nullfd < 0) {
+
1504 perror("fusermount: cannot open /dev/null");
+
1505 return -errno;
+
1506 }
+
1507
+
1508 /* Redirect stdin, stdout, stderr to /dev/null */
+
1509 dup2(nullfd, STDIN_FILENO);
+
1510 dup2(nullfd, STDOUT_FILENO);
+
1511 dup2(nullfd, STDERR_FILENO);
+
1512 if (nullfd > STDERR_FILENO)
+
1513 close(nullfd);
+
1514
+
1515 return 0;
+
1516}
+
1517
+
1518int main(int argc, char *argv[])
+
1519{
+
1520 sigset_t sigset;
+
1521 int ch;
+
1522 int fd;
+
1523 int res;
+
1524 char *origmnt;
+
1525 char *mnt;
+
1526 static int unmount = 0;
+
1527 static int lazy = 0;
+
1528 static int quiet = 0;
+
1529 char *commfd = NULL;
+
1530 long cfd;
+
1531 const char *opts = "";
+
1532 const char *type = NULL;
+
1533 int setup_auto_unmount_only = 0;
+
1534
+
1535 static const struct option long_opts[] = {
+
1536 {"unmount", no_argument, NULL, 'u'},
+
1537 {"lazy", no_argument, NULL, 'z'},
+
1538 {"quiet", no_argument, NULL, 'q'},
+
1539 {"help", no_argument, NULL, 'h'},
+
1540 {"version", no_argument, NULL, 'V'},
+
1541 {"options", required_argument, NULL, 'o'},
+
1542 // Note: auto-unmount and comm-fd don't have short versions.
+
1543 // They'ne meant for internal use by mount.c
+
1544 {"auto-unmount", no_argument, NULL, 'U'},
+
1545 {"comm-fd", required_argument, NULL, 'c'},
+
1546 {0, 0, 0, 0}};
+
1547
+
1548 progname = strdup(argc > 0 ? argv[0] : "fusermount");
+
1549 if (progname == NULL) {
+
1550 fprintf(stderr, "%s: failed to allocate memory\n", argv[0]);
+
1551 exit(1);
+
1552 }
+
1553
+
1554 while ((ch = getopt_long(argc, argv, "hVo:uzq", long_opts,
+
1555 NULL)) != -1) {
+
1556 switch (ch) {
+
1557 case 'h':
+
1558 usage();
+
1559 break;
+
1560
+
1561 case 'V':
+
1562 show_version();
+
1563 break;
+
1564
+
1565 case 'o':
+
1566 opts = optarg;
+
1567 break;
+
1568
+
1569 case 'u':
+
1570 unmount = 1;
+
1571 break;
+
1572 case 'U':
+
1573 unmount = 1;
+
1574 auto_unmount = 1;
+
1575 setup_auto_unmount_only = 1;
+
1576 break;
+
1577 case 'c':
+
1578 commfd = optarg;
+
1579 break;
+
1580 case 'z':
+
1581 lazy = 1;
+
1582 break;
+
1583
+
1584 case 'q':
+
1585 quiet = 1;
+
1586 break;
+
1587
+
1588 default:
+
1589 exit(1);
+
1590 }
+
1591 }
+
1592
+
1593 if (lazy && !unmount) {
+
1594 fprintf(stderr, "%s: -z can only be used with -u\n", progname);
+
1595 exit(1);
+
1596 }
+
1597
+
1598 if (optind >= argc) {
+
1599 fprintf(stderr, "%s: missing mountpoint argument\n", progname);
+
1600 exit(1);
+
1601 } else if (argc > optind + 1) {
+
1602 fprintf(stderr, "%s: extra arguments after the mountpoint\n",
+
1603 progname);
+
1604 exit(1);
+
1605 }
+
1606
+
1607 origmnt = argv[optind];
+
1608
+
1609 drop_privs();
+
1610 mnt = fuse_mnt_resolve_path(progname, origmnt);
+
1611 if (mnt != NULL) {
+
1612 res = chdir("/");
+
1613 if (res == -1) {
+
1614 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1615 goto err_out;
+
1616 }
+
1617 }
+
1618 restore_privs();
+
1619 if (mnt == NULL)
+
1620 exit(1);
+
1621
+
1622 umask(033);
+
1623 if (!setup_auto_unmount_only && unmount)
+
1624 goto do_unmount;
+
1625
+
1626 if(commfd == NULL)
+
1627 commfd = getenv(FUSE_COMMFD_ENV);
+
1628 if (commfd == NULL) {
+
1629 fprintf(stderr, "%s: old style mounting not supported\n",
+
1630 progname);
+
1631 goto err_out;
+
1632 }
+
1633
+
1634 res = libfuse_strtol(commfd, &cfd);
+
1635 if (res) {
+
1636 fprintf(stderr,
+
1637 "%s: invalid _FUSE_COMMFD: %s\n",
+
1638 progname, commfd);
+
1639 goto err_out;
+
1640
+
1641 }
+
1642
+
1643 {
+
1644 struct stat statbuf;
+
1645 fstat(cfd, &statbuf);
+
1646 if(!S_ISSOCK(statbuf.st_mode)) {
+
1647 fprintf(stderr,
+
1648 "%s: file descriptor %li is not a socket, can't send fuse fd\n",
+
1649 progname, cfd);
+
1650 goto err_out;
+
1651 }
+
1652 }
+
1653
+
1654 if (setup_auto_unmount_only)
+
1655 goto wait_for_auto_unmount;
+
1656
+
1657 fd = mount_fuse(mnt, opts, &type);
+
1658 if (fd == -1)
+
1659 goto err_out;
+
1660
+
1661 res = send_fd(cfd, fd);
+
1662 if (res != 0) {
+
1663 umount2(mnt, MNT_DETACH); /* lazy umount */
+
1664 goto err_out;
+
1665 }
+
1666 close(fd);
+
1667
+
1668 if (!auto_unmount) {
+
1669 free(mnt);
+
1670 free((void*) type);
+
1671 return 0;
+
1672 }
+
1673
+
1674wait_for_auto_unmount:
+
1675 /* Become a daemon and wait for the parent to exit or die.
+
1676 ie For the control socket to get closed.
+
1677 Btw, we don't want to use daemon() function here because
+
1678 it forks and messes with the file descriptors. */
+
1679
+
1680 res = close_inherited_fds(cfd);
+
1681 if (res < 0)
+
1682 exit(EXIT_FAILURE);
+
1683
+
1684 setsid();
+
1685 res = chdir("/");
+
1686 if (res == -1) {
+
1687 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1688 goto err_out;
+
1689 }
+
1690
+
1691 sigfillset(&sigset);
+
1692 sigprocmask(SIG_BLOCK, &sigset, NULL);
+
1693
+
1694 lazy = 1;
+
1695 quiet = 1;
+
1696
+
1697 while (1) {
+
1698 unsigned char buf[16];
+
1699 int n = recv(cfd, buf, sizeof(buf), 0);
+
1700 if (!n)
+
1701 break;
+
1702
+
1703 if (n < 0) {
+
1704 if (errno == EINTR)
+
1705 continue;
+
1706 break;
+
1707 }
+
1708 }
+
1709
+
1710 if (!should_auto_unmount(mnt, type)) {
+
1711 goto success_out;
+
1712 }
+
1713
+
1714do_unmount:
+
1715 if (geteuid() == 0)
+
1716 res = unmount_fuse(mnt, quiet, lazy);
+
1717 else {
+
1718 res = umount2(mnt, lazy ? UMOUNT_DETACH : 0);
+
1719 if (res == -1 && !quiet)
+
1720 fprintf(stderr,
+
1721 "%s: failed to unmount %s: %s\n",
+
1722 progname, mnt, strerror(errno));
+
1723 }
+
1724 if (res == -1)
+
1725 goto err_out;
+
1726
+
1727success_out:
+
1728 free((void*) type);
+
1729 free(mnt);
+
1730 return 0;
+
1731
+
1732err_out:
+
1733 free((void*) type);
+
1734 free(mnt);
+
1735 exit(1);
+
1736}
+
+ + + + diff --git a/doc/html/fuse-3_817_84_2util_2mount_8fuse_8c_source.html b/doc/html/fuse-3_817_84_2util_2mount_8fuse_8c_source.html new file mode 100644 index 0000000..a944049 --- /dev/null +++ b/doc/html/fuse-3_817_84_2util_2mount_8fuse_8c_source.html @@ -0,0 +1,515 @@ + + + + + + + +libfuse: fuse-3.17.4/util/mount.fuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount.fuse.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
9#include "fuse_config.h"
+
10
+
11#include <stdio.h>
+
12#include <stdlib.h>
+
13#include <string.h>
+
14#include <unistd.h>
+
15#include <errno.h>
+
16#include <stdint.h>
+
17#include <fcntl.h>
+
18#include <pwd.h>
+
19#include <sys/wait.h>
+
20
+
21#ifdef linux
+
22#include <sys/prctl.h>
+
23#include <sys/syscall.h>
+
24#include <linux/capability.h>
+
25#include <linux/securebits.h>
+
26/* for 2.6 kernels */
+
27#if !defined(SECBIT_KEEP_CAPS) && defined(SECURE_KEEP_CAPS)
+
28#define SECBIT_KEEP_CAPS (issecure_mask(SECURE_KEEP_CAPS))
+
29#endif
+
30#if !defined(SECBIT_KEEP_CAPS_LOCKED) && defined(SECURE_KEEP_CAPS_LOCKED)
+
31#define SECBIT_KEEP_CAPS_LOCKED (issecure_mask(SECURE_KEEP_CAPS_LOCKED))
+
32#endif
+
33#if !defined(SECBIT_NO_SETUID_FIXUP) && defined(SECURE_NO_SETUID_FIXUP)
+
34#define SECBIT_NO_SETUID_FIXUP (issecure_mask(SECURE_NO_SETUID_FIXUP))
+
35#endif
+
36#if !defined(SECBIT_NO_SETUID_FIXUP_LOCKED) && defined(SECURE_NO_SETUID_FIXUP_LOCKED)
+
37#define SECBIT_NO_SETUID_FIXUP_LOCKED (issecure_mask(SECURE_NO_SETUID_FIXUP_LOCKED))
+
38#endif
+
39#if !defined(SECBIT_NOROOT) && defined(SECURE_NOROOT)
+
40#define SECBIT_NOROOT (issecure_mask(SECURE_NOROOT))
+
41#endif
+
42#if !defined(SECBIT_NOROOT_LOCKED) && defined(SECURE_NOROOT_LOCKED)
+
43#define SECBIT_NOROOT_LOCKED (issecure_mask(SECURE_NOROOT_LOCKED))
+
44#endif
+
45#endif
+
46/* linux < 3.5 */
+
47#ifndef PR_SET_NO_NEW_PRIVS
+
48#define PR_SET_NO_NEW_PRIVS 38
+
49#endif
+
50
+
51#include "fuse.h"
+
52
+
53static char *progname;
+
54
+
55static char *xstrdup(const char *s)
+
56{
+
57 char *t = strdup(s);
+
58 if (!t) {
+
59 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
60 exit(1);
+
61 }
+
62 return t;
+
63}
+
64
+
65static void *xrealloc(void *oldptr, size_t size)
+
66{
+
67 void *ptr = realloc(oldptr, size);
+
68 if (!ptr) {
+
69 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
70 exit(1);
+
71 }
+
72 return ptr;
+
73}
+
74
+
75static void add_arg(char **cmdp, const char *opt)
+
76{
+
77 size_t optlen = strlen(opt);
+
78 size_t cmdlen = *cmdp ? strlen(*cmdp) : 0;
+
79 if (optlen >= (SIZE_MAX - cmdlen - 4)/4) {
+
80 fprintf(stderr, "%s: argument too long\n", progname);
+
81 exit(1);
+
82 }
+
83 char *cmd = xrealloc(*cmdp, cmdlen + optlen * 4 + 4);
+
84 char *s;
+
85 s = cmd + cmdlen;
+
86 if (*cmdp)
+
87 *s++ = ' ';
+
88
+
89 *s++ = '\'';
+
90 for (; *opt; opt++) {
+
91 if (*opt == '\'') {
+
92 *s++ = '\'';
+
93 *s++ = '\\';
+
94 *s++ = '\'';
+
95 *s++ = '\'';
+
96 } else
+
97 *s++ = *opt;
+
98 }
+
99 *s++ = '\'';
+
100 *s = '\0';
+
101 *cmdp = cmd;
+
102}
+
103
+
104static char *add_option(const char *opt, char *options)
+
105{
+
106 int oldlen = options ? strlen(options) : 0;
+
107
+
108 options = xrealloc(options, oldlen + 1 + strlen(opt) + 1);
+
109 if (!oldlen)
+
110 strcpy(options, opt);
+
111 else {
+
112 strcat(options, ",");
+
113 strcat(options, opt);
+
114 }
+
115 return options;
+
116}
+
117
+
118static int prepare_fuse_fd(const char *mountpoint, const char* subtype,
+
119 const char *options)
+
120{
+
121 int fuse_fd = -1;
+
122 int flags = -1;
+
123 int subtype_len = strlen(subtype) + 9;
+
124 char* options_copy = xrealloc(NULL, subtype_len);
+
125
+
126 snprintf(options_copy, subtype_len, "subtype=%s", subtype);
+
127 options_copy = add_option(options, options_copy);
+
128 fuse_fd = fuse_open_channel(mountpoint, options_copy);
+
129 if (fuse_fd == -1) {
+
130 exit(1);
+
131 }
+
132
+
133 flags = fcntl(fuse_fd, F_GETFD);
+
134 if (flags == -1 || fcntl(fuse_fd, F_SETFD, flags & ~FD_CLOEXEC) == 1) {
+
135 fprintf(stderr, "%s: Failed to clear CLOEXEC: %s\n",
+
136 progname, strerror(errno));
+
137 exit(1);
+
138 }
+
139
+
140 return fuse_fd;
+
141}
+
142
+
143#ifdef linux
+
144static uint64_t get_capabilities(void)
+
145{
+
146 /*
+
147 * This invokes the capset syscall directly to avoid the libcap
+
148 * dependency, which isn't really justified just for this.
+
149 */
+
150 struct __user_cap_header_struct header = {
+
151 .version = _LINUX_CAPABILITY_VERSION_3,
+
152 .pid = 0,
+
153 };
+
154 struct __user_cap_data_struct data[2];
+
155 memset(data, 0, sizeof(data));
+
156 if (syscall(SYS_capget, &header, data) == -1) {
+
157 fprintf(stderr, "%s: Failed to get capabilities: %s\n",
+
158 progname, strerror(errno));
+
159 exit(1);
+
160 }
+
161
+
162 return data[0].effective | ((uint64_t) data[1].effective << 32);
+
163}
+
164
+
165static void set_capabilities(uint64_t caps)
+
166{
+
167 /*
+
168 * This invokes the capset syscall directly to avoid the libcap
+
169 * dependency, which isn't really justified just for this.
+
170 */
+
171 struct __user_cap_header_struct header = {
+
172 .version = _LINUX_CAPABILITY_VERSION_3,
+
173 .pid = 0,
+
174 };
+
175 struct __user_cap_data_struct data[2];
+
176 memset(data, 0, sizeof(data));
+
177 data[0].effective = data[0].permitted = caps;
+
178 data[1].effective = data[1].permitted = caps >> 32;
+
179 if (syscall(SYS_capset, &header, data) == -1) {
+
180 fprintf(stderr, "%s: Failed to set capabilities: %s\n",
+
181 progname, strerror(errno));
+
182 exit(1);
+
183 }
+
184}
+
185
+
186static void drop_and_lock_capabilities(void)
+
187{
+
188 /* Set and lock securebits. */
+
189 if (prctl(PR_SET_SECUREBITS,
+
190 SECBIT_KEEP_CAPS_LOCKED |
+
191 SECBIT_NO_SETUID_FIXUP |
+
192 SECBIT_NO_SETUID_FIXUP_LOCKED |
+
193 SECBIT_NOROOT |
+
194 SECBIT_NOROOT_LOCKED) == -1) {
+
195 fprintf(stderr, "%s: Failed to set securebits %s\n",
+
196 progname, strerror(errno));
+
197 exit(1);
+
198 }
+
199
+
200 /* Clear the capability bounding set. */
+
201 int cap;
+
202 for (cap = 0; ; cap++) {
+
203 int cap_status = prctl(PR_CAPBSET_READ, cap);
+
204 if (cap_status == 0) {
+
205 continue;
+
206 }
+
207 if (cap_status == -1 && errno == EINVAL) {
+
208 break;
+
209 }
+
210
+
211 if (cap_status != 1) {
+
212 fprintf(stderr,
+
213 "%s: Failed to get capability %u: %s\n",
+
214 progname, cap, strerror(errno));
+
215 exit(1);
+
216 }
+
217 if (prctl(PR_CAPBSET_DROP, cap) == -1) {
+
218 fprintf(stderr,
+
219 "%s: Failed to drop capability %u: %s\n",
+
220 progname, cap, strerror(errno));
+
221 }
+
222 }
+
223
+
224 /* Drop capabilities. */
+
225 set_capabilities(0);
+
226
+
227 /* Prevent re-acquisition of privileges. */
+
228 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
+
229 fprintf(stderr, "%s: Failed to set no_new_privs: %s\n",
+
230 progname, strerror(errno));
+
231 exit(1);
+
232 }
+
233}
+
234#endif
+
235
+
236int main(int argc, char *argv[])
+
237{
+
238 char *type = NULL;
+
239 char *source;
+
240 char *dup_source = NULL;
+
241 const char *mountpoint;
+
242 char *basename;
+
243 char *options = NULL;
+
244 char *command = NULL;
+
245 char *setuid_name = NULL;
+
246 int i;
+
247 int dev = 1;
+
248 int suid = 1;
+
249 int pass_fuse_fd = 0;
+
250 int fuse_fd = 0;
+
251 int drop_privileges = 0;
+
252 char *dev_fd_mountpoint = NULL;
+
253
+
254 progname = argv[0];
+
255 basename = strrchr(argv[0], '/');
+
256 if (basename)
+
257 basename++;
+
258 else
+
259 basename = argv[0];
+
260
+
261 if (strncmp(basename, "mount.fuse.", 11) == 0)
+
262 type = basename + 11;
+
263 if (strncmp(basename, "mount.fuseblk.", 14) == 0)
+
264 type = basename + 14;
+
265
+
266 if (type && !type[0])
+
267 type = NULL;
+
268
+
269 if (argc < 3) {
+
270 fprintf(stderr,
+
271 "usage: %s %s destination [-t type] [-o opt[,opts...]]\n",
+
272 progname, type ? "source" : "type#[source]");
+
273 exit(1);
+
274 }
+
275
+
276 source = argv[1];
+
277 if (!source[0])
+
278 source = NULL;
+
279
+
280 mountpoint = argv[2];
+
281
+
282 for (i = 3; i < argc; i++) {
+
283 if (strcmp(argv[i], "-v") == 0) {
+
284 continue;
+
285 } else if (strcmp(argv[i], "-t") == 0) {
+
286 i++;
+
287
+
288 if (i == argc) {
+
289 fprintf(stderr,
+
290 "%s: missing argument to option '-t'\n",
+
291 progname);
+
292 exit(1);
+
293 }
+
294 type = argv[i];
+
295 if (strncmp(type, "fuse.", 5) == 0)
+
296 type += 5;
+
297 else if (strncmp(type, "fuseblk.", 8) == 0)
+
298 type += 8;
+
299
+
300 if (!type[0]) {
+
301 fprintf(stderr,
+
302 "%s: empty type given as argument to option '-t'\n",
+
303 progname);
+
304 exit(1);
+
305 }
+
306 } else if (strcmp(argv[i], "-o") == 0) {
+
307 char *opts;
+
308 char *opt;
+
309 i++;
+
310 if (i == argc)
+
311 break;
+
312
+
313 opts = xstrdup(argv[i]);
+
314 opt = strtok(opts, ",");
+
315 while (opt) {
+
316 int j;
+
317 int ignore = 0;
+
318 const char *ignore_opts[] = { "",
+
319 "user",
+
320 "nofail",
+
321 "nouser",
+
322 "users",
+
323 "auto",
+
324 "noauto",
+
325 "_netdev",
+
326 NULL};
+
327 if (strncmp(opt, "setuid=", 7) == 0) {
+
328 setuid_name = xstrdup(opt + 7);
+
329 ignore = 1;
+
330 } else if (strcmp(opt,
+
331 "drop_privileges") == 0) {
+
332 pass_fuse_fd = 1;
+
333 drop_privileges = 1;
+
334 ignore = 1;
+
335 }
+
336 for (j = 0; ignore_opts[j]; j++)
+
337 if (strcmp(opt, ignore_opts[j]) == 0)
+
338 ignore = 1;
+
339
+
340 if (!ignore) {
+
341 if (strcmp(opt, "nodev") == 0)
+
342 dev = 0;
+
343 else if (strcmp(opt, "nosuid") == 0)
+
344 suid = 0;
+
345
+
346 options = add_option(opt, options);
+
347 }
+
348 opt = strtok(NULL, ",");
+
349 }
+
350 free(opts);
+
351 }
+
352 }
+
353
+
354 if (drop_privileges) {
+
355 uint64_t required_caps = CAP_TO_MASK(CAP_SETPCAP) |
+
356 CAP_TO_MASK(CAP_SYS_ADMIN);
+
357 if ((get_capabilities() & required_caps) != required_caps) {
+
358 fprintf(stderr, "%s: drop_privileges was requested, which launches the FUSE file system fully unprivileged. In order to do so %s must be run with privileges, please invoke with CAP_SYS_ADMIN and CAP_SETPCAP (e.g. as root).\n",
+
359 progname, progname);
+
360 exit(1);
+
361 }
+
362 }
+
363
+
364 if (dev)
+
365 options = add_option("dev", options);
+
366 if (suid)
+
367 options = add_option("suid", options);
+
368
+
369 if (!type) {
+
370 if (source) {
+
371 dup_source = xstrdup(source);
+
372 type = dup_source;
+
373 source = strchr(type, '#');
+
374 if (source)
+
375 *source++ = '\0';
+
376 if (!type[0]) {
+
377 fprintf(stderr, "%s: empty filesystem type\n",
+
378 progname);
+
379 exit(1);
+
380 }
+
381 } else {
+
382 fprintf(stderr, "%s: empty source\n", progname);
+
383 exit(1);
+
384 }
+
385 }
+
386
+
387 if (setuid_name && setuid_name[0]) {
+
388#ifdef linux
+
389 if (drop_privileges) {
+
390 /*
+
391 * Make securebits more permissive before calling
+
392 * setuid(). Specifically, if SECBIT_KEEP_CAPS and
+
393 * SECBIT_NO_SETUID_FIXUP weren't set, setuid() would
+
394 * have the side effect of dropping all capabilities,
+
395 * and we need to retain CAP_SETPCAP in order to drop
+
396 * all privileges before exec().
+
397 */
+
398 if (prctl(PR_SET_SECUREBITS,
+
399 SECBIT_KEEP_CAPS |
+
400 SECBIT_NO_SETUID_FIXUP) == -1) {
+
401 fprintf(stderr,
+
402 "%s: Failed to set securebits %s\n",
+
403 progname, strerror(errno));
+
404 exit(1);
+
405 }
+
406 }
+
407#endif
+
408
+
409 struct passwd *pwd = getpwnam(setuid_name);
+
410 if (!pwd || setgid(pwd->pw_gid) == -1 || setuid(pwd->pw_uid) == -1) {
+
411 fprintf(stderr, "%s: Failed to setuid to %s: %s\n",
+
412 progname, setuid_name, strerror(errno));
+
413 exit(1);
+
414 }
+
415 } else if (!getenv("HOME")) {
+
416 /* Hack to make filesystems work in the boot environment */
+
417 setenv("HOME", "/root", 0);
+
418 }
+
419
+
420 if (pass_fuse_fd) {
+
421 fuse_fd = prepare_fuse_fd(mountpoint, type, options);
+
422 dev_fd_mountpoint = xrealloc(NULL, 20);
+
423 snprintf(dev_fd_mountpoint, 20, "/dev/fd/%u", fuse_fd);
+
424 mountpoint = dev_fd_mountpoint;
+
425 }
+
426
+
427#ifdef linux
+
428 if (drop_privileges) {
+
429 drop_and_lock_capabilities();
+
430 }
+
431#endif
+
432 add_arg(&command, type);
+
433 if (source)
+
434 add_arg(&command, source);
+
435 add_arg(&command, mountpoint);
+
436 if (options) {
+
437 add_arg(&command, "-o");
+
438 add_arg(&command, options);
+
439 }
+
440
+
441 free(options);
+
442 free(dev_fd_mountpoint);
+
443 free(dup_source);
+
444 free(setuid_name);
+
445
+
446 execl("/bin/sh", "/bin/sh", "-c", command, NULL);
+
447 fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
+
448 strerror(errno));
+
449
+
450 if (pass_fuse_fd)
+
451 close(fuse_fd);
+
452 free(command);
+
453 return 1;
+
454}
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:482
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2cuse_8c.html b/doc/html/fuse-3_818_81_2example_2cuse_8c.html new file mode 100644 index 0000000..d7d11f5 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2cuse_8c.html @@ -0,0 +1,409 @@ + + + + + + + +libfuse: fuse-3.18.1/example/cuse.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
cuse.c File Reference
+
+
+
#include <cuse_lowlevel.h>
+#include <fuse_opt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This example demonstrates how to implement a character device in userspace ("CUSE"). This is only allowed for root. The character device should appear in /dev under the specified name. It can be tested with the cuse_client.c program.

+

Mount the file system with:

cuse -f --name=mydevice
+

You should now have a new /dev/mydevice character device. To "unmount" it, kill the "cuse" process.

+

To compile this example, run

gcc -Wall cuse.c `pkg-config fuse3 --cflags --libs` -o cuse
+

+Source code

+
/*
+
CUSE example: Character device in Userspace
+
Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <cuse_lowlevel.h>
+
#include <fuse_opt.h>
+
#include <stddef.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <errno.h>
+
+
#include "ioctl.h"
+
+
static void *cusexmp_buf;
+
static size_t cusexmp_size;
+
+
static const char *usage =
+
"usage: cusexmp [options]\n"
+
"\n"
+
"options:\n"
+
" --help|-h print this help message\n"
+
" --maj=MAJ|-M MAJ device major number\n"
+
" --min=MIN|-m MIN device minor number\n"
+
" --name=NAME|-n NAME device name (mandatory)\n"
+
" -d -o debug enable debug output (implies -f)\n"
+
" -f foreground operation\n"
+
" -s disable multi-threaded operation\n"
+
"\n";
+
+
static int cusexmp_resize(size_t new_size)
+
{
+
void *new_buf;
+
+
if (new_size == cusexmp_size)
+
return 0;
+
+
new_buf = realloc(cusexmp_buf, new_size);
+
if (!new_buf && new_size)
+
return -ENOMEM;
+
+
if (new_size > cusexmp_size)
+
memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
+
cusexmp_buf = new_buf;
+
cusexmp_size = new_size;
+
+
return 0;
+
}
+
+
static int cusexmp_expand(size_t new_size)
+
{
+
if (new_size > cusexmp_size)
+
return cusexmp_resize(new_size);
+
return 0;
+
}
+
+
static void cusexmp_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+
{
+
fuse_reply_open(req, fi);
+
}
+
+
static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+
struct fuse_file_info *fi)
+
{
+
(void)fi;
+
+
if (off >= cusexmp_size)
+
off = cusexmp_size;
+
if (size > cusexmp_size - off)
+
size = cusexmp_size - off;
+
+
fuse_reply_buf(req, cusexmp_buf + off, size);
+
}
+
+
static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void)fi;
+
+
if (cusexmp_expand(off + size)) {
+
fuse_reply_err(req, ENOMEM);
+
return;
+
}
+
+
memcpy(cusexmp_buf + off, buf, size);
+
fuse_reply_write(req, size);
+
}
+
+
static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+
size_t in_bufsz, size_t out_bufsz, int is_read)
+
{
+
const struct fioc_rw_arg *arg;
+
struct iovec in_iov[2], out_iov[3], iov[3];
+
size_t cur_size;
+
+
/* read in arg */
+
in_iov[0].iov_base = addr;
+
in_iov[0].iov_len = sizeof(*arg);
+
if (!in_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+
return;
+
}
+
arg = in_buf;
+
in_buf += sizeof(*arg);
+
in_bufsz -= sizeof(*arg);
+
+
/* prepare size outputs */
+
out_iov[0].iov_base =
+
addr + offsetof(struct fioc_rw_arg, prev_size);
+
out_iov[0].iov_len = sizeof(arg->prev_size);
+
+
out_iov[1].iov_base =
+
addr + offsetof(struct fioc_rw_arg, new_size);
+
out_iov[1].iov_len = sizeof(arg->new_size);
+
+
/* prepare client buf */
+
if (is_read) {
+
out_iov[2].iov_base = arg->buf;
+
out_iov[2].iov_len = arg->size;
+
if (!out_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+
return;
+
}
+
} else {
+
in_iov[1].iov_base = arg->buf;
+
in_iov[1].iov_len = arg->size;
+
if (arg->size && !in_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+
return;
+
}
+
}
+
+
/* we're all set */
+
cur_size = cusexmp_size;
+
iov[0].iov_base = &cur_size;
+
iov[0].iov_len = sizeof(cur_size);
+
+
iov[1].iov_base = &cusexmp_size;
+
iov[1].iov_len = sizeof(cusexmp_size);
+
+
if (is_read) {
+
size_t off = arg->offset;
+
size_t size = arg->size;
+
+
if (off >= cusexmp_size)
+
off = cusexmp_size;
+
if (size > cusexmp_size - off)
+
size = cusexmp_size - off;
+
+
iov[2].iov_base = cusexmp_buf + off;
+
iov[2].iov_len = size;
+
fuse_reply_ioctl_iov(req, size, iov, 3);
+
} else {
+
if (cusexmp_expand(arg->offset + in_bufsz)) {
+
fuse_reply_err(req, ENOMEM);
+
return;
+
}
+
+
memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+
fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+
}
+
}
+
+
static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+
struct fuse_file_info *fi, unsigned flags,
+
const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
{
+
int is_read = 0;
+
+
(void)fi;
+
+
if (flags & FUSE_IOCTL_COMPAT) {
+
fuse_reply_err(req, ENOSYS);
+
return;
+
}
+
+
switch (cmd) {
+
case FIOC_GET_SIZE:
+
if (!out_bufsz) {
+
struct iovec iov = { arg, sizeof(size_t) };
+
+
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+
} else
+
fuse_reply_ioctl(req, 0, &cusexmp_size,
+
sizeof(cusexmp_size));
+
break;
+
+
case FIOC_SET_SIZE:
+
if (!in_bufsz) {
+
struct iovec iov = { arg, sizeof(size_t) };
+
+
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+
} else {
+
cusexmp_resize(*(size_t *)in_buf);
+
fuse_reply_ioctl(req, 0, NULL, 0);
+
}
+
break;
+
+
case FIOC_READ:
+
is_read = 1;
+
/* fall through */
+
case FIOC_WRITE:
+
fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+
break;
+
+
default:
+
fuse_reply_err(req, EINVAL);
+
}
+
}
+
+
struct cusexmp_param {
+
unsigned major;
+
unsigned minor;
+
char *dev_name;
+
int is_help;
+
};
+
+
#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
+
static const struct fuse_opt cusexmp_opts[] = {
+
CUSEXMP_OPT("-M %u", major),
+
CUSEXMP_OPT("--maj=%u", major),
+
CUSEXMP_OPT("-m %u", minor),
+
CUSEXMP_OPT("--min=%u", minor),
+
CUSEXMP_OPT("-n %s", dev_name),
+
CUSEXMP_OPT("--name=%s", dev_name),
+
FUSE_OPT_KEY("-h", 0),
+
FUSE_OPT_KEY("--help", 0),
+ +
};
+
+
static int cusexmp_process_arg(void *data, const char *arg, int key,
+
struct fuse_args *outargs)
+
{
+
struct cusexmp_param *param = data;
+
+
(void)outargs;
+
(void)arg;
+
+
switch (key) {
+
case 0:
+
param->is_help = 1;
+
fprintf(stderr, "%s", usage);
+
return fuse_opt_add_arg(outargs, "-ho");
+
default:
+
return 1;
+
}
+
}
+
+
static const struct cuse_lowlevel_ops cusexmp_clop = {
+
.init = cusexmp_init,
+
.open = cusexmp_open,
+
.read = cusexmp_read,
+
.write = cusexmp_write,
+
.ioctl = cusexmp_ioctl,
+
};
+
+
int main(int argc, char **argv)
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct cusexmp_param param = { 0, 0, NULL, 0 };
+
char dev_name[128] = "DEVNAME=";
+
const char *dev_info_argv[] = { dev_name };
+
struct cuse_info ci;
+
int ret = 1;
+
+
if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+
printf("failed to parse option\n");
+
free(param.dev_name);
+
goto out;
+
}
+
+
if (!param.is_help) {
+
if (!param.dev_name) {
+
fprintf(stderr, "Error: device name missing\n");
+
goto out;
+
}
+
strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+
free(param.dev_name);
+
}
+
+
memset(&ci, 0, sizeof(ci));
+
ci.dev_major = param.major;
+
ci.dev_minor = param.minor;
+
ci.dev_info_argc = 1;
+
ci.dev_info_argv = dev_info_argv;
+
ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
+
ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL);
+
+
out:
+ +
return ret;
+
}
+
#define FUSE_IOCTL_COMPAT
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t no_interrupt
+ + +
+

Definition in file cuse.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2cuse_8c_source.html b/doc/html/fuse-3_818_81_2example_2cuse_8c_source.html new file mode 100644 index 0000000..3058084 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2cuse_8c_source.html @@ -0,0 +1,395 @@ + + + + + + + +libfuse: fuse-3.18.1/example/cuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse.c
+
+
+Go to the documentation of this file.
1/*
+
2 CUSE example: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8
+
9*/
+
10
+
34#define FUSE_USE_VERSION 31
+
35
+
36#include <cuse_lowlevel.h>
+
37#include <fuse_opt.h>
+
38#include <stddef.h>
+
39#include <stdio.h>
+
40#include <stdlib.h>
+
41#include <string.h>
+
42#include <unistd.h>
+
43#include <errno.h>
+
44
+
45#include "ioctl.h"
+
46
+
47static void *cusexmp_buf;
+
48static size_t cusexmp_size;
+
49
+
50static const char *usage =
+
51"usage: cusexmp [options]\n"
+
52"\n"
+
53"options:\n"
+
54" --help|-h print this help message\n"
+
55" --maj=MAJ|-M MAJ device major number\n"
+
56" --min=MIN|-m MIN device minor number\n"
+
57" --name=NAME|-n NAME device name (mandatory)\n"
+
58" -d -o debug enable debug output (implies -f)\n"
+
59" -f foreground operation\n"
+
60" -s disable multi-threaded operation\n"
+
61"\n";
+
62
+
63static int cusexmp_resize(size_t new_size)
+
64{
+
65 void *new_buf;
+
66
+
67 if (new_size == cusexmp_size)
+
68 return 0;
+
69
+
70 new_buf = realloc(cusexmp_buf, new_size);
+
71 if (!new_buf && new_size)
+
72 return -ENOMEM;
+
73
+
74 if (new_size > cusexmp_size)
+
75 memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
76
+
77 cusexmp_buf = new_buf;
+
78 cusexmp_size = new_size;
+
79
+
80 return 0;
+
81}
+
82
+
83static int cusexmp_expand(size_t new_size)
+
84{
+
85 if (new_size > cusexmp_size)
+
86 return cusexmp_resize(new_size);
+
87 return 0;
+
88}
+
89
+
90static void cusexmp_init(void *userdata, struct fuse_conn_info *conn)
+
91{
+
92 (void)userdata;
+
93
+
94 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
95 conn->no_interrupt = 1;
+
96}
+
97
+
98static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+
99{
+
100 fuse_reply_open(req, fi);
+
101}
+
102
+
103static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+
104 struct fuse_file_info *fi)
+
105{
+
106 (void)fi;
+
107
+
108 if (off >= cusexmp_size)
+
109 off = cusexmp_size;
+
110 if (size > cusexmp_size - off)
+
111 size = cusexmp_size - off;
+
112
+
113 fuse_reply_buf(req, cusexmp_buf + off, size);
+
114}
+
115
+
116static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+
117 off_t off, struct fuse_file_info *fi)
+
118{
+
119 (void)fi;
+
120
+
121 if (cusexmp_expand(off + size)) {
+
122 fuse_reply_err(req, ENOMEM);
+
123 return;
+
124 }
+
125
+
126 memcpy(cusexmp_buf + off, buf, size);
+
127 fuse_reply_write(req, size);
+
128}
+
129
+
130static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+
131 size_t in_bufsz, size_t out_bufsz, int is_read)
+
132{
+
133 const struct fioc_rw_arg *arg;
+
134 struct iovec in_iov[2], out_iov[3], iov[3];
+
135 size_t cur_size;
+
136
+
137 /* read in arg */
+
138 in_iov[0].iov_base = addr;
+
139 in_iov[0].iov_len = sizeof(*arg);
+
140 if (!in_bufsz) {
+
141 fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+
142 return;
+
143 }
+
144 arg = in_buf;
+
145 in_buf += sizeof(*arg);
+
146 in_bufsz -= sizeof(*arg);
+
147
+
148 /* prepare size outputs */
+
149 out_iov[0].iov_base =
+
150 addr + offsetof(struct fioc_rw_arg, prev_size);
+
151 out_iov[0].iov_len = sizeof(arg->prev_size);
+
152
+
153 out_iov[1].iov_base =
+
154 addr + offsetof(struct fioc_rw_arg, new_size);
+
155 out_iov[1].iov_len = sizeof(arg->new_size);
+
156
+
157 /* prepare client buf */
+
158 if (is_read) {
+
159 out_iov[2].iov_base = arg->buf;
+
160 out_iov[2].iov_len = arg->size;
+
161 if (!out_bufsz) {
+
162 fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+
163 return;
+
164 }
+
165 } else {
+
166 in_iov[1].iov_base = arg->buf;
+
167 in_iov[1].iov_len = arg->size;
+
168 if (arg->size && !in_bufsz) {
+
169 fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+
170 return;
+
171 }
+
172 }
+
173
+
174 /* we're all set */
+
175 cur_size = cusexmp_size;
+
176 iov[0].iov_base = &cur_size;
+
177 iov[0].iov_len = sizeof(cur_size);
+
178
+
179 iov[1].iov_base = &cusexmp_size;
+
180 iov[1].iov_len = sizeof(cusexmp_size);
+
181
+
182 if (is_read) {
+
183 size_t off = arg->offset;
+
184 size_t size = arg->size;
+
185
+
186 if (off >= cusexmp_size)
+
187 off = cusexmp_size;
+
188 if (size > cusexmp_size - off)
+
189 size = cusexmp_size - off;
+
190
+
191 iov[2].iov_base = cusexmp_buf + off;
+
192 iov[2].iov_len = size;
+
193 fuse_reply_ioctl_iov(req, size, iov, 3);
+
194 } else {
+
195 if (cusexmp_expand(arg->offset + in_bufsz)) {
+
196 fuse_reply_err(req, ENOMEM);
+
197 return;
+
198 }
+
199
+
200 memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+
201 fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+
202 }
+
203}
+
204
+
205static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+
206 struct fuse_file_info *fi, unsigned flags,
+
207 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
208{
+
209 int is_read = 0;
+
210
+
211 (void)fi;
+
212
+
213 if (flags & FUSE_IOCTL_COMPAT) {
+
214 fuse_reply_err(req, ENOSYS);
+
215 return;
+
216 }
+
217
+
218 switch (cmd) {
+
219 case FIOC_GET_SIZE:
+
220 if (!out_bufsz) {
+
221 struct iovec iov = { arg, sizeof(size_t) };
+
222
+
223 fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+
224 } else
+
225 fuse_reply_ioctl(req, 0, &cusexmp_size,
+
226 sizeof(cusexmp_size));
+
227 break;
+
228
+
229 case FIOC_SET_SIZE:
+
230 if (!in_bufsz) {
+
231 struct iovec iov = { arg, sizeof(size_t) };
+
232
+
233 fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+
234 } else {
+
235 cusexmp_resize(*(size_t *)in_buf);
+
236 fuse_reply_ioctl(req, 0, NULL, 0);
+
237 }
+
238 break;
+
239
+
240 case FIOC_READ:
+
241 is_read = 1;
+
242 /* fall through */
+
243 case FIOC_WRITE:
+
244 fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+
245 break;
+
246
+
247 default:
+
248 fuse_reply_err(req, EINVAL);
+
249 }
+
250}
+
251
+
252struct cusexmp_param {
+
253 unsigned major;
+
254 unsigned minor;
+
255 char *dev_name;
+
256 int is_help;
+
257};
+
258
+
259#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
260
+
261static const struct fuse_opt cusexmp_opts[] = {
+
262 CUSEXMP_OPT("-M %u", major),
+
263 CUSEXMP_OPT("--maj=%u", major),
+
264 CUSEXMP_OPT("-m %u", minor),
+
265 CUSEXMP_OPT("--min=%u", minor),
+
266 CUSEXMP_OPT("-n %s", dev_name),
+
267 CUSEXMP_OPT("--name=%s", dev_name),
+
268 FUSE_OPT_KEY("-h", 0),
+
269 FUSE_OPT_KEY("--help", 0),
+ +
271};
+
272
+
273static int cusexmp_process_arg(void *data, const char *arg, int key,
+
274 struct fuse_args *outargs)
+
275{
+
276 struct cusexmp_param *param = data;
+
277
+
278 (void)outargs;
+
279 (void)arg;
+
280
+
281 switch (key) {
+
282 case 0:
+
283 param->is_help = 1;
+
284 fprintf(stderr, "%s", usage);
+
285 return fuse_opt_add_arg(outargs, "-ho");
+
286 default:
+
287 return 1;
+
288 }
+
289}
+
290
+
291static const struct cuse_lowlevel_ops cusexmp_clop = {
+
292 .init = cusexmp_init,
+
293 .open = cusexmp_open,
+
294 .read = cusexmp_read,
+
295 .write = cusexmp_write,
+
296 .ioctl = cusexmp_ioctl,
+
297};
+
298
+
299int main(int argc, char **argv)
+
300{
+
301 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
302 struct cusexmp_param param = { 0, 0, NULL, 0 };
+
303 char dev_name[128] = "DEVNAME=";
+
304 const char *dev_info_argv[] = { dev_name };
+
305 struct cuse_info ci;
+
306 int ret = 1;
+
307
+
308 if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+
309 printf("failed to parse option\n");
+
310 free(param.dev_name);
+
311 goto out;
+
312 }
+
313
+
314 if (!param.is_help) {
+
315 if (!param.dev_name) {
+
316 fprintf(stderr, "Error: device name missing\n");
+
317 goto out;
+
318 }
+
319 strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+
320 free(param.dev_name);
+
321 }
+
322
+
323 memset(&ci, 0, sizeof(ci));
+
324 ci.dev_major = param.major;
+
325 ci.dev_minor = param.minor;
+
326 ci.dev_info_argc = 1;
+
327 ci.dev_info_argv = dev_info_argv;
+
328 ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
329
+
330 ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL);
+
331
+
332out:
+
333 fuse_opt_free_args(&args);
+
334 return ret;
+
335}
+
#define FUSE_IOCTL_COMPAT
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t no_interrupt
+ + +
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2cuse__client_8c.html b/doc/html/fuse-3_818_81_2example_2cuse__client_8c.html new file mode 100644 index 0000000..70e6a23 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2cuse__client_8c.html @@ -0,0 +1,214 @@ + + + + + + + +libfuse: fuse-3.18.1/example/cuse_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
cuse_client.c File Reference
+
+
+
#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the cuse.c example file system.

+

Example usage (assuming that /dev/foobar is a CUSE device provided by the cuse.c example file system):

$ cuse_client /dev/foobar s
+0
+
+$ echo "hello" | cuse_client /dev/foobar w 6
+Writing 6 bytes
+transferred 6 bytes (0 -> 6)
+
+$ cuse_client /dev/foobar s
+6
+
+$ cuse_client /dev/foobar r 10
+hello
+transferred 6 bytes (6 -> 6)
+

Compiling this example

gcc -Wall cuse_client.c -o cuse_client
+

+Source Code

+
/*
+
FUSE fioclient: FUSE ioctl example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#include <sys/types.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <sys/ioctl.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <ctype.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include "ioctl.h"
+
+
const char *usage =
+
"Usage: cuse_client FIOC_FILE COMMAND\n"
+
"\n"
+
"COMMANDS\n"
+
" s [SIZE] : get size if SIZE is omitted, set size otherwise\n"
+
" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+
" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+
"\n";
+
+
static int do_rw(int fd, int is_read, size_t size, off_t offset,
+
size_t *prev_size, size_t *new_size)
+
{
+
struct fioc_rw_arg arg = { .offset = offset };
+
ssize_t ret;
+
+
arg.buf = calloc(1, size);
+
if (!arg.buf) {
+
fprintf(stderr, "failed to allocated %zu bytes\n", size);
+
return -1;
+
}
+
+
if (is_read) {
+
arg.size = size;
+
ret = ioctl(fd, FIOC_READ, &arg);
+
if (ret >= 0)
+
fwrite(arg.buf, 1, ret, stdout);
+
} else {
+
arg.size = fread(arg.buf, 1, size, stdin);
+
fprintf(stderr, "Writing %zu bytes\n", arg.size);
+
ret = ioctl(fd, FIOC_WRITE, &arg);
+
}
+
+
if (ret >= 0) {
+
*prev_size = arg.prev_size;
+
*new_size = arg.new_size;
+
} else
+
perror("ioctl");
+
+
free(arg.buf);
+
return ret;
+
}
+
+
int main(int argc, char **argv)
+
{
+
size_t param[2] = { };
+
size_t size, prev_size = 0, new_size = 0;
+
char cmd;
+
int fd, i, rc;
+
+
if (argc < 3)
+
goto usage;
+
+
fd = open(argv[1], O_RDWR);
+
if (fd < 0) {
+
perror("open");
+
return 1;
+
}
+
+
cmd = tolower(argv[2][0]);
+
argc -= 3;
+
argv += 3;
+
+
for (i = 0; i < argc; i++) {
+
char *endp;
+
param[i] = strtoul(argv[i], &endp, 0);
+
if (endp == argv[i] || *endp != '\0')
+
goto usage;
+
}
+
+
switch (cmd) {
+
case 's':
+
if (!argc) {
+
if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
perror("ioctl");
+
goto error;
+
}
+
printf("%zu\n", size);
+
} else {
+
size = param[0];
+
if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
perror("ioctl");
+
goto error;
+
}
+
}
+
close(fd);
+
return 0;
+
+
case 'r':
+
case 'w':
+
rc = do_rw(fd, cmd == 'r', param[0], param[1],
+
&prev_size, &new_size);
+
if (rc < 0)
+
goto error;
+
fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+
rc, prev_size, new_size);
+
close(fd);
+
return 0;
+
}
+
+
usage:
+
fprintf(stderr, "%s", usage);
+
return 1;
+
+
error:
+
close(fd);
+
return 1;
+
}
+
+

Definition in file cuse_client.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2cuse__client_8c_source.html b/doc/html/fuse-3_818_81_2example_2cuse__client_8c_source.html new file mode 100644 index 0000000..2f367b1 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2cuse__client_8c_source.html @@ -0,0 +1,188 @@ + + + + + + + +libfuse: fuse-3.18.1/example/cuse_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioclient: FUSE ioctl example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
40#include <sys/types.h>
+
41#include <fcntl.h>
+
42#include <sys/stat.h>
+
43#include <sys/ioctl.h>
+
44#include <stdio.h>
+
45#include <stdlib.h>
+
46#include <ctype.h>
+
47#include <errno.h>
+
48#include <unistd.h>
+
49#include "ioctl.h"
+
50
+
51const char *usage =
+
52"Usage: cuse_client FIOC_FILE COMMAND\n"
+
53"\n"
+
54"COMMANDS\n"
+
55" s [SIZE] : get size if SIZE is omitted, set size otherwise\n"
+
56" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+
57" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+
58"\n";
+
59
+
60static int do_rw(int fd, int is_read, size_t size, off_t offset,
+
61 size_t *prev_size, size_t *new_size)
+
62{
+
63 struct fioc_rw_arg arg = { .offset = offset };
+
64 ssize_t ret;
+
65
+
66 arg.buf = calloc(1, size);
+
67 if (!arg.buf) {
+
68 fprintf(stderr, "failed to allocated %zu bytes\n", size);
+
69 return -1;
+
70 }
+
71
+
72 if (is_read) {
+
73 arg.size = size;
+
74 ret = ioctl(fd, FIOC_READ, &arg);
+
75 if (ret >= 0)
+
76 fwrite(arg.buf, 1, ret, stdout);
+
77 } else {
+
78 arg.size = fread(arg.buf, 1, size, stdin);
+
79 fprintf(stderr, "Writing %zu bytes\n", arg.size);
+
80 ret = ioctl(fd, FIOC_WRITE, &arg);
+
81 }
+
82
+
83 if (ret >= 0) {
+
84 *prev_size = arg.prev_size;
+
85 *new_size = arg.new_size;
+
86 } else
+
87 perror("ioctl");
+
88
+
89 free(arg.buf);
+
90 return ret;
+
91}
+
92
+
93int main(int argc, char **argv)
+
94{
+
95 size_t param[2] = { };
+
96 size_t size, prev_size = 0, new_size = 0;
+
97 char cmd;
+
98 int fd, i, rc;
+
99
+
100 if (argc < 3)
+
101 goto usage;
+
102
+
103 fd = open(argv[1], O_RDWR);
+
104 if (fd < 0) {
+
105 perror("open");
+
106 return 1;
+
107 }
+
108
+
109 cmd = tolower(argv[2][0]);
+
110 argc -= 3;
+
111 argv += 3;
+
112
+
113 for (i = 0; i < argc; i++) {
+
114 char *endp;
+
115 param[i] = strtoul(argv[i], &endp, 0);
+
116 if (endp == argv[i] || *endp != '\0')
+
117 goto usage;
+
118 }
+
119
+
120 switch (cmd) {
+
121 case 's':
+
122 if (!argc) {
+
123 if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
124 perror("ioctl");
+
125 goto error;
+
126 }
+
127 printf("%zu\n", size);
+
128 } else {
+
129 size = param[0];
+
130 if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
131 perror("ioctl");
+
132 goto error;
+
133 }
+
134 }
+
135 close(fd);
+
136 return 0;
+
137
+
138 case 'r':
+
139 case 'w':
+
140 rc = do_rw(fd, cmd == 'r', param[0], param[1],
+
141 &prev_size, &new_size);
+
142 if (rc < 0)
+
143 goto error;
+
144 fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+
145 rc, prev_size, new_size);
+
146 close(fd);
+
147 return 0;
+
148 }
+
149
+
150usage:
+
151 fprintf(stderr, "%s", usage);
+
152 return 1;
+
153
+
154error:
+
155 close(fd);
+
156 return 1;
+
157}
+ +
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2hello_8c.html b/doc/html/fuse-3_818_81_2example_2hello_8c.html new file mode 100644 index 0000000..f71b26e --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2hello_8c.html @@ -0,0 +1,265 @@ + + + + + + + +libfuse: fuse-3.18.1/example/hello.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using high-level API

+

Compile with:

gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <stddef.h>
+
#include <assert.h>
+
+
/*
+
* Command line options
+
*
+
* We can't set default values for the char* fields here because
+
* fuse_opt_parse would attempt to free() them when the user specifies
+
* different values on the command line.
+
*/
+
static struct options {
+
const char *filename;
+
const char *contents;
+
int show_help;
+
} options;
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--name=%s", filename),
+
OPTION("--contents=%s", contents),
+
OPTION("-h", show_help),
+
OPTION("--help", show_help),
+ +
};
+
+
static void *hello_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->kernel_cache = 1;
+
+
/* Test setting flags the old way */
+ + +
+
return NULL;
+
}
+
+
static int hello_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res = 0;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
} else if (strcmp(path+1, options.filename) == 0) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(options.contents);
+
} else
+
res = -ENOENT;
+
+
return res;
+
}
+
+
static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int hello_open(const char *path, struct fuse_file_info *fi)
+
{
+
if (strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
+
return 0;
+
}
+
+
static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
size_t len;
+
(void) fi;
+
if(strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
len = strlen(options.contents);
+
if (offset < len) {
+
if (offset + size > len)
+
size = len - offset;
+
memcpy(buf, options.contents + offset, size);
+
} else
+
size = 0;
+
+
return size;
+
}
+
+
static const struct fuse_operations hello_oper = {
+
.init = hello_init,
+
.getattr = hello_getattr,
+
.readdir = hello_readdir,
+
.open = hello_open,
+
.read = hello_read,
+
};
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --name=<s> Name of the \"hello\" file\n"
+
" (default: \"hello\")\n"
+
" --contents=<s> Contents \"hello\" file\n"
+
" (default \"Hello, World!\\n\")\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[])
+
{
+
int ret;
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
+
/* Set defaults -- we have to use strdup so that
+
fuse_opt_parse can free the defaults if other
+
values are specified */
+
options.filename = strdup("hello");
+
options.contents = strdup("Hello World!\n");
+
+
/* Parse options */
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
/* When --help is specified, first print our own file-system
+
specific help text, then signal fuse_main to show
+
additional help (by adding `--help` to the options again)
+
without usage: line (by setting argv[0] to the empty
+
string) */
+
if (options.show_help) {
+
show_help(argv[0]);
+
assert(fuse_opt_add_arg(&args, "--help") == 0);
+
args.argv[0][0] = '\0';
+
}
+
+
ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+ +
return ret;
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_ASYNC_READ
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file hello.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2hello_8c_source.html b/doc/html/fuse-3_818_81_2example_2hello_8c_source.html new file mode 100644 index 0000000..fa8a869 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2hello_8c_source.html @@ -0,0 +1,254 @@ + + + + + + + +libfuse: fuse-3.18.1/example/hello.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
22#define FUSE_USE_VERSION 31
+
23
+
24#include <fuse.h>
+
25#include <stdio.h>
+
26#include <string.h>
+
27#include <errno.h>
+
28#include <fcntl.h>
+
29#include <stddef.h>
+
30#include <assert.h>
+
31
+
32/*
+
33 * Command line options
+
34 *
+
35 * We can't set default values for the char* fields here because
+
36 * fuse_opt_parse would attempt to free() them when the user specifies
+
37 * different values on the command line.
+
38 */
+
39static struct options {
+
40 const char *filename;
+
41 const char *contents;
+
42 int show_help;
+
43} options;
+
44
+
45#define OPTION(t, p) \
+
46 { t, offsetof(struct options, p), 1 }
+
47static const struct fuse_opt option_spec[] = {
+
48 OPTION("--name=%s", filename),
+
49 OPTION("--contents=%s", contents),
+
50 OPTION("-h", show_help),
+
51 OPTION("--help", show_help),
+ +
53};
+
54
+
55static void *hello_init(struct fuse_conn_info *conn,
+
56 struct fuse_config *cfg)
+
57{
+
58 (void) conn;
+
59 cfg->kernel_cache = 1;
+
60
+
61 /* Test setting flags the old way */
+ + +
64
+
65 return NULL;
+
66}
+
67
+
68static int hello_getattr(const char *path, struct stat *stbuf,
+
69 struct fuse_file_info *fi)
+
70{
+
71 (void) fi;
+
72 int res = 0;
+
73
+
74 memset(stbuf, 0, sizeof(struct stat));
+
75 if (strcmp(path, "/") == 0) {
+
76 stbuf->st_mode = S_IFDIR | 0755;
+
77 stbuf->st_nlink = 2;
+
78 } else if (strcmp(path+1, options.filename) == 0) {
+
79 stbuf->st_mode = S_IFREG | 0444;
+
80 stbuf->st_nlink = 1;
+
81 stbuf->st_size = strlen(options.contents);
+
82 } else
+
83 res = -ENOENT;
+
84
+
85 return res;
+
86}
+
87
+
88static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
89 off_t offset, struct fuse_file_info *fi,
+
90 enum fuse_readdir_flags flags)
+
91{
+
92 (void) offset;
+
93 (void) fi;
+
94 (void) flags;
+
95
+
96 if (strcmp(path, "/") != 0)
+
97 return -ENOENT;
+
98
+
99 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
100 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
101 filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
102
+
103 return 0;
+
104}
+
105
+
106static int hello_open(const char *path, struct fuse_file_info *fi)
+
107{
+
108 if (strcmp(path+1, options.filename) != 0)
+
109 return -ENOENT;
+
110
+
111 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
112 return -EACCES;
+
113
+
114 return 0;
+
115}
+
116
+
117static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
118 struct fuse_file_info *fi)
+
119{
+
120 size_t len;
+
121 (void) fi;
+
122 if(strcmp(path+1, options.filename) != 0)
+
123 return -ENOENT;
+
124
+
125 len = strlen(options.contents);
+
126 if (offset < len) {
+
127 if (offset + size > len)
+
128 size = len - offset;
+
129 memcpy(buf, options.contents + offset, size);
+
130 } else
+
131 size = 0;
+
132
+
133 return size;
+
134}
+
135
+
136static const struct fuse_operations hello_oper = {
+
137 .init = hello_init,
+
138 .getattr = hello_getattr,
+
139 .readdir = hello_readdir,
+
140 .open = hello_open,
+
141 .read = hello_read,
+
142};
+
143
+
144static void show_help(const char *progname)
+
145{
+
146 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
147 printf("File-system specific options:\n"
+
148 " --name=<s> Name of the \"hello\" file\n"
+
149 " (default: \"hello\")\n"
+
150 " --contents=<s> Contents \"hello\" file\n"
+
151 " (default \"Hello, World!\\n\")\n"
+
152 "\n");
+
153}
+
154
+
155int main(int argc, char *argv[])
+
156{
+
157 int ret;
+
158 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
159
+
160 /* Set defaults -- we have to use strdup so that
+
161 fuse_opt_parse can free the defaults if other
+
162 values are specified */
+
163 options.filename = strdup("hello");
+
164 options.contents = strdup("Hello World!\n");
+
165
+
166 /* Parse options */
+
167 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
168 return 1;
+
169
+
170 /* When --help is specified, first print our own file-system
+
171 specific help text, then signal fuse_main to show
+
172 additional help (by adding `--help` to the options again)
+
173 without usage: line (by setting argv[0] to the empty
+
174 string) */
+
175 if (options.show_help) {
+
176 show_help(argv[0]);
+
177 assert(fuse_opt_add_arg(&args, "--help") == 0);
+
178 args.argv[0][0] = '\0';
+
179 }
+
180
+
181 ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+
182 fuse_opt_free_args(&args);
+
183 return ret;
+
184}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_ASYNC_READ
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2hello__ll_8c.html b/doc/html/fuse-3_818_81_2example_2hello__ll_8c.html new file mode 100644 index 0000000..f449f62 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2hello__ll_8c.html @@ -0,0 +1,389 @@ + + + + + + + +libfuse: fuse-3.18.1/example/hello_ll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello_ll.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using low-level API

+

Compile with:

gcc -Wall hello_ll.c `pkg-config fuse3 --cflags --libs` -o hello_ll
+

Note: If the pkg-config command fails due to the absence of the fuse3.pc file, you should configure the path to the fuse3.pc file in the PKG_CONFIG_PATH variable.

+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <assert.h>
+
+
static const char *hello_str = "Hello World!\n";
+
static const char *hello_name = "hello";
+
+
static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
{
+
stbuf->st_ino = ino;
+
switch (ino) {
+
case 1:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
+
case 2:
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(hello_str);
+
break;
+
+
default:
+
return -1;
+
}
+
return 0;
+
}
+
+
static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
+
/* Test setting flags the old way */
+ +
conn->want &= ~FUSE_CAP_ASYNC_READ;
+
}
+
+
static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (hello_stat(ino, &stbuf) == -1)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, 1.0);
+
}
+
+
static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
+
if (parent != 1 || strcmp(name, hello_name) != 0)
+
fuse_reply_err(req, ENOENT);
+
else {
+
memset(&e, 0, sizeof(e));
+
e.ino = 2;
+
e.attr_timeout = 1.0;
+
e.entry_timeout = 1.0;
+
hello_stat(e.ino, &e.attr);
+
+
fuse_reply_entry(req, &e);
+
}
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+ +
{
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize)
+
{
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (ino != 1)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, ".", 1);
+
dirbuf_add(req, &b, "..", 1);
+
dirbuf_add(req, &b, hello_name, 2);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
if (ino != 2)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else
+
fuse_reply_open(req, fi);
+
}
+
+
static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
assert(ino == 2);
+
reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
}
+
+
static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
{
+
const char *buf = "hello_ll_getxattr_value";
+
fuse_reply_buf(req, buf, strlen(buf));
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
(void)flags;
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
const char* exp_val = "hello_ll_setxattr_value";
+
if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
strlen(exp_val) == size &&
+
strncmp(value, exp_val, size) == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops hello_ll_oper = {
+
.init = hello_ll_init,
+
.lookup = hello_ll_lookup,
+
.getattr = hello_ll_getattr,
+
.readdir = hello_ll_readdir,
+
.open = hello_ll_open,
+
.read = hello_ll_read,
+
.setxattr = hello_ll_setxattr,
+
.getxattr = hello_ll_getxattr,
+
.removexattr = hello_ll_removexattr,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
se = fuse_session_new(&args, &hello_ll_oper,
+
sizeof(hello_ll_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file hello_ll.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2hello__ll_8c_source.html b/doc/html/fuse-3_818_81_2example_2hello__ll_8c_source.html new file mode 100644 index 0000000..a938d65 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2hello__ll_8c_source.html @@ -0,0 +1,376 @@ + + + + + + + +libfuse: fuse-3.18.1/example/hello_ll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello_ll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
25#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
26
+
27#include <fuse_lowlevel.h>
+
28#include <stdio.h>
+
29#include <stdlib.h>
+
30#include <string.h>
+
31#include <errno.h>
+
32#include <fcntl.h>
+
33#include <unistd.h>
+
34#include <assert.h>
+
35
+
36static const char *hello_str = "Hello World!\n";
+
37static const char *hello_name = "hello";
+
38
+
39static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
40{
+
41 stbuf->st_ino = ino;
+
42 switch (ino) {
+
43 case 1:
+
44 stbuf->st_mode = S_IFDIR | 0755;
+
45 stbuf->st_nlink = 2;
+
46 break;
+
47
+
48 case 2:
+
49 stbuf->st_mode = S_IFREG | 0444;
+
50 stbuf->st_nlink = 1;
+
51 stbuf->st_size = strlen(hello_str);
+
52 break;
+
53
+
54 default:
+
55 return -1;
+
56 }
+
57 return 0;
+
58}
+
59
+
60static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
61{
+
62 (void)userdata;
+
63
+
64 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
65 conn->no_interrupt = 1;
+
66
+
67 /* Test setting flags the old way */
+ +
69 conn->want &= ~FUSE_CAP_ASYNC_READ;
+
70}
+
71
+
72static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
73 struct fuse_file_info *fi)
+
74{
+
75 struct stat stbuf;
+
76
+
77 (void) fi;
+
78
+
79 memset(&stbuf, 0, sizeof(stbuf));
+
80 if (hello_stat(ino, &stbuf) == -1)
+
81 fuse_reply_err(req, ENOENT);
+
82 else
+
83 fuse_reply_attr(req, &stbuf, 1.0);
+
84}
+
85
+
86static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
87{
+
88 struct fuse_entry_param e;
+
89
+
90 if (parent != 1 || strcmp(name, hello_name) != 0)
+
91 fuse_reply_err(req, ENOENT);
+
92 else {
+
93 memset(&e, 0, sizeof(e));
+
94 e.ino = 2;
+
95 e.attr_timeout = 1.0;
+
96 e.entry_timeout = 1.0;
+
97 hello_stat(e.ino, &e.attr);
+
98
+
99 fuse_reply_entry(req, &e);
+
100 }
+
101}
+
102
+
103struct dirbuf {
+
104 char *p;
+
105 size_t size;
+
106};
+
107
+
108static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
109 fuse_ino_t ino)
+
110{
+
111 struct stat stbuf;
+
112 size_t oldsize = b->size;
+
113 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
114 b->p = (char *) realloc(b->p, b->size);
+
115 memset(&stbuf, 0, sizeof(stbuf));
+
116 stbuf.st_ino = ino;
+
117 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
118 b->size);
+
119}
+
120
+
121#define min(x, y) ((x) < (y) ? (x) : (y))
+
122
+
123static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
124 off_t off, size_t maxsize)
+
125{
+
126 if (off < bufsize)
+
127 return fuse_reply_buf(req, buf + off,
+
128 min(bufsize - off, maxsize));
+
129 else
+
130 return fuse_reply_buf(req, NULL, 0);
+
131}
+
132
+
133static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
134 off_t off, struct fuse_file_info *fi)
+
135{
+
136 (void) fi;
+
137
+
138 if (ino != 1)
+
139 fuse_reply_err(req, ENOTDIR);
+
140 else {
+
141 struct dirbuf b;
+
142
+
143 memset(&b, 0, sizeof(b));
+
144 dirbuf_add(req, &b, ".", 1);
+
145 dirbuf_add(req, &b, "..", 1);
+
146 dirbuf_add(req, &b, hello_name, 2);
+
147 reply_buf_limited(req, b.p, b.size, off, size);
+
148 free(b.p);
+
149 }
+
150}
+
151
+
152static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
153 struct fuse_file_info *fi)
+
154{
+
155 if (ino != 2)
+
156 fuse_reply_err(req, EISDIR);
+
157 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
158 fuse_reply_err(req, EACCES);
+
159 else
+
160 fuse_reply_open(req, fi);
+
161}
+
162
+
163static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
164 off_t off, struct fuse_file_info *fi)
+
165{
+
166 (void) fi;
+
167
+
168 assert(ino == 2);
+
169 reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
170}
+
171
+
172static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
173 size_t size)
+
174{
+
175 (void)size;
+
176 assert(ino == 1 || ino == 2);
+
177 if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
178 {
+
179 const char *buf = "hello_ll_getxattr_value";
+
180 fuse_reply_buf(req, buf, strlen(buf));
+
181 }
+
182 else
+
183 {
+
184 fuse_reply_err(req, ENOTSUP);
+
185 }
+
186}
+
187
+
188static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
189 const char *value, size_t size, int flags)
+
190{
+
191 (void)flags;
+
192 (void)size;
+
193 assert(ino == 1 || ino == 2);
+
194 const char* exp_val = "hello_ll_setxattr_value";
+
195 if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
196 strlen(exp_val) == size &&
+
197 strncmp(value, exp_val, size) == 0)
+
198 {
+
199 fuse_reply_err(req, 0);
+
200 }
+
201 else
+
202 {
+
203 fuse_reply_err(req, ENOTSUP);
+
204 }
+
205}
+
206
+
207static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
208{
+
209 assert(ino == 1 || ino == 2);
+
210 if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
211 {
+
212 fuse_reply_err(req, 0);
+
213 }
+
214 else
+
215 {
+
216 fuse_reply_err(req, ENOTSUP);
+
217 }
+
218}
+
219
+
220static const struct fuse_lowlevel_ops hello_ll_oper = {
+
221 .init = hello_ll_init,
+
222 .lookup = hello_ll_lookup,
+
223 .getattr = hello_ll_getattr,
+
224 .readdir = hello_ll_readdir,
+
225 .open = hello_ll_open,
+
226 .read = hello_ll_read,
+
227 .setxattr = hello_ll_setxattr,
+
228 .getxattr = hello_ll_getxattr,
+
229 .removexattr = hello_ll_removexattr,
+
230};
+
231
+
232int main(int argc, char *argv[])
+
233{
+
234 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
235 struct fuse_session *se;
+
236 struct fuse_cmdline_opts opts;
+
237 struct fuse_loop_config *config;
+
238 int ret = -1;
+
239
+
240 if (fuse_parse_cmdline(&args, &opts) != 0)
+
241 return 1;
+
242 if (opts.show_help) {
+
243 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
246 ret = 0;
+
247 goto err_out1;
+
248 } else if (opts.show_version) {
+
249 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
251 ret = 0;
+
252 goto err_out1;
+
253 }
+
254
+
255 if(opts.mountpoint == NULL) {
+
256 printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
257 printf(" %s --help\n", argv[0]);
+
258 ret = 1;
+
259 goto err_out1;
+
260 }
+
261
+
262 se = fuse_session_new(&args, &hello_ll_oper,
+
263 sizeof(hello_ll_oper), NULL);
+
264 if (se == NULL)
+
265 goto err_out1;
+
266
+
267 if (fuse_set_signal_handlers(se) != 0)
+
268 goto err_out2;
+
269
+
270 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
271 goto err_out3;
+
272
+
273 fuse_daemonize(opts.foreground);
+
274
+
275 /* Block until ctrl+c or fusermount -u */
+
276 if (opts.singlethread)
+
277 ret = fuse_session_loop(se);
+
278 else {
+
279 config = fuse_loop_cfg_create();
+
280 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
281 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
282 ret = fuse_session_loop_mt(se, config);
+
283 fuse_loop_cfg_destroy(config);
+
284 config = NULL;
+
285 }
+
286
+ +
288err_out3:
+ +
290err_out2:
+ +
292err_out1:
+
293 free(opts.mountpoint);
+
294 fuse_opt_free_args(&args);
+
295
+
296 return ret ? 1 : 0;
+
297}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2hello__ll__uds_8c.html b/doc/html/fuse-3_818_81_2example_2hello__ll__uds_8c.html new file mode 100644 index 0000000..b09bede --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2hello__ll__uds_8c.html @@ -0,0 +1,391 @@ + + + + + + + +libfuse: fuse-3.18.1/example/hello_ll_uds.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello_ll_uds.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <fuse_kernel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using low-level API and a custom io. This custom io is implemented using UNIX domain sockets (of type SOCK_STREAM)

+

Compile with:

gcc -Wall hello_ll_uds.c `pkg-config fuse3 --cflags --libs` -o hello_ll_uds
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <assert.h>
+
+
static const char *hello_str = "Hello World!\n";
+
static const char *hello_name = "hello";
+
+
static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
{
+
stbuf->st_ino = ino;
+
switch (ino) {
+
case 1:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
+
case 2:
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(hello_str);
+
break;
+
+
default:
+
return -1;
+
}
+
return 0;
+
}
+
+
static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
+
/* Test setting flags the old way */
+ +
conn->want &= ~FUSE_CAP_ASYNC_READ;
+
}
+
+
static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (hello_stat(ino, &stbuf) == -1)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, 1.0);
+
}
+
+
static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
+
if (parent != 1 || strcmp(name, hello_name) != 0)
+
fuse_reply_err(req, ENOENT);
+
else {
+
memset(&e, 0, sizeof(e));
+
e.ino = 2;
+
e.attr_timeout = 1.0;
+
e.entry_timeout = 1.0;
+
hello_stat(e.ino, &e.attr);
+
+
fuse_reply_entry(req, &e);
+
}
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+ +
{
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize)
+
{
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (ino != 1)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, ".", 1);
+
dirbuf_add(req, &b, "..", 1);
+
dirbuf_add(req, &b, hello_name, 2);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
if (ino != 2)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else
+
fuse_reply_open(req, fi);
+
}
+
+
static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
assert(ino == 2);
+
reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
}
+
+
static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
{
+
const char *buf = "hello_ll_getxattr_value";
+
fuse_reply_buf(req, buf, strlen(buf));
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
(void)flags;
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
const char* exp_val = "hello_ll_setxattr_value";
+
if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
strlen(exp_val) == size &&
+
strncmp(value, exp_val, size) == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops hello_ll_oper = {
+
.init = hello_ll_init,
+
.lookup = hello_ll_lookup,
+
.getattr = hello_ll_getattr,
+
.readdir = hello_ll_readdir,
+
.open = hello_ll_open,
+
.read = hello_ll_read,
+
.setxattr = hello_ll_setxattr,
+
.getxattr = hello_ll_getxattr,
+
.removexattr = hello_ll_removexattr,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
se = fuse_session_new(&args, &hello_ll_oper,
+
sizeof(hello_ll_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file hello_ll_uds.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2hello__ll__uds_8c_source.html b/doc/html/fuse-3_818_81_2example_2hello__ll__uds_8c_source.html new file mode 100644 index 0000000..549e631 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2hello__ll__uds_8c_source.html @@ -0,0 +1,442 @@ + + + + + + + +libfuse: fuse-3.18.1/example/hello_ll_uds.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello_ll_uds.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2022 Tofik Sonono <tofik.sonono@intel.com>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
23#define FUSE_USE_VERSION 34
+
24
+
25
+
26#ifndef _GNU_SOURCE
+
27#define _GNU_SOURCE
+
28#endif
+
29
+
30#include <fuse_lowlevel.h>
+
31#include <fuse_kernel.h>
+
32#include <stdio.h>
+
33#include <stdlib.h>
+
34#include <string.h>
+
35#include <errno.h>
+
36#include <fcntl.h>
+
37#include <unistd.h>
+
38#include <assert.h>
+
39#include <sys/socket.h>
+
40#include <sys/un.h>
+
41
+
42static const char *hello_str = "Hello World!\n";
+
43static const char *hello_name = "hello";
+
44
+
45static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
46{
+
47 stbuf->st_ino = ino;
+
48 switch (ino) {
+
49 case 1:
+
50 stbuf->st_mode = S_IFDIR | 0755;
+
51 stbuf->st_nlink = 2;
+
52 break;
+
53
+
54 case 2:
+
55 stbuf->st_mode = S_IFREG | 0444;
+
56 stbuf->st_nlink = 1;
+
57 stbuf->st_size = strlen(hello_str);
+
58 break;
+
59
+
60 default:
+
61 return -1;
+
62 }
+
63 return 0;
+
64}
+
65
+
66static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
67 struct fuse_file_info *fi)
+
68{
+
69 struct stat stbuf;
+
70
+
71 (void) fi;
+
72
+
73 memset(&stbuf, 0, sizeof(stbuf));
+
74 if (hello_stat(ino, &stbuf) == -1)
+
75 fuse_reply_err(req, ENOENT);
+
76 else
+
77 fuse_reply_attr(req, &stbuf, 1.0);
+
78}
+
79
+
80static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
81{
+
82 (void)userdata;
+
83
+
84 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
85 conn->no_interrupt = 1;
+
86}
+
87
+
88static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
89{
+
90 struct fuse_entry_param e;
+
91
+
92 if (parent != 1 || strcmp(name, hello_name) != 0)
+
93 fuse_reply_err(req, ENOENT);
+
94 else {
+
95 memset(&e, 0, sizeof(e));
+
96 e.ino = 2;
+
97 e.attr_timeout = 1.0;
+
98 e.entry_timeout = 1.0;
+
99 hello_stat(e.ino, &e.attr);
+
100
+
101 fuse_reply_entry(req, &e);
+
102 }
+
103}
+
104
+
105struct dirbuf {
+
106 char *p;
+
107 size_t size;
+
108};
+
109
+
110static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
111 fuse_ino_t ino)
+
112{
+
113 struct stat stbuf;
+
114 size_t oldsize = b->size;
+
115 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
116 b->p = (char *) realloc(b->p, b->size);
+
117 memset(&stbuf, 0, sizeof(stbuf));
+
118 stbuf.st_ino = ino;
+
119 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
120 b->size);
+
121}
+
122
+
123#define min(x, y) ((x) < (y) ? (x) : (y))
+
124
+
125static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
126 off_t off, size_t maxsize)
+
127{
+
128 if (off < bufsize)
+
129 return fuse_reply_buf(req, buf + off,
+
130 min(bufsize - off, maxsize));
+
131 else
+
132 return fuse_reply_buf(req, NULL, 0);
+
133}
+
134
+
135static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
136 off_t off, struct fuse_file_info *fi)
+
137{
+
138 (void) fi;
+
139
+
140 if (ino != 1)
+
141 fuse_reply_err(req, ENOTDIR);
+
142 else {
+
143 struct dirbuf b;
+
144
+
145 memset(&b, 0, sizeof(b));
+
146 dirbuf_add(req, &b, ".", 1);
+
147 dirbuf_add(req, &b, "..", 1);
+
148 dirbuf_add(req, &b, hello_name, 2);
+
149 reply_buf_limited(req, b.p, b.size, off, size);
+
150 free(b.p);
+
151 }
+
152}
+
153
+
154static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
155 struct fuse_file_info *fi)
+
156{
+
157 if (ino != 2)
+
158 fuse_reply_err(req, EISDIR);
+
159 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
160 fuse_reply_err(req, EACCES);
+
161 else
+
162 fuse_reply_open(req, fi);
+
163}
+
164
+
165static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
166 off_t off, struct fuse_file_info *fi)
+
167{
+
168 (void) fi;
+
169
+
170 assert(ino == 2);
+
171 reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
172}
+
173
+
174static const struct fuse_lowlevel_ops hello_ll_oper = {
+
175 .init = hello_ll_init,
+
176 .lookup = hello_ll_lookup,
+
177 .getattr = hello_ll_getattr,
+
178 .readdir = hello_ll_readdir,
+
179 .open = hello_ll_open,
+
180 .read = hello_ll_read,
+
181};
+
182
+
183static int create_socket(const char *socket_path) {
+
184 struct sockaddr_un addr;
+
185
+
186 if (strnlen(socket_path, sizeof(addr.sun_path)) >=
+
187 sizeof(addr.sun_path)) {
+
188 printf("Socket path may not be longer than %zu characters\n",
+
189 sizeof(addr.sun_path) - 1);
+
190 return -1;
+
191 }
+
192
+
193 if (remove(socket_path) == -1 && errno != ENOENT) {
+
194 printf("Could not delete previous socket file entry at %s. Error: "
+
195 "%s\n", socket_path, strerror(errno));
+
196 return -1;
+
197 }
+
198
+
199 memset(&addr, 0, sizeof(struct sockaddr_un));
+
200 strcpy(addr.sun_path, socket_path);
+
201
+
202 int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+
203 if (sfd == -1) {
+
204 printf("Could not create socket. Error: %s\n", strerror(errno));
+
205 return -1;
+
206 }
+
207
+
208 addr.sun_family = AF_UNIX;
+
209 if (bind(sfd, (struct sockaddr *) &addr,
+
210 sizeof(struct sockaddr_un)) == -1) {
+
211 printf("Could not bind socket. Error: %s\n", strerror(errno));
+
212 return -1;
+
213 }
+
214
+
215 if (listen(sfd, 1) == -1)
+
216 return -1;
+
217
+
218 printf("Awaiting connection on socket at %s...\n", socket_path);
+
219 int cfd = accept(sfd, NULL, NULL);
+
220 if (cfd == -1) {
+
221 printf("Could not accept connection. Error: %s\n",
+
222 strerror(errno));
+
223 return -1;
+
224 } else {
+
225 printf("Accepted connection!\n");
+
226 }
+
227 return cfd;
+
228}
+
229
+
230static ssize_t stream_writev(int fd, struct iovec *iov, int count,
+
231 void *userdata) {
+
232 (void)userdata;
+
233
+
234 ssize_t written = 0;
+
235 int cur = 0;
+
236 for (;;) {
+
237 written = writev(fd, iov+cur, count-cur);
+
238 if (written < 0)
+
239 return written;
+
240
+
241 while (cur < count && written >= iov[cur].iov_len)
+
242 written -= iov[cur++].iov_len;
+
243 if (cur == count)
+
244 break;
+
245
+
246 iov[cur].iov_base = (char *)iov[cur].iov_base + written;
+
247 iov[cur].iov_len -= written;
+
248 }
+
249 return written;
+
250}
+
251
+
252
+
253static ssize_t readall(int fd, void *buf, size_t len) {
+
254 size_t count = 0;
+
255
+
256 while (count < len) {
+
257 int i = read(fd, (char *)buf + count, len - count);
+
258 if (!i)
+
259 break;
+
260
+
261 if (i < 0)
+
262 return i;
+
263
+
264 count += i;
+
265 }
+
266 return count;
+
267}
+
268
+
269static ssize_t stream_read(int fd, void *buf, size_t buf_len, void *userdata) {
+
270 (void)userdata;
+
271
+
272 int res = readall(fd, buf, sizeof(struct fuse_in_header));
+
273 if (res == -1)
+
274 return res;
+
275
+
276
+
277 uint32_t packet_len = ((struct fuse_in_header *)buf)->len;
+
278 if (packet_len > buf_len)
+
279 return -1;
+
280
+
281 int prev_res = res;
+
282
+
283 res = readall(fd, (char *)buf + sizeof(struct fuse_in_header),
+
284 packet_len - sizeof(struct fuse_in_header));
+
285
+
286 return (res == -1) ? res : (res + prev_res);
+
287}
+
288
+
289static ssize_t stream_splice_send(int fdin, off_t *offin, int fdout,
+
290 off_t *offout, size_t len,
+
291 unsigned int flags, void *userdata) {
+
292 (void)userdata;
+
293
+
294 size_t count = 0;
+
295 while (count < len) {
+
296 int i = splice(fdin, offin, fdout, offout, len - count, flags);
+
297 if (i < 1)
+
298 return i;
+
299
+
300 count += i;
+
301 }
+
302 return count;
+
303}
+
304
+
305static void fuse_cmdline_help_uds(void)
+
306{
+
307 printf(" -h --help print help\n"
+
308 " -V --version print version\n"
+
309 " -d -o debug enable debug output (implies -f)\n");
+
310}
+
311
+
312int main(int argc, char *argv[])
+
313{
+
314 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
315 struct fuse_session *se;
+
316 struct fuse_cmdline_opts opts;
+
317 const struct fuse_custom_io io = {
+
318 .writev = stream_writev,
+
319 .read = stream_read,
+
320 .splice_receive = NULL,
+
321 .splice_send = stream_splice_send,
+
322 };
+
323 int cfd = -1;
+
324 int ret = -1;
+
325
+
326 if (fuse_parse_cmdline(&args, &opts) != 0)
+
327 return 1;
+
328 if (opts.show_help) {
+
329 printf("usage: %s [options]\n\n", argv[0]);
+
330 fuse_cmdline_help_uds();
+ +
332 ret = 0;
+
333 goto err_out1;
+
334 } else if (opts.show_version) {
+
335 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
337 ret = 0;
+
338 goto err_out1;
+
339 }
+
340
+
341 se = fuse_session_new(&args, &hello_ll_oper,
+
342 sizeof(hello_ll_oper), NULL);
+
343 if (se == NULL)
+
344 goto err_out1;
+
345
+
346 if (fuse_set_signal_handlers(se) != 0)
+
347 goto err_out2;
+
348
+
349 cfd = create_socket("/tmp/libfuse-hello-ll.sock");
+
350 if (cfd == -1)
+
351 goto err_out3;
+
352
+
353 if (fuse_session_custom_io(se, &io, cfd) != 0)
+
354 goto err_out3;
+
355
+
356 /* Block until ctrl+c */
+
357 ret = fuse_session_loop(se);
+
358err_out3:
+ +
360err_out2:
+ +
362err_out1:
+
363 free(opts.mountpoint);
+
364 fuse_opt_free_args(&args);
+
365
+
366 return ret ? 1 : 0;
+
367}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_lowlevel_help(void)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2invalidate__path_8c.html b/doc/html/fuse-3_818_81_2example_2invalidate__path_8c.html new file mode 100644 index 0000000..a914d33 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2invalidate__path_8c.html @@ -0,0 +1,390 @@ + + + + + + + +libfuse: fuse-3.18.1/example/invalidate_path.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
invalidate_path.c File Reference
+
+
+
#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with two files:

    +
  • 'current-time', whose contents change dynamically: it always contains the current time (same as in notify_inval_inode.c).
  • +
  • 'growing', whose size changes dynamically, growing by 1 byte after each update. This aims to check if cached file metadata is also invalidated.
  • +
+

+Compilation

+
gcc -Wall invalidate_path.c `pkg-config fuse3 --cflags --libs` -o invalidate_path
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
(C) 2017 EditShare LLC <slawek.rudnicki@editshare.com>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 34
+
+
#include <fuse.h>
+
#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define TIME_FILE_NAME "current_time"
+
#define TIME_FILE_INO 2
+
#define GROW_FILE_NAME "growing"
+
#define GROW_FILE_INO 3
+
+
static char time_file_contents[MAX_STR_LEN];
+
static size_t grow_file_size;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->entry_timeout = NO_TIMEOUT;
+
cfg->attr_timeout = NO_TIMEOUT;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path,
+
struct stat *stbuf, struct fuse_file_info* fi) {
+
(void) fi;
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_ino = 1;
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
} else if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
stbuf->st_ino = TIME_FILE_INO;
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(time_file_contents);
+
} else if (strcmp(path, "/" GROW_FILE_NAME) == 0) {
+
stbuf->st_ino = GROW_FILE_INO;
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = grow_file_size;
+
} else {
+
return -ENOENT;
+
}
+
+
return 0;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags) {
+
(void) fi;
+
(void) offset;
+
(void) flags;
+
if (strcmp(path, "/") != 0) {
+
return -ENOTDIR;
+
} else {
+
(void) filler;
+
(void) buf;
+
struct stat file_stat;
+
xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL);
+
filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL);
+
filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
return 0;
+
}
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi) {
+
(void) path;
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi) {
+
(void) fi;
+
(void) offset;
+
if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
int file_length = strlen(time_file_contents);
+
int to_copy = offset + size <= file_length
+
? size
+
: file_length - offset;
+
memcpy(buf, time_file_contents, to_copy);
+
return to_copy;
+
} else {
+
assert(strcmp(path, "/" GROW_FILE_NAME) == 0);
+
int to_copy = offset + size <= grow_file_size
+
? size
+
: grow_file_size - offset;
+
memset(buf, 'x', to_copy);
+
return to_copy;
+
}
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.readdir = xmp_readdir,
+
.open = xmp_open,
+
.read = xmp_read,
+
};
+
+
static void update_fs(void) {
+
static int count = 0;
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
int time_file_size = strftime(time_file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(time_file_size != 0);
+
+
grow_file_size = count++;
+
}
+
+
static int invalidate(struct fuse *fuse, const char *path) {
+
int status = fuse_invalidate_path(fuse, path);
+
if (status == -ENOENT) {
+
return 0;
+
} else {
+
return status;
+
}
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse *fuse = (struct fuse*) data;
+
+
while (1) {
+
update_fs();
+
if (!options.no_notify) {
+
assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0);
+
assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0);
+
}
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse *fuse;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config config;
+
int res;
+
+
/* Initialize the files */
+
update_fs();
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
+
if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
res = 0;
+
goto out1;
+
} else if (opts.show_help) {
+
show_help(argv[0]);
+ +
fuse_lib_help(&args);
+
res = 0;
+
goto out1;
+
} else if (!opts.mountpoint) {
+
fprintf(stderr, "error: no mountpoint specified\n");
+
res = 1;
+
goto out1;
+
}
+
+
fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL);
+
if (fuse == NULL) {
+
res = 1;
+
goto out1;
+
}
+
+
if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
res = 1;
+
goto out2;
+
}
+
+
if (fuse_daemonize(opts.foreground) != 0) {
+
res = 1;
+
goto out3;
+
}
+
+
pthread_t updater; /* Start thread to update file contents */
+
int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n", strerror(ret));
+
return 1;
+
};
+
+
struct fuse_session *se = fuse_get_session(fuse);
+
if (fuse_set_signal_handlers(se) != 0) {
+
res = 1;
+
goto out3;
+
}
+
+
if (opts.singlethread)
+
res = fuse_loop(fuse);
+
else {
+
config.clone_fd = opts.clone_fd;
+
config.max_idle_threads = opts.max_idle_threads;
+
res = fuse_loop_mt(fuse, &config);
+
}
+
if (res)
+
res = 1;
+
+ +
out3:
+
fuse_unmount(fuse);
+
out2:
+
fuse_destroy(fuse);
+
out1:
+
free(opts.mountpoint);
+ +
return res;
+
}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5197
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5146
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5202
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint32_t keep_cache
Definition fuse_common.h:69
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file invalidate_path.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2invalidate__path_8c_source.html b/doc/html/fuse-3_818_81_2example_2invalidate__path_8c_source.html new file mode 100644 index 0000000..b347047 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2invalidate__path_8c_source.html @@ -0,0 +1,370 @@ + + + + + + + +libfuse: fuse-3.18.1/example/invalidate_path.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
invalidate_path.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4 (C) 2017 EditShare LLC <slawek.rudnicki@editshare.com>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8 */
+
9
+
28#define FUSE_USE_VERSION 34
+
29
+
30#include <fuse.h>
+
31#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
+
32
+
33#include <stdio.h>
+
34#include <stdlib.h>
+
35#include <string.h>
+
36#include <errno.h>
+
37#include <fcntl.h>
+
38#include <assert.h>
+
39#include <stddef.h>
+
40#include <unistd.h>
+
41#include <pthread.h>
+
42
+
43/* We can't actually tell the kernel that there is no
+
44 timeout, so we just send a big value */
+
45#define NO_TIMEOUT 500000
+
46
+
47#define MAX_STR_LEN 128
+
48#define TIME_FILE_NAME "current_time"
+
49#define TIME_FILE_INO 2
+
50#define GROW_FILE_NAME "growing"
+
51#define GROW_FILE_INO 3
+
52
+
53static char time_file_contents[MAX_STR_LEN];
+
54static size_t grow_file_size;
+
55
+
56/* Command line parsing */
+
57struct options {
+
58 int no_notify;
+
59 int update_interval;
+
60};
+
61static struct options options = {
+
62 .no_notify = 0,
+
63 .update_interval = 1,
+
64};
+
65
+
66#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
67static const struct fuse_opt option_spec[] = {
+
68 OPTION("--no-notify", no_notify),
+
69 OPTION("--update-interval=%d", update_interval),
+ +
71};
+
72
+
73static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
74{
+
75 (void) conn;
+
76 cfg->entry_timeout = NO_TIMEOUT;
+
77 cfg->attr_timeout = NO_TIMEOUT;
+
78 cfg->negative_timeout = 0;
+
79
+
80 return NULL;
+
81}
+
82
+
83static int xmp_getattr(const char *path,
+
84 struct stat *stbuf, struct fuse_file_info* fi) {
+
85 (void) fi;
+
86 if (strcmp(path, "/") == 0) {
+
87 stbuf->st_ino = 1;
+
88 stbuf->st_mode = S_IFDIR | 0755;
+
89 stbuf->st_nlink = 1;
+
90 } else if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
91 stbuf->st_ino = TIME_FILE_INO;
+
92 stbuf->st_mode = S_IFREG | 0444;
+
93 stbuf->st_nlink = 1;
+
94 stbuf->st_size = strlen(time_file_contents);
+
95 } else if (strcmp(path, "/" GROW_FILE_NAME) == 0) {
+
96 stbuf->st_ino = GROW_FILE_INO;
+
97 stbuf->st_mode = S_IFREG | 0444;
+
98 stbuf->st_nlink = 1;
+
99 stbuf->st_size = grow_file_size;
+
100 } else {
+
101 return -ENOENT;
+
102 }
+
103
+
104 return 0;
+
105}
+
106
+
107static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
108 off_t offset, struct fuse_file_info *fi,
+
109 enum fuse_readdir_flags flags) {
+
110 (void) fi;
+
111 (void) offset;
+
112 (void) flags;
+
113 if (strcmp(path, "/") != 0) {
+
114 return -ENOTDIR;
+
115 } else {
+
116 (void) filler;
+
117 (void) buf;
+
118 struct stat file_stat;
+
119 xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL);
+
120 filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
121 xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL);
+
122 filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
123 return 0;
+
124 }
+
125}
+
126
+
127static int xmp_open(const char *path, struct fuse_file_info *fi) {
+
128 (void) path;
+
129 /* Make cache persistent even if file is closed,
+
130 this makes it easier to see the effects */
+
131 fi->keep_cache = 1;
+
132 return 0;
+
133}
+
134
+
135static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
136 struct fuse_file_info *fi) {
+
137 (void) fi;
+
138 (void) offset;
+
139 if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
140 int file_length = strlen(time_file_contents);
+
141 int to_copy = offset + size <= file_length
+
142 ? size
+
143 : file_length - offset;
+
144 memcpy(buf, time_file_contents, to_copy);
+
145 return to_copy;
+
146 } else {
+
147 assert(strcmp(path, "/" GROW_FILE_NAME) == 0);
+
148 int to_copy = offset + size <= grow_file_size
+
149 ? size
+
150 : grow_file_size - offset;
+
151 memset(buf, 'x', to_copy);
+
152 return to_copy;
+
153 }
+
154}
+
155
+
156static const struct fuse_operations xmp_oper = {
+
157 .init = xmp_init,
+
158 .getattr = xmp_getattr,
+
159 .readdir = xmp_readdir,
+
160 .open = xmp_open,
+
161 .read = xmp_read,
+
162};
+
163
+
164static void update_fs(void) {
+
165 static int count = 0;
+
166 struct tm *now;
+
167 time_t t;
+
168 t = time(NULL);
+
169 now = localtime(&t);
+
170 assert(now != NULL);
+
171
+
172 int time_file_size = strftime(time_file_contents, MAX_STR_LEN,
+
173 "The current time is %H:%M:%S\n", now);
+
174 assert(time_file_size != 0);
+
175
+
176 grow_file_size = count++;
+
177}
+
178
+
179static int invalidate(struct fuse *fuse, const char *path) {
+
180 int status = fuse_invalidate_path(fuse, path);
+
181 if (status == -ENOENT) {
+
182 return 0;
+
183 } else {
+
184 return status;
+
185 }
+
186}
+
187
+
188static void* update_fs_loop(void *data) {
+
189 struct fuse *fuse = (struct fuse*) data;
+
190
+
191 while (1) {
+
192 update_fs();
+
193 if (!options.no_notify) {
+
194 assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0);
+
195 assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0);
+
196 }
+
197 sleep(options.update_interval);
+
198 }
+
199 return NULL;
+
200}
+
201
+
202static void show_help(const char *progname)
+
203{
+
204 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
205 printf("File-system specific options:\n"
+
206 " --update-interval=<secs> Update-rate of file system contents\n"
+
207 " --no-notify Disable kernel notifications\n"
+
208 "\n");
+
209}
+
210
+
211int main(int argc, char *argv[]) {
+
212 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
213 struct fuse *fuse;
+
214 struct fuse_cmdline_opts opts;
+
215 struct fuse_loop_config config;
+
216 int res;
+
217
+
218 /* Initialize the files */
+
219 update_fs();
+
220
+
221 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
222 return 1;
+
223
+
224 if (fuse_parse_cmdline(&args, &opts) != 0)
+
225 return 1;
+
226
+
227 if (opts.show_version) {
+
228 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
230 res = 0;
+
231 goto out1;
+
232 } else if (opts.show_help) {
+
233 show_help(argv[0]);
+ +
235 fuse_lib_help(&args);
+
236 res = 0;
+
237 goto out1;
+
238 } else if (!opts.mountpoint) {
+
239 fprintf(stderr, "error: no mountpoint specified\n");
+
240 res = 1;
+
241 goto out1;
+
242 }
+
243
+
244 fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL);
+
245 if (fuse == NULL) {
+
246 res = 1;
+
247 goto out1;
+
248 }
+
249
+
250 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
251 res = 1;
+
252 goto out2;
+
253 }
+
254
+
255 if (fuse_daemonize(opts.foreground) != 0) {
+
256 res = 1;
+
257 goto out3;
+
258 }
+
259
+
260 pthread_t updater; /* Start thread to update file contents */
+
261 int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse);
+
262 if (ret != 0) {
+
263 fprintf(stderr, "pthread_create failed with %s\n", strerror(ret));
+
264 return 1;
+
265 };
+
266
+
267 struct fuse_session *se = fuse_get_session(fuse);
+
268 if (fuse_set_signal_handlers(se) != 0) {
+
269 res = 1;
+
270 goto out3;
+
271 }
+
272
+
273 if (opts.singlethread)
+
274 res = fuse_loop(fuse);
+
275 else {
+
276 config.clone_fd = opts.clone_fd;
+
277 config.max_idle_threads = opts.max_idle_threads;
+
278 res = fuse_loop_mt(fuse, &config);
+
279 }
+
280 if (res)
+
281 res = 1;
+
282
+ +
284out3:
+
285 fuse_unmount(fuse);
+
286out2:
+
287 fuse_destroy(fuse);
+
288out1:
+
289 free(opts.mountpoint);
+
290 fuse_opt_free_args(&args);
+
291 return res;
+
292}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5197
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5146
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5202
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint32_t keep_cache
Definition fuse_common.h:69
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2ioctl_8c.html b/doc/html/fuse-3_818_81_2example_2ioctl_8c.html new file mode 100644 index 0000000..20481e5 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2ioctl_8c.html @@ -0,0 +1,294 @@ + + + + + + + +libfuse: fuse-3.18.1/example/ioctl.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl.c File Reference
+
+
+
#include <fuse.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This example illustrates how to write a FUSE file system that can process (a restricted set of) ioctls. It can be tested with the ioctl_client.c program.

+

Compile with:

gcc -Wall ioctl.c `pkg-config fuse3 --cflags --libs` -o ioctl
+

+Source code

+
/*
+
FUSE fioc: FUSE ioctl example
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 35
+
+
#include <fuse.h>
+
#include <stdlib.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <time.h>
+
#include <errno.h>
+
+
#include "ioctl.h"
+
+
#define FIOC_NAME "fioc"
+
+
enum {
+
FIOC_NONE,
+
FIOC_ROOT,
+
FIOC_FILE,
+
};
+
+
static void *fioc_buf;
+
static size_t fioc_size;
+
+
static int fioc_resize(size_t new_size)
+
{
+
void *new_buf;
+
+
if (new_size == fioc_size)
+
return 0;
+
+
new_buf = realloc(fioc_buf, new_size);
+
if (!new_buf && new_size)
+
return -ENOMEM;
+
+
if (new_size > fioc_size)
+
memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
+
fioc_buf = new_buf;
+
fioc_size = new_size;
+
+
return 0;
+
}
+
+
static int fioc_expand(size_t new_size)
+
{
+
if (new_size > fioc_size)
+
return fioc_resize(new_size);
+
return 0;
+
}
+
+
static int fioc_file_type(const char *path)
+
{
+
if (strcmp(path, "/") == 0)
+
return FIOC_ROOT;
+
if (strcmp(path, "/" FIOC_NAME) == 0)
+
return FIOC_FILE;
+
return FIOC_NONE;
+
}
+
+
static int fioc_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
stbuf->st_uid = getuid();
+
stbuf->st_gid = getgid();
+
stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
+
switch (fioc_file_type(path)) {
+
case FIOC_ROOT:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
case FIOC_FILE:
+
stbuf->st_mode = S_IFREG | 0644;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = fioc_size;
+
break;
+
case FIOC_NONE:
+
return -ENOENT;
+
}
+
+
return 0;
+
}
+
+
static int fioc_open(const char *path, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_NONE)
+
return 0;
+
return -ENOENT;
+
}
+
+
static int fioc_do_read(char *buf, size_t size, off_t offset)
+
{
+
if (offset >= fioc_size)
+
return 0;
+
+
if (size > fioc_size - offset)
+
size = fioc_size - offset;
+
+
memcpy(buf, fioc_buf + offset, size);
+
+
return size;
+
}
+
+
static int fioc_read(const char *path, char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_do_read(buf, size, offset);
+
}
+
+
static int fioc_do_write(const char *buf, size_t size, off_t offset)
+
{
+
if (fioc_expand(offset + size))
+
return -ENOMEM;
+
+
memcpy(fioc_buf + offset, buf, size);
+
+
return size;
+
}
+
+
static int fioc_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_do_write(buf, size, offset);
+
}
+
+
static int fioc_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_resize(size);
+
}
+
+
static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) fi;
+
(void) offset;
+
(void) flags;
+
+
if (fioc_file_type(path) != FIOC_ROOT)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int fioc_ioctl(const char *path, unsigned int cmd, void *arg,
+
struct fuse_file_info *fi, unsigned int flags, void *data)
+
{
+
(void) arg;
+
(void) fi;
+
(void) flags;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
if (flags & FUSE_IOCTL_COMPAT)
+
return -ENOSYS;
+
+
switch (cmd) {
+
case FIOC_GET_SIZE:
+
*(size_t *)data = fioc_size;
+
return 0;
+
+
case FIOC_SET_SIZE:
+
fioc_resize(*(size_t *)data);
+
return 0;
+
}
+
+
return -EINVAL;
+
}
+
+
static const struct fuse_operations fioc_oper = {
+
.getattr = fioc_getattr,
+
.readdir = fioc_readdir,
+
.truncate = fioc_truncate,
+
.open = fioc_open,
+
.read = fioc_read,
+
.write = fioc_write,
+
.ioctl = fioc_ioctl,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
return fuse_main(argc, argv, &fioc_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_IOCTL_COMPAT
+ + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+

Definition in file ioctl.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2ioctl_8c_source.html b/doc/html/fuse-3_818_81_2example_2ioctl_8c_source.html new file mode 100644 index 0000000..4cb5436 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2ioctl_8c_source.html @@ -0,0 +1,283 @@ + + + + + + + +libfuse: fuse-3.18.1/example/ioctl.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioc: FUSE ioctl example
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
25#define FUSE_USE_VERSION 35
+
26
+
27#include <fuse.h>
+
28#include <stdlib.h>
+
29#include <stdio.h>
+
30#include <string.h>
+
31#include <unistd.h>
+
32#include <time.h>
+
33#include <errno.h>
+
34
+
35#include "ioctl.h"
+
36
+
37#define FIOC_NAME "fioc"
+
38
+
39enum {
+
40 FIOC_NONE,
+
41 FIOC_ROOT,
+
42 FIOC_FILE,
+
43};
+
44
+
45static void *fioc_buf;
+
46static size_t fioc_size;
+
47
+
48static int fioc_resize(size_t new_size)
+
49{
+
50 void *new_buf;
+
51
+
52 if (new_size == fioc_size)
+
53 return 0;
+
54
+
55 new_buf = realloc(fioc_buf, new_size);
+
56 if (!new_buf && new_size)
+
57 return -ENOMEM;
+
58
+
59 if (new_size > fioc_size)
+
60 memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
61
+
62 fioc_buf = new_buf;
+
63 fioc_size = new_size;
+
64
+
65 return 0;
+
66}
+
67
+
68static int fioc_expand(size_t new_size)
+
69{
+
70 if (new_size > fioc_size)
+
71 return fioc_resize(new_size);
+
72 return 0;
+
73}
+
74
+
75static int fioc_file_type(const char *path)
+
76{
+
77 if (strcmp(path, "/") == 0)
+
78 return FIOC_ROOT;
+
79 if (strcmp(path, "/" FIOC_NAME) == 0)
+
80 return FIOC_FILE;
+
81 return FIOC_NONE;
+
82}
+
83
+
84static int fioc_getattr(const char *path, struct stat *stbuf,
+
85 struct fuse_file_info *fi)
+
86{
+
87 (void) fi;
+
88 stbuf->st_uid = getuid();
+
89 stbuf->st_gid = getgid();
+
90 stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
91
+
92 switch (fioc_file_type(path)) {
+
93 case FIOC_ROOT:
+
94 stbuf->st_mode = S_IFDIR | 0755;
+
95 stbuf->st_nlink = 2;
+
96 break;
+
97 case FIOC_FILE:
+
98 stbuf->st_mode = S_IFREG | 0644;
+
99 stbuf->st_nlink = 1;
+
100 stbuf->st_size = fioc_size;
+
101 break;
+
102 case FIOC_NONE:
+
103 return -ENOENT;
+
104 }
+
105
+
106 return 0;
+
107}
+
108
+
109static int fioc_open(const char *path, struct fuse_file_info *fi)
+
110{
+
111 (void) fi;
+
112
+
113 if (fioc_file_type(path) != FIOC_NONE)
+
114 return 0;
+
115 return -ENOENT;
+
116}
+
117
+
118static int fioc_do_read(char *buf, size_t size, off_t offset)
+
119{
+
120 if (offset >= fioc_size)
+
121 return 0;
+
122
+
123 if (size > fioc_size - offset)
+
124 size = fioc_size - offset;
+
125
+
126 memcpy(buf, fioc_buf + offset, size);
+
127
+
128 return size;
+
129}
+
130
+
131static int fioc_read(const char *path, char *buf, size_t size,
+
132 off_t offset, struct fuse_file_info *fi)
+
133{
+
134 (void) fi;
+
135
+
136 if (fioc_file_type(path) != FIOC_FILE)
+
137 return -EINVAL;
+
138
+
139 return fioc_do_read(buf, size, offset);
+
140}
+
141
+
142static int fioc_do_write(const char *buf, size_t size, off_t offset)
+
143{
+
144 if (fioc_expand(offset + size))
+
145 return -ENOMEM;
+
146
+
147 memcpy(fioc_buf + offset, buf, size);
+
148
+
149 return size;
+
150}
+
151
+
152static int fioc_write(const char *path, const char *buf, size_t size,
+
153 off_t offset, struct fuse_file_info *fi)
+
154{
+
155 (void) fi;
+
156
+
157 if (fioc_file_type(path) != FIOC_FILE)
+
158 return -EINVAL;
+
159
+
160 return fioc_do_write(buf, size, offset);
+
161}
+
162
+
163static int fioc_truncate(const char *path, off_t size,
+
164 struct fuse_file_info *fi)
+
165{
+
166 (void) fi;
+
167 if (fioc_file_type(path) != FIOC_FILE)
+
168 return -EINVAL;
+
169
+
170 return fioc_resize(size);
+
171}
+
172
+
173static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
174 off_t offset, struct fuse_file_info *fi,
+
175 enum fuse_readdir_flags flags)
+
176{
+
177 (void) fi;
+
178 (void) offset;
+
179 (void) flags;
+
180
+
181 if (fioc_file_type(path) != FIOC_ROOT)
+
182 return -ENOENT;
+
183
+
184 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
185 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
186 filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
187
+
188 return 0;
+
189}
+
190
+
191static int fioc_ioctl(const char *path, unsigned int cmd, void *arg,
+
192 struct fuse_file_info *fi, unsigned int flags, void *data)
+
193{
+
194 (void) arg;
+
195 (void) fi;
+
196 (void) flags;
+
197
+
198 if (fioc_file_type(path) != FIOC_FILE)
+
199 return -EINVAL;
+
200
+
201 if (flags & FUSE_IOCTL_COMPAT)
+
202 return -ENOSYS;
+
203
+
204 switch (cmd) {
+
205 case FIOC_GET_SIZE:
+
206 *(size_t *)data = fioc_size;
+
207 return 0;
+
208
+
209 case FIOC_SET_SIZE:
+
210 fioc_resize(*(size_t *)data);
+
211 return 0;
+
212 }
+
213
+
214 return -EINVAL;
+
215}
+
216
+
217static const struct fuse_operations fioc_oper = {
+
218 .getattr = fioc_getattr,
+
219 .readdir = fioc_readdir,
+
220 .truncate = fioc_truncate,
+
221 .open = fioc_open,
+
222 .read = fioc_read,
+
223 .write = fioc_write,
+
224 .ioctl = fioc_ioctl,
+
225};
+
226
+
227int main(int argc, char *argv[])
+
228{
+
229 return fuse_main(argc, argv, &fioc_oper, NULL);
+
230}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_IOCTL_COMPAT
+ + + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2ioctl_8h.html b/doc/html/fuse-3_818_81_2example_2ioctl_8h.html new file mode 100644 index 0000000..cc47317 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2ioctl_8h.html @@ -0,0 +1,96 @@ + + + + + + + +libfuse: fuse-3.18.1/example/ioctl.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl.h File Reference
+
+
+
#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

Header file to share definitions between the ioctl.c example file system and the ioctl_client.c test program.

+
/*
+
FUSE-ioctl: ioctl support for FUSE
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#include <sys/types.h>
+
#include <sys/uio.h>
+
#include <sys/ioctl.h>
+
+
enum {
+
FIOC_GET_SIZE = _IOR('E', 0, size_t),
+
FIOC_SET_SIZE = _IOW('E', 1, size_t),
+
+
/*
+
* The following two ioctls don't follow usual encoding rules
+
* and transfer variable amount of data.
+
*/
+
FIOC_READ = _IO('E', 2),
+
FIOC_WRITE = _IO('E', 3),
+
};
+
+
struct fioc_rw_arg {
+
off_t offset;
+
void *buf;
+
size_t size;
+
size_t prev_size; /* out param for previous total size */
+
size_t new_size; /* out param for new total size */
+
};
+
+

Definition in file ioctl.h.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2ioctl_8h_source.html b/doc/html/fuse-3_818_81_2example_2ioctl_8h_source.html new file mode 100644 index 0000000..ace43d8 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2ioctl_8h_source.html @@ -0,0 +1,92 @@ + + + + + + + +libfuse: fuse-3.18.1/example/ioctl.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE-ioctl: ioctl support for FUSE
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
20#include <sys/types.h>
+
21#include <sys/uio.h>
+
22#include <sys/ioctl.h>
+
23
+
24enum {
+
25 FIOC_GET_SIZE = _IOR('E', 0, size_t),
+
26 FIOC_SET_SIZE = _IOW('E', 1, size_t),
+
27
+
28 /*
+
29 * The following two ioctls don't follow usual encoding rules
+
30 * and transfer variable amount of data.
+
31 */
+
32 FIOC_READ = _IO('E', 2),
+
33 FIOC_WRITE = _IO('E', 3),
+
34};
+
35
+
36struct fioc_rw_arg {
+
37 off_t offset;
+
38 void *buf;
+
39 size_t size;
+
40 size_t prev_size; /* out param for previous total size */
+
41 size_t new_size; /* out param for new total size */
+
42};
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2ioctl__client_8c.html b/doc/html/fuse-3_818_81_2example_2ioctl__client_8c.html new file mode 100644 index 0000000..61c3e15 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2ioctl__client_8c.html @@ -0,0 +1,136 @@ + + + + + + + +libfuse: fuse-3.18.1/example/ioctl_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl_client.c File Reference
+
+
+
#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the ioctl.c example file systsem.

+

Compile with:

gcc -Wall ioctl_client.c -o ioctl_client
+

+Source code

+
/*
+
FUSE fioclient: FUSE ioctl example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#include <sys/types.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <sys/ioctl.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <ctype.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include "ioctl.h"
+
+
const char *usage =
+
"Usage: fioclient FIOC_FILE [size]\n"
+
"\n"
+
"Get size if <size> is omitted, set size otherwise\n"
+
"\n";
+
+
int main(int argc, char **argv)
+
{
+
size_t size;
+
int fd;
+
int ret = 0;
+
+
if (argc < 2) {
+
fprintf(stderr, "%s", usage);
+
return 1;
+
}
+
+
fd = open(argv[1], O_RDWR);
+
if (fd < 0) {
+
perror("open");
+
return 1;
+
}
+
+
if (argc == 2) {
+
if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
perror("ioctl");
+
ret = 1;
+
goto out;
+
}
+
printf("%zu\n", size);
+
} else {
+
size = strtoul(argv[2], NULL, 0);
+
if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
perror("ioctl");
+
ret = 1;
+
goto out;
+
}
+
}
+
out:
+
close(fd);
+
return ret;
+
}
+
+

Definition in file ioctl_client.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2ioctl__client_8c_source.html b/doc/html/fuse-3_818_81_2example_2ioctl__client_8c_source.html new file mode 100644 index 0000000..386f780 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2ioctl__client_8c_source.html @@ -0,0 +1,123 @@ + + + + + + + +libfuse: fuse-3.18.1/example/ioctl_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioclient: FUSE ioctl example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
22#include <sys/types.h>
+
23#include <fcntl.h>
+
24#include <sys/stat.h>
+
25#include <sys/ioctl.h>
+
26#include <stdio.h>
+
27#include <stdlib.h>
+
28#include <ctype.h>
+
29#include <errno.h>
+
30#include <unistd.h>
+
31#include "ioctl.h"
+
32
+
33const char *usage =
+
34"Usage: fioclient FIOC_FILE [size]\n"
+
35"\n"
+
36"Get size if <size> is omitted, set size otherwise\n"
+
37"\n";
+
38
+
39int main(int argc, char **argv)
+
40{
+
41 size_t size;
+
42 int fd;
+
43 int ret = 0;
+
44
+
45 if (argc < 2) {
+
46 fprintf(stderr, "%s", usage);
+
47 return 1;
+
48 }
+
49
+
50 fd = open(argv[1], O_RDWR);
+
51 if (fd < 0) {
+
52 perror("open");
+
53 return 1;
+
54 }
+
55
+
56 if (argc == 2) {
+
57 if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
58 perror("ioctl");
+
59 ret = 1;
+
60 goto out;
+
61 }
+
62 printf("%zu\n", size);
+
63 } else {
+
64 size = strtoul(argv[2], NULL, 0);
+
65 if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
66 perror("ioctl");
+
67 ret = 1;
+
68 goto out;
+
69 }
+
70 }
+
71out:
+
72 close(fd);
+
73 return ret;
+
74}
+ +
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2notify__inval__entry_8c.html b/doc/html/fuse-3_818_81_2example_2notify__inval__entry_8c.html new file mode 100644 index 0000000..7587638 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2notify__inval__entry_8c.html @@ -0,0 +1,497 @@ + + + + + + + +libfuse: fuse-3.18.1/example/notify_inval_entry.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_inval_entry.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <signal.h>
+#include <stddef.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <pthread.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose file name changes dynamically to reflect the current time.

+

It illustrates the use of the fuse_lowlevel_notify_inval_entry() and fuse_lowlevel_notify_expire_entry() functions.

+

To see the effect, first start the file system with the --no-notify

$ notify_inval_entry --update-interval=1 --timeout=30 --no-notify mnt/
+

Observe that ls always prints the correct directory contents (since readdir output is not cached)::

$ ls mnt; sleep 1; ls mnt; sleep 1; ls mnt
+Time_is_15h_48m_33s  current_time
+Time_is_15h_48m_34s  current_time
+Time_is_15h_48m_35s  current_time
+

However, if you try to access a file by name the kernel will report that it still exists:

$ file=$(ls mnt/); echo $file
+Time_is_15h_50m_09s
+$ sleep 5; stat mnt/$file
+  File: ‘mnt/Time_is_15h_50m_09s’
+  Size: 32                Blocks: 0          IO Block: 4096   regular file
+Device: 2ah/42d     Inode: 3           Links: 1
+Access: (0444/-r--r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
+Access: 1969-12-31 16:00:00.000000000 -0800
+Modify: 1969-12-31 16:00:00.000000000 -0800
+Change: 1969-12-31 16:00:00.000000000 -0800
+ Birth: -
+

Only once the kernel cache timeout has been reached will the stat call fail:

$ sleep 30; stat mnt/$file
+stat: cannot stat ‘mnt/Time_is_15h_50m_09s’: No such file or directory
+

In contrast, if you enable notifications you will be unable to stat the file as soon as the file system updates its name:

$ notify_inval_entry --update-interval=1 --timeout=30 mnt/
+$ file=$(ls mnt/); stat mnt/$file
+  File: ‘mnt/Time_is_20h_42m_11s’
+  Size: 0                 Blocks: 0          IO Block: 4096   regular empty file
+Device: 2ah/42d     Inode: 2           Links: 1
+Access: (0000/----------)  Uid: (    0/    root)   Gid: (    0/    root)
+Access: 1969-12-31 16:00:00.000000000 -0800
+Modify: 1969-12-31 16:00:00.000000000 -0800
+Change: 1969-12-31 16:00:00.000000000 -0800
+ Birth: -
+$ sleep 1; stat mnt/$file
+stat: cannot stat ‘mnt/Time_is_20h_42m_11s’: No such file or directory
+

To use the function fuse_lowlevel_notify_expire_entry() instead of fuse_lowlevel_notify_inval_entry(), use the command line option –only-expire

+

Another possible command-line option is –inc-epoch, which will use the FUSE low-level function fuse_lowlevel_notify_increment_epoch() instead. This will function will force the invalidation of all dentries next time they are revalidated. Note that –inc-epoch and –only-expire options are mutually exclusive.

+

+Compilation

+
gcc -Wall notify_inval_entry.c `pkg-config fuse3 --cflags --libs` -o notify_inval_entry
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <signal.h>
+
#include <stddef.h>
+
#include <sys/stat.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
+
#define MAX_STR_LEN 128
+
static char file_name[MAX_STR_LEN];
+
static fuse_ino_t file_ino = 2;
+
static int lookup_cnt = 0;
+
static pthread_t main_thread;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
float timeout;
+
int update_interval;
+
int only_expire;
+
int inc_epoch;
+
};
+
static struct options options = {
+
.timeout = 5,
+
.no_notify = 0,
+
.update_interval = 1,
+
.only_expire = 0,
+
.inc_epoch = 0,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+
OPTION("--timeout=%f", timeout),
+
OPTION("--only-expire", only_expire),
+
OPTION("--inc-epoch", inc_epoch),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == file_ino) {
+
stbuf->st_mode = S_IFREG | 0000;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = 0;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, file_name) == 0) {
+
e.ino = file_ino;
+
lookup_cnt++;
+
} else
+
goto err_out;
+
+
e.attr_timeout = options.timeout;
+
e.entry_timeout = options.timeout;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == file_ino)
+
lookup_cnt -= nlookup;
+
else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, options.timeout);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, file_name, file_ino);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.forget = tfs_forget,
+
};
+
+
static void update_fs(void) {
+
time_t t;
+
struct tm *now;
+
ssize_t ret;
+
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
ret = strftime(file_name, MAX_STR_LEN,
+
"Time_is_%Hh_%Mm_%Ss", now);
+
assert(ret != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
char *old_name;
+
int ret = 0;
+
+
while(!fuse_session_exited(se)) {
+
old_name = strdup(file_name);
+
update_fs();
+
+
if (!options.no_notify && lookup_cnt) {
+
if(options.only_expire) { // expire entry
+ +
(se, FUSE_ROOT_ID, old_name, strlen(old_name));
+
+
// no kernel support
+
if (ret == -ENOSYS) {
+
printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n");
+
break;
+
}
+
+
// 1) ret == 0: successful expire of an existing entry
+
// 2) ret == -ENOENT: kernel has already expired the entry /
+
// entry does not exist anymore in the kernel
+
assert(ret == 0 || ret == -ENOENT);
+
} else if (options.inc_epoch) { // increment epoch
+ +
+
if (ret == -ENOSYS) {
+
printf("fuse_lowlevel_notify_increment_epoch not supported by kernel\n");
+
break;
+
}
+
assert(ret == 0);
+
} else { // invalidate entry
+ +
(se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0);
+
}
+
}
+
free(old_name);
+
sleep(options.update_interval);
+
}
+
+
if (ret == -ENOSYS) {
+
printf("Exiting...\n");
+
+ +
// Make sure to exit now, rather than on next request from userspace
+
pthread_kill(main_thread, SIGPIPE);
+
}
+
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --timeout=<secs> Timeout for kernel caches\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
" --only-expire Expire entries instead of invalidating them\n"
+
" --inc-epoch Increment epoch, invalidating all dentries\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
pthread_t updater;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
if (options.only_expire && options.inc_epoch) {
+
printf("'only-expire' and 'inc-epoch' options are exclusive\n");
+
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), &se);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
// Needed to ensure that the main thread continues/restarts processing as soon
+
// as the fuse session ends (immediately after calling fuse_session_exit() )
+
// and not only on the next request from userspace
+
main_thread = pthread_self();
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread) {
+
ret = fuse_session_loop(se);
+
} else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_inval_entry.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2notify__inval__entry_8c_source.html b/doc/html/fuse-3_818_81_2example_2notify__inval__entry_8c_source.html new file mode 100644 index 0000000..cff8723 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2notify__inval__entry_8c_source.html @@ -0,0 +1,446 @@ + + + + + + + +libfuse: fuse-3.18.1/example/notify_inval_entry.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_inval_entry.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
85#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
86
+
87#include <fuse_lowlevel.h>
+
88#include <stdio.h>
+
89#include <stdlib.h>
+
90#include <string.h>
+
91#include <errno.h>
+
92#include <fcntl.h>
+
93#include <assert.h>
+
94#include <signal.h>
+
95#include <stddef.h>
+
96#include <sys/stat.h>
+
97#include <unistd.h>
+
98#include <pthread.h>
+
99
+
100#define MAX_STR_LEN 128
+
101static char file_name[MAX_STR_LEN];
+
102static fuse_ino_t file_ino = 2;
+
103static int lookup_cnt = 0;
+
104static pthread_t main_thread;
+
105
+
106/* Command line parsing */
+
107struct options {
+
108 int no_notify;
+
109 float timeout;
+
110 int update_interval;
+
111 int only_expire;
+
112 int inc_epoch;
+
113};
+
114static struct options options = {
+
115 .timeout = 5,
+
116 .no_notify = 0,
+
117 .update_interval = 1,
+
118 .only_expire = 0,
+
119 .inc_epoch = 0,
+
120};
+
121
+
122#define OPTION(t, p) \
+
123 { t, offsetof(struct options, p), 1 }
+
124static const struct fuse_opt option_spec[] = {
+
125 OPTION("--no-notify", no_notify),
+
126 OPTION("--update-interval=%d", update_interval),
+
127 OPTION("--timeout=%f", timeout),
+
128 OPTION("--only-expire", only_expire),
+
129 OPTION("--inc-epoch", inc_epoch),
+ +
131};
+
132
+
133static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
134 stbuf->st_ino = ino;
+
135 if (ino == FUSE_ROOT_ID) {
+
136 stbuf->st_mode = S_IFDIR | 0755;
+
137 stbuf->st_nlink = 1;
+
138 }
+
139
+
140 else if (ino == file_ino) {
+
141 stbuf->st_mode = S_IFREG | 0000;
+
142 stbuf->st_nlink = 1;
+
143 stbuf->st_size = 0;
+
144 }
+
145
+
146 else
+
147 return -1;
+
148
+
149 return 0;
+
150}
+
151
+
152static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
153 (void)userdata;
+
154
+
155 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
156 conn->no_interrupt = 1;
+
157}
+
158
+
159static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
160 const char *name) {
+
161 struct fuse_entry_param e;
+
162 memset(&e, 0, sizeof(e));
+
163
+
164 if (parent != FUSE_ROOT_ID)
+
165 goto err_out;
+
166 else if (strcmp(name, file_name) == 0) {
+
167 e.ino = file_ino;
+
168 lookup_cnt++;
+
169 } else
+
170 goto err_out;
+
171
+
172 e.attr_timeout = options.timeout;
+
173 e.entry_timeout = options.timeout;
+
174 if (tfs_stat(e.ino, &e.attr) != 0)
+
175 goto err_out;
+
176 fuse_reply_entry(req, &e);
+
177 return;
+
178
+
179err_out:
+
180 fuse_reply_err(req, ENOENT);
+
181}
+
182
+
183static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
184 uint64_t nlookup) {
+
185 (void) req;
+
186 if(ino == file_ino)
+
187 lookup_cnt -= nlookup;
+
188 else
+
189 assert(ino == FUSE_ROOT_ID);
+
190 fuse_reply_none(req);
+
191}
+
192
+
193static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
194 struct fuse_file_info *fi) {
+
195 struct stat stbuf;
+
196
+
197 (void) fi;
+
198
+
199 memset(&stbuf, 0, sizeof(stbuf));
+
200 if (tfs_stat(ino, &stbuf) != 0)
+
201 fuse_reply_err(req, ENOENT);
+
202 else
+
203 fuse_reply_attr(req, &stbuf, options.timeout);
+
204}
+
205
+
206struct dirbuf {
+
207 char *p;
+
208 size_t size;
+
209};
+
210
+
211static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
212 fuse_ino_t ino) {
+
213 struct stat stbuf;
+
214 size_t oldsize = b->size;
+
215 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
216 b->p = (char *) realloc(b->p, b->size);
+
217 memset(&stbuf, 0, sizeof(stbuf));
+
218 stbuf.st_ino = ino;
+
219 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
220 b->size);
+
221}
+
222
+
223#define min(x, y) ((x) < (y) ? (x) : (y))
+
224
+
225static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
226 off_t off, size_t maxsize) {
+
227 if (off < bufsize)
+
228 return fuse_reply_buf(req, buf + off,
+
229 min(bufsize - off, maxsize));
+
230 else
+
231 return fuse_reply_buf(req, NULL, 0);
+
232}
+
233
+
234static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
235 off_t off, struct fuse_file_info *fi) {
+
236 (void) fi;
+
237
+
238 if (ino != FUSE_ROOT_ID)
+
239 fuse_reply_err(req, ENOTDIR);
+
240 else {
+
241 struct dirbuf b;
+
242
+
243 memset(&b, 0, sizeof(b));
+
244 dirbuf_add(req, &b, file_name, file_ino);
+
245 reply_buf_limited(req, b.p, b.size, off, size);
+
246 free(b.p);
+
247 }
+
248}
+
249
+
250static const struct fuse_lowlevel_ops tfs_oper = {
+
251 .init = tfs_init,
+
252 .lookup = tfs_lookup,
+
253 .getattr = tfs_getattr,
+
254 .readdir = tfs_readdir,
+
255 .forget = tfs_forget,
+
256};
+
257
+
258static void update_fs(void) {
+
259 time_t t;
+
260 struct tm *now;
+
261 ssize_t ret;
+
262
+
263 t = time(NULL);
+
264 now = localtime(&t);
+
265 assert(now != NULL);
+
266
+
267 ret = strftime(file_name, MAX_STR_LEN,
+
268 "Time_is_%Hh_%Mm_%Ss", now);
+
269 assert(ret != 0);
+
270}
+
271
+
272static void* update_fs_loop(void *data) {
+
273 struct fuse_session *se = (struct fuse_session*) data;
+
274 char *old_name;
+
275 int ret = 0;
+
276
+
277 while(!fuse_session_exited(se)) {
+
278 old_name = strdup(file_name);
+
279 update_fs();
+
280
+
281 if (!options.no_notify && lookup_cnt) {
+
282 if(options.only_expire) { // expire entry
+ +
284 (se, FUSE_ROOT_ID, old_name, strlen(old_name));
+
285
+
286 // no kernel support
+
287 if (ret == -ENOSYS) {
+
288 printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n");
+
289 break;
+
290 }
+
291
+
292 // 1) ret == 0: successful expire of an existing entry
+
293 // 2) ret == -ENOENT: kernel has already expired the entry /
+
294 // entry does not exist anymore in the kernel
+
295 assert(ret == 0 || ret == -ENOENT);
+
296 } else if (options.inc_epoch) { // increment epoch
+ +
298
+
299 if (ret == -ENOSYS) {
+
300 printf("fuse_lowlevel_notify_increment_epoch not supported by kernel\n");
+
301 break;
+
302 }
+
303 assert(ret == 0);
+
304 } else { // invalidate entry
+ +
306 (se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0);
+
307 }
+
308 }
+
309 free(old_name);
+
310 sleep(options.update_interval);
+
311 }
+
312
+
313 if (ret == -ENOSYS) {
+
314 printf("Exiting...\n");
+
315
+ +
317 // Make sure to exit now, rather than on next request from userspace
+
318 pthread_kill(main_thread, SIGPIPE);
+
319 }
+
320
+
321 return NULL;
+
322}
+
323
+
324static void show_help(const char *progname)
+
325{
+
326 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
327 printf("File-system specific options:\n"
+
328 " --timeout=<secs> Timeout for kernel caches\n"
+
329 " --update-interval=<secs> Update-rate of file system contents\n"
+
330 " --no-notify Disable kernel notifications\n"
+
331 " --only-expire Expire entries instead of invalidating them\n"
+
332 " --inc-epoch Increment epoch, invalidating all dentries\n"
+
333 "\n");
+
334}
+
335
+
336int main(int argc, char *argv[]) {
+
337 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
338 struct fuse_session *se;
+
339 struct fuse_cmdline_opts opts;
+
340 struct fuse_loop_config *config;
+
341 pthread_t updater;
+
342 int ret = -1;
+
343
+
344 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
345 return 1;
+
346
+
347 if (fuse_parse_cmdline(&args, &opts) != 0)
+
348 return 1;
+
349 if (opts.show_help) {
+
350 show_help(argv[0]);
+ + +
353 ret = 0;
+
354 goto err_out1;
+
355 } else if (opts.show_version) {
+
356 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
358 ret = 0;
+
359 goto err_out1;
+
360 }
+
361 if (options.only_expire && options.inc_epoch) {
+
362 printf("'only-expire' and 'inc-epoch' options are exclusive\n");
+
363 ret = 0;
+
364 goto err_out1;
+
365 }
+
366
+
367 /* Initial contents */
+
368 update_fs();
+
369
+
370 se = fuse_session_new(&args, &tfs_oper,
+
371 sizeof(tfs_oper), &se);
+
372 if (se == NULL)
+
373 goto err_out1;
+
374
+
375 if (fuse_set_signal_handlers(se) != 0)
+
376 goto err_out2;
+
377
+
378 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
379 goto err_out3;
+
380
+
381 fuse_daemonize(opts.foreground);
+
382
+
383 // Needed to ensure that the main thread continues/restarts processing as soon
+
384 // as the fuse session ends (immediately after calling fuse_session_exit() )
+
385 // and not only on the next request from userspace
+
386 main_thread = pthread_self();
+
387
+
388 /* Start thread to update file contents */
+
389 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
390 if (ret != 0) {
+
391 fprintf(stderr, "pthread_create failed with %s\n",
+
392 strerror(ret));
+
393 goto err_out3;
+
394 }
+
395
+
396 /* Block until ctrl+c or fusermount -u */
+
397 if (opts.singlethread) {
+
398 ret = fuse_session_loop(se);
+
399 } else {
+
400 config = fuse_loop_cfg_create();
+
401 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
402 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
403 ret = fuse_session_loop_mt(se, config);
+
404 fuse_loop_cfg_destroy(config);
+
405 config = NULL;
+
406 }
+
407
+ +
409err_out3:
+ +
411err_out2:
+ +
413err_out1:
+
414 free(opts.mountpoint);
+
415 fuse_opt_free_args(&args);
+
416
+
417 return ret ? 1 : 0;
+
418}
+
419
+
420
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2notify__inval__inode_8c.html b/doc/html/fuse-3_818_81_2example_2notify__inval__inode_8c.html new file mode 100644 index 0000000..6b29a50 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2notify__inval__inode_8c.html @@ -0,0 +1,483 @@ + + + + + + + +libfuse: fuse-3.18.1/example/notify_inval_inode.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_inval_inode.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdatomic.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

+

While notify_store_retrieve.c uses fuse_lowlevel_notify_store() to actively push the updated data into the kernel cache, this example uses fuse_lowlevel_notify_inval_inode() to notify the kernel that the cache has to be invalidated - but the kernel still has to explicitly request the updated data on the next read.

+

To see the effect, first start the file system with the --no-notify option:

+

$ notify_inval_inode –update-interval=1 –no-notify mnt/

+

Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+

If you instead enable the notification functions, the changes become visible:

 $ notify_inval_inode --update-interval=1 mnt/
+ $ for i in 1 2 3 4 5; do
+ >     cat mnt/current_time
+ >     sleep 1
+ > done
+ The current time is 15:58:40
+ The current time is 15:58:41
+ The current time is 15:58:42
+ The current time is 15:58:43
+ The current time is 15:58:44
+

+Compilation

+
gcc -Wall notify_inval_inode.c `pkg-config fuse3 --cflags --libs` -o notify_inval_inode
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
#include <stdbool.h>
+
#include <stdatomic.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define FILE_INO 2
+
#define FILE_NAME "current_time"
+
static char file_contents[MAX_STR_LEN];
+
static int lookup_cnt = 0;
+
static size_t file_size;
+
static _Atomic bool is_stop = false;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == FILE_INO) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = file_size;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_destroy(void *userarg)
+
{
+
(void)userarg;
+
+
is_stop = true;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, FILE_NAME) == 0) {
+
e.ino = FILE_INO;
+
lookup_cnt++;
+
} else
+
goto err_out;
+
+
e.attr_timeout = NO_TIMEOUT;
+
e.entry_timeout = NO_TIMEOUT;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == FILE_INO)
+
lookup_cnt -= nlookup;
+
else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
+
if (ino == FUSE_ROOT_ID)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else if (ino == FILE_INO)
+
fuse_reply_open(req, fi);
+
else {
+
// This should not happen
+
fprintf(stderr, "Got open for non-existing inode!\n");
+
fuse_reply_err(req, ENOENT);
+
}
+
}
+
+
static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
assert(ino == FILE_INO);
+
reply_buf_limited(req, file_contents, file_size, off, size);
+
}
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.destroy = tfs_destroy,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.open = tfs_open,
+
.read = tfs_read,
+
.forget = tfs_forget,
+
};
+
+
static void update_fs(void) {
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
file_size = strftime(file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(file_size != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
+
while(!is_stop) {
+
update_fs();
+
if (!options.no_notify && lookup_cnt) {
+
/* Only send notification if the kernel is aware of the inode */
+
+
/* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
* might come up during umount, when kernel side already releases
+
* all inodes, but does not send FUSE_DESTROY yet.
+
*/
+
int ret =
+
fuse_lowlevel_notify_inval_inode(se, FILE_INO, 0, 0);
+
if ((ret != 0 && !is_stop) &&
+
ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
fprintf(stderr,
+
"ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
strerror(-ret), -ret);
+
abort();
+
}
+
}
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
pthread_t updater;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0) {
+
ret = 1;
+
goto err_out1;
+
}
+
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+ +
free(opts.mountpoint);
+
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_inval_inode.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2notify__inval__inode_8c_source.html b/doc/html/fuse-3_818_81_2example_2notify__inval__inode_8c_source.html new file mode 100644 index 0000000..5380502 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2notify__inval__inode_8c_source.html @@ -0,0 +1,443 @@ + + + + + + + +libfuse: fuse-3.18.1/example/notify_inval_inode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_inval_inode.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
62#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
63
+
64#include <fuse_lowlevel.h>
+
65#include <stdio.h>
+
66#include <stdlib.h>
+
67#include <string.h>
+
68#include <errno.h>
+
69#include <fcntl.h>
+
70#include <assert.h>
+
71#include <stddef.h>
+
72#include <unistd.h>
+
73#include <pthread.h>
+
74#include <stdbool.h>
+
75#include <stdatomic.h>
+
76
+
77/* We can't actually tell the kernel that there is no
+
78 timeout, so we just send a big value */
+
79#define NO_TIMEOUT 500000
+
80
+
81#define MAX_STR_LEN 128
+
82#define FILE_INO 2
+
83#define FILE_NAME "current_time"
+
84static char file_contents[MAX_STR_LEN];
+
85static int lookup_cnt = 0;
+
86static size_t file_size;
+
87static _Atomic bool is_stop = false;
+
88
+
89/* Command line parsing */
+
90struct options {
+
91 int no_notify;
+
92 int update_interval;
+
93};
+
94static struct options options = {
+
95 .no_notify = 0,
+
96 .update_interval = 1,
+
97};
+
98
+
99#define OPTION(t, p) \
+
100 { t, offsetof(struct options, p), 1 }
+
101static const struct fuse_opt option_spec[] = {
+
102 OPTION("--no-notify", no_notify),
+
103 OPTION("--update-interval=%d", update_interval),
+ +
105};
+
106
+
107static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
108 stbuf->st_ino = ino;
+
109 if (ino == FUSE_ROOT_ID) {
+
110 stbuf->st_mode = S_IFDIR | 0755;
+
111 stbuf->st_nlink = 1;
+
112 }
+
113
+
114 else if (ino == FILE_INO) {
+
115 stbuf->st_mode = S_IFREG | 0444;
+
116 stbuf->st_nlink = 1;
+
117 stbuf->st_size = file_size;
+
118 }
+
119
+
120 else
+
121 return -1;
+
122
+
123 return 0;
+
124}
+
125
+
126static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
127 (void)userdata;
+
128
+
129 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
130 conn->no_interrupt = 1;
+
131}
+
132
+
133static void tfs_destroy(void *userarg)
+
134{
+
135 (void)userarg;
+
136
+
137 is_stop = true;
+
138}
+
139
+
140static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
141 const char *name) {
+
142 struct fuse_entry_param e;
+
143 memset(&e, 0, sizeof(e));
+
144
+
145 if (parent != FUSE_ROOT_ID)
+
146 goto err_out;
+
147 else if (strcmp(name, FILE_NAME) == 0) {
+
148 e.ino = FILE_INO;
+
149 lookup_cnt++;
+
150 } else
+
151 goto err_out;
+
152
+
153 e.attr_timeout = NO_TIMEOUT;
+
154 e.entry_timeout = NO_TIMEOUT;
+
155 if (tfs_stat(e.ino, &e.attr) != 0)
+
156 goto err_out;
+
157 fuse_reply_entry(req, &e);
+
158 return;
+
159
+
160err_out:
+
161 fuse_reply_err(req, ENOENT);
+
162}
+
163
+
164static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
165 uint64_t nlookup) {
+
166 (void) req;
+
167 if(ino == FILE_INO)
+
168 lookup_cnt -= nlookup;
+
169 else
+
170 assert(ino == FUSE_ROOT_ID);
+
171 fuse_reply_none(req);
+
172}
+
173
+
174static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
175 struct fuse_file_info *fi) {
+
176 struct stat stbuf;
+
177
+
178 (void) fi;
+
179
+
180 memset(&stbuf, 0, sizeof(stbuf));
+
181 if (tfs_stat(ino, &stbuf) != 0)
+
182 fuse_reply_err(req, ENOENT);
+
183 else
+
184 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
185}
+
186
+
187struct dirbuf {
+
188 char *p;
+
189 size_t size;
+
190};
+
191
+
192static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
193 fuse_ino_t ino) {
+
194 struct stat stbuf;
+
195 size_t oldsize = b->size;
+
196 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
197 b->p = (char *) realloc(b->p, b->size);
+
198 memset(&stbuf, 0, sizeof(stbuf));
+
199 stbuf.st_ino = ino;
+
200 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
201 b->size);
+
202}
+
203
+
204#define min(x, y) ((x) < (y) ? (x) : (y))
+
205
+
206static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
207 off_t off, size_t maxsize) {
+
208 if (off < bufsize)
+
209 return fuse_reply_buf(req, buf + off,
+
210 min(bufsize - off, maxsize));
+
211 else
+
212 return fuse_reply_buf(req, NULL, 0);
+
213}
+
214
+
215static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
216 off_t off, struct fuse_file_info *fi) {
+
217 (void) fi;
+
218
+
219 if (ino != FUSE_ROOT_ID)
+
220 fuse_reply_err(req, ENOTDIR);
+
221 else {
+
222 struct dirbuf b;
+
223
+
224 memset(&b, 0, sizeof(b));
+
225 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
226 reply_buf_limited(req, b.p, b.size, off, size);
+
227 free(b.p);
+
228 }
+
229}
+
230
+
231static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
232 struct fuse_file_info *fi) {
+
233
+
234 /* Make cache persistent even if file is closed,
+
235 this makes it easier to see the effects */
+
236 fi->keep_cache = 1;
+
237
+
238 if (ino == FUSE_ROOT_ID)
+
239 fuse_reply_err(req, EISDIR);
+
240 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
241 fuse_reply_err(req, EACCES);
+
242 else if (ino == FILE_INO)
+
243 fuse_reply_open(req, fi);
+
244 else {
+
245 // This should not happen
+
246 fprintf(stderr, "Got open for non-existing inode!\n");
+
247 fuse_reply_err(req, ENOENT);
+
248 }
+
249}
+
250
+
251static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
252 off_t off, struct fuse_file_info *fi) {
+
253 (void) fi;
+
254
+
255 assert(ino == FILE_INO);
+
256 reply_buf_limited(req, file_contents, file_size, off, size);
+
257}
+
258
+
259static const struct fuse_lowlevel_ops tfs_oper = {
+
260 .init = tfs_init,
+
261 .destroy = tfs_destroy,
+
262 .lookup = tfs_lookup,
+
263 .getattr = tfs_getattr,
+
264 .readdir = tfs_readdir,
+
265 .open = tfs_open,
+
266 .read = tfs_read,
+
267 .forget = tfs_forget,
+
268};
+
269
+
270static void update_fs(void) {
+
271 struct tm *now;
+
272 time_t t;
+
273 t = time(NULL);
+
274 now = localtime(&t);
+
275 assert(now != NULL);
+
276
+
277 file_size = strftime(file_contents, MAX_STR_LEN,
+
278 "The current time is %H:%M:%S\n", now);
+
279 assert(file_size != 0);
+
280}
+
281
+
282static void* update_fs_loop(void *data) {
+
283 struct fuse_session *se = (struct fuse_session*) data;
+
284
+
285 while(!is_stop) {
+
286 update_fs();
+
287 if (!options.no_notify && lookup_cnt) {
+
288 /* Only send notification if the kernel is aware of the inode */
+
289
+
290 /* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
291 * might come up during umount, when kernel side already releases
+
292 * all inodes, but does not send FUSE_DESTROY yet.
+
293 */
+
294 int ret =
+
295 fuse_lowlevel_notify_inval_inode(se, FILE_INO, 0, 0);
+
296 if ((ret != 0 && !is_stop) &&
+
297 ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
298 fprintf(stderr,
+
299 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
300 strerror(-ret), -ret);
+
301 abort();
+
302 }
+
303 }
+
304 sleep(options.update_interval);
+
305 }
+
306 return NULL;
+
307}
+
308
+
309static void show_help(const char *progname)
+
310{
+
311 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
312 printf("File-system specific options:\n"
+
313 " --update-interval=<secs> Update-rate of file system contents\n"
+
314 " --no-notify Disable kernel notifications\n"
+
315 "\n");
+
316}
+
317
+
318int main(int argc, char *argv[]) {
+
319 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
320 struct fuse_session *se;
+
321 struct fuse_cmdline_opts opts;
+
322 struct fuse_loop_config *config;
+
323 pthread_t updater;
+
324 int ret = -1;
+
325
+
326 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
327 return 1;
+
328
+
329 if (fuse_parse_cmdline(&args, &opts) != 0) {
+
330 ret = 1;
+
331 goto err_out1;
+
332 }
+
333
+
334 if (opts.show_help) {
+
335 show_help(argv[0]);
+ + +
338 ret = 0;
+
339 goto err_out1;
+
340 } else if (opts.show_version) {
+
341 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
343 ret = 0;
+
344 goto err_out1;
+
345 }
+
346
+
347 /* Initial contents */
+
348 update_fs();
+
349
+
350 se = fuse_session_new(&args, &tfs_oper,
+
351 sizeof(tfs_oper), NULL);
+
352 if (se == NULL)
+
353 goto err_out1;
+
354
+
355 if (fuse_set_signal_handlers(se) != 0)
+
356 goto err_out2;
+
357
+
358 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
359 goto err_out3;
+
360
+
361 fuse_daemonize(opts.foreground);
+
362
+
363 /* Start thread to update file contents */
+
364 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
365 if (ret != 0) {
+
366 fprintf(stderr, "pthread_create failed with %s\n",
+
367 strerror(ret));
+
368 goto err_out3;
+
369 }
+
370
+
371 /* Block until ctrl+c or fusermount -u */
+
372 if (opts.singlethread)
+
373 ret = fuse_session_loop(se);
+
374 else {
+
375 config = fuse_loop_cfg_create();
+
376 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
377 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
378 ret = fuse_session_loop_mt(se, config);
+
379 fuse_loop_cfg_destroy(config);
+
380 config = NULL;
+
381 }
+
382
+ +
384err_out3:
+ +
386err_out2:
+ +
388err_out1:
+
389 fuse_opt_free_args(&args);
+
390 free(opts.mountpoint);
+
391
+
392 return ret ? 1 : 0;
+
393}
+
394
+
395
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2notify__store__retrieve_8c.html b/doc/html/fuse-3_818_81_2example_2notify__store__retrieve_8c.html new file mode 100644 index 0000000..023ee6c --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2notify__store__retrieve_8c.html @@ -0,0 +1,560 @@ + + + + + + + +libfuse: fuse-3.18.1/example/notify_store_retrieve.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_store_retrieve.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

+

While notify_inval_inode.c uses fuse_lowlevel_notify_inval_inode() to let the kernel know that it has to invalidate the cache, this example actively pushes the updated data into the kernel cache using fuse_lowlevel_notify_store().

+

To see the effect, first start the file system with the --no-notify option:

$ notify_store_retrieve --update-interval=1 --no-notify mnt/
+

Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+

If you instead enable the notification functions, the changes become visible:

$ notify_store_retrieve --update-interval=1 mnt/
+$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:40
+The current time is 15:58:41
+The current time is 15:58:42
+The current time is 15:58:43
+The current time is 15:58:44
+

+Compilation

+
gcc -Wall notify_store_retrieve.c `pkg-config fuse3 --cflags --libs` -o notify_store_retrieve
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
#include <stdbool.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define FILE_INO 2
+
#define FILE_NAME "current_time"
+
static char file_contents[MAX_STR_LEN];
+
static int lookup_cnt = 0;
+
static int open_cnt = 0;
+
static size_t file_size;
+
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
+
/* Keep track if we ever stored data (==1), and
+
received it back correctly (==2) */
+
static int retrieve_status = 0;
+
+
static bool is_umount = false;
+
+
/* updater thread tid */
+
static pthread_t updater;
+
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == FILE_INO) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = file_size;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, FILE_NAME) == 0) {
+
e.ino = FILE_INO;
+
} else
+
goto err_out;
+
+
e.attr_timeout = NO_TIMEOUT;
+
e.entry_timeout = NO_TIMEOUT;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
+
/*
+
* must only be set when the kernel knows about the entry,
+
* otherwise update_fs_loop() might see a positive count, but kernel
+
* would not have the entry yet
+
*/
+
if (e.ino == FILE_INO) {
+
pthread_mutex_lock(&lock);
+
lookup_cnt++;
+
pthread_mutex_unlock(&lock);
+
}
+
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == FILE_INO) {
+
pthread_mutex_lock(&lock);
+
lookup_cnt -= nlookup;
+
pthread_mutex_unlock(&lock);
+
} else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
+
if (ino == FUSE_ROOT_ID)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else if (ino == FILE_INO) {
+
fuse_reply_open(req, fi);
+
pthread_mutex_lock(&lock);
+
open_cnt++;
+
pthread_mutex_unlock(&lock);
+
} else {
+
// This should not happen
+
fprintf(stderr, "Got open for non-existing inode!\n");
+
fuse_reply_err(req, ENOENT);
+
}
+
}
+
+
static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
assert(ino == FILE_INO);
+
reply_buf_limited(req, file_contents, file_size, off, size);
+
}
+
+
static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
+
off_t offset, struct fuse_bufvec *data) {
+
struct fuse_bufvec bufv;
+
char buf[MAX_STR_LEN];
+
char *expected;
+
ssize_t ret;
+
+
assert(ino == FILE_INO);
+
assert(offset == 0);
+
expected = (char*) cookie;
+
+
bufv.count = 1;
+
bufv.idx = 0;
+
bufv.off = 0;
+
bufv.buf[0].size = MAX_STR_LEN;
+
bufv.buf[0].mem = buf;
+
bufv.buf[0].flags = 0;
+
+
ret = fuse_buf_copy(&bufv, data, 0);
+
assert(ret > 0);
+
assert(strncmp(buf, expected, ret) == 0);
+
free(expected);
+
retrieve_status = 2;
+ +
}
+
+
static void tfs_destroy(void *userdata)
+
{
+
(void)userdata;
+
+
is_umount = true;
+
+
pthread_join(updater, NULL);
+
}
+
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.open = tfs_open,
+
.read = tfs_read,
+
.forget = tfs_forget,
+
.retrieve_reply = tfs_retrieve_reply,
+
.destroy = tfs_destroy,
+
};
+
+
static void update_fs(void) {
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
file_size = strftime(file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(file_size != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
struct fuse_bufvec bufv;
+
int ret;
+
+
while(!is_umount) {
+
update_fs();
+
pthread_mutex_lock(&lock);
+
if (!options.no_notify && open_cnt && lookup_cnt) {
+
/* Only send notification if the kernel
+
is aware of the inode */
+
bufv.count = 1;
+
bufv.idx = 0;
+
bufv.off = 0;
+
bufv.buf[0].size = file_size;
+
bufv.buf[0].mem = file_contents;
+
bufv.buf[0].flags = 0;
+
+
/*
+
* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
* might come up during umount, when kernel side already releases
+
* all inodes, but does not send FUSE_DESTROY yet.
+
*/
+
+
ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
+
if ((ret != 0 && !is_umount) &&
+
ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
fprintf(stderr,
+
"ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
strerror(-ret), -ret);
+
abort();
+
}
+
+
/* To make sure that everything worked correctly, ask the
+
kernel to send us back the stored data */
+
ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
+
0, (void*) strdup(file_contents));
+
assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF ||
+
ret != -ENODEV);
+
if(retrieve_status == 0)
+
retrieve_status = 1;
+
}
+
pthread_mutex_unlock(&lock);
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+
assert(retrieve_status != 1);
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_store_retrieve.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2notify__store__retrieve_8c_source.html b/doc/html/fuse-3_818_81_2example_2notify__store__retrieve_8c_source.html new file mode 100644 index 0000000..901212d --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2notify__store__retrieve_8c_source.html @@ -0,0 +1,522 @@ + + + + + + + +libfuse: fuse-3.18.1/example/notify_store_retrieve.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_store_retrieve.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
61#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
62
+
63#include <fuse_lowlevel.h>
+
64#include <stdio.h>
+
65#include <stdlib.h>
+
66#include <string.h>
+
67#include <errno.h>
+
68#include <fcntl.h>
+
69#include <assert.h>
+
70#include <stddef.h>
+
71#include <unistd.h>
+
72#include <pthread.h>
+
73#include <stdbool.h>
+
74
+
75/* We can't actually tell the kernel that there is no
+
76 timeout, so we just send a big value */
+
77#define NO_TIMEOUT 500000
+
78
+
79#define MAX_STR_LEN 128
+
80#define FILE_INO 2
+
81#define FILE_NAME "current_time"
+
82static char file_contents[MAX_STR_LEN];
+
83static int lookup_cnt = 0;
+
84static int open_cnt = 0;
+
85static size_t file_size;
+
86static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
87
+
88/* Keep track if we ever stored data (==1), and
+
89 received it back correctly (==2) */
+
90static int retrieve_status = 0;
+
91
+
92static bool is_umount = false;
+
93
+
94/* updater thread tid */
+
95static pthread_t updater;
+
96
+
97
+
98/* Command line parsing */
+
99struct options {
+
100 int no_notify;
+
101 int update_interval;
+
102};
+
103static struct options options = {
+
104 .no_notify = 0,
+
105 .update_interval = 1,
+
106};
+
107
+
108#define OPTION(t, p) \
+
109 { t, offsetof(struct options, p), 1 }
+
110static const struct fuse_opt option_spec[] = {
+
111 OPTION("--no-notify", no_notify),
+
112 OPTION("--update-interval=%d", update_interval),
+ +
114};
+
115
+
116static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
117 stbuf->st_ino = ino;
+
118 if (ino == FUSE_ROOT_ID) {
+
119 stbuf->st_mode = S_IFDIR | 0755;
+
120 stbuf->st_nlink = 1;
+
121 }
+
122
+
123 else if (ino == FILE_INO) {
+
124 stbuf->st_mode = S_IFREG | 0444;
+
125 stbuf->st_nlink = 1;
+
126 stbuf->st_size = file_size;
+
127 }
+
128
+
129 else
+
130 return -1;
+
131
+
132 return 0;
+
133}
+
134
+
135static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
136 (void)userdata;
+
137
+
138 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
139 conn->no_interrupt = 1;
+
140}
+
141
+
142static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
143 const char *name) {
+
144 struct fuse_entry_param e;
+
145 memset(&e, 0, sizeof(e));
+
146
+
147 if (parent != FUSE_ROOT_ID)
+
148 goto err_out;
+
149 else if (strcmp(name, FILE_NAME) == 0) {
+
150 e.ino = FILE_INO;
+
151 } else
+
152 goto err_out;
+
153
+
154 e.attr_timeout = NO_TIMEOUT;
+
155 e.entry_timeout = NO_TIMEOUT;
+
156 if (tfs_stat(e.ino, &e.attr) != 0)
+
157 goto err_out;
+
158 fuse_reply_entry(req, &e);
+
159
+
160 /*
+
161 * must only be set when the kernel knows about the entry,
+
162 * otherwise update_fs_loop() might see a positive count, but kernel
+
163 * would not have the entry yet
+
164 */
+
165 if (e.ino == FILE_INO) {
+
166 pthread_mutex_lock(&lock);
+
167 lookup_cnt++;
+
168 pthread_mutex_unlock(&lock);
+
169 }
+
170
+
171 return;
+
172
+
173err_out:
+
174 fuse_reply_err(req, ENOENT);
+
175}
+
176
+
177static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
178 uint64_t nlookup) {
+
179 (void) req;
+
180 if(ino == FILE_INO) {
+
181 pthread_mutex_lock(&lock);
+
182 lookup_cnt -= nlookup;
+
183 pthread_mutex_unlock(&lock);
+
184 } else
+
185 assert(ino == FUSE_ROOT_ID);
+
186 fuse_reply_none(req);
+
187}
+
188
+
189static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
190 struct fuse_file_info *fi) {
+
191 struct stat stbuf;
+
192
+
193 (void) fi;
+
194
+
195 memset(&stbuf, 0, sizeof(stbuf));
+
196 if (tfs_stat(ino, &stbuf) != 0)
+
197 fuse_reply_err(req, ENOENT);
+
198 else
+
199 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
200}
+
201
+
202struct dirbuf {
+
203 char *p;
+
204 size_t size;
+
205};
+
206
+
207static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
208 fuse_ino_t ino) {
+
209 struct stat stbuf;
+
210 size_t oldsize = b->size;
+
211 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
212 b->p = (char *) realloc(b->p, b->size);
+
213 memset(&stbuf, 0, sizeof(stbuf));
+
214 stbuf.st_ino = ino;
+
215 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
216 b->size);
+
217}
+
218
+
219#define min(x, y) ((x) < (y) ? (x) : (y))
+
220
+
221static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
222 off_t off, size_t maxsize) {
+
223 if (off < bufsize)
+
224 return fuse_reply_buf(req, buf + off,
+
225 min(bufsize - off, maxsize));
+
226 else
+
227 return fuse_reply_buf(req, NULL, 0);
+
228}
+
229
+
230static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
231 off_t off, struct fuse_file_info *fi) {
+
232 (void) fi;
+
233
+
234 if (ino != FUSE_ROOT_ID)
+
235 fuse_reply_err(req, ENOTDIR);
+
236 else {
+
237 struct dirbuf b;
+
238
+
239 memset(&b, 0, sizeof(b));
+
240 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
241 reply_buf_limited(req, b.p, b.size, off, size);
+
242 free(b.p);
+
243 }
+
244}
+
245
+
246static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
247 struct fuse_file_info *fi) {
+
248
+
249 /* Make cache persistent even if file is closed,
+
250 this makes it easier to see the effects */
+
251 fi->keep_cache = 1;
+
252
+
253 if (ino == FUSE_ROOT_ID)
+
254 fuse_reply_err(req, EISDIR);
+
255 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
256 fuse_reply_err(req, EACCES);
+
257 else if (ino == FILE_INO) {
+
258 fuse_reply_open(req, fi);
+
259 pthread_mutex_lock(&lock);
+
260 open_cnt++;
+
261 pthread_mutex_unlock(&lock);
+
262 } else {
+
263 // This should not happen
+
264 fprintf(stderr, "Got open for non-existing inode!\n");
+
265 fuse_reply_err(req, ENOENT);
+
266 }
+
267}
+
268
+
269static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
270 off_t off, struct fuse_file_info *fi) {
+
271 (void) fi;
+
272
+
273 assert(ino == FILE_INO);
+
274 reply_buf_limited(req, file_contents, file_size, off, size);
+
275}
+
276
+
277static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
+
278 off_t offset, struct fuse_bufvec *data) {
+
279 struct fuse_bufvec bufv;
+
280 char buf[MAX_STR_LEN];
+
281 char *expected;
+
282 ssize_t ret;
+
283
+
284 assert(ino == FILE_INO);
+
285 assert(offset == 0);
+
286 expected = (char*) cookie;
+
287
+
288 bufv.count = 1;
+
289 bufv.idx = 0;
+
290 bufv.off = 0;
+
291 bufv.buf[0].size = MAX_STR_LEN;
+
292 bufv.buf[0].mem = buf;
+
293 bufv.buf[0].flags = 0;
+
294
+
295 ret = fuse_buf_copy(&bufv, data, 0);
+
296 assert(ret > 0);
+
297 assert(strncmp(buf, expected, ret) == 0);
+
298 free(expected);
+
299 retrieve_status = 2;
+
300 fuse_reply_none(req);
+
301}
+
302
+
303static void tfs_destroy(void *userdata)
+
304{
+
305 (void)userdata;
+
306
+
307 is_umount = true;
+
308
+
309 pthread_join(updater, NULL);
+
310}
+
311
+
312
+
313static const struct fuse_lowlevel_ops tfs_oper = {
+
314 .init = tfs_init,
+
315 .lookup = tfs_lookup,
+
316 .getattr = tfs_getattr,
+
317 .readdir = tfs_readdir,
+
318 .open = tfs_open,
+
319 .read = tfs_read,
+
320 .forget = tfs_forget,
+
321 .retrieve_reply = tfs_retrieve_reply,
+
322 .destroy = tfs_destroy,
+
323};
+
324
+
325static void update_fs(void) {
+
326 struct tm *now;
+
327 time_t t;
+
328 t = time(NULL);
+
329 now = localtime(&t);
+
330 assert(now != NULL);
+
331
+
332 file_size = strftime(file_contents, MAX_STR_LEN,
+
333 "The current time is %H:%M:%S\n", now);
+
334 assert(file_size != 0);
+
335}
+
336
+
337static void* update_fs_loop(void *data) {
+
338 struct fuse_session *se = (struct fuse_session*) data;
+
339 struct fuse_bufvec bufv;
+
340 int ret;
+
341
+
342 while(!is_umount) {
+
343 update_fs();
+
344 pthread_mutex_lock(&lock);
+
345 if (!options.no_notify && open_cnt && lookup_cnt) {
+
346 /* Only send notification if the kernel
+
347 is aware of the inode */
+
348 bufv.count = 1;
+
349 bufv.idx = 0;
+
350 bufv.off = 0;
+
351 bufv.buf[0].size = file_size;
+
352 bufv.buf[0].mem = file_contents;
+
353 bufv.buf[0].flags = 0;
+
354
+
355 /*
+
356 * Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
357 * might come up during umount, when kernel side already releases
+
358 * all inodes, but does not send FUSE_DESTROY yet.
+
359 */
+
360
+
361 ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
+
362 if ((ret != 0 && !is_umount) &&
+
363 ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
364 fprintf(stderr,
+
365 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
366 strerror(-ret), -ret);
+
367 abort();
+
368 }
+
369
+
370 /* To make sure that everything worked correctly, ask the
+
371 kernel to send us back the stored data */
+
372 ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
+
373 0, (void*) strdup(file_contents));
+
374 assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF ||
+
375 ret != -ENODEV);
+
376 if(retrieve_status == 0)
+
377 retrieve_status = 1;
+
378 }
+
379 pthread_mutex_unlock(&lock);
+
380 sleep(options.update_interval);
+
381 }
+
382 return NULL;
+
383}
+
384
+
385static void show_help(const char *progname)
+
386{
+
387 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
388 printf("File-system specific options:\n"
+
389 " --update-interval=<secs> Update-rate of file system contents\n"
+
390 " --no-notify Disable kernel notifications\n"
+
391 "\n");
+
392}
+
393
+
394int main(int argc, char *argv[]) {
+
395 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
396 struct fuse_session *se;
+
397 struct fuse_cmdline_opts opts;
+
398 struct fuse_loop_config *config;
+
399 int ret = -1;
+
400
+
401 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
402 return 1;
+
403
+
404 if (fuse_parse_cmdline(&args, &opts) != 0)
+
405 return 1;
+
406 if (opts.show_help) {
+
407 show_help(argv[0]);
+ + +
410 ret = 0;
+
411 goto err_out1;
+
412 } else if (opts.show_version) {
+
413 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
415 ret = 0;
+
416 goto err_out1;
+
417 }
+
418
+
419 /* Initial contents */
+
420 update_fs();
+
421
+
422 se = fuse_session_new(&args, &tfs_oper,
+
423 sizeof(tfs_oper), NULL);
+
424 if (se == NULL)
+
425 goto err_out1;
+
426
+
427 if (fuse_set_signal_handlers(se) != 0)
+
428 goto err_out2;
+
429
+
430 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
431 goto err_out3;
+
432
+
433 fuse_daemonize(opts.foreground);
+
434
+
435 /* Start thread to update file contents */
+
436 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
437 if (ret != 0) {
+
438 fprintf(stderr, "pthread_create failed with %s\n",
+
439 strerror(ret));
+
440 goto err_out3;
+
441 }
+
442
+
443 /* Block until ctrl+c or fusermount -u */
+
444 if (opts.singlethread)
+
445 ret = fuse_session_loop(se);
+
446 else {
+
447 config = fuse_loop_cfg_create();
+
448 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
449 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
450 ret = fuse_session_loop_mt(se, config);
+
451 fuse_loop_cfg_destroy(config);
+
452 config = NULL;
+
453 }
+
454
+
455 assert(retrieve_status != 1);
+ +
457err_out3:
+ +
459err_out2:
+ +
461err_out1:
+
462 free(opts.mountpoint);
+
463 fuse_opt_free_args(&args);
+
464
+
465 return ret ? 1 : 0;
+
466}
+
467
+
468
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2null_8c.html b/doc/html/fuse-3_818_81_2example_2null_8c.html new file mode 100644 index 0000000..5563532 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2null_8c.html @@ -0,0 +1,779 @@ + + + + + + + +libfuse: fuse-3.18.1/example/null.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
null.c File Reference
+
+
+
#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This "filesystem" provides only a single file. The mountpoint needs to be a file rather than a directory. All writes to the file will be discarded, and reading the file always returns \0.

+

Compile with:

gcc -Wall null.c `pkg-config fuse3 --cflags --libs` -o null
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
+
#define _GNU_SOURCE
+
+
#include <fuse.h>
+
+
#ifdef HAVE_LIBULOCKMGR
+
#include <ulockmgr.h>
+
#endif
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
#include <sys/file.h> /* flock(2) */
+
+
#include "passthrough_helpers.h"
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
cfg->nullpath_ok = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
+
if(fi)
+
res = fstat(fi->fh, stbuf);
+
else
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
struct xmp_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
if (d == NULL)
+
return -ENOMEM;
+
+
d->dp = opendir(path);
+
if (d->dp == NULL) {
+
res = -errno;
+
free(d);
+
return res;
+
}
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (unsigned long) d;
+
return 0;
+
}
+
+
static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
{
+
return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
+
(void) path;
+
if (offset != d->offset) {
+
#ifndef __FreeBSD__
+
seekdir(d->dp, offset);
+
#else
+
/* Subtract the one that we add when calling
+
telldir() below */
+
seekdir(d->dp, offset-1);
+
#endif
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
struct stat st;
+
off_t nextoff;
+ +
+
if (!d->entry) {
+
d->entry = readdir(d->dp);
+
if (!d->entry)
+
break;
+
}
+
#ifdef HAVE_FSTATAT
+
if (flags & FUSE_READDIR_PLUS) {
+
int res;
+
+
res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
if (res != -1)
+
fill_flags |= FUSE_FILL_DIR_PLUS;
+
}
+
#endif
+
if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = d->entry->d_ino;
+
st.st_mode = d->entry->d_type << 12;
+
}
+
nextoff = telldir(d->dp);
+
#ifdef __FreeBSD__
+
/* Under FreeBSD, telldir() may return 0 the first time
+
it is called. But for libfuse, an offset of zero
+
means that offsets are not supported, so we shift
+
everything by one. */
+
nextoff++;
+
#endif
+
if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
break;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
return 0;
+
}
+
+
static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
(void) path;
+
closedir(d->dp);
+
free(d);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
if (S_ISFIFO(mode))
+
res = mkfifo(path, mode);
+
else
+
res = mknod(path, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
/* When we have renameat2() in libc, then we can implement flags */
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = fchmod(fi->fh, mode);
+
else
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi)
+
res = fchown(fi->fh, uid, gid);
+
else
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
if (fi)
+
res = futimens(fi->fh, ts);
+
else
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags, mode);
+
if (fd == -1)
+
return -errno;
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags);
+
if (fd == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pread(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
size_t size, off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec *src;
+
+
(void) path;
+
+
src = malloc(sizeof(struct fuse_bufvec));
+
if (src == NULL)
+
return -ENOMEM;
+
+
*src = FUSE_BUFVEC_INIT(size);
+
+ +
src->buf[0].fd = fi->fh;
+
src->buf[0].pos = offset;
+
+
*bufp = src;
+
+
return 0;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pwrite(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
+
(void) path;
+
+ +
dst.buf[0].fd = fi->fh;
+
dst.buf[0].pos = offset;
+
+ +
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
/* This is called from every close on an open file, so call the
+
close on the underlying filesystem. But since flush may be
+
called multiple times for an open file, this must not really
+
close the file. This is important if used on a network
+
filesystem like NFS which flush the data/metadata on close() */
+
res = close(dup(fi->fh));
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) path;
+
+
#ifndef HAVE_FDATASYNC
+
(void) isdatasync;
+
#else
+
if (isdatasync)
+
res = fdatasync(fi->fh);
+
else
+
#endif
+
res = fsync(fi->fh);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
(void) path;
+
+
return do_fallocate(fi->fh, mode, offset, length);
+
}
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_LIBULOCKMGR
+
static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
struct flock *lock)
+
{
+
(void) path;
+
+
return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
sizeof(fi->lock_owner));
+
}
+
#endif
+
+
static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
{
+
int res;
+
(void) path;
+
+
res = flock(fi->fh, op);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t off_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t off_out, size_t len, int flags)
+
{
+
ssize_t res;
+
(void) path_in;
+
(void) path_out;
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
off_t res;
+
(void) path;
+
+
res = lseek(fi->fh, off, whence);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
+
#ifdef HAVE_STATX
+
static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
struct fuse_file_info *fi)
+
{
+
int fd = -1;
+
int res;
+
+
if (fi)
+
fd = fi->fh;
+
+
res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.opendir = xmp_opendir,
+
.readdir = xmp_readdir,
+
.releasedir = xmp_releasedir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.create = xmp_create,
+
.open = xmp_open,
+
.read = xmp_read,
+
.read_buf = xmp_read_buf,
+
.write = xmp_write,
+
.write_buf = xmp_write_buf,
+
.statfs = xmp_statfs,
+
.flush = xmp_flush,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
.fallocate = xmp_fallocate,
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_LIBULOCKMGR
+
.lock = xmp_lock,
+
#endif
+
.flock = xmp_flock,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
#ifdef HAVE_STATX
+
.statx = xmp_statx,
+
#endif
+
};
+
+
int main(int argc, char *argv[])
+
{
+
umask(0);
+
return fuse_main(argc, argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file null.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2null_8c_source.html b/doc/html/fuse-3_818_81_2example_2null_8c_source.html new file mode 100644 index 0000000..4cb38c3 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2null_8c_source.html @@ -0,0 +1,196 @@ + + + + + + + +libfuse: fuse-3.18.1/example/null.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
null.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
25#define FUSE_USE_VERSION 31
+
26
+
27#include <fuse.h>
+
28#include <fuse_lowlevel.h>
+
29#include <stdio.h>
+
30#include <stdlib.h>
+
31#include <string.h>
+
32#include <unistd.h>
+
33#include <time.h>
+
34#include <errno.h>
+
35
+
36static int null_getattr(const char *path, struct stat *stbuf,
+
37 struct fuse_file_info *fi)
+
38{
+
39 (void) fi;
+
40
+
41 if(strcmp(path, "/") != 0)
+
42 return -ENOENT;
+
43
+
44 stbuf->st_mode = S_IFREG | 0644;
+
45 stbuf->st_nlink = 1;
+
46 stbuf->st_uid = getuid();
+
47 stbuf->st_gid = getgid();
+
48 stbuf->st_size = (1ULL << 32); /* 4G */
+
49 stbuf->st_blocks = 0;
+
50 stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = time(NULL);
+
51
+
52 return 0;
+
53}
+
54
+
55static int null_truncate(const char *path, off_t size,
+
56 struct fuse_file_info *fi)
+
57{
+
58 (void) size;
+
59 (void) fi;
+
60
+
61 if(strcmp(path, "/") != 0)
+
62 return -ENOENT;
+
63
+
64 return 0;
+
65}
+
66
+
67static int null_open(const char *path, struct fuse_file_info *fi)
+
68{
+
69 (void) fi;
+
70
+
71 if(strcmp(path, "/") != 0)
+
72 return -ENOENT;
+
73
+
74 return 0;
+
75}
+
76
+
77static int null_read(const char *path, char *buf, size_t size,
+
78 off_t offset, struct fuse_file_info *fi)
+
79{
+
80 (void) buf;
+
81 (void) offset;
+
82 (void) fi;
+
83
+
84 if(strcmp(path, "/") != 0)
+
85 return -ENOENT;
+
86
+
87 if (offset >= (1ULL << 32))
+
88 return 0;
+
89
+
90 memset(buf, 0, size);
+
91 return size;
+
92}
+
93
+
94static int null_write(const char *path, const char *buf, size_t size,
+
95 off_t offset, struct fuse_file_info *fi)
+
96{
+
97 (void) buf;
+
98 (void) offset;
+
99 (void) fi;
+
100
+
101 if(strcmp(path, "/") != 0)
+
102 return -ENOENT;
+
103
+
104 return size;
+
105}
+
106
+
107static const struct fuse_operations null_oper = {
+
108 .getattr = null_getattr,
+
109 .truncate = null_truncate,
+
110 .open = null_open,
+
111 .read = null_read,
+
112 .write = null_write,
+
113};
+
114
+
115int main(int argc, char *argv[])
+
116{
+
117 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
118 struct fuse_cmdline_opts opts;
+
119 struct stat stbuf;
+
120
+
121 if (fuse_parse_cmdline(&args, &opts) != 0)
+
122 return 1;
+
123 fuse_opt_free_args(&args);
+
124
+
125 if (!opts.mountpoint) {
+
126 fprintf(stderr, "missing mountpoint parameter\n");
+
127 return 1;
+
128 }
+
129
+
130 if (stat(opts.mountpoint, &stbuf) == -1) {
+
131 fprintf(stderr ,"failed to access mountpoint %s: %s\n",
+
132 opts.mountpoint, strerror(errno));
+
133 free(opts.mountpoint);
+
134 return 1;
+
135 }
+
136 free(opts.mountpoint);
+
137 if (!S_ISREG(stbuf.st_mode)) {
+
138 fprintf(stderr, "mountpoint is not a regular file\n");
+
139 return 1;
+
140 }
+
141
+
142 return fuse_main(argc, argv, &null_oper, NULL);
+
143}
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2passthrough_8c.html b/doc/html/fuse-3_818_81_2example_2passthrough_8c.html new file mode 100644 index 0000000..694654b --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2passthrough_8c.html @@ -0,0 +1,679 @@ + + + + + + + +libfuse: fuse-3.18.1/example/passthrough.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. Its performance is terrible.

+

Compile with

gcc -Wall passthrough.c `pkg-config fuse3 --cflags --libs` -o passthrough
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
+
#define _GNU_SOURCE
+
+
#ifdef linux
+
/* For pread()/pwrite()/utimensat() */
+
#define _XOPEN_SOURCE 700
+
#endif
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
+
#include "passthrough_helpers.h"
+
+
static int fill_dir_plus = 0;
+
static int readdir_zero_ino;
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = !readdir_zero_ino;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
if (!cfg->auto_cache) {
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
}
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
DIR *dp;
+
struct dirent *de;
+
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
dp = opendir(path);
+
if (dp == NULL)
+
return -errno;
+
+
while ((de = readdir(dp)) != NULL) {
+
struct stat st;
+
if (fill_dir_plus) {
+
fstatat(dirfd(dp), de->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
} else {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = de->d_ino;
+
st.st_mode = de->d_type << 12;
+
}
+
if (readdir_zero_ino)
+
st.st_ino = 0;
+
if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
+
break;
+
}
+
+
closedir(dp);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi != NULL)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
res = open(path, fi->flags, mode);
+
if (res == -1)
+
return -errno;
+
+
fi->fh = res;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
res = open(path, fi->flags);
+
if (res == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = res;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
if(fi == NULL)
+
fd = open(path, O_RDONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = pread(fd, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
(void) fi;
+
if(fi == NULL)
+
fd = open(path, O_WRONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = pwrite(fd, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
/* Just a stub. This method is optional and can safely be left
+
unimplemented */
+
+
(void) path;
+
(void) isdatasync;
+
(void) fi;
+
return 0;
+
}
+
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
(void) fi;
+
+
if(fi == NULL)
+
fd = open(path, O_WRONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = do_fallocate(fd, mode, offset, length);
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t offset_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t offset_out, size_t len, int flags)
+
{
+
int fd_in, fd_out;
+
ssize_t res;
+
+
if(fi_in == NULL)
+
fd_in = open(path_in, O_RDONLY);
+
else
+
fd_in = fi_in->fh;
+
+
if (fd_in == -1)
+
return -errno;
+
+
if(fi_out == NULL)
+
fd_out = open(path_out, O_WRONLY);
+
else
+
fd_out = fi_out->fh;
+
+
if (fd_out == -1) {
+
close(fd_in);
+
return -errno;
+
}
+
+
res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
+
flags);
+
if (res == -1)
+
res = -errno;
+
+
if (fi_out == NULL)
+
close(fd_out);
+
if (fi_in == NULL)
+
close(fd_in);
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
int fd;
+
off_t res;
+
+
if (fi == NULL)
+
fd = open(path, O_RDONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = lseek(fd, off, whence);
+
if (res == -1)
+
res = -errno;
+
+
if (fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
#ifdef HAVE_STATX
+
static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
struct fuse_file_info *fi)
+
{
+
int fd = -1;
+
int res;
+
+
if (fi)
+
fd = fi->fh;
+
+
res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.readdir = xmp_readdir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.open = xmp_open,
+
.create = xmp_create,
+
.read = xmp_read,
+
.write = xmp_write,
+
.statfs = xmp_statfs,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
.fallocate = xmp_fallocate,
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
#ifdef HAVE_STATX
+
.statx = xmp_statx,
+
#endif
+
};
+
+
int main(int argc, char *argv[])
+
{
+
enum { MAX_ARGS = 10 };
+
int i,new_argc;
+
char *new_argv[MAX_ARGS];
+
+
umask(0);
+
/* Process the "--plus" option apart */
+
for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
+
if (!strcmp(argv[i], "--plus")) {
+
fill_dir_plus = FUSE_FILL_DIR_PLUS;
+
} else if (!strcmp(argv[i], "--readdir-zero-inodes")) {
+
// Return zero inodes from readdir
+
readdir_zero_ino = 1;
+
} else {
+
new_argv[new_argc++] = argv[i];
+
}
+
}
+
return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ +
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t auto_cache
Definition fuse.h:253
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file passthrough.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2passthrough_8c_source.html b/doc/html/fuse-3_818_81_2example_2passthrough_8c_source.html new file mode 100644 index 0000000..c0dd4bd --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2passthrough_8c_source.html @@ -0,0 +1,665 @@ + + + + + + + +libfuse: fuse-3.18.1/example/passthrough.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
26#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
27
+
28#define _GNU_SOURCE
+
29
+
30#ifdef linux
+
31/* For pread()/pwrite()/utimensat() */
+
32#define _XOPEN_SOURCE 700
+
33#endif
+
34
+
35#include <fuse.h>
+
36#include <stdio.h>
+
37#include <string.h>
+
38#include <unistd.h>
+
39#include <fcntl.h>
+
40#include <sys/stat.h>
+
41#include <dirent.h>
+
42#include <errno.h>
+
43#include <sys/time.h>
+
44#ifdef HAVE_SETXATTR
+
45#include <sys/xattr.h>
+
46#endif
+
47
+
48#include "passthrough_helpers.h"
+
49
+
50static int fill_dir_plus = 0;
+
51static int readdir_zero_ino;
+
52
+
53static void *xmp_init(struct fuse_conn_info *conn,
+
54 struct fuse_config *cfg)
+
55{
+
56 (void) conn;
+
57 cfg->use_ino = !readdir_zero_ino;
+
58
+
59 /* parallel_direct_writes feature depends on direct_io features.
+
60 To make parallel_direct_writes valid, need either set cfg->direct_io
+
61 in current function (recommended in high level API) or set fi->direct_io
+
62 in xmp_create() or xmp_open(). */
+
63 // cfg->direct_io = 1;
+ +
65
+
66 /* Pick up changes from lower filesystem right away. This is
+
67 also necessary for better hardlink support. When the kernel
+
68 calls the unlink() handler, it does not know the inode of
+
69 the to-be-removed entry and can therefore not invalidate
+
70 the cache of the associated inode - resulting in an
+
71 incorrect st_nlink value being reported for any remaining
+
72 hardlinks to this inode. */
+
73 if (!cfg->auto_cache) {
+
74 cfg->entry_timeout = 0;
+
75 cfg->attr_timeout = 0;
+
76 cfg->negative_timeout = 0;
+
77 }
+
78
+
79 return NULL;
+
80}
+
81
+
82static int xmp_getattr(const char *path, struct stat *stbuf,
+
83 struct fuse_file_info *fi)
+
84{
+
85 (void) fi;
+
86 int res;
+
87
+
88 res = lstat(path, stbuf);
+
89 if (res == -1)
+
90 return -errno;
+
91
+
92 return 0;
+
93}
+
94
+
95static int xmp_access(const char *path, int mask)
+
96{
+
97 int res;
+
98
+
99 res = access(path, mask);
+
100 if (res == -1)
+
101 return -errno;
+
102
+
103 return 0;
+
104}
+
105
+
106static int xmp_readlink(const char *path, char *buf, size_t size)
+
107{
+
108 int res;
+
109
+
110 res = readlink(path, buf, size - 1);
+
111 if (res == -1)
+
112 return -errno;
+
113
+
114 buf[res] = '\0';
+
115 return 0;
+
116}
+
117
+
118
+
119static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
120 off_t offset, struct fuse_file_info *fi,
+
121 enum fuse_readdir_flags flags)
+
122{
+
123 DIR *dp;
+
124 struct dirent *de;
+
125
+
126 (void) offset;
+
127 (void) fi;
+
128 (void) flags;
+
129
+
130 dp = opendir(path);
+
131 if (dp == NULL)
+
132 return -errno;
+
133
+
134 while ((de = readdir(dp)) != NULL) {
+
135 struct stat st;
+
136 if (fill_dir_plus) {
+
137 fstatat(dirfd(dp), de->d_name, &st,
+
138 AT_SYMLINK_NOFOLLOW);
+
139 } else {
+
140 memset(&st, 0, sizeof(st));
+
141 st.st_ino = de->d_ino;
+
142 st.st_mode = de->d_type << 12;
+
143 }
+
144 if (readdir_zero_ino)
+
145 st.st_ino = 0;
+
146 if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
+
147 break;
+
148 }
+
149
+
150 closedir(dp);
+
151 return 0;
+
152}
+
153
+
154static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
155{
+
156 int res;
+
157
+
158 res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
+
159 if (res == -1)
+
160 return -errno;
+
161
+
162 return 0;
+
163}
+
164
+
165static int xmp_mkdir(const char *path, mode_t mode)
+
166{
+
167 int res;
+
168
+
169 res = mkdir(path, mode);
+
170 if (res == -1)
+
171 return -errno;
+
172
+
173 return 0;
+
174}
+
175
+
176static int xmp_unlink(const char *path)
+
177{
+
178 int res;
+
179
+
180 res = unlink(path);
+
181 if (res == -1)
+
182 return -errno;
+
183
+
184 return 0;
+
185}
+
186
+
187static int xmp_rmdir(const char *path)
+
188{
+
189 int res;
+
190
+
191 res = rmdir(path);
+
192 if (res == -1)
+
193 return -errno;
+
194
+
195 return 0;
+
196}
+
197
+
198static int xmp_symlink(const char *from, const char *to)
+
199{
+
200 int res;
+
201
+
202 res = symlink(from, to);
+
203 if (res == -1)
+
204 return -errno;
+
205
+
206 return 0;
+
207}
+
208
+
209static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
210{
+
211 int res;
+
212
+
213 if (flags)
+
214 return -EINVAL;
+
215
+
216 res = rename(from, to);
+
217 if (res == -1)
+
218 return -errno;
+
219
+
220 return 0;
+
221}
+
222
+
223static int xmp_link(const char *from, const char *to)
+
224{
+
225 int res;
+
226
+
227 res = link(from, to);
+
228 if (res == -1)
+
229 return -errno;
+
230
+
231 return 0;
+
232}
+
233
+
234static int xmp_chmod(const char *path, mode_t mode,
+
235 struct fuse_file_info *fi)
+
236{
+
237 (void) fi;
+
238 int res;
+
239
+
240 res = chmod(path, mode);
+
241 if (res == -1)
+
242 return -errno;
+
243
+
244 return 0;
+
245}
+
246
+
247static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
248 struct fuse_file_info *fi)
+
249{
+
250 (void) fi;
+
251 int res;
+
252
+
253 res = lchown(path, uid, gid);
+
254 if (res == -1)
+
255 return -errno;
+
256
+
257 return 0;
+
258}
+
259
+
260static int xmp_truncate(const char *path, off_t size,
+
261 struct fuse_file_info *fi)
+
262{
+
263 int res;
+
264
+
265 if (fi != NULL)
+
266 res = ftruncate(fi->fh, size);
+
267 else
+
268 res = truncate(path, size);
+
269 if (res == -1)
+
270 return -errno;
+
271
+
272 return 0;
+
273}
+
274
+
275#ifdef HAVE_UTIMENSAT
+
276static int xmp_utimens(const char *path, const struct timespec ts[2],
+
277 struct fuse_file_info *fi)
+
278{
+
279 (void) fi;
+
280 int res;
+
281
+
282 /* don't use utime/utimes since they follow symlinks */
+
283 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
284 if (res == -1)
+
285 return -errno;
+
286
+
287 return 0;
+
288}
+
289#endif
+
290
+
291static int xmp_create(const char *path, mode_t mode,
+
292 struct fuse_file_info *fi)
+
293{
+
294 int res;
+
295
+
296 res = open(path, fi->flags, mode);
+
297 if (res == -1)
+
298 return -errno;
+
299
+
300 fi->fh = res;
+
301 return 0;
+
302}
+
303
+
304static int xmp_open(const char *path, struct fuse_file_info *fi)
+
305{
+
306 int res;
+
307
+
308 res = open(path, fi->flags);
+
309 if (res == -1)
+
310 return -errno;
+
311
+
312 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
313 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
314 for writes to the same file). */
+
315 if (fi->flags & O_DIRECT) {
+
316 fi->direct_io = 1;
+ +
318 }
+
319
+
320 fi->fh = res;
+
321 return 0;
+
322}
+
323
+
324static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
325 struct fuse_file_info *fi)
+
326{
+
327 int fd;
+
328 int res;
+
329
+
330 if(fi == NULL)
+
331 fd = open(path, O_RDONLY);
+
332 else
+
333 fd = fi->fh;
+
334
+
335 if (fd == -1)
+
336 return -errno;
+
337
+
338 res = pread(fd, buf, size, offset);
+
339 if (res == -1)
+
340 res = -errno;
+
341
+
342 if(fi == NULL)
+
343 close(fd);
+
344 return res;
+
345}
+
346
+
347static int xmp_write(const char *path, const char *buf, size_t size,
+
348 off_t offset, struct fuse_file_info *fi)
+
349{
+
350 int fd;
+
351 int res;
+
352
+
353 (void) fi;
+
354 if(fi == NULL)
+
355 fd = open(path, O_WRONLY);
+
356 else
+
357 fd = fi->fh;
+
358
+
359 if (fd == -1)
+
360 return -errno;
+
361
+
362 res = pwrite(fd, buf, size, offset);
+
363 if (res == -1)
+
364 res = -errno;
+
365
+
366 if(fi == NULL)
+
367 close(fd);
+
368 return res;
+
369}
+
370
+
371static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
372{
+
373 int res;
+
374
+
375 res = statvfs(path, stbuf);
+
376 if (res == -1)
+
377 return -errno;
+
378
+
379 return 0;
+
380}
+
381
+
382static int xmp_release(const char *path, struct fuse_file_info *fi)
+
383{
+
384 (void) path;
+
385 close(fi->fh);
+
386 return 0;
+
387}
+
388
+
389static int xmp_fsync(const char *path, int isdatasync,
+
390 struct fuse_file_info *fi)
+
391{
+
392 /* Just a stub. This method is optional and can safely be left
+
393 unimplemented */
+
394
+
395 (void) path;
+
396 (void) isdatasync;
+
397 (void) fi;
+
398 return 0;
+
399}
+
400
+
401static int xmp_fallocate(const char *path, int mode,
+
402 off_t offset, off_t length, struct fuse_file_info *fi)
+
403{
+
404 int fd;
+
405 int res;
+
406
+
407 (void) fi;
+
408
+
409 if(fi == NULL)
+
410 fd = open(path, O_WRONLY);
+
411 else
+
412 fd = fi->fh;
+
413
+
414 if (fd == -1)
+
415 return -errno;
+
416
+
417 res = do_fallocate(fd, mode, offset, length);
+
418
+
419 if(fi == NULL)
+
420 close(fd);
+
421 return res;
+
422}
+
423
+
424#ifdef HAVE_SETXATTR
+
425/* xattr operations are optional and can safely be left unimplemented */
+
426static int xmp_setxattr(const char *path, const char *name, const char *value,
+
427 size_t size, int flags)
+
428{
+
429 int res = lsetxattr(path, name, value, size, flags);
+
430 if (res == -1)
+
431 return -errno;
+
432 return 0;
+
433}
+
434
+
435static int xmp_getxattr(const char *path, const char *name, char *value,
+
436 size_t size)
+
437{
+
438 int res = lgetxattr(path, name, value, size);
+
439 if (res == -1)
+
440 return -errno;
+
441 return res;
+
442}
+
443
+
444static int xmp_listxattr(const char *path, char *list, size_t size)
+
445{
+
446 int res = llistxattr(path, list, size);
+
447 if (res == -1)
+
448 return -errno;
+
449 return res;
+
450}
+
451
+
452static int xmp_removexattr(const char *path, const char *name)
+
453{
+
454 int res = lremovexattr(path, name);
+
455 if (res == -1)
+
456 return -errno;
+
457 return 0;
+
458}
+
459#endif /* HAVE_SETXATTR */
+
460
+
461#ifdef HAVE_COPY_FILE_RANGE
+
462static ssize_t xmp_copy_file_range(const char *path_in,
+
463 struct fuse_file_info *fi_in,
+
464 off_t offset_in, const char *path_out,
+
465 struct fuse_file_info *fi_out,
+
466 off_t offset_out, size_t len, int flags)
+
467{
+
468 int fd_in, fd_out;
+
469 ssize_t res;
+
470
+
471 if(fi_in == NULL)
+
472 fd_in = open(path_in, O_RDONLY);
+
473 else
+
474 fd_in = fi_in->fh;
+
475
+
476 if (fd_in == -1)
+
477 return -errno;
+
478
+
479 if(fi_out == NULL)
+
480 fd_out = open(path_out, O_WRONLY);
+
481 else
+
482 fd_out = fi_out->fh;
+
483
+
484 if (fd_out == -1) {
+
485 close(fd_in);
+
486 return -errno;
+
487 }
+
488
+
489 res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
+
490 flags);
+
491 if (res == -1)
+
492 res = -errno;
+
493
+
494 if (fi_out == NULL)
+
495 close(fd_out);
+
496 if (fi_in == NULL)
+
497 close(fd_in);
+
498
+
499 return res;
+
500}
+
501#endif
+
502
+
503static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
504{
+
505 int fd;
+
506 off_t res;
+
507
+
508 if (fi == NULL)
+
509 fd = open(path, O_RDONLY);
+
510 else
+
511 fd = fi->fh;
+
512
+
513 if (fd == -1)
+
514 return -errno;
+
515
+
516 res = lseek(fd, off, whence);
+
517 if (res == -1)
+
518 res = -errno;
+
519
+
520 if (fi == NULL)
+
521 close(fd);
+
522 return res;
+
523}
+
524
+
525#ifdef HAVE_STATX
+
526static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
527 struct fuse_file_info *fi)
+
528{
+
529 int fd = -1;
+
530 int res;
+
531
+
532 if (fi)
+
533 fd = fi->fh;
+
534
+
535 res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
+
536 if (res == -1)
+
537 return -errno;
+
538
+
539 return 0;
+
540}
+
541#endif
+
542
+
543static const struct fuse_operations xmp_oper = {
+
544 .init = xmp_init,
+
545 .getattr = xmp_getattr,
+
546 .access = xmp_access,
+
547 .readlink = xmp_readlink,
+
548 .readdir = xmp_readdir,
+
549 .mknod = xmp_mknod,
+
550 .mkdir = xmp_mkdir,
+
551 .symlink = xmp_symlink,
+
552 .unlink = xmp_unlink,
+
553 .rmdir = xmp_rmdir,
+
554 .rename = xmp_rename,
+
555 .link = xmp_link,
+
556 .chmod = xmp_chmod,
+
557 .chown = xmp_chown,
+
558 .truncate = xmp_truncate,
+
559#ifdef HAVE_UTIMENSAT
+
560 .utimens = xmp_utimens,
+
561#endif
+
562 .open = xmp_open,
+
563 .create = xmp_create,
+
564 .read = xmp_read,
+
565 .write = xmp_write,
+
566 .statfs = xmp_statfs,
+
567 .release = xmp_release,
+
568 .fsync = xmp_fsync,
+
569 .fallocate = xmp_fallocate,
+
570#ifdef HAVE_SETXATTR
+
571 .setxattr = xmp_setxattr,
+
572 .getxattr = xmp_getxattr,
+
573 .listxattr = xmp_listxattr,
+
574 .removexattr = xmp_removexattr,
+
575#endif
+
576#ifdef HAVE_COPY_FILE_RANGE
+
577 .copy_file_range = xmp_copy_file_range,
+
578#endif
+
579 .lseek = xmp_lseek,
+
580#ifdef HAVE_STATX
+
581 .statx = xmp_statx,
+
582#endif
+
583};
+
584
+
585int main(int argc, char *argv[])
+
586{
+
587 enum { MAX_ARGS = 10 };
+
588 int i,new_argc;
+
589 char *new_argv[MAX_ARGS];
+
590
+
591 umask(0);
+
592 /* Process the "--plus" option apart */
+
593 for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
+
594 if (!strcmp(argv[i], "--plus")) {
+
595 fill_dir_plus = FUSE_FILL_DIR_PLUS;
+
596 } else if (!strcmp(argv[i], "--readdir-zero-inodes")) {
+
597 // Return zero inodes from readdir
+
598 readdir_zero_ino = 1;
+
599 } else {
+
600 new_argv[new_argc++] = argv[i];
+
601 }
+
602 }
+
603 return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
+
604}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ +
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t auto_cache
Definition fuse.h:253
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2passthrough__fh_8c.html b/doc/html/fuse-3_818_81_2example_2passthrough__fh_8c.html new file mode 100644 index 0000000..236c01b --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2passthrough__fh_8c.html @@ -0,0 +1,783 @@ + + + + + + + +libfuse: fuse-3.18.1/example/passthrough_fh.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough_fh.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/file.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. This implementation is a little more sophisticated than the one in passthrough.c, so performance is not quite as bad.

+

Compile with:

gcc -Wall passthrough_fh.c `pkg-config fuse3 --cflags --libs` -lulockmgr -o passthrough_fh
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
+
#define _GNU_SOURCE
+
+
#include <fuse.h>
+
+
#ifdef HAVE_LIBULOCKMGR
+
#include <ulockmgr.h>
+
#endif
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
#include <sys/file.h> /* flock(2) */
+
+
#include "passthrough_helpers.h"
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
cfg->nullpath_ok = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
+
if(fi)
+
res = fstat(fi->fh, stbuf);
+
else
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
struct xmp_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
if (d == NULL)
+
return -ENOMEM;
+
+
d->dp = opendir(path);
+
if (d->dp == NULL) {
+
res = -errno;
+
free(d);
+
return res;
+
}
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (unsigned long) d;
+
return 0;
+
}
+
+
static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
{
+
return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
+
(void) path;
+
if (offset != d->offset) {
+
#ifndef __FreeBSD__
+
seekdir(d->dp, offset);
+
#else
+
/* Subtract the one that we add when calling
+
telldir() below */
+
seekdir(d->dp, offset-1);
+
#endif
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
struct stat st;
+
off_t nextoff;
+ +
+
if (!d->entry) {
+
d->entry = readdir(d->dp);
+
if (!d->entry)
+
break;
+
}
+
#ifdef HAVE_FSTATAT
+
if (flags & FUSE_READDIR_PLUS) {
+
int res;
+
+
res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
if (res != -1)
+
fill_flags |= FUSE_FILL_DIR_PLUS;
+
}
+
#endif
+
if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = d->entry->d_ino;
+
st.st_mode = d->entry->d_type << 12;
+
}
+
nextoff = telldir(d->dp);
+
#ifdef __FreeBSD__
+
/* Under FreeBSD, telldir() may return 0 the first time
+
it is called. But for libfuse, an offset of zero
+
means that offsets are not supported, so we shift
+
everything by one. */
+
nextoff++;
+
#endif
+
if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
break;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
return 0;
+
}
+
+
static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
(void) path;
+
closedir(d->dp);
+
free(d);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
if (S_ISFIFO(mode))
+
res = mkfifo(path, mode);
+
else
+
res = mknod(path, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
/* When we have renameat2() in libc, then we can implement flags */
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = fchmod(fi->fh, mode);
+
else
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi)
+
res = fchown(fi->fh, uid, gid);
+
else
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
if (fi)
+
res = futimens(fi->fh, ts);
+
else
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags, mode);
+
if (fd == -1)
+
return -errno;
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags);
+
if (fd == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pread(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
size_t size, off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec *src;
+
+
(void) path;
+
+
src = malloc(sizeof(struct fuse_bufvec));
+
if (src == NULL)
+
return -ENOMEM;
+
+
*src = FUSE_BUFVEC_INIT(size);
+
+ +
src->buf[0].fd = fi->fh;
+
src->buf[0].pos = offset;
+
+
*bufp = src;
+
+
return 0;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pwrite(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
+
(void) path;
+
+ +
dst.buf[0].fd = fi->fh;
+
dst.buf[0].pos = offset;
+
+ +
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
/* This is called from every close on an open file, so call the
+
close on the underlying filesystem. But since flush may be
+
called multiple times for an open file, this must not really
+
close the file. This is important if used on a network
+
filesystem like NFS which flush the data/metadata on close() */
+
res = close(dup(fi->fh));
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) path;
+
+
#ifndef HAVE_FDATASYNC
+
(void) isdatasync;
+
#else
+
if (isdatasync)
+
res = fdatasync(fi->fh);
+
else
+
#endif
+
res = fsync(fi->fh);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
(void) path;
+
+
return do_fallocate(fi->fh, mode, offset, length);
+
}
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_LIBULOCKMGR
+
static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
struct flock *lock)
+
{
+
(void) path;
+
+
return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
sizeof(fi->lock_owner));
+
}
+
#endif
+
+
static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
{
+
int res;
+
(void) path;
+
+
res = flock(fi->fh, op);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t off_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t off_out, size_t len, int flags)
+
{
+
ssize_t res;
+
(void) path_in;
+
(void) path_out;
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
off_t res;
+
(void) path;
+
+
res = lseek(fi->fh, off, whence);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
+
#ifdef HAVE_STATX
+
static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
struct fuse_file_info *fi)
+
{
+
int fd = -1;
+
int res;
+
+
if (fi)
+
fd = fi->fh;
+
+
res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.opendir = xmp_opendir,
+
.readdir = xmp_readdir,
+
.releasedir = xmp_releasedir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.create = xmp_create,
+
.open = xmp_open,
+
.read = xmp_read,
+
.read_buf = xmp_read_buf,
+
.write = xmp_write,
+
.write_buf = xmp_write_buf,
+
.statfs = xmp_statfs,
+
.flush = xmp_flush,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
.fallocate = xmp_fallocate,
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_LIBULOCKMGR
+
.lock = xmp_lock,
+
#endif
+
.flock = xmp_flock,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
#ifdef HAVE_STATX
+
.statx = xmp_statx,
+
#endif
+
};
+
+
int main(int argc, char *argv[])
+
{
+
umask(0);
+
return fuse_main(argc, argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file passthrough_fh.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2passthrough__fh_8c_source.html b/doc/html/fuse-3_818_81_2example_2passthrough__fh_8c_source.html new file mode 100644 index 0000000..c6e950d --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2passthrough__fh_8c_source.html @@ -0,0 +1,767 @@ + + + + + + + +libfuse: fuse-3.18.1/example/passthrough_fh.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_fh.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
26#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
27
+
28#define _GNU_SOURCE
+
29
+
30#include <fuse.h>
+
31
+
32#ifdef HAVE_LIBULOCKMGR
+
33#include <ulockmgr.h>
+
34#endif
+
35
+
36#include <stdio.h>
+
37#include <stdlib.h>
+
38#include <string.h>
+
39#include <unistd.h>
+
40#include <fcntl.h>
+
41#include <sys/stat.h>
+
42#include <dirent.h>
+
43#include <errno.h>
+
44#include <sys/time.h>
+
45#ifdef HAVE_SETXATTR
+
46#include <sys/xattr.h>
+
47#endif
+
48#include <sys/file.h> /* flock(2) */
+
49
+
50#include "passthrough_helpers.h"
+
51
+
52static void *xmp_init(struct fuse_conn_info *conn,
+
53 struct fuse_config *cfg)
+
54{
+
55 (void) conn;
+
56 cfg->use_ino = 1;
+
57 cfg->nullpath_ok = 1;
+
58
+
59 /* parallel_direct_writes feature depends on direct_io features.
+
60 To make parallel_direct_writes valid, need either set cfg->direct_io
+
61 in current function (recommended in high level API) or set fi->direct_io
+
62 in xmp_create() or xmp_open(). */
+
63 // cfg->direct_io = 1;
+ +
65
+
66 /* Pick up changes from lower filesystem right away. This is
+
67 also necessary for better hardlink support. When the kernel
+
68 calls the unlink() handler, it does not know the inode of
+
69 the to-be-removed entry and can therefore not invalidate
+
70 the cache of the associated inode - resulting in an
+
71 incorrect st_nlink value being reported for any remaining
+
72 hardlinks to this inode. */
+
73 cfg->entry_timeout = 0;
+
74 cfg->attr_timeout = 0;
+
75 cfg->negative_timeout = 0;
+
76
+
77 return NULL;
+
78}
+
79
+
80static int xmp_getattr(const char *path, struct stat *stbuf,
+
81 struct fuse_file_info *fi)
+
82{
+
83 int res;
+
84
+
85 (void) path;
+
86
+
87 if(fi)
+
88 res = fstat(fi->fh, stbuf);
+
89 else
+
90 res = lstat(path, stbuf);
+
91 if (res == -1)
+
92 return -errno;
+
93
+
94 return 0;
+
95}
+
96
+
97static int xmp_access(const char *path, int mask)
+
98{
+
99 int res;
+
100
+
101 res = access(path, mask);
+
102 if (res == -1)
+
103 return -errno;
+
104
+
105 return 0;
+
106}
+
107
+
108static int xmp_readlink(const char *path, char *buf, size_t size)
+
109{
+
110 int res;
+
111
+
112 res = readlink(path, buf, size - 1);
+
113 if (res == -1)
+
114 return -errno;
+
115
+
116 buf[res] = '\0';
+
117 return 0;
+
118}
+
119
+
120struct xmp_dirp {
+
121 DIR *dp;
+
122 struct dirent *entry;
+
123 off_t offset;
+
124};
+
125
+
126static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
127{
+
128 int res;
+
129 struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
130 if (d == NULL)
+
131 return -ENOMEM;
+
132
+
133 d->dp = opendir(path);
+
134 if (d->dp == NULL) {
+
135 res = -errno;
+
136 free(d);
+
137 return res;
+
138 }
+
139 d->offset = 0;
+
140 d->entry = NULL;
+
141
+
142 fi->fh = (unsigned long) d;
+
143 return 0;
+
144}
+
145
+
146static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
147{
+
148 return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
149}
+
150
+
151static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
152 off_t offset, struct fuse_file_info *fi,
+
153 enum fuse_readdir_flags flags)
+
154{
+
155 struct xmp_dirp *d = get_dirp(fi);
+
156
+
157 (void) path;
+
158 if (offset != d->offset) {
+
159#ifndef __FreeBSD__
+
160 seekdir(d->dp, offset);
+
161#else
+
162 /* Subtract the one that we add when calling
+
163 telldir() below */
+
164 seekdir(d->dp, offset-1);
+
165#endif
+
166 d->entry = NULL;
+
167 d->offset = offset;
+
168 }
+
169 while (1) {
+
170 struct stat st;
+
171 off_t nextoff;
+ +
173
+
174 if (!d->entry) {
+
175 d->entry = readdir(d->dp);
+
176 if (!d->entry)
+
177 break;
+
178 }
+
179#ifdef HAVE_FSTATAT
+
180 if (flags & FUSE_READDIR_PLUS) {
+
181 int res;
+
182
+
183 res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
184 AT_SYMLINK_NOFOLLOW);
+
185 if (res != -1)
+
186 fill_flags |= FUSE_FILL_DIR_PLUS;
+
187 }
+
188#endif
+
189 if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
190 memset(&st, 0, sizeof(st));
+
191 st.st_ino = d->entry->d_ino;
+
192 st.st_mode = d->entry->d_type << 12;
+
193 }
+
194 nextoff = telldir(d->dp);
+
195#ifdef __FreeBSD__
+
196 /* Under FreeBSD, telldir() may return 0 the first time
+
197 it is called. But for libfuse, an offset of zero
+
198 means that offsets are not supported, so we shift
+
199 everything by one. */
+
200 nextoff++;
+
201#endif
+
202 if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
203 break;
+
204
+
205 d->entry = NULL;
+
206 d->offset = nextoff;
+
207 }
+
208
+
209 return 0;
+
210}
+
211
+
212static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
213{
+
214 struct xmp_dirp *d = get_dirp(fi);
+
215 (void) path;
+
216 closedir(d->dp);
+
217 free(d);
+
218 return 0;
+
219}
+
220
+
221static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
222{
+
223 int res;
+
224
+
225 if (S_ISFIFO(mode))
+
226 res = mkfifo(path, mode);
+
227 else
+
228 res = mknod(path, mode, rdev);
+
229 if (res == -1)
+
230 return -errno;
+
231
+
232 return 0;
+
233}
+
234
+
235static int xmp_mkdir(const char *path, mode_t mode)
+
236{
+
237 int res;
+
238
+
239 res = mkdir(path, mode);
+
240 if (res == -1)
+
241 return -errno;
+
242
+
243 return 0;
+
244}
+
245
+
246static int xmp_unlink(const char *path)
+
247{
+
248 int res;
+
249
+
250 res = unlink(path);
+
251 if (res == -1)
+
252 return -errno;
+
253
+
254 return 0;
+
255}
+
256
+
257static int xmp_rmdir(const char *path)
+
258{
+
259 int res;
+
260
+
261 res = rmdir(path);
+
262 if (res == -1)
+
263 return -errno;
+
264
+
265 return 0;
+
266}
+
267
+
268static int xmp_symlink(const char *from, const char *to)
+
269{
+
270 int res;
+
271
+
272 res = symlink(from, to);
+
273 if (res == -1)
+
274 return -errno;
+
275
+
276 return 0;
+
277}
+
278
+
279static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
280{
+
281 int res;
+
282
+
283 /* When we have renameat2() in libc, then we can implement flags */
+
284 if (flags)
+
285 return -EINVAL;
+
286
+
287 res = rename(from, to);
+
288 if (res == -1)
+
289 return -errno;
+
290
+
291 return 0;
+
292}
+
293
+
294static int xmp_link(const char *from, const char *to)
+
295{
+
296 int res;
+
297
+
298 res = link(from, to);
+
299 if (res == -1)
+
300 return -errno;
+
301
+
302 return 0;
+
303}
+
304
+
305static int xmp_chmod(const char *path, mode_t mode,
+
306 struct fuse_file_info *fi)
+
307{
+
308 int res;
+
309
+
310 if(fi)
+
311 res = fchmod(fi->fh, mode);
+
312 else
+
313 res = chmod(path, mode);
+
314 if (res == -1)
+
315 return -errno;
+
316
+
317 return 0;
+
318}
+
319
+
320static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
321 struct fuse_file_info *fi)
+
322{
+
323 int res;
+
324
+
325 if (fi)
+
326 res = fchown(fi->fh, uid, gid);
+
327 else
+
328 res = lchown(path, uid, gid);
+
329 if (res == -1)
+
330 return -errno;
+
331
+
332 return 0;
+
333}
+
334
+
335static int xmp_truncate(const char *path, off_t size,
+
336 struct fuse_file_info *fi)
+
337{
+
338 int res;
+
339
+
340 if(fi)
+
341 res = ftruncate(fi->fh, size);
+
342 else
+
343 res = truncate(path, size);
+
344
+
345 if (res == -1)
+
346 return -errno;
+
347
+
348 return 0;
+
349}
+
350
+
351#ifdef HAVE_UTIMENSAT
+
352static int xmp_utimens(const char *path, const struct timespec ts[2],
+
353 struct fuse_file_info *fi)
+
354{
+
355 int res;
+
356
+
357 /* don't use utime/utimes since they follow symlinks */
+
358 if (fi)
+
359 res = futimens(fi->fh, ts);
+
360 else
+
361 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
362 if (res == -1)
+
363 return -errno;
+
364
+
365 return 0;
+
366}
+
367#endif
+
368
+
369static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
370{
+
371 int fd;
+
372
+
373 fd = open(path, fi->flags, mode);
+
374 if (fd == -1)
+
375 return -errno;
+
376
+
377 fi->fh = fd;
+
378 return 0;
+
379}
+
380
+
381static int xmp_open(const char *path, struct fuse_file_info *fi)
+
382{
+
383 int fd;
+
384
+
385 fd = open(path, fi->flags);
+
386 if (fd == -1)
+
387 return -errno;
+
388
+
389 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
390 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
391 for writes to the same file). */
+
392 if (fi->flags & O_DIRECT) {
+
393 fi->direct_io = 1;
+ +
395 }
+
396
+
397 fi->fh = fd;
+
398 return 0;
+
399}
+
400
+
401static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
402 struct fuse_file_info *fi)
+
403{
+
404 int res;
+
405
+
406 (void) path;
+
407 res = pread(fi->fh, buf, size, offset);
+
408 if (res == -1)
+
409 res = -errno;
+
410
+
411 return res;
+
412}
+
413
+
414static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
415 size_t size, off_t offset, struct fuse_file_info *fi)
+
416{
+
417 struct fuse_bufvec *src;
+
418
+
419 (void) path;
+
420
+
421 src = malloc(sizeof(struct fuse_bufvec));
+
422 if (src == NULL)
+
423 return -ENOMEM;
+
424
+
425 *src = FUSE_BUFVEC_INIT(size);
+
426
+ +
428 src->buf[0].fd = fi->fh;
+
429 src->buf[0].pos = offset;
+
430
+
431 *bufp = src;
+
432
+
433 return 0;
+
434}
+
435
+
436static int xmp_write(const char *path, const char *buf, size_t size,
+
437 off_t offset, struct fuse_file_info *fi)
+
438{
+
439 int res;
+
440
+
441 (void) path;
+
442 res = pwrite(fi->fh, buf, size, offset);
+
443 if (res == -1)
+
444 res = -errno;
+
445
+
446 return res;
+
447}
+
448
+
449static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
450 off_t offset, struct fuse_file_info *fi)
+
451{
+
452 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
453
+
454 (void) path;
+
455
+ +
457 dst.buf[0].fd = fi->fh;
+
458 dst.buf[0].pos = offset;
+
459
+ +
461}
+
462
+
463static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
464{
+
465 int res;
+
466
+
467 res = statvfs(path, stbuf);
+
468 if (res == -1)
+
469 return -errno;
+
470
+
471 return 0;
+
472}
+
473
+
474static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
475{
+
476 int res;
+
477
+
478 (void) path;
+
479 /* This is called from every close on an open file, so call the
+
480 close on the underlying filesystem. But since flush may be
+
481 called multiple times for an open file, this must not really
+
482 close the file. This is important if used on a network
+
483 filesystem like NFS which flush the data/metadata on close() */
+
484 res = close(dup(fi->fh));
+
485 if (res == -1)
+
486 return -errno;
+
487
+
488 return 0;
+
489}
+
490
+
491static int xmp_release(const char *path, struct fuse_file_info *fi)
+
492{
+
493 (void) path;
+
494 close(fi->fh);
+
495
+
496 return 0;
+
497}
+
498
+
499static int xmp_fsync(const char *path, int isdatasync,
+
500 struct fuse_file_info *fi)
+
501{
+
502 int res;
+
503 (void) path;
+
504
+
505#ifndef HAVE_FDATASYNC
+
506 (void) isdatasync;
+
507#else
+
508 if (isdatasync)
+
509 res = fdatasync(fi->fh);
+
510 else
+
511#endif
+
512 res = fsync(fi->fh);
+
513 if (res == -1)
+
514 return -errno;
+
515
+
516 return 0;
+
517}
+
518
+
519static int xmp_fallocate(const char *path, int mode,
+
520 off_t offset, off_t length, struct fuse_file_info *fi)
+
521{
+
522 (void) path;
+
523
+
524 return do_fallocate(fi->fh, mode, offset, length);
+
525}
+
526
+
527#ifdef HAVE_SETXATTR
+
528/* xattr operations are optional and can safely be left unimplemented */
+
529static int xmp_setxattr(const char *path, const char *name, const char *value,
+
530 size_t size, int flags)
+
531{
+
532 int res = lsetxattr(path, name, value, size, flags);
+
533 if (res == -1)
+
534 return -errno;
+
535 return 0;
+
536}
+
537
+
538static int xmp_getxattr(const char *path, const char *name, char *value,
+
539 size_t size)
+
540{
+
541 int res = lgetxattr(path, name, value, size);
+
542 if (res == -1)
+
543 return -errno;
+
544 return res;
+
545}
+
546
+
547static int xmp_listxattr(const char *path, char *list, size_t size)
+
548{
+
549 int res = llistxattr(path, list, size);
+
550 if (res == -1)
+
551 return -errno;
+
552 return res;
+
553}
+
554
+
555static int xmp_removexattr(const char *path, const char *name)
+
556{
+
557 int res = lremovexattr(path, name);
+
558 if (res == -1)
+
559 return -errno;
+
560 return 0;
+
561}
+
562#endif /* HAVE_SETXATTR */
+
563
+
564#ifdef HAVE_LIBULOCKMGR
+
565static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
566 struct flock *lock)
+
567{
+
568 (void) path;
+
569
+
570 return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
571 sizeof(fi->lock_owner));
+
572}
+
573#endif
+
574
+
575static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
576{
+
577 int res;
+
578 (void) path;
+
579
+
580 res = flock(fi->fh, op);
+
581 if (res == -1)
+
582 return -errno;
+
583
+
584 return 0;
+
585}
+
586
+
587#ifdef HAVE_COPY_FILE_RANGE
+
588static ssize_t xmp_copy_file_range(const char *path_in,
+
589 struct fuse_file_info *fi_in,
+
590 off_t off_in, const char *path_out,
+
591 struct fuse_file_info *fi_out,
+
592 off_t off_out, size_t len, int flags)
+
593{
+
594 ssize_t res;
+
595 (void) path_in;
+
596 (void) path_out;
+
597
+
598 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
599 flags);
+
600 if (res == -1)
+
601 return -errno;
+
602
+
603 return res;
+
604}
+
605#endif
+
606
+
607static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
608{
+
609 off_t res;
+
610 (void) path;
+
611
+
612 res = lseek(fi->fh, off, whence);
+
613 if (res == -1)
+
614 return -errno;
+
615
+
616 return res;
+
617}
+
618
+
619#ifdef HAVE_STATX
+
620static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
621 struct fuse_file_info *fi)
+
622{
+
623 int fd = -1;
+
624 int res;
+
625
+
626 if (fi)
+
627 fd = fi->fh;
+
628
+
629 res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
+
630 if (res == -1)
+
631 return -errno;
+
632
+
633 return 0;
+
634}
+
635#endif
+
636
+
637static const struct fuse_operations xmp_oper = {
+
638 .init = xmp_init,
+
639 .getattr = xmp_getattr,
+
640 .access = xmp_access,
+
641 .readlink = xmp_readlink,
+
642 .opendir = xmp_opendir,
+
643 .readdir = xmp_readdir,
+
644 .releasedir = xmp_releasedir,
+
645 .mknod = xmp_mknod,
+
646 .mkdir = xmp_mkdir,
+
647 .symlink = xmp_symlink,
+
648 .unlink = xmp_unlink,
+
649 .rmdir = xmp_rmdir,
+
650 .rename = xmp_rename,
+
651 .link = xmp_link,
+
652 .chmod = xmp_chmod,
+
653 .chown = xmp_chown,
+
654 .truncate = xmp_truncate,
+
655#ifdef HAVE_UTIMENSAT
+
656 .utimens = xmp_utimens,
+
657#endif
+
658 .create = xmp_create,
+
659 .open = xmp_open,
+
660 .read = xmp_read,
+
661 .read_buf = xmp_read_buf,
+
662 .write = xmp_write,
+
663 .write_buf = xmp_write_buf,
+
664 .statfs = xmp_statfs,
+
665 .flush = xmp_flush,
+
666 .release = xmp_release,
+
667 .fsync = xmp_fsync,
+
668 .fallocate = xmp_fallocate,
+
669#ifdef HAVE_SETXATTR
+
670 .setxattr = xmp_setxattr,
+
671 .getxattr = xmp_getxattr,
+
672 .listxattr = xmp_listxattr,
+
673 .removexattr = xmp_removexattr,
+
674#endif
+
675#ifdef HAVE_LIBULOCKMGR
+
676 .lock = xmp_lock,
+
677#endif
+
678 .flock = xmp_flock,
+
679#ifdef HAVE_COPY_FILE_RANGE
+
680 .copy_file_range = xmp_copy_file_range,
+
681#endif
+
682 .lseek = xmp_lseek,
+
683#ifdef HAVE_STATX
+
684 .statx = xmp_statx,
+
685#endif
+
686};
+
687
+
688int main(int argc, char *argv[])
+
689{
+
690 umask(0);
+
691 return fuse_main(argc, argv, &xmp_oper, NULL);
+
692}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2passthrough__helpers_8h_source.html b/doc/html/fuse-3_818_81_2example_2passthrough__helpers_8h_source.html new file mode 100644 index 0000000..1eda9ff --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2passthrough__helpers_8h_source.html @@ -0,0 +1,182 @@ + + + + + + + +libfuse: fuse-3.18.1/example/passthrough_helpers.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_helpers.h
+
+
+
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 *
+
4 * Redistribution and use in source and binary forms, with or without
+
5 * modification, are permitted provided that the following conditions
+
6 * are met:
+
7 * 1. Redistributions of source code must retain the above copyright
+
8 * notice, this list of conditions and the following disclaimer.
+
9 * 2. Redistributions in binary form must reproduce the above copyright
+
10 * notice, this list of conditions and the following disclaimer in the
+
11 * documentation and/or other materials provided with the distribution.
+
12 *
+
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
23 * SUCH DAMAGE
+
24 */
+
25
+
26#ifndef FUSE_EXAMPLE_PASSTHROUGH_HELPERS_H_
+
27#define FUSE_EXAMPLE_PASSTHROUGH_HELPERS_H_
+
28
+
29#include <errno.h>
+
30#include <fcntl.h>
+
31#include <string.h>
+
32#include <sys/stat.h>
+
33#include <unistd.h>
+
34
+
35#ifdef __FreeBSD__
+
36#include <sys/socket.h>
+
37#include <sys/un.h>
+
38#endif
+
39
+
40static inline int do_fallocate(int fd, int mode, off_t offset, off_t length)
+
41{
+
42#ifdef HAVE_FALLOCATE
+
43 if (fallocate(fd, mode, offset, length) == -1)
+
44 return -errno;
+
45 return 0;
+
46#else // HAVE_FALLOCATE
+
47
+
48#ifdef HAVE_POSIX_FALLOCATE
+
49 if (mode == 0)
+
50 return -posix_fallocate(fd, offset, length);
+
51#endif
+
52
+
53#ifdef HAVE_FSPACECTL
+
54 // 0x3 == FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE
+
55 if (mode == 0x3) {
+
56 struct spacectl_range sr;
+
57
+
58 sr.r_offset = offset;
+
59 sr.r_len = length;
+
60 if (fspacectl(fd, SPACECTL_DEALLOC, &sr, 0, NULL) == -1)
+
61 return -errno;
+
62 return 0;
+
63 }
+
64#endif
+
65
+
66 return -EOPNOTSUPP;
+
67#endif // HAVE_FALLOCATE
+
68}
+
69
+
70/*
+
71 * Creates files on the underlying file system in response to a FUSE_MKNOD
+
72 * operation
+
73 */
+
74static inline int mknod_wrapper(int dirfd, const char *path, const char *link,
+
75 int mode, dev_t rdev)
+
76{
+
77 int res;
+
78
+
79 if (S_ISREG(mode)) {
+
80 res = openat(dirfd, path, O_CREAT | O_EXCL | O_WRONLY, mode);
+
81 if (res >= 0)
+
82 res = close(res);
+
83 } else if (S_ISDIR(mode)) {
+
84 res = mkdirat(dirfd, path, mode);
+
85 } else if (S_ISLNK(mode) && link != NULL) {
+
86 res = symlinkat(link, dirfd, path);
+
87 } else if (S_ISFIFO(mode)) {
+
88 res = mkfifoat(dirfd, path, mode);
+
89#ifdef __FreeBSD__
+
90 } else if (S_ISSOCK(mode)) {
+
91 struct sockaddr_un su;
+
92 int fd;
+
93
+
94 if (strlen(path) >= sizeof(su.sun_path)) {
+
95 errno = ENAMETOOLONG;
+
96 return -1;
+
97 }
+
98 fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
99 if (fd >= 0) {
+
100 /*
+
101 * We must bind the socket to the underlying file
+
102 * system to create the socket file, even though
+
103 * we'll never listen on this socket.
+
104 */
+
105 su.sun_family = AF_UNIX;
+
106 strncpy(su.sun_path, path, sizeof(su.sun_path));
+
107 res = bindat(dirfd, fd, (struct sockaddr*)&su,
+
108 sizeof(su));
+
109 if (res == 0)
+
110 close(fd);
+
111 } else {
+
112 res = -1;
+
113 }
+
114#endif
+
115 } else {
+
116 res = mknodat(dirfd, path, mode, rdev);
+
117 }
+
118
+
119 return res;
+
120}
+
121
+
122#endif // FUSE_PASSTHROUGH_HELPERS_H_
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2passthrough__ll_8c.html b/doc/html/fuse-3_818_81_2example_2passthrough__ll_8c.html new file mode 100644 index 0000000..6c2763f --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2passthrough__ll_8c.html @@ -0,0 +1,1546 @@ + + + + + + + +libfuse: fuse-3.18.1/example/passthrough_ll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough_ll.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <sys/file.h>
+#include <sys/xattr.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. In contrast to passthrough.c and passthrough_fh.c, this implementation uses the low-level API. Its performance should be the least bad among the three.

+

When writeback caching is enabled (-o writeback mount option), it is only possible to write to files for which the mounting user has read permissions. This is because the writeback cache requires the kernel to be able to issue read requests for all files (which the passthrough filesystem cannot satisfy if it can't read the file in the underlying filesystem).

+

Compile with:

gcc -Wall passthrough_ll.c `pkg-config fuse3 --cflags --libs` -o passthrough_ll
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define _GNU_SOURCE
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
+
#include <fuse_lowlevel.h>
+
#include <unistd.h>
+
#include <stdlib.h>
+
#include <stdio.h>
+
#include <stddef.h>
+
#include <stdbool.h>
+
#include <string.h>
+
#include <limits.h>
+
#include <dirent.h>
+
#include <assert.h>
+
#include <errno.h>
+
#include <inttypes.h>
+
#include <pthread.h>
+
#include <sys/file.h>
+
#include <sys/xattr.h>
+
+
#include "passthrough_helpers.h"
+
+
/* We are re-using pointers to our `struct lo_inode` and `struct
+
lo_dirp` elements as inodes. This means that we must be able to
+
store uintptr_t values in a fuse_ino_t variable. The following
+
incantation checks this condition at compile time. */
+
#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
+
_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
+
"fuse_ino_t too small to hold uintptr_t values!");
+
#else
+
struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
+
{ unsigned _uintptr_to_must_hold_fuse_ino_t:
+
((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
+
#endif
+
+
struct lo_inode {
+
struct lo_inode *next; /* protected by lo->mutex */
+
struct lo_inode *prev; /* protected by lo->mutex */
+
int fd;
+
ino_t ino;
+
dev_t dev;
+
uint64_t refcount; /* protected by lo->mutex */
+
};
+
+
enum {
+
CACHE_NEVER,
+
CACHE_NORMAL,
+
CACHE_ALWAYS,
+
};
+
+
struct lo_data {
+
pthread_mutex_t mutex;
+
int debug;
+
int writeback;
+
int flock;
+
int xattr;
+
char *source;
+
double timeout;
+
int cache;
+
int timeout_set;
+
struct lo_inode root; /* protected by lo->mutex */
+
};
+
+
static const struct fuse_opt lo_opts[] = {
+
{ "writeback",
+
offsetof(struct lo_data, writeback), 1 },
+
{ "no_writeback",
+
offsetof(struct lo_data, writeback), 0 },
+
{ "source=%s",
+
offsetof(struct lo_data, source), 0 },
+
{ "flock",
+
offsetof(struct lo_data, flock), 1 },
+
{ "no_flock",
+
offsetof(struct lo_data, flock), 0 },
+
{ "xattr",
+
offsetof(struct lo_data, xattr), 1 },
+
{ "no_xattr",
+
offsetof(struct lo_data, xattr), 0 },
+
{ "timeout=%lf",
+
offsetof(struct lo_data, timeout), 0 },
+
{ "timeout=",
+
offsetof(struct lo_data, timeout_set), 1 },
+
{ "cache=never",
+
offsetof(struct lo_data, cache), CACHE_NEVER },
+
{ "cache=auto",
+
offsetof(struct lo_data, cache), CACHE_NORMAL },
+
{ "cache=always",
+
offsetof(struct lo_data, cache), CACHE_ALWAYS },
+
+ +
};
+
+
static void passthrough_ll_help(void)
+
{
+
printf(
+
" -o writeback Enable writeback\n"
+
" -o no_writeback Disable write back\n"
+
" -o source=/home/dir Source directory to be mounted\n"
+
" -o flock Enable flock\n"
+
" -o no_flock Disable flock\n"
+
" -o xattr Enable xattr\n"
+
" -o no_xattr Disable xattr\n"
+
" -o timeout=1.0 Caching timeout\n"
+
" -o timeout=0/1 Timeout is set\n"
+
" -o cache=never Disable cache\n"
+
" -o cache=auto Auto enable cache\n"
+
" -o cache=always Cache always\n");
+
}
+
+
static struct lo_data *lo_data(fuse_req_t req)
+
{
+
return (struct lo_data *) fuse_req_userdata(req);
+
}
+
+
static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
+
{
+
if (ino == FUSE_ROOT_ID)
+
return &lo_data(req)->root;
+
else
+
return (struct lo_inode *) (uintptr_t) ino;
+
}
+
+
static int lo_fd(fuse_req_t req, fuse_ino_t ino)
+
{
+
return lo_inode(req, ino)->fd;
+
}
+
+
static bool lo_debug(fuse_req_t req)
+
{
+
return lo_data(req)->debug != 0;
+
}
+
+
static void lo_init(void *userdata,
+
struct fuse_conn_info *conn)
+
{
+
struct lo_data *lo = (struct lo_data *)userdata;
+
bool has_flag;
+
+
if (lo->writeback) {
+ +
if (lo->debug && has_flag)
+
fuse_log(FUSE_LOG_DEBUG,
+
"lo_init: activating writeback\n");
+
}
+
if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
+ +
if (lo->debug && has_flag)
+
fuse_log(FUSE_LOG_DEBUG,
+
"lo_init: activating flock locks\n");
+
}
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void lo_destroy(void *userdata)
+
{
+
struct lo_data *lo = (struct lo_data*) userdata;
+
+
while (lo->root.next != &lo->root) {
+
struct lo_inode* next = lo->root.next;
+
lo->root.next = next->next;
+
close(next->fd);
+
free(next);
+
}
+
}
+
+
static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
struct stat buf;
+
struct lo_data *lo = lo_data(req);
+
int fd = fi ? fi->fh : lo_fd(req, ino);
+
+
(void) fi;
+
+
res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fuse_reply_attr(req, &buf, lo->timeout);
+
}
+
+
static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
int valid, struct fuse_file_info *fi)
+
{
+
int saverr;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
int ifd = inode->fd;
+
int res;
+
+
if (valid & FUSE_SET_ATTR_MODE) {
+
if (fi) {
+
res = fchmod(fi->fh, attr->st_mode);
+
} else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = chmod(procname, attr->st_mode);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+
uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
attr->st_uid : (uid_t) -1;
+
gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
attr->st_gid : (gid_t) -1;
+
+
res = fchownat(ifd, "", uid, gid,
+
AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & FUSE_SET_ATTR_SIZE) {
+
if (fi) {
+
res = ftruncate(fi->fh, attr->st_size);
+
} else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = truncate(procname, attr->st_size);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
struct timespec tv[2];
+
+
tv[0].tv_sec = 0;
+
tv[1].tv_sec = 0;
+
tv[0].tv_nsec = UTIME_OMIT;
+
tv[1].tv_nsec = UTIME_OMIT;
+
+
if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
tv[0].tv_nsec = UTIME_NOW;
+
else if (valid & FUSE_SET_ATTR_ATIME)
+
tv[0] = attr->st_atim;
+
+
if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
tv[1].tv_nsec = UTIME_NOW;
+
else if (valid & FUSE_SET_ATTR_MTIME)
+
tv[1] = attr->st_mtim;
+
+
if (fi)
+
res = futimens(fi->fh, tv);
+
else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = utimensat(AT_FDCWD, procname, tv, 0);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
+
return lo_getattr(req, ino, fi);
+
+
out_err:
+
saverr = errno;
+
fuse_reply_err(req, saverr);
+
}
+
+
static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+
{
+
struct lo_inode *p;
+
struct lo_inode *ret = NULL;
+
+
pthread_mutex_lock(&lo->mutex);
+
for (p = lo->root.next; p != &lo->root; p = p->next) {
+
if (p->ino == st->st_ino && p->dev == st->st_dev) {
+
assert(p->refcount > 0);
+
ret = p;
+
ret->refcount++;
+
break;
+
}
+
}
+
pthread_mutex_unlock(&lo->mutex);
+
return ret;
+
}
+
+
+
static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
+
{
+
struct lo_inode *inode = NULL;
+
struct lo_inode *prev, *next;
+
+
inode = calloc(1, sizeof(struct lo_inode));
+
if (!inode)
+
return NULL;
+
+
inode->refcount = 1;
+
inode->fd = fd;
+
inode->ino = e->attr.st_ino;
+
inode->dev = e->attr.st_dev;
+
+
pthread_mutex_lock(&lo->mutex);
+
prev = &lo->root;
+
next = prev->next;
+
next->prev = inode;
+
inode->next = next;
+
inode->prev = prev;
+
prev->next = inode;
+
pthread_mutex_unlock(&lo->mutex);
+
return inode;
+
}
+
+
static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
+
{
+
int res;
+
struct lo_data *lo = lo_data(req);
+
+
memset(e, 0, sizeof(*e));
+
e->attr_timeout = lo->timeout;
+
e->entry_timeout = lo->timeout;
+
+
res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return errno;
+
+
e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%d -> %lli\n",
+
(unsigned long long) parent, fd, (unsigned long long) e->ino);
+
+
return 0;
+
+
}
+
+
static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
+
struct fuse_entry_param *e)
+
{
+
int newfd;
+
int res;
+
int saverr;
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode;
+
+
memset(e, 0, sizeof(*e));
+
e->attr_timeout = lo->timeout;
+
e->entry_timeout = lo->timeout;
+
+
newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
+
if (newfd == -1)
+
goto out_err;
+
+
res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
inode = lo_find(lo_data(req), &e->attr);
+
if (inode) {
+
close(newfd);
+
newfd = -1;
+
} else {
+
inode = create_new_inode(newfd, e, lo);
+
if (!inode)
+
goto out_err;
+
}
+
e->ino = (uintptr_t) inode;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name, (unsigned long long) e->ino);
+
+
return 0;
+
+
out_err:
+
saverr = errno;
+
if (newfd != -1)
+
close(newfd);
+
return saverr;
+
}
+
+
static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
+
parent, name);
+
+
err = lo_do_lookup(req, parent, name, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_entry(req, &e);
+
}
+
+
static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
+
const char *name, mode_t mode, dev_t rdev,
+
const char *link)
+
{
+
int res;
+
int saverr;
+
struct lo_inode *dir = lo_inode(req, parent);
+
struct fuse_entry_param e;
+
+
res = mknod_wrapper(dir->fd, name, link, mode, rdev);
+
+
saverr = errno;
+
if (res == -1)
+
goto out;
+
+
saverr = lo_do_lookup(req, parent, name, &e);
+
if (saverr)
+
goto out;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name, (unsigned long long) e.ino);
+
+
fuse_reply_entry(req, &e);
+
return;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
+
const char *name, mode_t mode, dev_t rdev)
+
{
+
lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
+
}
+
+
static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
mode_t mode)
+
{
+
lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
+
}
+
+
static void lo_symlink(fuse_req_t req, const char *link,
+
fuse_ino_t parent, const char *name)
+
{
+
lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
+
}
+
+
static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
+
const char *name)
+
{
+
int res;
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode = lo_inode(req, ino);
+
struct fuse_entry_param e;
+
char procname[64];
+
int saverr;
+
+
memset(&e, 0, sizeof(struct fuse_entry_param));
+
e.attr_timeout = lo->timeout;
+
e.entry_timeout = lo->timeout;
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
+
AT_SYMLINK_FOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
pthread_mutex_lock(&lo->mutex);
+
inode->refcount++;
+
pthread_mutex_unlock(&lo->mutex);
+
e.ino = (uintptr_t) inode;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name,
+
(unsigned long long) e.ino);
+
+
fuse_reply_entry(req, &e);
+
return;
+
+
out_err:
+
saverr = errno;
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
int res;
+
+
res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
+
fuse_ino_t newparent, const char *newname,
+
unsigned int flags)
+
{
+
int res;
+
+
if (flags) {
+
fuse_reply_err(req, EINVAL);
+
return;
+
}
+
+
res = renameat(lo_fd(req, parent), name,
+
lo_fd(req, newparent), newname);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
int res;
+
+
res = unlinkat(lo_fd(req, parent), name, 0);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
+
{
+
if (!inode)
+
return;
+
+
pthread_mutex_lock(&lo->mutex);
+
assert(inode->refcount >= n);
+
inode->refcount -= n;
+
if (!inode->refcount) {
+
struct lo_inode *prev, *next;
+
+
prev = inode->prev;
+
next = inode->next;
+
next->prev = prev;
+
prev->next = next;
+
+
pthread_mutex_unlock(&lo->mutex);
+
close(inode->fd);
+
free(inode);
+
+
} else {
+
pthread_mutex_unlock(&lo->mutex);
+
}
+
}
+
+
static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
{
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode = lo_inode(req, ino);
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
+
(unsigned long long) ino,
+
(unsigned long long) inode->refcount,
+
(unsigned long long) nlookup);
+
}
+
+
unref_inode(lo, inode, nlookup);
+
}
+
+
static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
{
+
lo_forget_one(req, ino, nlookup);
+ +
}
+
+
static void lo_forget_multi(fuse_req_t req, size_t count,
+
struct fuse_forget_data *forgets)
+
{
+
int i;
+
+
for (i = 0; i < count; i++)
+
lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
+ +
}
+
+
static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
+
{
+
char buf[PATH_MAX + 1];
+
int res;
+
+
res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
+
if (res == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
if (res == sizeof(buf))
+
return (void) fuse_reply_err(req, ENAMETOOLONG);
+
+
buf[res] = '\0';
+
+ +
}
+
+
struct lo_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
+
{
+
return (struct lo_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int error = ENOMEM;
+
struct lo_data *lo = lo_data(req);
+
struct lo_dirp *d;
+
int fd = -1;
+
+
d = calloc(1, sizeof(struct lo_dirp));
+
if (d == NULL)
+
goto out_err;
+
+
fd = openat(lo_fd(req, ino), ".", O_RDONLY);
+
if (fd == -1)
+
goto out_errno;
+
+
d->dp = fdopendir(fd);
+
if (d->dp == NULL)
+
goto out_errno;
+
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (uintptr_t) d;
+
if (lo->cache != CACHE_NEVER)
+
fi->cache_readdir = 1;
+
if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
fuse_reply_open(req, fi);
+
return;
+
+
out_errno:
+
error = errno;
+
out_err:
+
if (d) {
+
if (fd != -1)
+
close(fd);
+
free(d);
+
}
+
fuse_reply_err(req, error);
+
}
+
+
static int is_dot_or_dotdot(const char *name)
+
{
+
return name[0] == '.' && (name[1] == '\0' ||
+
(name[1] == '.' && name[2] == '\0'));
+
}
+
+
static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi, int plus)
+
{
+
struct lo_dirp *d = lo_dirp(fi);
+
char *buf;
+
char *p;
+
size_t rem = size;
+
int err;
+
+
(void) ino;
+
+
buf = calloc(1, size);
+
if (!buf) {
+
err = ENOMEM;
+
goto error;
+
}
+
p = buf;
+
+
if (offset != d->offset) {
+
seekdir(d->dp, offset);
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
size_t entsize;
+
off_t nextoff;
+
const char *name;
+
+
if (!d->entry) {
+
errno = 0;
+
d->entry = readdir(d->dp);
+
if (!d->entry) {
+
if (errno) { // Error
+
err = errno;
+
goto error;
+
} else { // End of stream
+
break;
+
}
+
}
+
}
+
nextoff = d->entry->d_off;
+
name = d->entry->d_name;
+
fuse_ino_t entry_ino = 0;
+
if (plus) {
+
struct fuse_entry_param e;
+
if (is_dot_or_dotdot(name)) {
+
e = (struct fuse_entry_param) {
+
.attr.st_ino = d->entry->d_ino,
+
.attr.st_mode = d->entry->d_type << 12,
+
};
+
} else {
+
err = lo_do_lookup(req, ino, name, &e);
+
if (err)
+
goto error;
+
entry_ino = e.ino;
+
}
+
+
entsize = fuse_add_direntry_plus(req, p, rem, name,
+
&e, nextoff);
+
} else {
+
struct stat st = {
+
.st_ino = d->entry->d_ino,
+
.st_mode = d->entry->d_type << 12,
+
};
+
entsize = fuse_add_direntry(req, p, rem, name,
+
&st, nextoff);
+
}
+
if (entsize > rem) {
+
if (entry_ino != 0)
+
lo_forget_one(req, entry_ino, 1);
+
break;
+
}
+
+
p += entsize;
+
rem -= entsize;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
err = 0;
+
error:
+
// If there's an error, we can only signal it if we haven't stored
+
// any entries yet - otherwise we'd end up with wrong lookup
+
// counts for the entries that are already in the buffer. So we
+
// return what we've collected until that point.
+
if (err && rem == size)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_buf(req, buf, size - rem);
+
free(buf);
+
}
+
+
static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
lo_do_readdir(req, ino, size, offset, fi, 0);
+
}
+
+
static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
lo_do_readdir(req, ino, size, offset, fi, 1);
+
}
+
+
static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
struct lo_dirp *d = lo_dirp(fi);
+
(void) ino;
+
closedir(d->dp);
+
free(d);
+
fuse_reply_err(req, 0);
+
}
+
+
static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
+
mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
struct lo_data *lo = lo_data(req);
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
+
parent);
+
+
fd = openat(lo_fd(req, parent), ".",
+
(fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
err = fill_entry_param_new_inode(req, parent, fd, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_create(req, &e, fi);
+
}
+
+
static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+
mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
struct lo_data *lo = lo_data(req);
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
+
parent, name);
+
+
fd = openat(lo_fd(req, parent), name,
+
(fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
err = lo_do_lookup(req, parent, name, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_create(req, &e, fi);
+
}
+
+
static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
int fd = dirfd(lo_dirp(fi)->dp);
+
(void) ino;
+
if (datasync)
+
res = fdatasync(fd);
+
else
+
res = fsync(fd);
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int fd;
+
char buf[64];
+
struct lo_data *lo = lo_data(req);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
+
ino, fi->flags);
+
+
/* With writeback cache, kernel may send read requests even
+
when userspace opened write-only */
+
if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
+
fi->flags &= ~O_ACCMODE;
+
fi->flags |= O_RDWR;
+
}
+
+
/* With writeback cache, O_APPEND is handled by the kernel.
+
This breaks atomicity (since the file may change in the
+
underlying filesystem, so that the kernel's idea of the
+
end of the file isn't accurate anymore). In this example,
+
we just accept that. A more rigorous filesystem may want
+
to return an error here */
+
if (lo->writeback && (fi->flags & O_APPEND))
+
fi->flags &= ~O_APPEND;
+
+
sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
+
fd = open(buf, fi->flags & ~O_NOFOLLOW);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file in the kernel). */
+
if (fi->flags & O_DIRECT)
+
fi->direct_io = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
fuse_reply_open(req, fi);
+
}
+
+
static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
(void) ino;
+
+
close(fi->fh);
+
fuse_reply_err(req, 0);
+
}
+
+
static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int res;
+
(void) ino;
+
res = close(dup(fi->fh));
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) ino;
+
if (datasync)
+
res = fdatasync(fi->fh);
+
else
+
res = fsync(fi->fh);
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
+
"off=%lu)\n", ino, size, (unsigned long) offset);
+
+ +
buf.buf[0].fd = fi->fh;
+
buf.buf[0].pos = offset;
+
+ +
}
+
+
static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_bufvec *in_buf, off_t off,
+
struct fuse_file_info *fi)
+
{
+
(void) ino;
+
ssize_t res;
+
struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
+
+ +
out_buf.buf[0].fd = fi->fh;
+
out_buf.buf[0].pos = off;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%jd)\n",
+
ino, out_buf.buf[0].size, (intmax_t) off);
+
+
res = fuse_buf_copy(&out_buf, in_buf, 0);
+
if(res < 0)
+
fuse_reply_err(req, -res);
+
else
+
fuse_reply_write(req, (size_t) res);
+
}
+
+
static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
+
{
+
int res;
+
struct statvfs stbuf;
+
+
res = fstatvfs(lo_fd(req, ino), &stbuf);
+
if (res == -1)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_statfs(req, &stbuf);
+
}
+
+
static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
int err;
+
(void) ino;
+
+
err = -do_fallocate(fi->fh, mode, offset, length);
+
+
fuse_reply_err(req, err);
+
}
+
+
static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
int op)
+
{
+
int res;
+
(void) ino;
+
+
res = flock(fi->fh, op);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
char *value = NULL;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
+
ino, name, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
if (size) {
+
value = malloc(size);
+
if (!value)
+
goto out_err;
+
+
ret = getxattr(procname, name, value, size);
+
if (ret == -1)
+
goto out_err;
+
saverr = 0;
+
if (ret == 0)
+
goto out;
+
+
fuse_reply_buf(req, value, ret);
+
} else {
+
ret = getxattr(procname, name, NULL, 0);
+
if (ret == -1)
+
goto out_err;
+
+
fuse_reply_xattr(req, ret);
+
}
+
out_free:
+
free(value);
+
return;
+
+
out_err:
+
saverr = errno;
+
out:
+
fuse_reply_err(req, saverr);
+
goto out_free;
+
}
+
+
static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
{
+
char *value = NULL;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
+
ino, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
if (size) {
+
value = malloc(size);
+
if (!value)
+
goto out_err;
+
+
ret = listxattr(procname, value, size);
+
if (ret == -1)
+
goto out_err;
+
saverr = 0;
+
if (ret == 0)
+
goto out;
+
+
fuse_reply_buf(req, value, ret);
+
} else {
+
ret = listxattr(procname, NULL, 0);
+
if (ret == -1)
+
goto out_err;
+
+
fuse_reply_xattr(req, ret);
+
}
+
out_free:
+
free(value);
+
return;
+
+
out_err:
+
saverr = errno;
+
out:
+
fuse_reply_err(req, saverr);
+
goto out_free;
+
}
+
+
static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
+
ino, name, value, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
ret = setxattr(procname, name, value, size, flags);
+
saverr = ret == -1 ? errno : 0;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
+
ino, name);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
ret = removexattr(procname, name);
+
saverr = ret == -1 ? errno : 0;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
+
struct fuse_file_info *fi_in,
+
fuse_ino_t ino_out, off_t off_out,
+
struct fuse_file_info *fi_out, size_t len,
+
int flags)
+
{
+
ssize_t res;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG,
+
"%s(ino=%lld fd=%lld off=%jd ino=%lld fd=%lld off=%jd, size=%zd, flags=0x%x)\n",
+
__func__, (unsigned long long)ino_in,
+
(unsigned long long)fi_in->fh,
+
(intmax_t) off_in, (unsigned long long)ino_out,
+
(unsigned long long)fi_out->fh, (intmax_t) off_out,
+
len, flags);
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res < 0)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_write(req, res);
+
}
+
#endif
+
+
static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
struct fuse_file_info *fi)
+
{
+
off_t res;
+
+
(void)ino;
+
res = lseek(fi->fh, off, whence);
+
if (res != -1)
+
fuse_reply_lseek(req, res);
+
else
+
fuse_reply_err(req, errno);
+
}
+
+
#ifdef HAVE_STATX
+
static void lo_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
+
struct fuse_file_info *fi)
+
{
+
struct lo_data *lo = lo_data(req);
+
struct statx buf;
+
int res;
+
int fd;
+
+
if (fi)
+
fd = fi->fh;
+
else
+
fd = lo_fd(req, ino);
+
+
res = statx(fd, "", flags | AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, mask, &buf);
+
if (res == -1)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_statx(req, 0, &buf, lo->timeout);
+
}
+
#endif
+
+
static const struct fuse_lowlevel_ops lo_oper = {
+
.init = lo_init,
+
.destroy = lo_destroy,
+
.lookup = lo_lookup,
+
.mkdir = lo_mkdir,
+
.mknod = lo_mknod,
+
.symlink = lo_symlink,
+
.link = lo_link,
+
.unlink = lo_unlink,
+
.rmdir = lo_rmdir,
+
.rename = lo_rename,
+
.forget = lo_forget,
+
.forget_multi = lo_forget_multi,
+
.getattr = lo_getattr,
+
.setattr = lo_setattr,
+
.readlink = lo_readlink,
+
.opendir = lo_opendir,
+
.readdir = lo_readdir,
+
.readdirplus = lo_readdirplus,
+
.releasedir = lo_releasedir,
+
.fsyncdir = lo_fsyncdir,
+
.create = lo_create,
+
.tmpfile = lo_tmpfile,
+
.open = lo_open,
+
.release = lo_release,
+
.flush = lo_flush,
+
.fsync = lo_fsync,
+
.read = lo_read,
+
.write_buf = lo_write_buf,
+
.statfs = lo_statfs,
+
.fallocate = lo_fallocate,
+
.flock = lo_flock,
+
.getxattr = lo_getxattr,
+
.listxattr = lo_listxattr,
+
.setxattr = lo_setxattr,
+
.removexattr = lo_removexattr,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = lo_copy_file_range,
+
#endif
+
.lseek = lo_lseek,
+
#ifdef HAVE_STATX
+
.statx = lo_statx,
+
#endif
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
struct lo_data lo = { .debug = 0,
+
.writeback = 0 };
+
int ret = -1;
+
+
/* Don't mask creation mode, kernel already did that */
+
umask(0);
+
+
pthread_mutex_init(&lo.mutex, NULL);
+
lo.root.next = lo.root.prev = &lo.root;
+
lo.root.fd = -1;
+
lo.cache = CACHE_NORMAL;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
passthrough_ll_help();
+
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
+
return 1;
+
+
lo.debug = opts.debug;
+
lo.root.refcount = 2;
+
if (lo.source) {
+
struct stat stat;
+
int res;
+
+
res = lstat(lo.source, &stat);
+
if (res == -1) {
+
fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
+
lo.source);
+
exit(1);
+
}
+
if (!S_ISDIR(stat.st_mode)) {
+
fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
+
exit(1);
+
}
+
+
} else {
+
lo.source = strdup("/");
+
if(!lo.source) {
+
fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
exit(1);
+
}
+
}
+
if (!lo.timeout_set) {
+
switch (lo.cache) {
+
case CACHE_NEVER:
+
lo.timeout = 0.0;
+
break;
+
+
case CACHE_NORMAL:
+
lo.timeout = 1.0;
+
break;
+
+
case CACHE_ALWAYS:
+
lo.timeout = 86400.0;
+
break;
+
}
+
} else if (lo.timeout < 0) {
+
fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
+
lo.timeout);
+
exit(1);
+
}
+
+
lo.root.fd = open(lo.source, O_PATH);
+
if (lo.root.fd == -1) {
+
fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
+
lo.source);
+
exit(1);
+
}
+
+
se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
if (lo.root.fd >= 0)
+
close(lo.root.fd);
+
+
free(lo.source);
+
return ret ? 1 : 0;
+
}
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
off_t pos
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
uint32_t capable
+
+
double entry_timeout
+
fuse_ino_t ino
+
double attr_timeout
+
struct stat attr
+ + +
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file passthrough_ll.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2passthrough__ll_8c_source.html b/doc/html/fuse-3_818_81_2example_2passthrough__ll_8c_source.html new file mode 100644 index 0000000..6d3dbc0 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2passthrough__ll_8c_source.html @@ -0,0 +1,1525 @@ + + + + + + + +libfuse: fuse-3.18.1/example/passthrough_ll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_ll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
33#define _GNU_SOURCE
+
34#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
35
+
36#include <fuse_lowlevel.h>
+
37#include <unistd.h>
+
38#include <stdlib.h>
+
39#include <stdio.h>
+
40#include <stddef.h>
+
41#include <stdbool.h>
+
42#include <string.h>
+
43#include <limits.h>
+
44#include <dirent.h>
+
45#include <assert.h>
+
46#include <errno.h>
+
47#include <inttypes.h>
+
48#include <pthread.h>
+
49#include <sys/file.h>
+
50#include <sys/xattr.h>
+
51
+
52#include "passthrough_helpers.h"
+
53
+
54/* We are re-using pointers to our `struct lo_inode` and `struct
+
55 lo_dirp` elements as inodes. This means that we must be able to
+
56 store uintptr_t values in a fuse_ino_t variable. The following
+
57 incantation checks this condition at compile time. */
+
58#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
+
59_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
+
60 "fuse_ino_t too small to hold uintptr_t values!");
+
61#else
+
62struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
+
63 { unsigned _uintptr_to_must_hold_fuse_ino_t:
+
64 ((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
+
65#endif
+
66
+
67struct lo_inode {
+
68 struct lo_inode *next; /* protected by lo->mutex */
+
69 struct lo_inode *prev; /* protected by lo->mutex */
+
70 int fd;
+
71 ino_t ino;
+
72 dev_t dev;
+
73 uint64_t refcount; /* protected by lo->mutex */
+
74};
+
75
+
76enum {
+
77 CACHE_NEVER,
+
78 CACHE_NORMAL,
+
79 CACHE_ALWAYS,
+
80};
+
81
+
82struct lo_data {
+
83 pthread_mutex_t mutex;
+
84 int debug;
+
85 int writeback;
+
86 int flock;
+
87 int xattr;
+
88 char *source;
+
89 double timeout;
+
90 int cache;
+
91 int timeout_set;
+
92 struct lo_inode root; /* protected by lo->mutex */
+
93};
+
94
+
95static const struct fuse_opt lo_opts[] = {
+
96 { "writeback",
+
97 offsetof(struct lo_data, writeback), 1 },
+
98 { "no_writeback",
+
99 offsetof(struct lo_data, writeback), 0 },
+
100 { "source=%s",
+
101 offsetof(struct lo_data, source), 0 },
+
102 { "flock",
+
103 offsetof(struct lo_data, flock), 1 },
+
104 { "no_flock",
+
105 offsetof(struct lo_data, flock), 0 },
+
106 { "xattr",
+
107 offsetof(struct lo_data, xattr), 1 },
+
108 { "no_xattr",
+
109 offsetof(struct lo_data, xattr), 0 },
+
110 { "timeout=%lf",
+
111 offsetof(struct lo_data, timeout), 0 },
+
112 { "timeout=",
+
113 offsetof(struct lo_data, timeout_set), 1 },
+
114 { "cache=never",
+
115 offsetof(struct lo_data, cache), CACHE_NEVER },
+
116 { "cache=auto",
+
117 offsetof(struct lo_data, cache), CACHE_NORMAL },
+
118 { "cache=always",
+
119 offsetof(struct lo_data, cache), CACHE_ALWAYS },
+
120
+ +
122};
+
123
+
124static void passthrough_ll_help(void)
+
125{
+
126 printf(
+
127" -o writeback Enable writeback\n"
+
128" -o no_writeback Disable write back\n"
+
129" -o source=/home/dir Source directory to be mounted\n"
+
130" -o flock Enable flock\n"
+
131" -o no_flock Disable flock\n"
+
132" -o xattr Enable xattr\n"
+
133" -o no_xattr Disable xattr\n"
+
134" -o timeout=1.0 Caching timeout\n"
+
135" -o timeout=0/1 Timeout is set\n"
+
136" -o cache=never Disable cache\n"
+
137" -o cache=auto Auto enable cache\n"
+
138" -o cache=always Cache always\n");
+
139}
+
140
+
141static struct lo_data *lo_data(fuse_req_t req)
+
142{
+
143 return (struct lo_data *) fuse_req_userdata(req);
+
144}
+
145
+
146static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
+
147{
+
148 if (ino == FUSE_ROOT_ID)
+
149 return &lo_data(req)->root;
+
150 else
+
151 return (struct lo_inode *) (uintptr_t) ino;
+
152}
+
153
+
154static int lo_fd(fuse_req_t req, fuse_ino_t ino)
+
155{
+
156 return lo_inode(req, ino)->fd;
+
157}
+
158
+
159static bool lo_debug(fuse_req_t req)
+
160{
+
161 return lo_data(req)->debug != 0;
+
162}
+
163
+
164static void lo_init(void *userdata,
+
165 struct fuse_conn_info *conn)
+
166{
+
167 struct lo_data *lo = (struct lo_data *)userdata;
+
168 bool has_flag;
+
169
+
170 if (lo->writeback) {
+ +
172 if (lo->debug && has_flag)
+
173 fuse_log(FUSE_LOG_DEBUG,
+
174 "lo_init: activating writeback\n");
+
175 }
+
176 if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
+ +
178 if (lo->debug && has_flag)
+
179 fuse_log(FUSE_LOG_DEBUG,
+
180 "lo_init: activating flock locks\n");
+
181 }
+
182
+
183 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
184 conn->no_interrupt = 1;
+
185}
+
186
+
187static void lo_destroy(void *userdata)
+
188{
+
189 struct lo_data *lo = (struct lo_data*) userdata;
+
190
+
191 while (lo->root.next != &lo->root) {
+
192 struct lo_inode* next = lo->root.next;
+
193 lo->root.next = next->next;
+
194 close(next->fd);
+
195 free(next);
+
196 }
+
197}
+
198
+
199static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
+
200 struct fuse_file_info *fi)
+
201{
+
202 int res;
+
203 struct stat buf;
+
204 struct lo_data *lo = lo_data(req);
+
205 int fd = fi ? fi->fh : lo_fd(req, ino);
+
206
+
207 (void) fi;
+
208
+
209 res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
210 if (res == -1)
+
211 return (void) fuse_reply_err(req, errno);
+
212
+
213 fuse_reply_attr(req, &buf, lo->timeout);
+
214}
+
215
+
216static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
217 int valid, struct fuse_file_info *fi)
+
218{
+
219 int saverr;
+
220 char procname[64];
+
221 struct lo_inode *inode = lo_inode(req, ino);
+
222 int ifd = inode->fd;
+
223 int res;
+
224
+
225 if (valid & FUSE_SET_ATTR_MODE) {
+
226 if (fi) {
+
227 res = fchmod(fi->fh, attr->st_mode);
+
228 } else {
+
229 sprintf(procname, "/proc/self/fd/%i", ifd);
+
230 res = chmod(procname, attr->st_mode);
+
231 }
+
232 if (res == -1)
+
233 goto out_err;
+
234 }
+
235 if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+
236 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
237 attr->st_uid : (uid_t) -1;
+
238 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
239 attr->st_gid : (gid_t) -1;
+
240
+
241 res = fchownat(ifd, "", uid, gid,
+
242 AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
243 if (res == -1)
+
244 goto out_err;
+
245 }
+
246 if (valid & FUSE_SET_ATTR_SIZE) {
+
247 if (fi) {
+
248 res = ftruncate(fi->fh, attr->st_size);
+
249 } else {
+
250 sprintf(procname, "/proc/self/fd/%i", ifd);
+
251 res = truncate(procname, attr->st_size);
+
252 }
+
253 if (res == -1)
+
254 goto out_err;
+
255 }
+
256 if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
257 struct timespec tv[2];
+
258
+
259 tv[0].tv_sec = 0;
+
260 tv[1].tv_sec = 0;
+
261 tv[0].tv_nsec = UTIME_OMIT;
+
262 tv[1].tv_nsec = UTIME_OMIT;
+
263
+
264 if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
265 tv[0].tv_nsec = UTIME_NOW;
+
266 else if (valid & FUSE_SET_ATTR_ATIME)
+
267 tv[0] = attr->st_atim;
+
268
+
269 if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
270 tv[1].tv_nsec = UTIME_NOW;
+
271 else if (valid & FUSE_SET_ATTR_MTIME)
+
272 tv[1] = attr->st_mtim;
+
273
+
274 if (fi)
+
275 res = futimens(fi->fh, tv);
+
276 else {
+
277 sprintf(procname, "/proc/self/fd/%i", ifd);
+
278 res = utimensat(AT_FDCWD, procname, tv, 0);
+
279 }
+
280 if (res == -1)
+
281 goto out_err;
+
282 }
+
283
+
284 return lo_getattr(req, ino, fi);
+
285
+
286out_err:
+
287 saverr = errno;
+
288 fuse_reply_err(req, saverr);
+
289}
+
290
+
291static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+
292{
+
293 struct lo_inode *p;
+
294 struct lo_inode *ret = NULL;
+
295
+
296 pthread_mutex_lock(&lo->mutex);
+
297 for (p = lo->root.next; p != &lo->root; p = p->next) {
+
298 if (p->ino == st->st_ino && p->dev == st->st_dev) {
+
299 assert(p->refcount > 0);
+
300 ret = p;
+
301 ret->refcount++;
+
302 break;
+
303 }
+
304 }
+
305 pthread_mutex_unlock(&lo->mutex);
+
306 return ret;
+
307}
+
308
+
309
+
310static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
+
311{
+
312 struct lo_inode *inode = NULL;
+
313 struct lo_inode *prev, *next;
+
314
+
315 inode = calloc(1, sizeof(struct lo_inode));
+
316 if (!inode)
+
317 return NULL;
+
318
+
319 inode->refcount = 1;
+
320 inode->fd = fd;
+
321 inode->ino = e->attr.st_ino;
+
322 inode->dev = e->attr.st_dev;
+
323
+
324 pthread_mutex_lock(&lo->mutex);
+
325 prev = &lo->root;
+
326 next = prev->next;
+
327 next->prev = inode;
+
328 inode->next = next;
+
329 inode->prev = prev;
+
330 prev->next = inode;
+
331 pthread_mutex_unlock(&lo->mutex);
+
332 return inode;
+
333}
+
334
+
335static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
+
336{
+
337 int res;
+
338 struct lo_data *lo = lo_data(req);
+
339
+
340 memset(e, 0, sizeof(*e));
+
341 e->attr_timeout = lo->timeout;
+
342 e->entry_timeout = lo->timeout;
+
343
+
344 res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
345 if (res == -1)
+
346 return errno;
+
347
+
348 e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
+
349
+
350 if (lo_debug(req))
+
351 fuse_log(FUSE_LOG_DEBUG, " %lli/%d -> %lli\n",
+
352 (unsigned long long) parent, fd, (unsigned long long) e->ino);
+
353
+
354 return 0;
+
355
+
356}
+
357
+
358static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
+
359 struct fuse_entry_param *e)
+
360{
+
361 int newfd;
+
362 int res;
+
363 int saverr;
+
364 struct lo_data *lo = lo_data(req);
+
365 struct lo_inode *inode;
+
366
+
367 memset(e, 0, sizeof(*e));
+
368 e->attr_timeout = lo->timeout;
+
369 e->entry_timeout = lo->timeout;
+
370
+
371 newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
+
372 if (newfd == -1)
+
373 goto out_err;
+
374
+
375 res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
376 if (res == -1)
+
377 goto out_err;
+
378
+
379 inode = lo_find(lo_data(req), &e->attr);
+
380 if (inode) {
+
381 close(newfd);
+
382 newfd = -1;
+
383 } else {
+
384 inode = create_new_inode(newfd, e, lo);
+
385 if (!inode)
+
386 goto out_err;
+
387 }
+
388 e->ino = (uintptr_t) inode;
+
389
+
390 if (lo_debug(req))
+
391 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
392 (unsigned long long) parent, name, (unsigned long long) e->ino);
+
393
+
394 return 0;
+
395
+
396out_err:
+
397 saverr = errno;
+
398 if (newfd != -1)
+
399 close(newfd);
+
400 return saverr;
+
401}
+
402
+
403static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
404{
+
405 struct fuse_entry_param e;
+
406 int err;
+
407
+
408 if (lo_debug(req))
+
409 fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
+
410 parent, name);
+
411
+
412 err = lo_do_lookup(req, parent, name, &e);
+
413 if (err)
+
414 fuse_reply_err(req, err);
+
415 else
+
416 fuse_reply_entry(req, &e);
+
417}
+
418
+
419static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
+
420 const char *name, mode_t mode, dev_t rdev,
+
421 const char *link)
+
422{
+
423 int res;
+
424 int saverr;
+
425 struct lo_inode *dir = lo_inode(req, parent);
+
426 struct fuse_entry_param e;
+
427
+
428 res = mknod_wrapper(dir->fd, name, link, mode, rdev);
+
429
+
430 saverr = errno;
+
431 if (res == -1)
+
432 goto out;
+
433
+
434 saverr = lo_do_lookup(req, parent, name, &e);
+
435 if (saverr)
+
436 goto out;
+
437
+
438 if (lo_debug(req))
+
439 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
440 (unsigned long long) parent, name, (unsigned long long) e.ino);
+
441
+
442 fuse_reply_entry(req, &e);
+
443 return;
+
444
+
445out:
+
446 fuse_reply_err(req, saverr);
+
447}
+
448
+
449static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
+
450 const char *name, mode_t mode, dev_t rdev)
+
451{
+
452 lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
+
453}
+
454
+
455static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
456 mode_t mode)
+
457{
+
458 lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
+
459}
+
460
+
461static void lo_symlink(fuse_req_t req, const char *link,
+
462 fuse_ino_t parent, const char *name)
+
463{
+
464 lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
+
465}
+
466
+
467static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
+
468 const char *name)
+
469{
+
470 int res;
+
471 struct lo_data *lo = lo_data(req);
+
472 struct lo_inode *inode = lo_inode(req, ino);
+
473 struct fuse_entry_param e;
+
474 char procname[64];
+
475 int saverr;
+
476
+
477 memset(&e, 0, sizeof(struct fuse_entry_param));
+
478 e.attr_timeout = lo->timeout;
+
479 e.entry_timeout = lo->timeout;
+
480
+
481 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
482 res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
+
483 AT_SYMLINK_FOLLOW);
+
484 if (res == -1)
+
485 goto out_err;
+
486
+
487 res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
488 if (res == -1)
+
489 goto out_err;
+
490
+
491 pthread_mutex_lock(&lo->mutex);
+
492 inode->refcount++;
+
493 pthread_mutex_unlock(&lo->mutex);
+
494 e.ino = (uintptr_t) inode;
+
495
+
496 if (lo_debug(req))
+
497 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
498 (unsigned long long) parent, name,
+
499 (unsigned long long) e.ino);
+
500
+
501 fuse_reply_entry(req, &e);
+
502 return;
+
503
+
504out_err:
+
505 saverr = errno;
+
506 fuse_reply_err(req, saverr);
+
507}
+
508
+
509static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
510{
+
511 int res;
+
512
+
513 res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
+
514
+
515 fuse_reply_err(req, res == -1 ? errno : 0);
+
516}
+
517
+
518static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
+
519 fuse_ino_t newparent, const char *newname,
+
520 unsigned int flags)
+
521{
+
522 int res;
+
523
+
524 if (flags) {
+
525 fuse_reply_err(req, EINVAL);
+
526 return;
+
527 }
+
528
+
529 res = renameat(lo_fd(req, parent), name,
+
530 lo_fd(req, newparent), newname);
+
531
+
532 fuse_reply_err(req, res == -1 ? errno : 0);
+
533}
+
534
+
535static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
+
536{
+
537 int res;
+
538
+
539 res = unlinkat(lo_fd(req, parent), name, 0);
+
540
+
541 fuse_reply_err(req, res == -1 ? errno : 0);
+
542}
+
543
+
544static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
+
545{
+
546 if (!inode)
+
547 return;
+
548
+
549 pthread_mutex_lock(&lo->mutex);
+
550 assert(inode->refcount >= n);
+
551 inode->refcount -= n;
+
552 if (!inode->refcount) {
+
553 struct lo_inode *prev, *next;
+
554
+
555 prev = inode->prev;
+
556 next = inode->next;
+
557 next->prev = prev;
+
558 prev->next = next;
+
559
+
560 pthread_mutex_unlock(&lo->mutex);
+
561 close(inode->fd);
+
562 free(inode);
+
563
+
564 } else {
+
565 pthread_mutex_unlock(&lo->mutex);
+
566 }
+
567}
+
568
+
569static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
570{
+
571 struct lo_data *lo = lo_data(req);
+
572 struct lo_inode *inode = lo_inode(req, ino);
+
573
+
574 if (lo_debug(req)) {
+
575 fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
+
576 (unsigned long long) ino,
+
577 (unsigned long long) inode->refcount,
+
578 (unsigned long long) nlookup);
+
579 }
+
580
+
581 unref_inode(lo, inode, nlookup);
+
582}
+
583
+
584static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
585{
+
586 lo_forget_one(req, ino, nlookup);
+
587 fuse_reply_none(req);
+
588}
+
589
+
590static void lo_forget_multi(fuse_req_t req, size_t count,
+
591 struct fuse_forget_data *forgets)
+
592{
+
593 int i;
+
594
+
595 for (i = 0; i < count; i++)
+
596 lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
+
597 fuse_reply_none(req);
+
598}
+
599
+
600static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
+
601{
+
602 char buf[PATH_MAX + 1];
+
603 int res;
+
604
+
605 res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
+
606 if (res == -1)
+
607 return (void) fuse_reply_err(req, errno);
+
608
+
609 if (res == sizeof(buf))
+
610 return (void) fuse_reply_err(req, ENAMETOOLONG);
+
611
+
612 buf[res] = '\0';
+
613
+
614 fuse_reply_readlink(req, buf);
+
615}
+
616
+
617struct lo_dirp {
+
618 DIR *dp;
+
619 struct dirent *entry;
+
620 off_t offset;
+
621};
+
622
+
623static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
+
624{
+
625 return (struct lo_dirp *) (uintptr_t) fi->fh;
+
626}
+
627
+
628static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
629{
+
630 int error = ENOMEM;
+
631 struct lo_data *lo = lo_data(req);
+
632 struct lo_dirp *d;
+
633 int fd = -1;
+
634
+
635 d = calloc(1, sizeof(struct lo_dirp));
+
636 if (d == NULL)
+
637 goto out_err;
+
638
+
639 fd = openat(lo_fd(req, ino), ".", O_RDONLY);
+
640 if (fd == -1)
+
641 goto out_errno;
+
642
+
643 d->dp = fdopendir(fd);
+
644 if (d->dp == NULL)
+
645 goto out_errno;
+
646
+
647 d->offset = 0;
+
648 d->entry = NULL;
+
649
+
650 fi->fh = (uintptr_t) d;
+
651 if (lo->cache != CACHE_NEVER)
+
652 fi->cache_readdir = 1;
+
653 if (lo->cache == CACHE_ALWAYS)
+
654 fi->keep_cache = 1;
+
655 fuse_reply_open(req, fi);
+
656 return;
+
657
+
658out_errno:
+
659 error = errno;
+
660out_err:
+
661 if (d) {
+
662 if (fd != -1)
+
663 close(fd);
+
664 free(d);
+
665 }
+
666 fuse_reply_err(req, error);
+
667}
+
668
+
669static int is_dot_or_dotdot(const char *name)
+
670{
+
671 return name[0] == '.' && (name[1] == '\0' ||
+
672 (name[1] == '.' && name[2] == '\0'));
+
673}
+
674
+
675static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
676 off_t offset, struct fuse_file_info *fi, int plus)
+
677{
+
678 struct lo_dirp *d = lo_dirp(fi);
+
679 char *buf;
+
680 char *p;
+
681 size_t rem = size;
+
682 int err;
+
683
+
684 (void) ino;
+
685
+
686 buf = calloc(1, size);
+
687 if (!buf) {
+
688 err = ENOMEM;
+
689 goto error;
+
690 }
+
691 p = buf;
+
692
+
693 if (offset != d->offset) {
+
694 seekdir(d->dp, offset);
+
695 d->entry = NULL;
+
696 d->offset = offset;
+
697 }
+
698 while (1) {
+
699 size_t entsize;
+
700 off_t nextoff;
+
701 const char *name;
+
702
+
703 if (!d->entry) {
+
704 errno = 0;
+
705 d->entry = readdir(d->dp);
+
706 if (!d->entry) {
+
707 if (errno) { // Error
+
708 err = errno;
+
709 goto error;
+
710 } else { // End of stream
+
711 break;
+
712 }
+
713 }
+
714 }
+
715 nextoff = d->entry->d_off;
+
716 name = d->entry->d_name;
+
717 fuse_ino_t entry_ino = 0;
+
718 if (plus) {
+
719 struct fuse_entry_param e;
+
720 if (is_dot_or_dotdot(name)) {
+
721 e = (struct fuse_entry_param) {
+
722 .attr.st_ino = d->entry->d_ino,
+
723 .attr.st_mode = d->entry->d_type << 12,
+
724 };
+
725 } else {
+
726 err = lo_do_lookup(req, ino, name, &e);
+
727 if (err)
+
728 goto error;
+
729 entry_ino = e.ino;
+
730 }
+
731
+
732 entsize = fuse_add_direntry_plus(req, p, rem, name,
+
733 &e, nextoff);
+
734 } else {
+
735 struct stat st = {
+
736 .st_ino = d->entry->d_ino,
+
737 .st_mode = d->entry->d_type << 12,
+
738 };
+
739 entsize = fuse_add_direntry(req, p, rem, name,
+
740 &st, nextoff);
+
741 }
+
742 if (entsize > rem) {
+
743 if (entry_ino != 0)
+
744 lo_forget_one(req, entry_ino, 1);
+
745 break;
+
746 }
+
747
+
748 p += entsize;
+
749 rem -= entsize;
+
750
+
751 d->entry = NULL;
+
752 d->offset = nextoff;
+
753 }
+
754
+
755 err = 0;
+
756error:
+
757 // If there's an error, we can only signal it if we haven't stored
+
758 // any entries yet - otherwise we'd end up with wrong lookup
+
759 // counts for the entries that are already in the buffer. So we
+
760 // return what we've collected until that point.
+
761 if (err && rem == size)
+
762 fuse_reply_err(req, err);
+
763 else
+
764 fuse_reply_buf(req, buf, size - rem);
+
765 free(buf);
+
766}
+
767
+
768static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
769 off_t offset, struct fuse_file_info *fi)
+
770{
+
771 lo_do_readdir(req, ino, size, offset, fi, 0);
+
772}
+
773
+
774static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
775 off_t offset, struct fuse_file_info *fi)
+
776{
+
777 lo_do_readdir(req, ino, size, offset, fi, 1);
+
778}
+
779
+
780static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
781{
+
782 struct lo_dirp *d = lo_dirp(fi);
+
783 (void) ino;
+
784 closedir(d->dp);
+
785 free(d);
+
786 fuse_reply_err(req, 0);
+
787}
+
788
+
789static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
+
790 mode_t mode, struct fuse_file_info *fi)
+
791{
+
792 int fd;
+
793 struct lo_data *lo = lo_data(req);
+
794 struct fuse_entry_param e;
+
795 int err;
+
796
+
797 if (lo_debug(req))
+
798 fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
+
799 parent);
+
800
+
801 fd = openat(lo_fd(req, parent), ".",
+
802 (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
+
803 if (fd == -1)
+
804 return (void) fuse_reply_err(req, errno);
+
805
+
806 fi->fh = fd;
+
807 if (lo->cache == CACHE_NEVER)
+
808 fi->direct_io = 1;
+
809 else if (lo->cache == CACHE_ALWAYS)
+
810 fi->keep_cache = 1;
+
811
+
812 /* parallel_direct_writes feature depends on direct_io features.
+
813 To make parallel_direct_writes valid, need set fi->direct_io
+
814 in current function. */
+ +
816
+
817 err = fill_entry_param_new_inode(req, parent, fd, &e);
+
818 if (err)
+
819 fuse_reply_err(req, err);
+
820 else
+
821 fuse_reply_create(req, &e, fi);
+
822}
+
823
+
824static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+
825 mode_t mode, struct fuse_file_info *fi)
+
826{
+
827 int fd;
+
828 struct lo_data *lo = lo_data(req);
+
829 struct fuse_entry_param e;
+
830 int err;
+
831
+
832 if (lo_debug(req))
+
833 fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
+
834 parent, name);
+
835
+
836 fd = openat(lo_fd(req, parent), name,
+
837 (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
+
838 if (fd == -1)
+
839 return (void) fuse_reply_err(req, errno);
+
840
+
841 fi->fh = fd;
+
842 if (lo->cache == CACHE_NEVER)
+
843 fi->direct_io = 1;
+
844 else if (lo->cache == CACHE_ALWAYS)
+
845 fi->keep_cache = 1;
+
846
+
847 /* parallel_direct_writes feature depends on direct_io features.
+
848 To make parallel_direct_writes valid, need set fi->direct_io
+
849 in current function. */
+ +
851
+
852 err = lo_do_lookup(req, parent, name, &e);
+
853 if (err)
+
854 fuse_reply_err(req, err);
+
855 else
+
856 fuse_reply_create(req, &e, fi);
+
857}
+
858
+
859static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
860 struct fuse_file_info *fi)
+
861{
+
862 int res;
+
863 int fd = dirfd(lo_dirp(fi)->dp);
+
864 (void) ino;
+
865 if (datasync)
+
866 res = fdatasync(fd);
+
867 else
+
868 res = fsync(fd);
+
869 fuse_reply_err(req, res == -1 ? errno : 0);
+
870}
+
871
+
872static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
873{
+
874 int fd;
+
875 char buf[64];
+
876 struct lo_data *lo = lo_data(req);
+
877
+
878 if (lo_debug(req))
+
879 fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
+
880 ino, fi->flags);
+
881
+
882 /* With writeback cache, kernel may send read requests even
+
883 when userspace opened write-only */
+
884 if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
+
885 fi->flags &= ~O_ACCMODE;
+
886 fi->flags |= O_RDWR;
+
887 }
+
888
+
889 /* With writeback cache, O_APPEND is handled by the kernel.
+
890 This breaks atomicity (since the file may change in the
+
891 underlying filesystem, so that the kernel's idea of the
+
892 end of the file isn't accurate anymore). In this example,
+
893 we just accept that. A more rigorous filesystem may want
+
894 to return an error here */
+
895 if (lo->writeback && (fi->flags & O_APPEND))
+
896 fi->flags &= ~O_APPEND;
+
897
+
898 sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
+
899 fd = open(buf, fi->flags & ~O_NOFOLLOW);
+
900 if (fd == -1)
+
901 return (void) fuse_reply_err(req, errno);
+
902
+
903 fi->fh = fd;
+
904 if (lo->cache == CACHE_NEVER)
+
905 fi->direct_io = 1;
+
906 else if (lo->cache == CACHE_ALWAYS)
+
907 fi->keep_cache = 1;
+
908
+
909 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
910 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
911 for writes to the same file in the kernel). */
+
912 if (fi->flags & O_DIRECT)
+
913 fi->direct_io = 1;
+
914
+
915 /* parallel_direct_writes feature depends on direct_io features.
+
916 To make parallel_direct_writes valid, need set fi->direct_io
+
917 in current function. */
+ +
919
+
920 fuse_reply_open(req, fi);
+
921}
+
922
+
923static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
924{
+
925 (void) ino;
+
926
+
927 close(fi->fh);
+
928 fuse_reply_err(req, 0);
+
929}
+
930
+
931static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
932{
+
933 int res;
+
934 (void) ino;
+
935 res = close(dup(fi->fh));
+
936 fuse_reply_err(req, res == -1 ? errno : 0);
+
937}
+
938
+
939static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
940 struct fuse_file_info *fi)
+
941{
+
942 int res;
+
943 (void) ino;
+
944 if (datasync)
+
945 res = fdatasync(fi->fh);
+
946 else
+
947 res = fsync(fi->fh);
+
948 fuse_reply_err(req, res == -1 ? errno : 0);
+
949}
+
950
+
951static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
952 off_t offset, struct fuse_file_info *fi)
+
953{
+
954 struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
+
955
+
956 if (lo_debug(req))
+
957 fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
+
958 "off=%lu)\n", ino, size, (unsigned long) offset);
+
959
+ +
961 buf.buf[0].fd = fi->fh;
+
962 buf.buf[0].pos = offset;
+
963
+ +
965}
+
966
+
967static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
+
968 struct fuse_bufvec *in_buf, off_t off,
+
969 struct fuse_file_info *fi)
+
970{
+
971 (void) ino;
+
972 ssize_t res;
+
973 struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
+
974
+ +
976 out_buf.buf[0].fd = fi->fh;
+
977 out_buf.buf[0].pos = off;
+
978
+
979 if (lo_debug(req))
+
980 fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%jd)\n",
+
981 ino, out_buf.buf[0].size, (intmax_t) off);
+
982
+
983 res = fuse_buf_copy(&out_buf, in_buf, 0);
+
984 if(res < 0)
+
985 fuse_reply_err(req, -res);
+
986 else
+
987 fuse_reply_write(req, (size_t) res);
+
988}
+
989
+
990static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
+
991{
+
992 int res;
+
993 struct statvfs stbuf;
+
994
+
995 res = fstatvfs(lo_fd(req, ino), &stbuf);
+
996 if (res == -1)
+
997 fuse_reply_err(req, errno);
+
998 else
+
999 fuse_reply_statfs(req, &stbuf);
+
1000}
+
1001
+
1002static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
1003 off_t offset, off_t length, struct fuse_file_info *fi)
+
1004{
+
1005 int err;
+
1006 (void) ino;
+
1007
+
1008 err = -do_fallocate(fi->fh, mode, offset, length);
+
1009
+
1010 fuse_reply_err(req, err);
+
1011}
+
1012
+
1013static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
1014 int op)
+
1015{
+
1016 int res;
+
1017 (void) ino;
+
1018
+
1019 res = flock(fi->fh, op);
+
1020
+
1021 fuse_reply_err(req, res == -1 ? errno : 0);
+
1022}
+
1023
+
1024static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
1025 size_t size)
+
1026{
+
1027 char *value = NULL;
+
1028 char procname[64];
+
1029 struct lo_inode *inode = lo_inode(req, ino);
+
1030 ssize_t ret;
+
1031 int saverr;
+
1032
+
1033 saverr = ENOSYS;
+
1034 if (!lo_data(req)->xattr)
+
1035 goto out;
+
1036
+
1037 if (lo_debug(req)) {
+
1038 fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
+
1039 ino, name, size);
+
1040 }
+
1041
+
1042 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1043
+
1044 if (size) {
+
1045 value = malloc(size);
+
1046 if (!value)
+
1047 goto out_err;
+
1048
+
1049 ret = getxattr(procname, name, value, size);
+
1050 if (ret == -1)
+
1051 goto out_err;
+
1052 saverr = 0;
+
1053 if (ret == 0)
+
1054 goto out;
+
1055
+
1056 fuse_reply_buf(req, value, ret);
+
1057 } else {
+
1058 ret = getxattr(procname, name, NULL, 0);
+
1059 if (ret == -1)
+
1060 goto out_err;
+
1061
+
1062 fuse_reply_xattr(req, ret);
+
1063 }
+
1064out_free:
+
1065 free(value);
+
1066 return;
+
1067
+
1068out_err:
+
1069 saverr = errno;
+
1070out:
+
1071 fuse_reply_err(req, saverr);
+
1072 goto out_free;
+
1073}
+
1074
+
1075static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
1076{
+
1077 char *value = NULL;
+
1078 char procname[64];
+
1079 struct lo_inode *inode = lo_inode(req, ino);
+
1080 ssize_t ret;
+
1081 int saverr;
+
1082
+
1083 saverr = ENOSYS;
+
1084 if (!lo_data(req)->xattr)
+
1085 goto out;
+
1086
+
1087 if (lo_debug(req)) {
+
1088 fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
+
1089 ino, size);
+
1090 }
+
1091
+
1092 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1093
+
1094 if (size) {
+
1095 value = malloc(size);
+
1096 if (!value)
+
1097 goto out_err;
+
1098
+
1099 ret = listxattr(procname, value, size);
+
1100 if (ret == -1)
+
1101 goto out_err;
+
1102 saverr = 0;
+
1103 if (ret == 0)
+
1104 goto out;
+
1105
+
1106 fuse_reply_buf(req, value, ret);
+
1107 } else {
+
1108 ret = listxattr(procname, NULL, 0);
+
1109 if (ret == -1)
+
1110 goto out_err;
+
1111
+
1112 fuse_reply_xattr(req, ret);
+
1113 }
+
1114out_free:
+
1115 free(value);
+
1116 return;
+
1117
+
1118out_err:
+
1119 saverr = errno;
+
1120out:
+
1121 fuse_reply_err(req, saverr);
+
1122 goto out_free;
+
1123}
+
1124
+
1125static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
1126 const char *value, size_t size, int flags)
+
1127{
+
1128 char procname[64];
+
1129 struct lo_inode *inode = lo_inode(req, ino);
+
1130 ssize_t ret;
+
1131 int saverr;
+
1132
+
1133 saverr = ENOSYS;
+
1134 if (!lo_data(req)->xattr)
+
1135 goto out;
+
1136
+
1137 if (lo_debug(req)) {
+
1138 fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
+
1139 ino, name, value, size);
+
1140 }
+
1141
+
1142 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1143
+
1144 ret = setxattr(procname, name, value, size, flags);
+
1145 saverr = ret == -1 ? errno : 0;
+
1146
+
1147out:
+
1148 fuse_reply_err(req, saverr);
+
1149}
+
1150
+
1151static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
1152{
+
1153 char procname[64];
+
1154 struct lo_inode *inode = lo_inode(req, ino);
+
1155 ssize_t ret;
+
1156 int saverr;
+
1157
+
1158 saverr = ENOSYS;
+
1159 if (!lo_data(req)->xattr)
+
1160 goto out;
+
1161
+
1162 if (lo_debug(req)) {
+
1163 fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
+
1164 ino, name);
+
1165 }
+
1166
+
1167 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1168
+
1169 ret = removexattr(procname, name);
+
1170 saverr = ret == -1 ? errno : 0;
+
1171
+
1172out:
+
1173 fuse_reply_err(req, saverr);
+
1174}
+
1175
+
1176#ifdef HAVE_COPY_FILE_RANGE
+
1177static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
+
1178 struct fuse_file_info *fi_in,
+
1179 fuse_ino_t ino_out, off_t off_out,
+
1180 struct fuse_file_info *fi_out, size_t len,
+
1181 int flags)
+
1182{
+
1183 ssize_t res;
+
1184
+
1185 if (lo_debug(req))
+
1186 fuse_log(FUSE_LOG_DEBUG,
+
1187 "%s(ino=%lld fd=%lld off=%jd ino=%lld fd=%lld off=%jd, size=%zd, flags=0x%x)\n",
+
1188 __func__, (unsigned long long)ino_in,
+
1189 (unsigned long long)fi_in->fh,
+
1190 (intmax_t) off_in, (unsigned long long)ino_out,
+
1191 (unsigned long long)fi_out->fh, (intmax_t) off_out,
+
1192 len, flags);
+
1193
+
1194 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
1195 flags);
+
1196 if (res < 0)
+
1197 fuse_reply_err(req, errno);
+
1198 else
+
1199 fuse_reply_write(req, res);
+
1200}
+
1201#endif
+
1202
+
1203static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
1204 struct fuse_file_info *fi)
+
1205{
+
1206 off_t res;
+
1207
+
1208 (void)ino;
+
1209 res = lseek(fi->fh, off, whence);
+
1210 if (res != -1)
+
1211 fuse_reply_lseek(req, res);
+
1212 else
+
1213 fuse_reply_err(req, errno);
+
1214}
+
1215
+
1216#ifdef HAVE_STATX
+
1217static void lo_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
+
1218 struct fuse_file_info *fi)
+
1219{
+
1220 struct lo_data *lo = lo_data(req);
+
1221 struct statx buf;
+
1222 int res;
+
1223 int fd;
+
1224
+
1225 if (fi)
+
1226 fd = fi->fh;
+
1227 else
+
1228 fd = lo_fd(req, ino);
+
1229
+
1230 res = statx(fd, "", flags | AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, mask, &buf);
+
1231 if (res == -1)
+
1232 fuse_reply_err(req, errno);
+
1233 else
+
1234 fuse_reply_statx(req, 0, &buf, lo->timeout);
+
1235}
+
1236#endif
+
1237
+
1238static const struct fuse_lowlevel_ops lo_oper = {
+
1239 .init = lo_init,
+
1240 .destroy = lo_destroy,
+
1241 .lookup = lo_lookup,
+
1242 .mkdir = lo_mkdir,
+
1243 .mknod = lo_mknod,
+
1244 .symlink = lo_symlink,
+
1245 .link = lo_link,
+
1246 .unlink = lo_unlink,
+
1247 .rmdir = lo_rmdir,
+
1248 .rename = lo_rename,
+
1249 .forget = lo_forget,
+
1250 .forget_multi = lo_forget_multi,
+
1251 .getattr = lo_getattr,
+
1252 .setattr = lo_setattr,
+
1253 .readlink = lo_readlink,
+
1254 .opendir = lo_opendir,
+
1255 .readdir = lo_readdir,
+
1256 .readdirplus = lo_readdirplus,
+
1257 .releasedir = lo_releasedir,
+
1258 .fsyncdir = lo_fsyncdir,
+
1259 .create = lo_create,
+
1260 .tmpfile = lo_tmpfile,
+
1261 .open = lo_open,
+
1262 .release = lo_release,
+
1263 .flush = lo_flush,
+
1264 .fsync = lo_fsync,
+
1265 .read = lo_read,
+
1266 .write_buf = lo_write_buf,
+
1267 .statfs = lo_statfs,
+
1268 .fallocate = lo_fallocate,
+
1269 .flock = lo_flock,
+
1270 .getxattr = lo_getxattr,
+
1271 .listxattr = lo_listxattr,
+
1272 .setxattr = lo_setxattr,
+
1273 .removexattr = lo_removexattr,
+
1274#ifdef HAVE_COPY_FILE_RANGE
+
1275 .copy_file_range = lo_copy_file_range,
+
1276#endif
+
1277 .lseek = lo_lseek,
+
1278#ifdef HAVE_STATX
+
1279 .statx = lo_statx,
+
1280#endif
+
1281};
+
1282
+
1283int main(int argc, char *argv[])
+
1284{
+
1285 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
1286 struct fuse_session *se;
+
1287 struct fuse_cmdline_opts opts;
+
1288 struct fuse_loop_config *config;
+
1289 struct lo_data lo = { .debug = 0,
+
1290 .writeback = 0 };
+
1291 int ret = -1;
+
1292
+
1293 /* Don't mask creation mode, kernel already did that */
+
1294 umask(0);
+
1295
+
1296 pthread_mutex_init(&lo.mutex, NULL);
+
1297 lo.root.next = lo.root.prev = &lo.root;
+
1298 lo.root.fd = -1;
+
1299 lo.cache = CACHE_NORMAL;
+
1300
+
1301 if (fuse_parse_cmdline(&args, &opts) != 0)
+
1302 return 1;
+
1303 if (opts.show_help) {
+
1304 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
1307 passthrough_ll_help();
+
1308 ret = 0;
+
1309 goto err_out1;
+
1310 } else if (opts.show_version) {
+
1311 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
1313 ret = 0;
+
1314 goto err_out1;
+
1315 }
+
1316
+
1317 if(opts.mountpoint == NULL) {
+
1318 printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
1319 printf(" %s --help\n", argv[0]);
+
1320 ret = 1;
+
1321 goto err_out1;
+
1322 }
+
1323
+
1324 if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
+
1325 return 1;
+
1326
+
1327 lo.debug = opts.debug;
+
1328 lo.root.refcount = 2;
+
1329 if (lo.source) {
+
1330 struct stat stat;
+
1331 int res;
+
1332
+
1333 res = lstat(lo.source, &stat);
+
1334 if (res == -1) {
+
1335 fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
+
1336 lo.source);
+
1337 exit(1);
+
1338 }
+
1339 if (!S_ISDIR(stat.st_mode)) {
+
1340 fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
+
1341 exit(1);
+
1342 }
+
1343
+
1344 } else {
+
1345 lo.source = strdup("/");
+
1346 if(!lo.source) {
+
1347 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
1348 exit(1);
+
1349 }
+
1350 }
+
1351 if (!lo.timeout_set) {
+
1352 switch (lo.cache) {
+
1353 case CACHE_NEVER:
+
1354 lo.timeout = 0.0;
+
1355 break;
+
1356
+
1357 case CACHE_NORMAL:
+
1358 lo.timeout = 1.0;
+
1359 break;
+
1360
+
1361 case CACHE_ALWAYS:
+
1362 lo.timeout = 86400.0;
+
1363 break;
+
1364 }
+
1365 } else if (lo.timeout < 0) {
+
1366 fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
+
1367 lo.timeout);
+
1368 exit(1);
+
1369 }
+
1370
+
1371 lo.root.fd = open(lo.source, O_PATH);
+
1372 if (lo.root.fd == -1) {
+
1373 fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
+
1374 lo.source);
+
1375 exit(1);
+
1376 }
+
1377
+
1378 se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
+
1379 if (se == NULL)
+
1380 goto err_out1;
+
1381
+
1382 if (fuse_set_signal_handlers(se) != 0)
+
1383 goto err_out2;
+
1384
+
1385 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
1386 goto err_out3;
+
1387
+
1388 fuse_daemonize(opts.foreground);
+
1389
+
1390 /* Block until ctrl+c or fusermount -u */
+
1391 if (opts.singlethread)
+
1392 ret = fuse_session_loop(se);
+
1393 else {
+
1394 config = fuse_loop_cfg_create();
+
1395 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
1396 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
1397 ret = fuse_session_loop_mt(se, config);
+
1398 fuse_loop_cfg_destroy(config);
+
1399 config = NULL;
+
1400 }
+
1401
+ +
1403err_out3:
+ +
1405err_out2:
+ +
1407err_out1:
+
1408 free(opts.mountpoint);
+
1409 fuse_opt_free_args(&args);
+
1410
+
1411 if (lo.root.fd >= 0)
+
1412 close(lo.root.fd);
+
1413
+
1414 free(lo.source);
+
1415 return ret ? 1 : 0;
+
1416}
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
off_t pos
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
uint32_t capable
+
+
double entry_timeout
+
fuse_ino_t ino
+
double attr_timeout
+
struct stat attr
+ + +
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2poll_8c.html b/doc/html/fuse-3_818_81_2example_2poll_8c.html new file mode 100644 index 0000000..d4318b1 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2poll_8c.html @@ -0,0 +1,379 @@ + + + + + + + +libfuse: fuse-3.18.1/example/poll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
poll.c File Reference
+
+
+
#include <fuse.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include <pthread.h>
+#include <poll.h>
+#include <stdbool.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example illustrates how to write a FUSE file system that supports polling for changes that don't come through the kernel. It can be tested with the poll_client.c program.

+

Compile with:

gcc -Wall poll.c `pkg-config fuse3 --cflags --libs` -o poll
+

+Source code

+
/*
+
FUSE fsel: FUSE select example
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <unistd.h>
+
#include <ctype.h>
+
#include <string.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <errno.h>
+
#include <time.h>
+
#include <pthread.h>
+
#include <poll.h>
+
#include <stdbool.h>
+
+
/*
+
* fsel_open_mask is used to limit the number of opens to 1 per file.
+
* This is to use file index (0-F) as fh as poll support requires
+
* unique fh per open file. Lifting this would require proper open
+
* file management.
+
*/
+
static unsigned fsel_open_mask;
+
static const char fsel_hex_map[] = "0123456789ABCDEF";
+
static struct fuse *fsel_fuse; /* needed for poll notification */
+
+
#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
+
#define FSEL_FILES 16
+
+
static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
+
static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
+
static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
+
static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
+
static _Atomic bool fsel_stop = false;
+
static pthread_t fsel_producer_thread;
+
+
+
static int fsel_path_index(const char *path)
+
{
+
char ch = path[1];
+
+
if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
+
return -1;
+
return ch <= '9' ? ch - '0' : ch - 'A' + 10;
+
}
+
+
static void fsel_destroy(void *private_data)
+
{
+
(void)private_data;
+
+
fsel_stop = true;
+
+
pthread_join(fsel_producer_thread, NULL);
+
}
+
+
static int fsel_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int idx;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0555;
+
stbuf->st_nlink = 2;
+
return 0;
+
}
+
+
idx = fsel_path_index(path);
+
if (idx < 0)
+
return -ENOENT;
+
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = fsel_cnt[idx];
+
return 0;
+
}
+
+
static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
char name[2] = { };
+
int i;
+
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
name[0] = fsel_hex_map[i];
+
filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
}
+
+
return 0;
+
}
+
+
static int fsel_open(const char *path, struct fuse_file_info *fi)
+
{
+
int idx = fsel_path_index(path);
+
+
if (idx < 0)
+
return -ENOENT;
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
if (fsel_open_mask & (1 << idx))
+
return -EBUSY;
+
fsel_open_mask |= (1 << idx);
+
+
/*
+
* fsel files are nonseekable somewhat pipe-like files which
+
* gets filled up periodically by producer thread and consumed
+
* on read. Tell FUSE as such.
+
*/
+
fi->fh = idx;
+
fi->direct_io = 1;
+
fi->nonseekable = 1;
+
+
return 0;
+
}
+
+
static int fsel_release(const char *path, struct fuse_file_info *fi)
+
{
+
int idx = fi->fh;
+
+
(void) path;
+
+
fsel_open_mask &= ~(1 << idx);
+
return 0;
+
}
+
+
static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int idx = fi->fh;
+
+
(void) path;
+
(void) offset;
+
+
pthread_mutex_lock(&fsel_mutex);
+
if (fsel_cnt[idx] < size)
+
size = fsel_cnt[idx];
+
printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
+
fsel_cnt[idx] -= size;
+
pthread_mutex_unlock(&fsel_mutex);
+
+
memset(buf, fsel_hex_map[idx], size);
+
return size;
+
}
+
+
static int fsel_poll(const char *path, struct fuse_file_info *fi,
+
struct fuse_pollhandle *ph, unsigned *reventsp)
+
{
+
static unsigned polled_zero;
+
int idx = fi->fh;
+
+
(void) path;
+
+
/*
+
* Poll notification requires pointer to struct fuse which
+
* can't be obtained when using fuse_main(). As notification
+
* happens only after poll is called, fill it here from
+
* fuse_context.
+
*/
+
if (!fsel_fuse) {
+
struct fuse_context *cxt = fuse_get_context();
+
if (cxt)
+
fsel_fuse = cxt->fuse;
+
}
+
+
pthread_mutex_lock(&fsel_mutex);
+
+
if (ph != NULL) {
+
struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
+
+
if (oldph)
+ +
+
fsel_poll_notify_mask |= (1 << idx);
+
fsel_poll_handle[idx] = ph;
+
}
+
+
if (fsel_cnt[idx]) {
+
*reventsp |= POLLIN;
+
printf("POLL %X cnt=%u polled_zero=%u\n",
+
idx, fsel_cnt[idx], polled_zero);
+
polled_zero = 0;
+
} else
+
polled_zero++;
+
+
pthread_mutex_unlock(&fsel_mutex);
+
return 0;
+
}
+
+
static const struct fuse_operations fsel_oper = {
+
.destroy = fsel_destroy,
+
.getattr = fsel_getattr,
+
.readdir = fsel_readdir,
+
.open = fsel_open,
+
.release = fsel_release,
+
.read = fsel_read,
+
.poll = fsel_poll,
+
};
+
+
static void *fsel_producer(void *data)
+
{
+
const struct timespec interval = { 0, 250000000 };
+
unsigned idx = 0, nr = 1;
+
+
(void) data;
+
+
while (!fsel_stop) {
+
int i, t;
+
+
pthread_mutex_lock(&fsel_mutex);
+
+
/*
+
* This is the main producer loop which is executed
+
* ever 500ms. On each iteration, it fills one byte
+
* to 1, 2 or 4 files and sends poll notification if
+
* requested.
+
*/
+
for (i = 0, t = idx; i < nr;
+
i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
+
if (fsel_cnt[t] == FSEL_CNT_MAX)
+
continue;
+
+
fsel_cnt[t]++;
+
if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
+
struct fuse_pollhandle *ph;
+
+
printf("NOTIFY %X\n", t);
+
ph = fsel_poll_handle[t];
+
fuse_notify_poll(ph);
+ +
fsel_poll_notify_mask &= ~(1 << t);
+
fsel_poll_handle[t] = NULL;
+
}
+
}
+
+
idx = (idx + 1) % FSEL_FILES;
+
if (idx == 0)
+
nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
+
+
pthread_mutex_unlock(&fsel_mutex);
+
+
nanosleep(&interval, NULL);
+
}
+
+
return NULL;
+
}
+
+
int main(int argc, char *argv[])
+
{
+
pthread_attr_t attr;
+
int ret;
+
+
errno = pthread_mutex_init(&fsel_mutex, NULL);
+
if (errno) {
+
perror("pthread_mutex_init");
+
return 1;
+
}
+
+
errno = pthread_attr_init(&attr);
+
if (errno) {
+
perror("pthread_attr_init");
+
return 1;
+
}
+
+
errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
+
if (errno) {
+
perror("pthread_create");
+
return 1;
+
}
+
+
ret = fuse_main(argc, argv, &fsel_oper, NULL);
+
+
return ret;
+
}
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+ +
struct fuse * fuse
Definition fuse.h:862
+ + +
uint32_t nonseekable
Definition fuse_common.h:78
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+
+

Definition in file poll.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2poll_8c_source.html b/doc/html/fuse-3_818_81_2example_2poll_8c_source.html new file mode 100644 index 0000000..18b73f8 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2poll_8c_source.html @@ -0,0 +1,364 @@ + + + + + + + +libfuse: fuse-3.18.1/example/poll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
poll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fsel: FUSE select example
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
24#define FUSE_USE_VERSION 31
+
25
+
26#include <fuse.h>
+
27#include <unistd.h>
+
28#include <ctype.h>
+
29#include <string.h>
+
30#include <stdio.h>
+
31#include <stdlib.h>
+
32#include <errno.h>
+
33#include <time.h>
+
34#include <pthread.h>
+
35#include <poll.h>
+
36#include <stdbool.h>
+
37
+
38/*
+
39 * fsel_open_mask is used to limit the number of opens to 1 per file.
+
40 * This is to use file index (0-F) as fh as poll support requires
+
41 * unique fh per open file. Lifting this would require proper open
+
42 * file management.
+
43 */
+
44static unsigned fsel_open_mask;
+
45static const char fsel_hex_map[] = "0123456789ABCDEF";
+
46static struct fuse *fsel_fuse; /* needed for poll notification */
+
47
+
48#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
+
49#define FSEL_FILES 16
+
50
+
51static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
+
52static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
+
53static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
+
54static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
+
55static _Atomic bool fsel_stop = false;
+
56static pthread_t fsel_producer_thread;
+
57
+
58
+
59static int fsel_path_index(const char *path)
+
60{
+
61 char ch = path[1];
+
62
+
63 if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
+
64 return -1;
+
65 return ch <= '9' ? ch - '0' : ch - 'A' + 10;
+
66}
+
67
+
68static void fsel_destroy(void *private_data)
+
69{
+
70 (void)private_data;
+
71
+
72 fsel_stop = true;
+
73
+
74 pthread_join(fsel_producer_thread, NULL);
+
75}
+
76
+
77static int fsel_getattr(const char *path, struct stat *stbuf,
+
78 struct fuse_file_info *fi)
+
79{
+
80 (void) fi;
+
81 int idx;
+
82
+
83 memset(stbuf, 0, sizeof(struct stat));
+
84
+
85 if (strcmp(path, "/") == 0) {
+
86 stbuf->st_mode = S_IFDIR | 0555;
+
87 stbuf->st_nlink = 2;
+
88 return 0;
+
89 }
+
90
+
91 idx = fsel_path_index(path);
+
92 if (idx < 0)
+
93 return -ENOENT;
+
94
+
95 stbuf->st_mode = S_IFREG | 0444;
+
96 stbuf->st_nlink = 1;
+
97 stbuf->st_size = fsel_cnt[idx];
+
98 return 0;
+
99}
+
100
+
101static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
102 off_t offset, struct fuse_file_info *fi,
+
103 enum fuse_readdir_flags flags)
+
104{
+
105 char name[2] = { };
+
106 int i;
+
107
+
108 (void) offset;
+
109 (void) fi;
+
110 (void) flags;
+
111
+
112 if (strcmp(path, "/") != 0)
+
113 return -ENOENT;
+
114
+
115 for (i = 0; i < FSEL_FILES; i++) {
+
116 name[0] = fsel_hex_map[i];
+
117 filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
118 }
+
119
+
120 return 0;
+
121}
+
122
+
123static int fsel_open(const char *path, struct fuse_file_info *fi)
+
124{
+
125 int idx = fsel_path_index(path);
+
126
+
127 if (idx < 0)
+
128 return -ENOENT;
+
129 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
130 return -EACCES;
+
131 if (fsel_open_mask & (1 << idx))
+
132 return -EBUSY;
+
133 fsel_open_mask |= (1 << idx);
+
134
+
135 /*
+
136 * fsel files are nonseekable somewhat pipe-like files which
+
137 * gets filled up periodically by producer thread and consumed
+
138 * on read. Tell FUSE as such.
+
139 */
+
140 fi->fh = idx;
+
141 fi->direct_io = 1;
+
142 fi->nonseekable = 1;
+
143
+
144 return 0;
+
145}
+
146
+
147static int fsel_release(const char *path, struct fuse_file_info *fi)
+
148{
+
149 int idx = fi->fh;
+
150
+
151 (void) path;
+
152
+
153 fsel_open_mask &= ~(1 << idx);
+
154 return 0;
+
155}
+
156
+
157static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
+
158 struct fuse_file_info *fi)
+
159{
+
160 int idx = fi->fh;
+
161
+
162 (void) path;
+
163 (void) offset;
+
164
+
165 pthread_mutex_lock(&fsel_mutex);
+
166 if (fsel_cnt[idx] < size)
+
167 size = fsel_cnt[idx];
+
168 printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
+
169 fsel_cnt[idx] -= size;
+
170 pthread_mutex_unlock(&fsel_mutex);
+
171
+
172 memset(buf, fsel_hex_map[idx], size);
+
173 return size;
+
174}
+
175
+
176static int fsel_poll(const char *path, struct fuse_file_info *fi,
+
177 struct fuse_pollhandle *ph, unsigned *reventsp)
+
178{
+
179 static unsigned polled_zero;
+
180 int idx = fi->fh;
+
181
+
182 (void) path;
+
183
+
184 /*
+
185 * Poll notification requires pointer to struct fuse which
+
186 * can't be obtained when using fuse_main(). As notification
+
187 * happens only after poll is called, fill it here from
+
188 * fuse_context.
+
189 */
+
190 if (!fsel_fuse) {
+
191 struct fuse_context *cxt = fuse_get_context();
+
192 if (cxt)
+
193 fsel_fuse = cxt->fuse;
+
194 }
+
195
+
196 pthread_mutex_lock(&fsel_mutex);
+
197
+
198 if (ph != NULL) {
+
199 struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
+
200
+
201 if (oldph)
+ +
203
+
204 fsel_poll_notify_mask |= (1 << idx);
+
205 fsel_poll_handle[idx] = ph;
+
206 }
+
207
+
208 if (fsel_cnt[idx]) {
+
209 *reventsp |= POLLIN;
+
210 printf("POLL %X cnt=%u polled_zero=%u\n",
+
211 idx, fsel_cnt[idx], polled_zero);
+
212 polled_zero = 0;
+
213 } else
+
214 polled_zero++;
+
215
+
216 pthread_mutex_unlock(&fsel_mutex);
+
217 return 0;
+
218}
+
219
+
220static const struct fuse_operations fsel_oper = {
+
221 .destroy = fsel_destroy,
+
222 .getattr = fsel_getattr,
+
223 .readdir = fsel_readdir,
+
224 .open = fsel_open,
+
225 .release = fsel_release,
+
226 .read = fsel_read,
+
227 .poll = fsel_poll,
+
228};
+
229
+
230static void *fsel_producer(void *data)
+
231{
+
232 const struct timespec interval = { 0, 250000000 };
+
233 unsigned idx = 0, nr = 1;
+
234
+
235 (void) data;
+
236
+
237 while (!fsel_stop) {
+
238 int i, t;
+
239
+
240 pthread_mutex_lock(&fsel_mutex);
+
241
+
242 /*
+
243 * This is the main producer loop which is executed
+
244 * ever 500ms. On each iteration, it fills one byte
+
245 * to 1, 2 or 4 files and sends poll notification if
+
246 * requested.
+
247 */
+
248 for (i = 0, t = idx; i < nr;
+
249 i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
+
250 if (fsel_cnt[t] == FSEL_CNT_MAX)
+
251 continue;
+
252
+
253 fsel_cnt[t]++;
+
254 if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
+
255 struct fuse_pollhandle *ph;
+
256
+
257 printf("NOTIFY %X\n", t);
+
258 ph = fsel_poll_handle[t];
+
259 fuse_notify_poll(ph);
+ +
261 fsel_poll_notify_mask &= ~(1 << t);
+
262 fsel_poll_handle[t] = NULL;
+
263 }
+
264 }
+
265
+
266 idx = (idx + 1) % FSEL_FILES;
+
267 if (idx == 0)
+
268 nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
+
269
+
270 pthread_mutex_unlock(&fsel_mutex);
+
271
+
272 nanosleep(&interval, NULL);
+
273 }
+
274
+
275 return NULL;
+
276}
+
277
+
278int main(int argc, char *argv[])
+
279{
+
280 pthread_attr_t attr;
+
281 int ret;
+
282
+
283 errno = pthread_mutex_init(&fsel_mutex, NULL);
+
284 if (errno) {
+
285 perror("pthread_mutex_init");
+
286 return 1;
+
287 }
+
288
+
289 errno = pthread_attr_init(&attr);
+
290 if (errno) {
+
291 perror("pthread_attr_init");
+
292 return 1;
+
293 }
+
294
+
295 errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
+
296 if (errno) {
+
297 perror("pthread_create");
+
298 return 1;
+
299 }
+
300
+
301 ret = fuse_main(argc, argv, &fsel_oper, NULL);
+
302
+
303 return ret;
+
304}
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+ +
struct fuse * fuse
Definition fuse.h:862
+ + +
uint32_t nonseekable
Definition fuse_common.h:78
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2poll__client_8c.html b/doc/html/fuse-3_818_81_2example_2poll__client_8c.html new file mode 100644 index 0000000..d8bc80d --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2poll__client_8c.html @@ -0,0 +1,145 @@ + + + + + + + +libfuse: fuse-3.18.1/example/poll_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
poll_client.c File Reference
+
+
+
#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the poll.c example file systsem.

+

Compile with:

 gcc -Wall poll_client.c -o poll_client
+

+Source code

+
/*
+
FUSE fselclient: FUSE select example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#include <sys/select.h>
+
#include <sys/time.h>
+
#include <sys/types.h>
+
#include <sys/stat.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <ctype.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <errno.h>
+
+
#define FSEL_FILES 16
+
+
int main(void)
+
{
+
static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
+
int fds[FSEL_FILES];
+
int i, nfds, tries;
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
char name[] = { hex_map[i], '\0' };
+
fds[i] = open(name, O_RDONLY);
+
if (fds[i] < 0) {
+
perror("open");
+
return 1;
+
}
+
}
+
nfds = fds[FSEL_FILES - 1] + 1;
+
+
for(tries=0; tries < 16; tries++) {
+
static char buf[4096];
+
fd_set rfds;
+
int rc;
+
+
FD_ZERO(&rfds);
+
for (i = 0; i < FSEL_FILES; i++)
+
FD_SET(fds[i], &rfds);
+
+
rc = select(nfds, &rfds, NULL, NULL, NULL);
+
+
if (rc < 0) {
+
perror("select");
+
return 1;
+
}
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
if (!FD_ISSET(fds[i], &rfds)) {
+
printf("_: ");
+
continue;
+
}
+
printf("%X:", i);
+
rc = read(fds[i], buf, sizeof(buf));
+
if (rc < 0) {
+
perror("read");
+
return 1;
+
}
+
printf("%02d ", rc);
+
}
+
printf("\n");
+
}
+
return 0;
+
}
+
+

Definition in file poll_client.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2poll__client_8c_source.html b/doc/html/fuse-3_818_81_2example_2poll__client_8c_source.html new file mode 100644 index 0000000..c223274 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2poll__client_8c_source.html @@ -0,0 +1,131 @@ + + + + + + + +libfuse: fuse-3.18.1/example/poll_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
poll_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fselclient: FUSE select example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
23#include <sys/select.h>
+
24#include <sys/time.h>
+
25#include <sys/types.h>
+
26#include <sys/stat.h>
+
27#include <fcntl.h>
+
28#include <unistd.h>
+
29#include <ctype.h>
+
30#include <stdio.h>
+
31#include <stdlib.h>
+
32#include <errno.h>
+
33
+
34#define FSEL_FILES 16
+
35
+
36int main(void)
+
37{
+
38 static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
+
39 int fds[FSEL_FILES];
+
40 int i, nfds, tries;
+
41
+
42 for (i = 0; i < FSEL_FILES; i++) {
+
43 char name[] = { hex_map[i], '\0' };
+
44 fds[i] = open(name, O_RDONLY);
+
45 if (fds[i] < 0) {
+
46 perror("open");
+
47 return 1;
+
48 }
+
49 }
+
50 nfds = fds[FSEL_FILES - 1] + 1;
+
51
+
52 for(tries=0; tries < 16; tries++) {
+
53 static char buf[4096];
+
54 fd_set rfds;
+
55 int rc;
+
56
+
57 FD_ZERO(&rfds);
+
58 for (i = 0; i < FSEL_FILES; i++)
+
59 FD_SET(fds[i], &rfds);
+
60
+
61 rc = select(nfds, &rfds, NULL, NULL, NULL);
+
62
+
63 if (rc < 0) {
+
64 perror("select");
+
65 return 1;
+
66 }
+
67
+
68 for (i = 0; i < FSEL_FILES; i++) {
+
69 if (!FD_ISSET(fds[i], &rfds)) {
+
70 printf("_: ");
+
71 continue;
+
72 }
+
73 printf("%X:", i);
+
74 rc = read(fds[i], buf, sizeof(buf));
+
75 if (rc < 0) {
+
76 perror("read");
+
77 return 1;
+
78 }
+
79 printf("%02d ", rc);
+
80 }
+
81 printf("\n");
+
82 }
+
83 return 0;
+
84}
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2printcap_8c.html b/doc/html/fuse-3_818_81_2example_2printcap_8c.html new file mode 100644 index 0000000..8901402 --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2printcap_8c.html @@ -0,0 +1,240 @@ + + + + + + + +libfuse: fuse-3.18.1/example/printcap.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
printcap.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem that prints out all capabilities supported by the kernel and then exits.

+

Compile with:

gcc -Wall printcap.c `pkg-config fuse3 --cflags --libs` -o printcap
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <unistd.h>
+
#include <string.h>
+
#include <stdlib.h>
+
+
struct fuse_session *se;
+
+
// Define a structure to hold capability information
+
struct cap_info {
+
uint64_t flag;
+
const char *name;
+
};
+
+
// Define an array of all capabilities
+
static const struct cap_info capabilities[] = {
+
{FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
+
{FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
+
{FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
+
{FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
+
{FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
+
{FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
+
{FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
+
{FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
+
{FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
+
{FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
+
{FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
+
{FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
+
{FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
+
{FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
+
{FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
+
{FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
+
{FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
+
{FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
+
{FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
+
{FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
+
{FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
+
{FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
+
{FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
+
{FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
+
{FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
+
{FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
+
{FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
+
{FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
+
// Add any new capabilities here
+
{0, NULL} // Sentinel to mark the end of the array
+
};
+
+
static void print_capabilities(struct fuse_conn_info *conn)
+
{
+
printf("Capabilities:\n");
+
for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
+
if (fuse_get_feature_flag(conn, cap->flag)) {
+
printf("\t%s\n", cap->name);
+
}
+
}
+
}
+
+
static void pc_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void) userdata;
+
+
printf("Protocol version: %d.%d\n", conn->proto_major,
+
conn->proto_minor);
+
print_capabilities(conn);
+ +
}
+
+
+
static const struct fuse_lowlevel_ops pc_oper = {
+
.init = pc_init,
+
};
+
+
int main(int argc, char **argv)
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
char *mountpoint;
+
int ret = -1;
+
+
mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
+
if(mkdtemp(mountpoint) == NULL) {
+
perror("mkdtemp");
+
return 1;
+
}
+
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
+
se = fuse_session_new(&args, &pc_oper,
+
sizeof(pc_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, mountpoint) != 0)
+
goto err_out3;
+
+
ret = fuse_session_loop(se);
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
rmdir(mountpoint);
+
free(mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t proto_major
+
uint32_t proto_minor
+ +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file printcap.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2example_2printcap_8c_source.html b/doc/html/fuse-3_818_81_2example_2printcap_8c_source.html new file mode 100644 index 0000000..6bc0c0c --- /dev/null +++ b/doc/html/fuse-3_818_81_2example_2printcap_8c_source.html @@ -0,0 +1,231 @@ + + + + + + + +libfuse: fuse-3.18.1/example/printcap.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
printcap.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
22#define FUSE_USE_VERSION 31
+
23
+
24#include <fuse_lowlevel.h>
+
25#include <stdio.h>
+
26#include <unistd.h>
+
27#include <string.h>
+
28#include <stdlib.h>
+
29
+
30struct fuse_session *se;
+
31
+
32// Define a structure to hold capability information
+
33struct cap_info {
+
34 uint64_t flag;
+
35 const char *name;
+
36};
+
37
+
38// Define an array of all capabilities
+
39static const struct cap_info capabilities[] = {
+
40 {FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
+
41 {FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
+
42 {FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
+
43 {FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
+
44 {FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
+
45 {FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
+
46 {FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
+
47 {FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
+
48 {FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
+
49 {FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
+
50 {FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
+
51 {FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
+
52 {FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
+
53 {FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
+
54 {FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
+
55 {FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
+
56 {FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
+
57 {FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
+
58 {FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
+
59 {FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
+
60 {FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
+
61 {FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
+
62 {FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
+
63 {FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
+
64 {FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
+
65 {FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
+
66 {FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
+
67 {FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
+
68 // Add any new capabilities here
+
69 {0, NULL} // Sentinel to mark the end of the array
+
70};
+
71
+
72static void print_capabilities(struct fuse_conn_info *conn)
+
73{
+
74 printf("Capabilities:\n");
+
75 for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
+
76 if (fuse_get_feature_flag(conn, cap->flag)) {
+
77 printf("\t%s\n", cap->name);
+
78 }
+
79 }
+
80}
+
81
+
82static void pc_init(void *userdata, struct fuse_conn_info *conn)
+
83{
+
84 (void) userdata;
+
85
+
86 printf("Protocol version: %d.%d\n", conn->proto_major,
+
87 conn->proto_minor);
+
88 print_capabilities(conn);
+ +
90}
+
91
+
92
+
93static const struct fuse_lowlevel_ops pc_oper = {
+
94 .init = pc_init,
+
95};
+
96
+
97int main(int argc, char **argv)
+
98{
+
99 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
100 char *mountpoint;
+
101 int ret = -1;
+
102
+
103 mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
+
104 if(mkdtemp(mountpoint) == NULL) {
+
105 perror("mkdtemp");
+
106 return 1;
+
107 }
+
108
+
109 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
111
+
112 se = fuse_session_new(&args, &pc_oper,
+
113 sizeof(pc_oper), NULL);
+
114 if (se == NULL)
+
115 goto err_out1;
+
116
+
117 if (fuse_set_signal_handlers(se) != 0)
+
118 goto err_out2;
+
119
+
120 if (fuse_session_mount(se, mountpoint) != 0)
+
121 goto err_out3;
+
122
+
123 ret = fuse_session_loop(se);
+
124
+ +
126err_out3:
+ +
128err_out2:
+ +
130err_out1:
+
131 rmdir(mountpoint);
+
132 free(mountpoint);
+
133 fuse_opt_free_args(&args);
+
134
+
135 return ret ? 1 : 0;
+
136}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t proto_major
+
uint32_t proto_minor
+ +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2include_2cuse__lowlevel_8h_source.html b/doc/html/fuse-3_818_81_2include_2cuse__lowlevel_8h_source.html new file mode 100644 index 0000000..0113142 --- /dev/null +++ b/doc/html/fuse-3_818_81_2include_2cuse__lowlevel_8h_source.html @@ -0,0 +1,152 @@ + + + + + + + +libfuse: fuse-3.18.1/include/cuse_lowlevel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_lowlevel.h
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file LGPL2.txt.
+
8
+
9 Read example/cusexmp.c for usages.
+
10*/
+
11
+
12#ifndef CUSE_LOWLEVEL_H_
+
13#define CUSE_LOWLEVEL_H_
+
14
+
15#ifndef FUSE_USE_VERSION
+
16#define FUSE_USE_VERSION 29
+
17#endif
+
18
+
19#include "fuse_lowlevel.h"
+
20
+
21#include <fcntl.h>
+
22#include <sys/types.h>
+
23#include <sys/uio.h>
+
24
+
25#ifdef __cplusplus
+
26extern "C" {
+
27#endif
+
28
+
29#define CUSE_UNRESTRICTED_IOCTL (1 << 0) /* use unrestricted ioctl */
+
30
+
31struct fuse_session;
+
32
+
33struct cuse_info {
+
34 unsigned dev_major;
+
35 unsigned dev_minor;
+
36 unsigned dev_info_argc;
+
37 const char **dev_info_argv;
+
38 unsigned flags;
+
39};
+
40
+
41/*
+
42 * Most ops behave almost identically to the matching fuse_lowlevel
+
43 * ops except that they don't take @ino.
+
44 *
+
45 * init_done : called after initialization is complete
+
46 * read/write : always direct IO, simultaneous operations allowed
+
47 * ioctl : might be in unrestricted mode depending on ci->flags
+
48 */
+
49struct cuse_lowlevel_ops {
+
50 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
51 void (*init_done) (void *userdata);
+
52 void (*destroy) (void *userdata);
+
53 void (*open) (fuse_req_t req, struct fuse_file_info *fi);
+
54 void (*read) (fuse_req_t req, size_t size, off_t off,
+
55 struct fuse_file_info *fi);
+
56 void (*write) (fuse_req_t req, const char *buf, size_t size, off_t off,
+
57 struct fuse_file_info *fi);
+
58 void (*flush) (fuse_req_t req, struct fuse_file_info *fi);
+
59 void (*release) (fuse_req_t req, struct fuse_file_info *fi);
+
60 void (*fsync) (fuse_req_t req, int datasync, struct fuse_file_info *fi);
+
61 void (*ioctl) (fuse_req_t req, int cmd, void *arg,
+
62 struct fuse_file_info *fi, unsigned int flags,
+
63 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
64 void (*poll) (fuse_req_t req, struct fuse_file_info *fi,
+
65 struct fuse_pollhandle *ph);
+
66};
+
67
+
68struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
69 const struct cuse_info *ci,
+
70 const struct cuse_lowlevel_ops *clop,
+
71 void *userdata);
+
72
+
73struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
74 const struct cuse_info *ci,
+
75 const struct cuse_lowlevel_ops *clop,
+
76 int *multithreaded, void *userdata);
+
77
+
78void cuse_lowlevel_teardown(struct fuse_session *se);
+
79
+
80int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
81 const struct cuse_lowlevel_ops *clop, void *userdata);
+
82
+
83#ifdef __cplusplus
+
84}
+
85#endif
+
86
+
87#endif /* CUSE_LOWLEVEL_H_ */
+
struct fuse_req * fuse_req_t
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_818_81_2include_2fuse_8h.html b/doc/html/fuse-3_818_81_2include_2fuse_8h.html new file mode 100644 index 0000000..024fc24 --- /dev/null +++ b/doc/html/fuse-3_818_81_2include_2fuse_8h.html @@ -0,0 +1,839 @@ + + + + + + + +libfuse: fuse-3.18.1/include/fuse.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse.h File Reference
+
+
+
#include "fuse_common.h"
+#include <fcntl.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + + + +

+Data Structures

struct  fuse_config
 
struct  fuse_operations
 
struct  fuse_context
 
+ + + +

+Macros

#define FUSE_REGISTER_MODULE(name_, factory_)    fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
 
+ + + + + +

+Typedefs

typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
 
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
 
+ + + + + +

+Enumerations

enum  fuse_readdir_flags { FUSE_READDIR_DEFAULTS = 0 +, FUSE_READDIR_PLUS = (1 << 0) + }
 
enum  fuse_fill_dir_flags { FUSE_FILL_DIR_DEFAULTS = 0 +, FUSE_FILL_DIR_PLUS = (1 << 1) + }
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

int fuse_main_real_versioned (int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
 
void fuse_lib_help (struct fuse_args *args)
 
int fuse_mount (struct fuse *f, const char *mountpoint)
 
void fuse_unmount (struct fuse *f)
 
void fuse_destroy (struct fuse *f)
 
int fuse_loop (struct fuse *f)
 
void fuse_exit (struct fuse *f)
 
struct fuse_contextfuse_get_context (void)
 
int fuse_getgroups (int size, gid_t list[])
 
int fuse_interrupted (void)
 
int fuse_invalidate_path (struct fuse *f, const char *path)
 
int fuse_start_cleanup_thread (struct fuse *fuse)
 
void fuse_stop_cleanup_thread (struct fuse *fuse)
 
int fuse_clean_cache (struct fuse *fuse)
 
struct fuse_fs * fuse_fs_new (const struct fuse_operations *op, size_t op_size, void *private_data)
 
struct fuse_session * fuse_get_session (struct fuse *f)
 
int fuse_open_channel (const char *mountpoint, const char *options)
 
+

Detailed Description

+

This file defines the library interface of FUSE

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header.

+ +

Definition in file fuse.h.

+

Macro Definition Documentation

+ +

◆ FUSE_REGISTER_MODULE

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_REGISTER_MODULE( name_,
 factory_ 
)    fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+

Register filesystem module

+

If the "-omodules=*name*_:..." option is present, filesystem objects are created and pushed onto the stack with the factory_ function.

+
Parameters
+ + + +
name_the name of this filesystem module
factory_the factory function for this filesystem module
+
+
+ +

Definition at line 1414 of file fuse.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_fill_dir_t

+ +
+
+ + + + +
typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
+
+

Function to add an entry in a readdir() operation

+

The off parameter can be any non-zero value that enables the filesystem to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to indicate that seeking in directories is not supported.

+
Parameters
+ + + + + + +
bufthe buffer passed to the readdir() operation
namethe file name of the directory entry
stbuffile attributes, can be NULL
offoffset of the next entry or zero
flagsfill flags
+
+
+
Returns
1 if buffer is full, zero otherwise
+ +

Definition at line 93 of file fuse.h.

+ +
+
+ +

◆ fuse_module_factory_t

+ +
+
+ + + + +
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
+
+

Factory for creating filesystem objects

+

The function may use and remove options from 'args' that belong to this module.

+

For now the 'fs' vector always contains exactly one filesystem. This is the filesystem which will be below the newly created filesystem in the stack.

+
Parameters
+ + + +
argsthe command line arguments
fsNULL terminated filesystem object vector
+
+
+
Returns
the new filesystem object
+ +

Definition at line 1385 of file fuse.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_fill_dir_flags

+ +
+
+ + + + +
enum fuse_fill_dir_flags
+
+

Readdir flags, passed to fuse_fill_dir_t callback.

+ + +
Enumerator
FUSE_FILL_DIR_DEFAULTS 

"Plus" mode: file attributes are valid

+

The attributes are used by the kernel to prefill the inode cache during a readdir.

+

It is okay to set FUSE_FILL_DIR_PLUS if FUSE_READDIR_PLUS is not set and vice versa.

+

This does not make libfuse honor the 'st_ino' field. That is controlled by the 'use_ino' option instead.

+
+ +

Definition at line 61 of file fuse.h.

+ +
+
+ +

◆ fuse_readdir_flags

+ +
+
+ + + + +
enum fuse_readdir_flags
+
+

Readdir flags, passed to ->readdir()

+ + +
Enumerator
FUSE_READDIR_DEFAULTS 

"Plus" mode.

+

The kernel wants to prefill the inode cache during readdir. The filesystem may honour this by filling in the attributes and setting FUSE_FILL_DIR_FLAGS for the filler function. The filesystem may also just ignore this flag completely.

+
+ +

Definition at line 45 of file fuse.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_clean_cache()

+ +
+
+ + + + + + + + +
int fuse_clean_cache (struct fuse * fuse)
+
+

Iterate over cache removing stale entries use in conjunction with "-oremember"

+

NOTE: This is already done for the standard sessions

+
Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
the number of seconds until the next cleanup
+ +

Definition at line 4433 of file fuse.c.

+ +
+
+ +

◆ fuse_destroy()

+ +
+
+ + + + + + + + +
void fuse_destroy (struct fuse * f)
+
+

Destroy the FUSE handle.

+

NOTE: This function does not unmount the filesystem. If this is needed, call fuse_unmount() before calling this function.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5146 of file fuse.c.

+ +
+
+ +

◆ fuse_exit()

+ +
+
+ + + + + + + + +
void fuse_exit (struct fuse * f)
+
+

Flag session as terminated

+

This function will cause any running event loops to exit on the next opportunity.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 4639 of file fuse.c.

+ +
+
+ +

◆ fuse_fs_new()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
struct fuse_fs * fuse_fs_new (const struct fuse_operationsop,
size_t op_size,
void * private_data 
)
+
+

Create a new fuse filesystem object

+

This is usually called from the factory of a fuse module to create a new instance of a filesystem.

+
Parameters
+ + + + +
opthe filesystem operations
op_sizethe size of the fuse_operations structure
private_dataInitial value for the private_data field of struct fuse_context. May be overridden by the struct fuse_operations.init handler.
+
+
+
Returns
a new filesystem object
+ +

Definition at line 4854 of file fuse.c.

+ +
+
+ +

◆ fuse_get_context()

+ +
+
+ + + + + + + + +
struct fuse_context * fuse_get_context (void )
+
+

Get the current context

+

The context is only valid for the duration of a filesystem operation, and thus must not be stored and used later.

+
Returns
the context
+ +

Definition at line 4644 of file fuse.c.

+ +
+
+ +

◆ fuse_get_session()

+ +
+
+ + + + + + + + +
struct fuse_session * fuse_get_session (struct fuse * f)
+
+

Get session from fuse object

+ +

Definition at line 4520 of file fuse.c.

+ +
+
+ +

◆ fuse_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_getgroups (int size,
gid_t list[] 
)
+
+

Get the current supplementary group IDs for the current request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + +
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 4654 of file fuse.c.

+ +
+
+ +

◆ fuse_interrupted()

+ +
+
+ + + + + + + + +
int fuse_interrupted (void )
+
+

Check if the current request has already been interrupted

+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 4663 of file fuse.c.

+ +
+
+ +

◆ fuse_invalidate_path()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_invalidate_path (struct fuse * f,
const char * path 
)
+
+

Invalidates cache for the given path.

+

This calls fuse_lowlevel_notify_inval_inode internally.

+
Returns
0 on successful invalidation, negative error value otherwise. This routine may return -ENOENT to indicate that there was no entry to be invalidated, e.g., because the path has not been seen before or has been forgotten; this should not be considered to be an error.
+ +

Definition at line 4673 of file fuse.c.

+ +
+
+ +

◆ fuse_lib_help()

+ +
+
+ + + + + + + + +
void fuse_lib_help (struct fuse_argsargs)
+
+

Print available options (high- and low-level) to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+

The function looks at the argument vector only to determine if there are additional modules to be loaded (module=foo option), and attempts to call their help functions as well.

+
Parameters
+ + +
argsthe argument vector.
+
+
+ +

Definition at line 4744 of file fuse.c.

+ +
+
+ +

◆ fuse_loop()

+ +
+
+ + + + + + + + +
int fuse_loop (struct fuse * f)
+
+

FUSE event loop.

+

Requests from the kernel are processed, and the appropriate operations are called.

+

For a description of the return value and the conditions when the event loop exits, refer to the documentation of fuse_session_loop().

+
Parameters
+ + +
fthe FUSE handle
+
+
+
Returns
see fuse_session_loop()
+

See also: fuse_loop_mt()

+ +

Definition at line 4577 of file fuse.c.

+ +
+
+ +

◆ fuse_main_real_versioned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_main_real_versioned (int argc,
char * argv[],
const struct fuse_operationsop,
size_t op_size,
struct libfuse_versionversion,
void * user_data 
)
+
+

The real main function

+

Do not call this directly, use fuse_main()

+ +

Definition at line 307 of file helper.c.

+ +
+
+ +

◆ fuse_mount()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_mount (struct fuse * f,
const char * mountpoint 
)
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
fthe FUSE handle
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 5197 of file fuse.c.

+ +
+
+ +

◆ fuse_open_channel()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_open_channel (const char * mountpoint,
const char * options 
)
+
+

Open a FUSE file descriptor and set up the mount for the given mountpoint and flags.

+
Parameters
+ + + +
mountpointreference to the mount in the file system
optionsmount options
+
+
+
Returns
the FUSE file descriptor or -1 upon error
+ +

Definition at line 482 of file helper.c.

+ +
+
+ +

◆ fuse_start_cleanup_thread()

+ +
+
+ + + + + + + + +
int fuse_start_cleanup_thread (struct fuse * fuse)
+
+

Start the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
0 on success and -1 on error
+ +

Definition at line 4906 of file fuse.c.

+ +
+
+ +

◆ fuse_stop_cleanup_thread()

+ +
+
+ + + + + + + + +
void fuse_stop_cleanup_thread (struct fuse * fuse)
+
+

Stop the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+ +

Definition at line 4914 of file fuse.c.

+ +
+
+ +

◆ fuse_unmount()

+ +
+
+ + + + + + + + +
void fuse_unmount (struct fuse * f)
+
+

Unmount a FUSE file system.

+

See fuse_session_unmount() for additional information.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5202 of file fuse.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2include_2fuse_8h_source.html b/doc/html/fuse-3_818_81_2include_2fuse_8h_source.html new file mode 100644 index 0000000..fa98cd1 --- /dev/null +++ b/doc/html/fuse-3_818_81_2include_2fuse_8h_source.html @@ -0,0 +1,650 @@ + + + + + + + +libfuse: fuse-3.18.1/include/fuse.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#ifndef FUSE_H_
+
10#define FUSE_H_
+
11
+
19#include "fuse_common.h"
+
20
+
21#include <fcntl.h>
+
22#include <time.h>
+
23#include <sys/types.h>
+
24#include <sys/stat.h>
+
25#include <sys/statvfs.h>
+
26#include <sys/uio.h>
+
27
+
28#ifdef __cplusplus
+
29extern "C" {
+
30#endif
+
31
+
32/* ----------------------------------------------------------- *
+
33 * Basic FUSE API *
+
34 * ----------------------------------------------------------- */
+
35
+
36/* Forward declaration */
+
37struct statx;
+
38
+
40struct fuse;
+
41
+
+ + +
55 FUSE_READDIR_PLUS = (1 << 0)
+
56};
+
+
57
+
+ + +
75 FUSE_FILL_DIR_PLUS = (1 << 1)
+
76};
+
+
77
+
93typedef int (*fuse_fill_dir_t) (void *buf, const char *name,
+
94 const struct stat *stbuf, off_t off,
+
95 enum fuse_fill_dir_flags flags);
+
107struct fuse_config {
+
112 int32_t set_gid;
+
113 uint32_t gid;
+
114
+
119 int32_t set_uid;
+
120 uint32_t uid;
+
121
+
126 int32_t set_mode;
+
127 uint32_t umask;
+
128
+
133 double entry_timeout;
+
134
+
143 double negative_timeout;
+
144
+
149 double attr_timeout;
+
150
+
154 int32_t intr;
+
155
+
161 int32_t intr_signal;
+
162
+
173 int32_t remember;
+
174
+
191 int32_t hard_remove;
+
192
+
204 int32_t use_ino;
+
205
+
213 int32_t readdir_ino;
+
214
+
232 int32_t direct_io;
+
233
+
251 int32_t kernel_cache;
+
252
+
259 int32_t auto_cache;
+
260
+
261 /*
+
262 * The timeout in seconds for which file attributes are cached
+
263 * for the purpose of checking if auto_cache should flush the
+
264 * file data on open.
+
265 */
+
266 int32_t ac_attr_timeout_set;
+
267 double ac_attr_timeout;
+
268
+
279 int32_t nullpath_ok;
+
280
+
285 int32_t show_help;
+
286 char *modules;
+
287 int32_t debug;
+
288
+
294 uint32_t fmask;
+
295 uint32_t dmask;
+
296
+
303 int32_t no_rofd_flush;
+
304
+ +
319
+
320
+
324 uint32_t flags;
+
325
+
329 uint64_t reserved[48];
+
330};
+
331
+
332
+
355struct fuse_operations {
+
367 int (*getattr) (const char *, struct stat *, struct fuse_file_info *fi);
+
368
+
377 int (*readlink) (const char *, char *, size_t);
+
378
+
385 int (*mknod) (const char *, mode_t, dev_t);
+
386
+
393 int (*mkdir) (const char *, mode_t);
+
394
+
396 int (*unlink) (const char *);
+
397
+
399 int (*rmdir) (const char *);
+
400
+
402 int (*symlink) (const char *, const char *);
+
403
+
413 int (*rename) (const char *, const char *, unsigned int flags);
+
414
+
416 int (*link) (const char *, const char *);
+
417
+
423 int (*chmod) (const char *, mode_t, struct fuse_file_info *fi);
+
424
+
433 int (*chown) (const char *, uid_t, gid_t, struct fuse_file_info *fi);
+
434
+
443 int (*truncate) (const char *, off_t, struct fuse_file_info *fi);
+
444
+
492 int (*open) (const char *, struct fuse_file_info *);
+
493
+
503 int (*read) (const char *, char *, size_t, off_t,
+
504 struct fuse_file_info *);
+
505
+
515 int (*write) (const char *, const char *, size_t, off_t,
+
516 struct fuse_file_info *);
+
517
+
522 int (*statfs) (const char *, struct statvfs *);
+
523
+
552 int (*flush) (const char *, struct fuse_file_info *);
+
553
+
566 int (*release) (const char *, struct fuse_file_info *);
+
567
+
573 int (*fsync) (const char *, int, struct fuse_file_info *);
+
574
+
576 int (*setxattr) (const char *, const char *, const char *, size_t, int);
+
577
+
579 int (*getxattr) (const char *, const char *, char *, size_t);
+
580
+
582 int (*listxattr) (const char *, char *, size_t);
+
583
+
585 int (*removexattr) (const char *, const char *);
+
586
+
595 int (*opendir) (const char *, struct fuse_file_info *);
+
596
+
619 int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t,
+
620 struct fuse_file_info *, enum fuse_readdir_flags);
+
621
+
627 int (*releasedir) (const char *, struct fuse_file_info *);
+
628
+
637 int (*fsyncdir) (const char *, int, struct fuse_file_info *);
+
638
+
647 void *(*init) (struct fuse_conn_info *conn,
+
648 struct fuse_config *cfg);
+
649
+
655 void (*destroy) (void *private_data);
+
656
+
666 int (*access) (const char *, int);
+
667
+
678 int (*create) (const char *, mode_t, struct fuse_file_info *);
+
679
+
710 int (*lock) (const char *, struct fuse_file_info *, int cmd,
+
711 struct flock *);
+
712
+
725 int (*utimens) (const char *, const struct timespec tv[2],
+
726 struct fuse_file_info *fi);
+
727
+
734 int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
+
735
+
736#if FUSE_USE_VERSION < 35
+
737 int (*ioctl) (const char *, int cmd, void *arg,
+
738 struct fuse_file_info *, unsigned int flags, void *data);
+
739#else
+
756 int (*ioctl) (const char *, unsigned int cmd, void *arg,
+
757 struct fuse_file_info *, unsigned int flags, void *data);
+
758#endif
+
759
+
775 int (*poll) (const char *, struct fuse_file_info *,
+
776 struct fuse_pollhandle *ph, unsigned *reventsp);
+
777
+
787 int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off,
+
788 struct fuse_file_info *);
+
789
+
804 int (*read_buf) (const char *, struct fuse_bufvec **bufp,
+
805 size_t size, off_t off, struct fuse_file_info *);
+
824 int (*flock) (const char *, struct fuse_file_info *, int op);
+
825
+
834 int (*fallocate) (const char *, int, off_t, off_t,
+
835 struct fuse_file_info *);
+
836
+
849 ssize_t (*copy_file_range) (const char *path_in,
+
850 struct fuse_file_info *fi_in,
+
851 off_t offset_in, const char *path_out,
+
852 struct fuse_file_info *fi_out,
+
853 off_t offset_out, size_t size, int flags);
+
854
+
858 off_t (*lseek) (const char *, off_t off, int whence, struct fuse_file_info *);
+
859
+
868 int (*statx)(const char *path, int flags, int mask, struct statx *stxbuf,
+
869 struct fuse_file_info *fi);
+
870};
+
871
+
877struct fuse_context {
+
879 struct fuse *fuse;
+
880
+
882 uid_t uid;
+
883
+
885 gid_t gid;
+
886
+
888 pid_t pid;
+
889
+
891 void *private_data;
+
892
+
894 mode_t umask;
+
895};
+
896
+
902int fuse_main_real_versioned(int argc, char *argv[],
+
903 const struct fuse_operations *op, size_t op_size,
+
904 struct libfuse_version *version, void *user_data);
+
905static inline int fuse_main_real(int argc, char *argv[],
+
906 const struct fuse_operations *op,
+
907 size_t op_size, void *user_data)
+
908{
+
909 struct libfuse_version version = { .major = FUSE_MAJOR_VERSION,
+
910 .minor = FUSE_MINOR_VERSION,
+
911 .hotfix = FUSE_HOTFIX_VERSION,
+
912 .padding = 0 };
+
913
+
914 fuse_log(FUSE_LOG_ERR,
+
915 "%s is a libfuse internal function, please use fuse_main()\n",
+
916 __func__);
+
917
+
918 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
919 user_data);
+
920}
+
921
+
976static inline int fuse_main_fn(int argc, char *argv[],
+
977 const struct fuse_operations *op,
+
978 void *user_data)
+
979{
+
980 struct libfuse_version version = {
+
981 .major = FUSE_MAJOR_VERSION,
+
982 .minor = FUSE_MINOR_VERSION,
+
983 .hotfix = FUSE_HOTFIX_VERSION,
+
984 .padding = 0
+
985 };
+
986
+
987 return fuse_main_real_versioned(argc, argv, op, sizeof(*(op)), &version,
+
988 user_data);
+
989}
+
990#define fuse_main(argc, argv, op, user_data) \
+
991 fuse_main_fn(argc, argv, op, user_data)
+
992
+
993/* ----------------------------------------------------------- *
+
994 * More detailed API *
+
995 * ----------------------------------------------------------- */
+
996
+
1008void fuse_lib_help(struct fuse_args *args);
+
1009
+
1010/* Do not call this directly, use fuse_new() instead */
+
1011struct fuse *_fuse_new_30(struct fuse_args *args,
+
1012 const struct fuse_operations *op, size_t op_size,
+
1013 struct libfuse_version *version, void *user_data);
+
1014struct fuse *_fuse_new_31(struct fuse_args *args,
+
1015 const struct fuse_operations *op, size_t op_size,
+
1016 struct libfuse_version *version, void *user_data);
+
1017
+
1045#if FUSE_USE_VERSION == 30
+
1046static inline struct fuse *fuse_new_fn(struct fuse_args *args,
+
1047 const struct fuse_operations *op,
+
1048 size_t op_size, void *user_data)
+
1049{
+
1050 struct libfuse_version version = {
+
1051 .major = FUSE_MAJOR_VERSION,
+
1052 .minor = FUSE_MINOR_VERSION,
+
1053 .hotfix = FUSE_HOTFIX_VERSION,
+
1054 .padding = 0
+
1055 };
+
1056
+
1057 return _fuse_new_30(args, op, op_size, &version, user_data);
+
1058}
+
1059#else /* FUSE_USE_VERSION */
+
1060static inline struct fuse *fuse_new_fn(struct fuse_args *args,
+
1061 const struct fuse_operations *op,
+
1062 size_t op_size, void *user_data)
+
1063{
+
1064 struct libfuse_version version = {
+
1065 .major = FUSE_MAJOR_VERSION,
+
1066 .minor = FUSE_MINOR_VERSION,
+
1067 .hotfix = FUSE_HOTFIX_VERSION,
+
1068 .padding = 0
+
1069 };
+
1070
+
1071 return _fuse_new_31(args, op, op_size, &version, user_data);
+
1072}
+
1073#endif
+
1074#define fuse_new(args, op, size, data) fuse_new_fn(args, op, size, data)
+
1075
+
1084int fuse_mount(struct fuse *f, const char *mountpoint);
+
1085
+
1093void fuse_unmount(struct fuse *f);
+
1094
+
1103void fuse_destroy(struct fuse *f);
+
1104
+
1120int fuse_loop(struct fuse *f);
+
1121
+
1130void fuse_exit(struct fuse *f);
+
1131
+
1132#if FUSE_USE_VERSION < 32
+
1133int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
1134#define fuse_loop_mt(f, clone_fd) fuse_loop_mt_31(f, clone_fd)
+
1135#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
1136int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config *config);
+
1137#define fuse_loop_mt(f, config) fuse_loop_mt_32(f, config)
+
1138#else
+
1170#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
1171int fuse_loop_mt(struct fuse *f, struct fuse_loop_config *config);
+
1172#else
+
1173#define fuse_loop_mt(f, config) fuse_loop_mt_312(f, config)
+
1174#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
1175#endif
+
1176
+
1177
+
1186struct fuse_context *fuse_get_context(void);
+
1187
+
1206int fuse_getgroups(int size, gid_t list[]);
+
1207
+
1213int fuse_interrupted(void);
+
1214
+
1226int fuse_invalidate_path(struct fuse *f, const char *path);
+
1227
+ +
1236
+
1243void fuse_stop_cleanup_thread(struct fuse *fuse);
+
1244
+
1254int fuse_clean_cache(struct fuse *fuse);
+
1255
+
1256/*
+
1257 * Stacking API
+
1258 */
+
1259
+
1265struct fuse_fs;
+
1266
+
1267/*
+
1268 * These functions call the relevant filesystem operation, and return
+
1269 * the result.
+
1270 *
+
1271 * If the operation is not defined, they return -ENOSYS, with the
+
1272 * exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir,
+
1273 * fuse_fs_releasedir and fuse_fs_statfs, which return 0.
+
1274 */
+
1275
+
1276int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1277 struct fuse_file_info *fi);
+
1278int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1279 const char *newpath, unsigned int flags);
+
1280int fuse_fs_unlink(struct fuse_fs *fs, const char *path);
+
1281int fuse_fs_rmdir(struct fuse_fs *fs, const char *path);
+
1282int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname,
+
1283 const char *path);
+
1284int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath);
+
1285int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1286 struct fuse_file_info *fi);
+
1287int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1288 struct fuse_file_info *fi);
+
1289int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size,
+
1290 off_t off, struct fuse_file_info *fi);
+
1291int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1292 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1293 struct fuse_file_info *fi);
+
1294int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf,
+
1295 size_t size, off_t off, struct fuse_file_info *fi);
+
1296int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1297 struct fuse_bufvec *buf, off_t off,
+
1298 struct fuse_file_info *fi);
+
1299int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1300 struct fuse_file_info *fi);
+
1301int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1302 struct fuse_file_info *fi);
+
1303int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf);
+
1304int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1305 struct fuse_file_info *fi);
+
1306int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
1307 fuse_fill_dir_t filler, off_t off,
+
1308 struct fuse_file_info *fi, enum fuse_readdir_flags flags);
+
1309int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1310 struct fuse_file_info *fi);
+
1311int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1312 struct fuse_file_info *fi);
+
1313int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
1314 struct fuse_file_info *fi);
+
1315int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
1316 struct fuse_file_info *fi, int cmd, struct flock *lock);
+
1317int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
1318 struct fuse_file_info *fi, int op);
+
1319int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1320 struct fuse_file_info *fi);
+
1321int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid,
+
1322 struct fuse_file_info *fi);
+
1323int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
1324 struct fuse_file_info *fi);
+
1325int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
1326 const struct timespec tv[2], struct fuse_file_info *fi);
+
1327int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask);
+
1328int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
1329 size_t len);
+
1330int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1331 dev_t rdev);
+
1332int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode);
+
1333int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1334 const char *value, size_t size, int flags);
+
1335int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1336 char *value, size_t size);
+
1337int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
1338 size_t size);
+
1339int fuse_fs_removexattr(struct fuse_fs *fs, const char *path,
+
1340 const char *name);
+
1341int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
1342 uint64_t *idx);
+
1343#if FUSE_USE_VERSION < 35
+
1344int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd,
+
1345 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1346 void *data);
+
1347#else
+
1348int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
1349 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1350 void *data);
+
1351#endif
+
1352int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
1353 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
1354 unsigned *reventsp);
+
1355int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
1356 off_t offset, off_t length, struct fuse_file_info *fi);
+
1357ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
1358 struct fuse_file_info *fi_in, off_t off_in,
+
1359 const char *path_out,
+
1360 struct fuse_file_info *fi_out, off_t off_out,
+
1361 size_t len, int flags);
+
1362off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
1363 struct fuse_file_info *fi);
+
1364int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask,
+
1365 struct statx *stxbuf, struct fuse_file_info *fi);
+
1366void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
1367 struct fuse_config *cfg);
+
1368void fuse_fs_destroy(struct fuse_fs *fs);
+
1369
+
1370int fuse_notify_poll(struct fuse_pollhandle *ph);
+
1371
+
1385struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
1386 void *private_data);
+
1387
+
1402typedef struct fuse_fs *(*fuse_module_factory_t)(struct fuse_args *args,
+
1403 struct fuse_fs *fs[]);
+
+
1414#define FUSE_REGISTER_MODULE(name_, factory_) \
+
1415 fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+
1416
+
1418struct fuse_session *fuse_get_session(struct fuse *f);
+
1419
+
1428int fuse_open_channel(const char *mountpoint, const char *options);
+
1429
+
1430#ifdef __cplusplus
+
1431}
+
1432#endif
+
1433
+
1434#endif /* FUSE_H_ */
+
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4654
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5197
+
int fuse_interrupted(void)
Definition fuse.c:4663
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5146
+
int fuse_main_real_versioned(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
Definition helper.c:307
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4906
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4639
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4433
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:482
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5202
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4914
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
@ FUSE_READDIR_DEFAULTS
Definition fuse.h:51
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:93
+
fuse_readdir_flags
Definition fuse.h:45
+ + + + +
uint32_t fmask
Definition fuse.h:288
+
int32_t show_help
Definition fuse.h:279
+
uint32_t flags
Definition fuse.h:318
+
int32_t direct_io
Definition fuse.h:226
+
int32_t no_rofd_flush
Definition fuse.h:297
+
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t intr_signal
Definition fuse.h:155
+
int32_t remember
Definition fuse.h:167
+
int32_t kernel_cache
Definition fuse.h:245
+
int32_t readdir_ino
Definition fuse.h:207
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t set_mode
Definition fuse.h:120
+
int32_t auto_cache
Definition fuse.h:253
+
uint64_t reserved[48]
Definition fuse.h:323
+
int32_t intr
Definition fuse.h:148
+
double negative_timeout
Definition fuse.h:137
+
int32_t set_gid
Definition fuse.h:106
+
int32_t set_uid
Definition fuse.h:113
+
int32_t hard_remove
Definition fuse.h:185
+
double attr_timeout
Definition fuse.h:143
+ + +
void * private_data
Definition fuse.h:874
+
uid_t uid
Definition fuse.h:865
+
pid_t pid
Definition fuse.h:871
+
struct fuse * fuse
Definition fuse.h:862
+
gid_t gid
Definition fuse.h:868
+
mode_t umask
Definition fuse.h:877
+ + + +
int(* mkdir)(const char *, mode_t)
Definition fuse.h:387
+
int(* truncate)(const char *, off_t, struct fuse_file_info *fi)
Definition fuse.h:437
+
int(* mknod)(const char *, mode_t, dev_t)
Definition fuse.h:379
+
int(* utimens)(const char *, const struct timespec tv[2], struct fuse_file_info *fi)
Definition fuse.h:719
+
int(* open)(const char *, struct fuse_file_info *)
Definition fuse.h:486
+
int(* opendir)(const char *, struct fuse_file_info *)
Definition fuse.h:589
+
int(* link)(const char *, const char *)
Definition fuse.h:410
+
int(* lock)(const char *, struct fuse_file_info *, int cmd, struct flock *)
Definition fuse.h:704
+
int(* read_buf)(const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *)
Definition fuse.h:798
+
int(* access)(const char *, int)
Definition fuse.h:660
+
int(* read)(const char *, char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:497
+
int(* poll)(const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp)
Definition fuse.h:769
+
void(* destroy)(void *private_data)
Definition fuse.h:649
+
int(* statfs)(const char *, struct statvfs *)
Definition fuse.h:516
+
int(* fallocate)(const char *, int, off_t, off_t, struct fuse_file_info *)
Definition fuse.h:828
+
int(* removexattr)(const char *, const char *)
Definition fuse.h:579
+
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
int(* releasedir)(const char *, struct fuse_file_info *)
Definition fuse.h:621
+
int(* rename)(const char *, const char *, unsigned int flags)
Definition fuse.h:407
+
off_t(* lseek)(const char *, off_t off, int whence, struct fuse_file_info *)
Definition fuse.h:852
+
int(* write)(const char *, const char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:509
+
int(* write_buf)(const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *)
Definition fuse.h:781
+
int(* unlink)(const char *)
Definition fuse.h:390
+
ssize_t(* copy_file_range)(const char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags)
Definition fuse.h:843
+
int(* fsync)(const char *, int, struct fuse_file_info *)
Definition fuse.h:567
+
int(* create)(const char *, mode_t, struct fuse_file_info *)
Definition fuse.h:672
+
int(* setxattr)(const char *, const char *, const char *, size_t, int)
Definition fuse.h:570
+
int(* statx)(const char *path, int flags, int mask, struct statx *stxbuf, struct fuse_file_info *fi)
Definition fuse.h:868
+
int(* listxattr)(const char *, char *, size_t)
Definition fuse.h:576
+
int(* readlink)(const char *, char *, size_t)
Definition fuse.h:371
+
int(* symlink)(const char *, const char *)
Definition fuse.h:396
+
int(* fsyncdir)(const char *, int, struct fuse_file_info *)
Definition fuse.h:631
+
int(* release)(const char *, struct fuse_file_info *)
Definition fuse.h:560
+
int(* chown)(const char *, uid_t, gid_t, struct fuse_file_info *fi)
Definition fuse.h:427
+
int(* chmod)(const char *, mode_t, struct fuse_file_info *fi)
Definition fuse.h:417
+
int(* rmdir)(const char *)
Definition fuse.h:393
+
int(* flush)(const char *, struct fuse_file_info *)
Definition fuse.h:546
+
int(* flock)(const char *, struct fuse_file_info *, int op)
Definition fuse.h:818
+
int(* ioctl)(const char *, unsigned int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data)
Definition fuse.h:750
+
int(* readdir)(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags)
Definition fuse.h:613
+
int(* getxattr)(const char *, const char *, char *, size_t)
Definition fuse.h:573
+
int(* bmap)(const char *, size_t blocksize, uint64_t *idx)
Definition fuse.h:728
+ +
+ + + + diff --git a/doc/html/fuse-3_818_81_2include_2fuse__common_8h.html b/doc/html/fuse-3_818_81_2include_2fuse__common_8h.html new file mode 100644 index 0000000..d724a76 --- /dev/null +++ b/doc/html/fuse-3_818_81_2include_2fuse__common_8h.html @@ -0,0 +1,1303 @@ + + + + + + + +libfuse: fuse-3.18.1/include/fuse_common.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_common.h File Reference
+
+
+
#include "libfuse_config.h"
+#include "fuse_opt.h"
+#include "fuse_log.h"
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+ + + + + + + + + + + + + + +

+Data Structures

struct  fuse_file_info
 
struct  fuse_loop_config
 
struct  fuse_conn_info
 
struct  fuse_buf
 
struct  fuse_bufvec
 
struct  libfuse_version
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Macros

#define FUSE_CAP_ASYNC_READ   (1UL << 0)
 
#define FUSE_CAP_POSIX_LOCKS   (1UL << 1)
 
#define FUSE_CAP_ATOMIC_O_TRUNC   (1UL << 3)
 
#define FUSE_CAP_EXPORT_SUPPORT   (1UL << 4)
 
#define FUSE_CAP_DONT_MASK   (1UL << 6)
 
#define FUSE_CAP_SPLICE_WRITE   (1UL << 7)
 
#define FUSE_CAP_SPLICE_MOVE   (1UL << 8)
 
#define FUSE_CAP_SPLICE_READ   (1UL << 9)
 
#define FUSE_CAP_FLOCK_LOCKS   (1UL << 10)
 
#define FUSE_CAP_IOCTL_DIR   (1UL << 11)
 
#define FUSE_CAP_AUTO_INVAL_DATA   (1UL << 12)
 
#define FUSE_CAP_READDIRPLUS   (1UL << 13)
 
#define FUSE_CAP_READDIRPLUS_AUTO   (1UL << 14)
 
#define FUSE_CAP_ASYNC_DIO   (1UL << 15)
 
#define FUSE_CAP_WRITEBACK_CACHE   (1UL << 16)
 
#define FUSE_CAP_NO_OPEN_SUPPORT   (1UL << 17)
 
#define FUSE_CAP_PARALLEL_DIROPS   (1UL << 18)
 
#define FUSE_CAP_POSIX_ACL   (1UL << 19)
 
#define FUSE_CAP_HANDLE_KILLPRIV   (1UL << 20)
 
#define FUSE_CAP_HANDLE_KILLPRIV_V2   (1UL << 21)
 
#define FUSE_CAP_CACHE_SYMLINKS   (1UL << 23)
 
#define FUSE_CAP_NO_OPENDIR_SUPPORT   (1UL << 24)
 
#define FUSE_CAP_EXPLICIT_INVAL_DATA   (1UL << 25)
 
#define FUSE_CAP_EXPIRE_ONLY   (1UL << 26)
 
#define FUSE_CAP_SETXATTR_EXT   (1UL << 27)
 
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP   (1UL << 28)
 
#define FUSE_CAP_PASSTHROUGH   (1UL << 29)
 
#define FUSE_CAP_NO_EXPORT_SUPPORT   (1UL << 30)
 
#define FUSE_CAP_OVER_IO_URING   (1UL << 31)
 
#define FUSE_IOCTL_COMPAT   (1 << 0)
 
#define FUSE_BACKING_STACKED_UNDER   (0)
 
+ + + + + +

+Enumerations

enum  fuse_buf_flags { FUSE_BUF_IS_FD = (1 << 1) +, FUSE_BUF_FD_SEEK = (1 << 2) +, FUSE_BUF_FD_RETRY = (1 << 3) + }
 
enum  fuse_buf_copy_flags { FUSE_BUF_NO_SPLICE = (1 << 1) +, FUSE_BUF_FORCE_SPLICE = (1 << 2) +, FUSE_BUF_SPLICE_MOVE = (1 << 3) +, FUSE_BUF_SPLICE_NONBLOCK = (1 << 4) + }
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_args *args)
 
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
 
int fuse_daemonize (int foreground)
 
int fuse_version (void)
 
const char * fuse_pkgversion (void)
 
void fuse_pollhandle_destroy (struct fuse_pollhandle *ph)
 
size_t fuse_buf_size (const struct fuse_bufvec *bufv)
 
ssize_t fuse_buf_copy (struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
 
int fuse_set_signal_handlers (struct fuse_session *se)
 
int fuse_set_fail_signal_handlers (struct fuse_session *se)
 
void fuse_remove_signal_handlers (struct fuse_session *se)
 
bool fuse_set_feature_flag (struct fuse_conn_info *conn, uint64_t flag)
 
void fuse_unset_feature_flag (struct fuse_conn_info *conn, uint64_t flag)
 
bool fuse_get_feature_flag (struct fuse_conn_info *conn, uint64_t flag)
 
int fuse_convert_to_conn_want_ext (struct fuse_conn_info *conn)
 
+

Macro Definition Documentation

+ +

◆ FUSE_BACKING_STACKED_UNDER

+ +
+
+ + + + +
#define FUSE_BACKING_STACKED_UNDER   (0)
+
+

When FUSE_CAP_PASSTHROUGH is enabled, this is the maximum allowed stacking depth of the backing files. In current kernel, the maximum allowed stack depth if FILESYSTEM_MAX_STACK_DEPTH (2), which includes the FUSE passthrough layer, so the maximum stacking depth for backing files is 1.

+

The default is FUSE_BACKING_STACKED_UNDER (0), meaning that the backing files cannot be on a stacked filesystem, but another stacked filesystem can be stacked over this FUSE passthrough filesystem.

+

Set this to FUSE_BACKING_STACKED_OVER (1) if backing files may be on a stacked filesystem, such as overlayfs or another FUSE passthrough. In this configuration, another stacked filesystem cannot be stacked over this FUSE passthrough filesystem.

+ +

Definition at line 670 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ASYNC_DIO

+ +
+
+ + + + +
#define FUSE_CAP_ASYNC_DIO   (1UL << 15)
+
+

Indicates that the filesystem supports asynchronous direct I/O submission.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read and one pending write request per direct I/O file-handle at any time.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 328 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ASYNC_READ

+ +
+
+ + + + +
#define FUSE_CAP_ASYNC_READ   (1UL << 0)
+
+

Indicates that the filesystem supports asynchronous read requests.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read request per file-handle at any time, and will attempt to order read requests by increasing offset.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 177 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ATOMIC_O_TRUNC

+ +
+
+ + + + +
#define FUSE_CAP_ATOMIC_O_TRUNC   (1UL << 3)
+
+

Indicates that the filesystem supports the O_TRUNC open flag. If disabled, and an application specifies O_TRUNC, fuse first calls truncate() and then open() with O_TRUNC filtered out.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 194 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_AUTO_INVAL_DATA

+ +
+
+ + + + +
#define FUSE_CAP_AUTO_INVAL_DATA   (1UL << 12)
+
+

Traditionally, while a file is open the FUSE kernel module only asks the filesystem for an update of the file's attributes when a client attempts to read beyond EOF. This is unsuitable for e.g. network filesystems, where the file contents may change without the kernel knowing about it.

+

If this flag is set, FUSE will check the validity of the attributes on every read. If the attributes are no longer valid (i.e., if the attr_timeout passed to fuse_reply_attr() or set in struct fuse_entry_param has passed), it will first issue a getattr request. If the new mtime differs from the previous value, any cached file contents will be invalidated as well.

+

This flag should always be set when available. If all file changes go through the kernel, attr_timeout should be set to a very large number to avoid unnecessary getattr() calls.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 281 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_CACHE_SYMLINKS

+ +
+
+ + + + +
#define FUSE_CAP_CACHE_SYMLINKS   (1UL << 23)
+
+

Indicates that the kernel supports caching symlinks in its page cache.

+

When this feature is enabled, symlink targets are saved in the page cache. You can invalidate a cached link by calling: fuse_lowlevel_notify_inval_inode(se, ino, 0, 0);

+

This feature is disabled by default. If the kernel supports it (>= 4.20), you can enable this feature by setting this flag in the want field of the fuse_conn_info structure.

+ +

Definition at line 418 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_DIRECT_IO_ALLOW_MMAP

+ +
+
+ + + + +
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP   (1UL << 28)
+
+

Files opened with FUSE_DIRECT_IO do not support MAP_SHARED mmap. This restriction is relaxed through FUSE_CAP_DIRECT_IO_RELAX (kernel flag: FUSE_DIRECT_IO_RELAX). MAP_SHARED is disabled by default for FUSE_DIRECT_IO, as this flag can be used to ensure coherency between mount points (or network clients) and with kernel page cache as enforced by mmap that cannot be guaranteed anymore.

+ +

Definition at line 488 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_DONT_MASK

+ +
+
+ + + + +
#define FUSE_CAP_DONT_MASK   (1UL << 6)
+
+

Indicates that the kernel should not apply the umask to the file mode on create operations.

+

This feature is disabled by default.

+ +

Definition at line 214 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPIRE_ONLY

+ +
+
+ + + + +
#define FUSE_CAP_EXPIRE_ONLY   (1UL << 26)
+
+

Indicates support that dentries can be expired.

+

Expiring dentries, instead of invalidating them, makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+ +

Definition at line 472 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPLICIT_INVAL_DATA

+ +
+
+ + + + +
#define FUSE_CAP_EXPLICIT_INVAL_DATA   (1UL << 25)
+
+

Indicates support for invalidating cached pages only on explicit request.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports invalidating cached pages only on explicit request by the filesystem through fuse_lowlevel_notify_inval_inode() or fuse_invalidate_path().

+

By setting this flag in the want field of the fuse_conn_info structure, the filesystem is responsible for invalidating cached pages through explicit requests to the kernel.

+

Note that setting this flag does not prevent the cached pages from being flushed by OS itself and/or through user actions.

+

Note that if both FUSE_CAP_EXPLICIT_INVAL_DATA and FUSE_CAP_AUTO_INVAL_DATA are set in the capable field of the fuse_conn_info structure then FUSE_CAP_AUTO_INVAL_DATA takes precedence.

+

This feature is disabled by default.

+ +

Definition at line 456 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPORT_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_EXPORT_SUPPORT   (1UL << 4)
+
+

Indicates that the filesystem supports lookups of "." and "..".

+

When this flag is set, the filesystem must be prepared to receive requests for invalid inodes (i.e., for which a FORGET request was received or which have been used in a previous instance of the filesystem daemon) and must not reuse node-ids (even when setting generation numbers).

+

This feature is disabled by default.

+ +

Definition at line 206 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_FLOCK_LOCKS

+ +
+
+ + + + +
#define FUSE_CAP_FLOCK_LOCKS   (1UL << 10)
+
+

If set, the calls to flock(2) will be emulated using POSIX locks and must then be handled by the filesystem's setlock() handler.

+

If not set, flock(2) calls will be handled by the FUSE kernel module internally (so any access that does not go through the kernel cannot be taken into account).

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a flock() handler.

+ +

Definition at line 252 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_HANDLE_KILLPRIV

+ +
+
+ + + + +
#define FUSE_CAP_HANDLE_KILLPRIV   (1UL << 20)
+
+

Indicates that the filesystem is responsible for unsetting setuid and setgid bits when a file is written, truncated, or its owner is changed.

+

This feature is disabled by default.

+ +

Definition at line 388 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_HANDLE_KILLPRIV_V2

+ +
+
+ + + + +
#define FUSE_CAP_HANDLE_KILLPRIV_V2   (1UL << 21)
+
+

Indicates that the filesystem is responsible for unsetting setuid and setgid bit and additionally cap (stored as xattr) when a file is written, truncated, or its owner is changed. Upon write/truncate suid/sgid is only killed if caller does not have CAP_FSETID. Additionally upon write/truncate sgid is killed only if file has group execute permission. (Same as Linux VFS behavior). KILLPRIV_V2 requires handling of

    +
  • FUSE_OPEN_KILL_SUIDGID (set in struct fuse_create_in::open_flags)
  • +
  • FATTR_KILL_SUIDGID (set in struct fuse_setattr_in::valid)
  • +
  • FUSE_WRITE_KILL_SUIDGID (set in struct fuse_write_in::write_flags)
  • +
+

This feature is disabled by default.

+ +

Definition at line 405 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_IOCTL_DIR

+ +
+
+ + + + +
#define FUSE_CAP_IOCTL_DIR   (1UL << 11)
+
+

Indicates that the filesystem supports ioctl's on directories.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 259 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_EXPORT_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_EXPORT_SUPPORT   (1UL << 30)
+
+

Indicates that the file system cannot handle NFS export

+

If this flag is set NFS export and name_to_handle_at is not going to work at all and will fail with EOPNOTSUPP.

+ +

Definition at line 508 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_OPEN_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_OPEN_SUPPORT   (1UL << 17)
+
+

Indicates support for zero-message opens. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the open() handler to indicate success. Further attempts to open files will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signaled to the caller).

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users wish to have this behavior you must return ENOSYS from the open() handler on supporting kernels.

+ +

Definition at line 352 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_OPENDIR_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_OPENDIR_SUPPORT   (1UL << 24)
+
+

Indicates support for zero-message opendirs. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the opendir() handler to indicate success. Further opendir and releasedir messages will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signalled to the caller.)

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users with to have this behavior you must return ENOSYS from the opendir() handler on supporting kernels.

+ +

Definition at line 433 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_OVER_IO_URING

+ +
+
+ + + + +
#define FUSE_CAP_OVER_IO_URING   (1UL << 31)
+
+

Indicates support for io-uring between fuse-server and fuse-client

+ +

Definition at line 513 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_PARALLEL_DIROPS

+ +
+
+ + + + +
#define FUSE_CAP_PARALLEL_DIROPS   (1UL << 18)
+
+

Indicates support for parallel directory operations. If this flag is unset, the FUSE kernel module will ensure that lookup() and readdir() requests are never issued concurrently for the same directory.

+ +

Definition at line 360 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_PASSTHROUGH

+ +
+
+ + + + +
#define FUSE_CAP_PASSTHROUGH   (1UL << 29)
+
+

Indicates support for passthrough mode access for read/write operations.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports redirecting read/write operations to the backing file instead of letting them to be handled by the FUSE daemon.

+

This feature is disabled by default.

+ +

Definition at line 500 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_POSIX_ACL

+ +
+
+ + + + +
#define FUSE_CAP_POSIX_ACL   (1UL << 19)
+
+

Indicates support for POSIX ACLs.

+

If this feature is enabled, the kernel will cache and have responsibility for enforcing ACLs. ACL will be stored as xattrs and passed to userspace, which is responsible for updating the ACLs in the filesystem, keeping the file mode in sync with the ACL, and ensuring inheritance of default ACLs when new filesystem nodes are created. Note that this requires that the file system is able to parse and interpret the xattr representation of ACLs.

+

Enabling this feature implicitly turns on the default_permissions mount option (even if it was not passed to mount(2)).

+

This feature is disabled by default.

+ +

Definition at line 379 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_POSIX_LOCKS

+ +
+
+ + + + +
#define FUSE_CAP_POSIX_LOCKS   (1UL << 1)
+
+

Indicates that the filesystem supports "remote" locking.

+

This feature is enabled by default when supported by the kernel, and if getlk() and setlk() handlers are implemented.

+ +

Definition at line 185 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_READDIRPLUS

+ +
+
+ + + + +
#define FUSE_CAP_READDIRPLUS   (1UL << 13)
+
+

Indicates that the filesystem supports readdirplus.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a readdirplus() handler.

+ +

Definition at line 289 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_READDIRPLUS_AUTO

+ +
+
+ + + + +
#define FUSE_CAP_READDIRPLUS_AUTO   (1UL << 14)
+
+

Indicates that the filesystem supports adaptive readdirplus.

+

If FUSE_CAP_READDIRPLUS is not set, this flag has no effect.

+

If FUSE_CAP_READDIRPLUS is set and this flag is not set, the kernel will always issue readdirplus() requests to retrieve directory contents.

+

If FUSE_CAP_READDIRPLUS is set and this flag is set, the kernel will issue both readdir() and readdirplus() requests, depending on how much information is expected to be required.

+

As of Linux 4.20, the algorithm is as follows: when userspace starts to read directory entries, issue a READDIRPLUS request to the filesystem. If any entry attributes have been looked up by the time userspace requests the next batch of entries continue with READDIRPLUS, otherwise switch to plain READDIR. This will reasult in eg plain "ls" triggering READDIRPLUS first then READDIR after that because it doesn't do lookups. "ls -l" should result in all READDIRPLUS, except if dentries are already cached.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements both a readdirplus() and a readdir() handler.

+ +

Definition at line 317 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SETXATTR_EXT

+ +
+
+ + + + +
#define FUSE_CAP_SETXATTR_EXT   (1UL << 27)
+
+

Indicates that an extended 'struct fuse_setxattr' is used by the kernel side - extra_flags are passed, which are used (as of now by acl) processing. For example FUSE_SETXATTR_ACL_KILL_SGID might be set.

+ +

Definition at line 479 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_MOVE

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_MOVE   (1UL << 8)
+
+

Indicates that libfuse should try to move pages instead of copying when writing to / reading from the fuse device. This may improve performance.

+

This feature is disabled by default.

+ +

Definition at line 230 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_READ

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_READ   (1UL << 9)
+
+

Indicates that libfuse should try to use splice() when reading from the fuse device. This may improve performance.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a write_buf() handler.

+ +

Definition at line 239 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_WRITE

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_WRITE   (1UL << 7)
+
+

Indicates that libfuse should try to use splice() when writing to the fuse device. This may improve performance.

+

This feature is disabled by default.

+ +

Definition at line 222 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_WRITEBACK_CACHE

+ +
+
+ + + + +
#define FUSE_CAP_WRITEBACK_CACHE   (1UL << 16)
+
+

Indicates that writeback caching should be enabled. This means that individual write request may be buffered and merged in the kernel before they are send to the filesystem.

+

This feature is disabled by default.

+ +

Definition at line 337 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_IOCTL_COMPAT

+ +
+
+ + + + +
#define FUSE_IOCTL_COMPAT   (1 << 0)
+
+

Ioctl flags

+

FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed FUSE_IOCTL_RETRY: retry with new iovecs FUSE_IOCTL_DIR: is a directory

+

FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs

+ +

Definition at line 525 of file fuse_common.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_buf_copy_flags

+ +
+
+ + + + +
enum fuse_buf_copy_flags
+
+

Buffer copy flags

+ + + + + +
Enumerator
FUSE_BUF_NO_SPLICE 

Don't use splice(2)

+

Always fall back to using read and write instead of splice(2) to copy data from one file descriptor to another.

+

If this flag is not set, then only fall back if splice is unavailable.

+
FUSE_BUF_FORCE_SPLICE 

Force splice

+

Always use splice(2) to copy data from one file descriptor to another. If splice is not available, return -EINVAL.

+
FUSE_BUF_SPLICE_MOVE 

Try to move data with splice.

+

If splice is used, try to move pages from the source to the destination instead of copying. See documentation of SPLICE_F_MOVE in splice(2) man page.

+
FUSE_BUF_SPLICE_NONBLOCK 

Don't block on the pipe when copying data with splice

+

Makes the operations on the pipe non-blocking (if the pipe is full or empty). See SPLICE_F_NONBLOCK in the splice(2) man page.

+
+ +

Definition at line 840 of file fuse_common.h.

+ +
+
+ +

◆ fuse_buf_flags

+ +
+
+ + + + +
enum fuse_buf_flags
+
+

Buffer flags

+ + + + +
Enumerator
FUSE_BUF_IS_FD 

Buffer contains a file descriptor

+

If this flag is set, the .fd field is valid, otherwise the .mem fields is valid.

+
FUSE_BUF_FD_SEEK 

Seek on the file descriptor

+

If this flag is set then the .pos field is valid and is used to seek to the given offset before performing operation on file descriptor.

+
FUSE_BUF_FD_RETRY 

Retry operation on file descriptor

+

If this flag is set then retry operation on file descriptor until .size bytes have been copied or an error or EOF is detected.

+
+ +

Definition at line 809 of file fuse_common.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_apply_conn_info_opts()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts * opts,
struct fuse_conn_infoconn 
)
+
+

This function applies the (parsed) parameters in opts to the conn pointer. It may modify the following fields: wants, max_write, max_readahead, congestion_threshold, max_background, time_gran. A field is only set (or unset) if the corresponding option has been explicitly set.

+ +

Definition at line 412 of file helper.c.

+ +
+
+ +

◆ fuse_buf_copy()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
ssize_t fuse_buf_copy (struct fuse_bufvecdst,
struct fuse_bufvecsrc,
enum fuse_buf_copy_flags flags 
)
+
+

Copy data from one buffer vector to another

+
Parameters
+ + + + +
dstdestination buffer vector
srcsource buffer vector
flagsflags controlling the copy
+
+
+
Returns
actual number of bytes copied or -errno on error
+ +

Definition at line 284 of file buffer.c.

+ +
+
+ +

◆ fuse_buf_size()

+ +
+
+ + + + + + + + +
size_t fuse_buf_size (const struct fuse_bufvecbufv)
+
+

Get total size of data in a fuse buffer vector

+
Parameters
+ + +
bufvbuffer vector
+
+
+
Returns
size of data
+ +

Definition at line 22 of file buffer.c.

+ +
+
+ +

◆ fuse_convert_to_conn_want_ext()

+ +
+
+ + + + + + + + +
int fuse_convert_to_conn_want_ext (struct fuse_conn_infoconn)
+
+

Get the wanted capability flags, converting from old format if necessary

+ +

Definition at line 2000 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_daemonize()

+ +
+
+ + + + + + + + +
int fuse_daemonize (int foreground)
+
+

Go into the background

+
Parameters
+ + +
foregroundif true, stay in the foreground
+
+
+
Returns
0 on success, -1 on failure
+ +

Definition at line 253 of file helper.c.

+ +
+
+ +

◆ fuse_get_feature_flag()

+ +
+
+ + + + + + + + + + + + + + + + + + +
bool fuse_get_feature_flag (struct fuse_conn_infoconn,
uint64_t flag 
)
+
+

Get the value of a feature flag in the want_ext field of fuse_conn_info.

+
Parameters
+ + + +
connconnection information
flagfeature flag to be checked
+
+
+
Returns
true if the flag is set, false otherwise
+ +

Definition at line 2061 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_parse_conn_info_opts()

+ +
+
+ + + + + + + + +
struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_argsargs)
+
+

This function parses several command-line options that can be used to override elements of struct fuse_conn_info. The pointer returned by this function should be passed to the fuse_apply_conn_info_opts() method by the file system's init() handler.

+

Before using this function, think twice if you really want these parameters to be adjustable from the command line. In most cases, they should be determined by the file system internally.

+

The following options are recognized:

+

-o max_write=N sets conn->max_write -o max_readahead=N sets conn->max_readahead -o max_background=N sets conn->max_background -o congestion_threshold=N sets conn->congestion_threshold -o async_read sets FUSE_CAP_ASYNC_READ in conn->want -o sync_read unsets FUSE_CAP_ASYNC_READ in conn->want -o atomic_o_trunc sets FUSE_CAP_ATOMIC_O_TRUNC in conn->want -o no_remote_lock Equivalent to -o no_remote_flock,no_remote_posix_lock -o no_remote_flock Unsets FUSE_CAP_FLOCK_LOCKS in conn->want -o no_remote_posix_lock Unsets FUSE_CAP_POSIX_LOCKS in conn->want -o [no_]splice_write (un-)sets FUSE_CAP_SPLICE_WRITE in conn->want -o [no_]splice_move (un-)sets FUSE_CAP_SPLICE_MOVE in conn->want -o [no_]splice_read (un-)sets FUSE_CAP_SPLICE_READ in conn->want -o [no_]auto_inval_data (un-)sets FUSE_CAP_AUTO_INVAL_DATA in conn->want -o readdirplus=no unsets FUSE_CAP_READDIRPLUS in conn->want -o readdirplus=yes sets FUSE_CAP_READDIRPLUS and unsets FUSE_CAP_READDIRPLUS_AUTO in conn->want -o readdirplus=auto sets FUSE_CAP_READDIRPLUS and FUSE_CAP_READDIRPLUS_AUTO in conn->want -o [no_]async_dio (un-)sets FUSE_CAP_ASYNC_DIO in conn->want -o [no_]writeback_cache (un-)sets FUSE_CAP_WRITEBACK_CACHE in conn->want -o time_gran=N sets conn->time_gran

+

Known options will be removed from args, unknown options will be passed through unchanged.

+
Parameters
+ + +
argsargument vector (input+output)
+
+
+
Returns
parsed options
+ +

Definition at line 466 of file helper.c.

+ +
+
+ +

◆ fuse_pkgversion()

+ +
+
+ + + + + + + + +
const char * fuse_pkgversion (void )
+
+

Get the full package version string of the library

+
Returns
the package version
+ +

Definition at line 5211 of file fuse.c.

+ +
+
+ +

◆ fuse_pollhandle_destroy()

+ +
+
+ + + + + + + + +
void fuse_pollhandle_destroy (struct fuse_pollhandle * ph)
+
+

Destroy poll handle

+
Parameters
+ + +
phthe poll handle
+
+
+ +

Definition at line 1903 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_remove_signal_handlers()

+ +
+
+ + + + + + + + +
void fuse_remove_signal_handlers (struct fuse_session * se)
+
+

Restore default signal handlers

+

Resets global session. After this fuse_set_signal_handlers() may be called again.

+
Parameters
+ + +
sethe same session as given in fuse_set_signal_handlers()
+
+
+

See also: fuse_set_signal_handlers()

+ +

Definition at line 187 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_fail_signal_handlers()

+ +
+
+ + + + + + + + +
int fuse_set_fail_signal_handlers (struct fuse_session * se)
+
+

Print a stack backtrace diagnostic on critical signals ()

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 165 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_feature_flag()

+ +
+
+ + + + + + + + + + + + + + + + + + +
bool fuse_set_feature_flag (struct fuse_conn_infoconn,
uint64_t flag 
)
+
+

Config operations. These APIs are introduced in version 312 (FUSE_MAKE_VERSION(3, 12)). Using them in earlier versions will result in errors. Set a feature flag in the want_ext field of fuse_conn_info.

+
Parameters
+ + + +
connconnection information
flagfeature flag to be set
+
+
+
Returns
true if the flag was set, false if the flag is not supported
+ +

Definition at line 2035 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_set_signal_handlers()

+ +
+
+ + + + + + + + +
int fuse_set_signal_handlers (struct fuse_session * se)
+
+

Exit session on HUP, TERM and INT signals and ignore PIPE signal

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 140 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_unset_feature_flag()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_unset_feature_flag (struct fuse_conn_infoconn,
uint64_t flag 
)
+
+

Unset a feature flag in the want_ext field of fuse_conn_info.

+
Parameters
+ + + +
connconnection information
flagfeature flag to be unset
+
+
+ +

Definition at line 2050 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_version()

+ +
+
+ + + + + + + + +
int fuse_version (void )
+
+

Get the version of the library

+
Returns
the version
+ +

Definition at line 5206 of file fuse.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2include_2fuse__common_8h_source.html b/doc/html/fuse-3_818_81_2include_2fuse__common_8h_source.html new file mode 100644 index 0000000..48cb481 --- /dev/null +++ b/doc/html/fuse-3_818_81_2include_2fuse__common_8h_source.html @@ -0,0 +1,470 @@ + + + + + + + +libfuse: fuse-3.18.1/include/fuse_common.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_common.h
+
+
+Go to the documentation of this file.
1/* FUSE: Filesystem in Userspace
+
2 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
3
+
4 This program can be distributed under the terms of the GNU LGPLv2.
+
5 See the file LGPL2.txt.
+
6*/
+
7
+
10#if !defined(FUSE_H_) && !defined(FUSE_LOWLEVEL_H_)
+
11#error "Never include <fuse_common.h> directly; use <fuse.h> or <fuse_lowlevel.h> instead."
+
12#endif
+
13
+
14#ifndef FUSE_COMMON_H_
+
15#define FUSE_COMMON_H_
+
16
+
17#ifdef HAVE_LIBFUSE_PRIVATE_CONFIG_H
+
18#include "fuse_config.h"
+
19#endif
+
20
+
21#include "libfuse_config.h"
+
22
+
23#include "fuse_opt.h"
+
24#include "fuse_log.h"
+
25#include <stdint.h>
+
26#include <stdbool.h>
+
27#include <sys/types.h>
+
28#include <assert.h>
+
29
+
30#define FUSE_MAKE_VERSION(maj, min) ((maj) * 100 + (min))
+
31#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION)
+
32
+
33#ifdef __cplusplus
+
34extern "C" {
+
35#endif
+
36
+
50struct fuse_file_info {
+
52 int32_t flags;
+
53
+
60 uint32_t writepage : 1;
+
61
+
63 uint32_t direct_io : 1;
+
64
+
69 uint32_t keep_cache : 1;
+
70
+
74 uint32_t flush : 1;
+
75
+
78 uint32_t nonseekable : 1;
+
79
+
80 /* Indicates that flock locks for this file should be
+
81 released. If set, lock_owner shall contain a valid value.
+
82 May only be set in ->release(). */
+
83 uint32_t flock_release : 1;
+
84
+
89 uint32_t cache_readdir : 1;
+
90
+
93 uint32_t noflush : 1;
+
94
+
97 uint32_t parallel_direct_writes : 1;
+
98
+
100 uint32_t padding : 23;
+
101 uint32_t padding2 : 32;
+
102 uint32_t padding3 : 32;
+
103
+
107 uint64_t fh;
+
108
+
110 uint64_t lock_owner;
+
111
+
114 uint32_t poll_events;
+
115
+
119 int32_t backing_id;
+
120
+
122 uint64_t compat_flags;
+
123
+
124 uint64_t reserved[2];
+
125};
+
126
+
137#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
138struct fuse_loop_config_v1; /* forward declaration */
+
139struct fuse_loop_config {
+
140#else
+
141struct fuse_loop_config_v1 {
+
142#endif
+
147 int clone_fd;
+
148
+
159 unsigned int max_idle_threads;
+
160};
+
161
+
162
+
163/**************************************************************************
+
164 * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want' *
+
165 **************************************************************************/
+
166
+
177#define FUSE_CAP_ASYNC_READ (1UL << 0)
+
178
+
185#define FUSE_CAP_POSIX_LOCKS (1UL << 1)
+
186
+
194#define FUSE_CAP_ATOMIC_O_TRUNC (1UL << 3)
+
195
+
206#define FUSE_CAP_EXPORT_SUPPORT (1UL << 4)
+
207
+
214#define FUSE_CAP_DONT_MASK (1UL << 6)
+
215
+
222#define FUSE_CAP_SPLICE_WRITE (1UL << 7)
+
223
+
230#define FUSE_CAP_SPLICE_MOVE (1UL << 8)
+
231
+
239#define FUSE_CAP_SPLICE_READ (1UL << 9)
+
240
+
252#define FUSE_CAP_FLOCK_LOCKS (1UL << 10)
+
253
+
259#define FUSE_CAP_IOCTL_DIR (1UL << 11)
+
260
+
281#define FUSE_CAP_AUTO_INVAL_DATA (1UL << 12)
+
282
+
289#define FUSE_CAP_READDIRPLUS (1UL << 13)
+
290
+
317#define FUSE_CAP_READDIRPLUS_AUTO (1UL << 14)
+
318
+
328#define FUSE_CAP_ASYNC_DIO (1UL << 15)
+
329
+
337#define FUSE_CAP_WRITEBACK_CACHE (1UL << 16)
+
338
+
352#define FUSE_CAP_NO_OPEN_SUPPORT (1UL << 17)
+
353
+
360#define FUSE_CAP_PARALLEL_DIROPS (1UL << 18)
+
361
+
379#define FUSE_CAP_POSIX_ACL (1UL << 19)
+
380
+
388#define FUSE_CAP_HANDLE_KILLPRIV (1UL << 20)
+
389
+
405#define FUSE_CAP_HANDLE_KILLPRIV_V2 (1UL << 21)
+
406
+
418#define FUSE_CAP_CACHE_SYMLINKS (1UL << 23)
+
419
+
433#define FUSE_CAP_NO_OPENDIR_SUPPORT (1UL << 24)
+
434
+
456#define FUSE_CAP_EXPLICIT_INVAL_DATA (1UL << 25)
+
457
+
472#define FUSE_CAP_EXPIRE_ONLY (1UL << 26)
+
473
+
479#define FUSE_CAP_SETXATTR_EXT (1UL << 27)
+
480
+
488#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP (1UL << 28)
+
489
+
500#define FUSE_CAP_PASSTHROUGH (1UL << 29)
+
501
+
508#define FUSE_CAP_NO_EXPORT_SUPPORT (1UL << 30)
+
509
+
513#define FUSE_CAP_OVER_IO_URING (1UL << 31)
+
514
+
525#define FUSE_IOCTL_COMPAT (1 << 0)
+
526#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
527#define FUSE_IOCTL_RETRY (1 << 2)
+
528#define FUSE_IOCTL_DIR (1 << 4)
+
529
+
530#define FUSE_IOCTL_MAX_IOV 256
+
531
+
543struct fuse_conn_info {
+
547 uint32_t proto_major;
+
548
+
552 uint32_t proto_minor;
+
553
+
557 uint32_t max_write;
+
558
+
571 uint32_t max_read;
+
572
+
576 uint32_t max_readahead;
+
577
+
583 uint32_t capable;
+
584
+
595 uint32_t want;
+
596
+
625 uint32_t max_background;
+
626
+
635 uint32_t congestion_threshold;
+
636
+
652 uint32_t time_gran;
+
653
+
670#define FUSE_BACKING_STACKED_UNDER (0)
+
671#define FUSE_BACKING_STACKED_OVER (1)
+
672 uint32_t max_backing_stack_depth;
+
673
+
682 uint32_t no_interrupt : 1;
+
683
+
684 /* reserved bits for future use */
+
685 uint32_t padding : 31;
+
686
+
691 uint64_t capable_ext;
+
692
+
701 uint64_t want_ext;
+
702
+ +
708
+
712 uint16_t reserved[31];
+
713};
+
714
+
715struct fuse_session;
+
716struct fuse_pollhandle;
+
717struct fuse_conn_info_opts;
+
718
+
761struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args);
+
762
+
770void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
771 struct fuse_conn_info *conn);
+
772
+
779int fuse_daemonize(int foreground);
+
780
+
786int fuse_version(void);
+
787
+
793const char *fuse_pkgversion(void);
+
794
+
800void fuse_pollhandle_destroy(struct fuse_pollhandle *ph);
+
801
+
802/* ----------------------------------------------------------- *
+
803 * Data buffer *
+
804 * ----------------------------------------------------------- */
+
805
+
+ +
816 FUSE_BUF_IS_FD = (1 << 1),
+
817
+ +
826
+
834 FUSE_BUF_FD_RETRY = (1 << 3)
+ +
+
836
+
+ + +
851
+ +
859
+ +
868
+ + +
+
878
+
885struct fuse_buf {
+
889 size_t size;
+
890
+
894 enum fuse_buf_flags flags;
+
895
+
901 void *mem;
+
902
+
908 int fd;
+
909
+
915 off_t pos;
+
916
+
923 size_t mem_size;
+
924};
+
925
+
934struct fuse_bufvec {
+
938 size_t count;
+
939
+
943 size_t idx;
+
944
+
948 size_t off;
+
949
+
953 struct fuse_buf buf[1];
+
954};
+
955
+
960struct libfuse_version
+
961{
+
962 uint32_t major;
+
963 uint32_t minor;
+
964 uint32_t hotfix;
+
965 uint32_t padding;
+
966};
+
967
+
968/* Initialize bufvec with a single buffer of given size */
+
969#define FUSE_BUFVEC_INIT(size__) \
+
970 ((struct fuse_bufvec) { \
+
971 /* .count= */ 1, \
+
972 /* .idx = */ 0, \
+
973 /* .off = */ 0, \
+
974 /* .buf = */ { /* [0] = */ { \
+
975 /* .size = */ (size__), \
+
976 /* .flags = */ (enum fuse_buf_flags) 0, \
+
977 /* .mem = */ NULL, \
+
978 /* .fd = */ -1, \
+
979 /* .pos = */ 0, \
+
980 /* .mem_size = */ 0, \
+
981 } } \
+
982 } )
+
983
+
990size_t fuse_buf_size(const struct fuse_bufvec *bufv);
+
991
+
1000ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src,
+
1001 enum fuse_buf_copy_flags flags);
+
1002
+
1003/* ----------------------------------------------------------- *
+
1004 * Signal handling *
+
1005 * ----------------------------------------------------------- */
+
1006
+
1022int fuse_set_signal_handlers(struct fuse_session *se);
+
1023
+
1039int fuse_set_fail_signal_handlers(struct fuse_session *se);
+
1040
+
1052void fuse_remove_signal_handlers(struct fuse_session *se);
+
1053
+
1059#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
1065struct fuse_loop_config *fuse_loop_cfg_create(void);
+
1066
+
1070void fuse_loop_cfg_destroy(struct fuse_loop_config *config);
+
1071
+
1075void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
1076 unsigned int value);
+
1077
+
1081void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
1082 unsigned int value);
+
1083
+
1087void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
1088 unsigned int value);
+
1089
+
1096void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
1097 struct fuse_loop_config_v1 *v1_conf);
+
1098#endif
+
1099
+
1107bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag);
+
1108
+
1115void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag);
+
1116
+
1124bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag);
+
1125
+
1126/*
+
1127 * DO NOT USE: Not part of public API, for internal test use only.
+
1128 * The function signature or any use of it is not guaranteeed to
+
1129 * remain stable. And neither are results of what this function does.
+
1130 */
+ +
1132
+
1133
+
1134
+
1135/* ----------------------------------------------------------- *
+
1136 * Compatibility stuff *
+
1137 * ----------------------------------------------------------- */
+
1138
+
1139#if !defined(FUSE_USE_VERSION) || FUSE_USE_VERSION < 30
+
1140# error only API version 30 or greater is supported
+
1141#endif
+
1142
+
1143#ifdef __cplusplus
+
1144}
+
1145#endif
+
1146
+
1147
+
1148/*
+
1149 * This interface uses 64 bit off_t.
+
1150 *
+
1151 * On 32bit systems please add -D_FILE_OFFSET_BITS=64 to your compile flags!
+
1152 */
+
1153
+
1154#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
+
1155_Static_assert(sizeof(off_t) == 8, "fuse: off_t must be 64bit");
+
1156#else
+
1157struct _fuse_off_t_must_be_64bit_dummy_struct \
+
1158 { unsigned _fuse_off_t_must_be_64bit:((sizeof(off_t) == 8) ? 1 : -1); };
+
1159#endif
+
1160
+
1161#endif /* FUSE_COMMON_H_ */
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:412
+
fuse_buf_flags
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:466
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
int fuse_version(void)
Definition fuse.c:5206
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+ + + + + + +
uint16_t request_timeout
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:60
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t nonseekable
Definition fuse_common.h:78
+
int32_t backing_id
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t padding
+
uint32_t noflush
Definition fuse_common.h:93
+
uint64_t compat_flags
+
uint32_t flush
Definition fuse_common.h:74
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
+ + + + diff --git a/doc/html/fuse-3_818_81_2include_2fuse__kernel_8h_source.html b/doc/html/fuse-3_818_81_2include_2fuse__kernel_8h_source.html new file mode 100644 index 0000000..9498999 --- /dev/null +++ b/doc/html/fuse-3_818_81_2include_2fuse__kernel_8h_source.html @@ -0,0 +1,1184 @@ + + + + + + + +libfuse: fuse-3.18.1/include/fuse_kernel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_kernel.h
+
+
+
1/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */
+
2/*
+
3 This file defines the kernel interface of FUSE
+
4 Copyright (C) 2001-2008 Miklos Szeredi <miklos@szeredi.hu>
+
5
+
6 This program can be distributed under the terms of the GNU GPL.
+
7 See the file COPYING.
+
8
+
9 This -- and only this -- header file may also be distributed under
+
10 the terms of the BSD Licence as follows:
+
11
+
12 Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
+
13
+
14 Redistribution and use in source and binary forms, with or without
+
15 modification, are permitted provided that the following conditions
+
16 are met:
+
17 1. Redistributions of source code must retain the above copyright
+
18 notice, this list of conditions and the following disclaimer.
+
19 2. Redistributions in binary form must reproduce the above copyright
+
20 notice, this list of conditions and the following disclaimer in the
+
21 documentation and/or other materials provided with the distribution.
+
22
+
23 THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+
24 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
25 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
26 ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+
27 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
28 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
29 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
30 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
32 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
33 SUCH DAMAGE.
+
34*/
+
35
+
36/*
+
37 * This file defines the kernel interface of FUSE
+
38 *
+
39 * Protocol changelog:
+
40 *
+
41 * 7.1:
+
42 * - add the following messages:
+
43 * FUSE_SETATTR, FUSE_SYMLINK, FUSE_MKNOD, FUSE_MKDIR, FUSE_UNLINK,
+
44 * FUSE_RMDIR, FUSE_RENAME, FUSE_LINK, FUSE_OPEN, FUSE_READ, FUSE_WRITE,
+
45 * FUSE_RELEASE, FUSE_FSYNC, FUSE_FLUSH, FUSE_SETXATTR, FUSE_GETXATTR,
+
46 * FUSE_LISTXATTR, FUSE_REMOVEXATTR, FUSE_OPENDIR, FUSE_READDIR,
+
47 * FUSE_RELEASEDIR
+
48 * - add padding to messages to accommodate 32-bit servers on 64-bit kernels
+
49 *
+
50 * 7.2:
+
51 * - add FOPEN_DIRECT_IO and FOPEN_KEEP_CACHE flags
+
52 * - add FUSE_FSYNCDIR message
+
53 *
+
54 * 7.3:
+
55 * - add FUSE_ACCESS message
+
56 * - add FUSE_CREATE message
+
57 * - add filehandle to fuse_setattr_in
+
58 *
+
59 * 7.4:
+
60 * - add frsize to fuse_kstatfs
+
61 * - clean up request size limit checking
+
62 *
+
63 * 7.5:
+
64 * - add flags and max_write to fuse_init_out
+
65 *
+
66 * 7.6:
+
67 * - add max_readahead to fuse_init_in and fuse_init_out
+
68 *
+
69 * 7.7:
+
70 * - add FUSE_INTERRUPT message
+
71 * - add POSIX file lock support
+
72 *
+
73 * 7.8:
+
74 * - add lock_owner and flags fields to fuse_release_in
+
75 * - add FUSE_BMAP message
+
76 * - add FUSE_DESTROY message
+
77 *
+
78 * 7.9:
+
79 * - new fuse_getattr_in input argument of GETATTR
+
80 * - add lk_flags in fuse_lk_in
+
81 * - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in
+
82 * - add blksize field to fuse_attr
+
83 * - add file flags field to fuse_read_in and fuse_write_in
+
84 * - Add ATIME_NOW and MTIME_NOW flags to fuse_setattr_in
+
85 *
+
86 * 7.10
+
87 * - add nonseekable open flag
+
88 *
+
89 * 7.11
+
90 * - add IOCTL message
+
91 * - add unsolicited notification support
+
92 * - add POLL message and NOTIFY_POLL notification
+
93 *
+
94 * 7.12
+
95 * - add umask flag to input argument of create, mknod and mkdir
+
96 * - add notification messages for invalidation of inodes and
+
97 * directory entries
+
98 *
+
99 * 7.13
+
100 * - make max number of background requests and congestion threshold
+
101 * tunables
+
102 *
+
103 * 7.14
+
104 * - add splice support to fuse device
+
105 *
+
106 * 7.15
+
107 * - add store notify
+
108 * - add retrieve notify
+
109 *
+
110 * 7.16
+
111 * - add BATCH_FORGET request
+
112 * - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct
+
113 * fuse_ioctl_iovec' instead of ambiguous 'struct iovec'
+
114 * - add FUSE_IOCTL_32BIT flag
+
115 *
+
116 * 7.17
+
117 * - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK
+
118 *
+
119 * 7.18
+
120 * - add FUSE_IOCTL_DIR flag
+
121 * - add FUSE_NOTIFY_DELETE
+
122 *
+
123 * 7.19
+
124 * - add FUSE_FALLOCATE
+
125 *
+
126 * 7.20
+
127 * - add FUSE_AUTO_INVAL_DATA
+
128 *
+
129 * 7.21
+
130 * - add FUSE_READDIRPLUS
+
131 * - send the requested events in POLL request
+
132 *
+
133 * 7.22
+
134 * - add FUSE_ASYNC_DIO
+
135 *
+
136 * 7.23
+
137 * - add FUSE_WRITEBACK_CACHE
+
138 * - add time_gran to fuse_init_out
+
139 * - add reserved space to fuse_init_out
+
140 * - add FATTR_CTIME
+
141 * - add ctime and ctimensec to fuse_setattr_in
+
142 * - add FUSE_RENAME2 request
+
143 * - add FUSE_NO_OPEN_SUPPORT flag
+
144 *
+
145 * 7.24
+
146 * - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support
+
147 *
+
148 * 7.25
+
149 * - add FUSE_PARALLEL_DIROPS
+
150 *
+
151 * 7.26
+
152 * - add FUSE_HANDLE_KILLPRIV
+
153 * - add FUSE_POSIX_ACL
+
154 *
+
155 * 7.27
+
156 * - add FUSE_ABORT_ERROR
+
157 *
+
158 * 7.28
+
159 * - add FUSE_COPY_FILE_RANGE
+
160 * - add FOPEN_CACHE_DIR
+
161 * - add FUSE_MAX_PAGES, add max_pages to init_out
+
162 * - add FUSE_CACHE_SYMLINKS
+
163 *
+
164 * 7.29
+
165 * - add FUSE_NO_OPENDIR_SUPPORT flag
+
166 *
+
167 * 7.30
+
168 * - add FUSE_EXPLICIT_INVAL_DATA
+
169 * - add FUSE_IOCTL_COMPAT_X32
+
170 *
+
171 * 7.31
+
172 * - add FUSE_WRITE_KILL_PRIV flag
+
173 * - add FUSE_SETUPMAPPING and FUSE_REMOVEMAPPING
+
174 * - add map_alignment to fuse_init_out, add FUSE_MAP_ALIGNMENT flag
+
175 *
+
176 * 7.32
+
177 * - add flags to fuse_attr, add FUSE_ATTR_SUBMOUNT, add FUSE_SUBMOUNTS
+
178 *
+
179 * 7.33
+
180 * - add FUSE_HANDLE_KILLPRIV_V2, FUSE_WRITE_KILL_SUIDGID, FATTR_KILL_SUIDGID
+
181 * - add FUSE_OPEN_KILL_SUIDGID
+
182 * - extend fuse_setxattr_in, add FUSE_SETXATTR_EXT
+
183 * - add FUSE_SETXATTR_ACL_KILL_SGID
+
184 *
+
185 * 7.34
+
186 * - add FUSE_SYNCFS
+
187 *
+
188 * 7.35
+
189 * - add FOPEN_NOFLUSH
+
190 *
+
191 * 7.36
+
192 * - extend fuse_init_in with reserved fields, add FUSE_INIT_EXT init flag
+
193 * - add flags2 to fuse_init_in and fuse_init_out
+
194 * - add FUSE_SECURITY_CTX init flag
+
195 * - add security context to create, mkdir, symlink, and mknod requests
+
196 * - add FUSE_HAS_INODE_DAX, FUSE_ATTR_DAX
+
197 *
+
198 * 7.37
+
199 * - add FUSE_TMPFILE
+
200 *
+
201 * 7.38
+
202 * - add FUSE_EXPIRE_ONLY flag to fuse_notify_inval_entry
+
203 * - add FOPEN_PARALLEL_DIRECT_WRITES
+
204 * - add total_extlen to fuse_in_header
+
205 * - add FUSE_MAX_NR_SECCTX
+
206 * - add extension header
+
207 * - add FUSE_EXT_GROUPS
+
208 * - add FUSE_CREATE_SUPP_GROUP
+
209 * - add FUSE_HAS_EXPIRE_ONLY
+
210 *
+
211 * 7.39
+
212 * - add FUSE_DIRECT_IO_ALLOW_MMAP
+
213 * - add FUSE_STATX and related structures
+
214 *
+
215 * 7.40
+
216 * - add max_stack_depth to fuse_init_out, add FUSE_PASSTHROUGH init flag
+
217 * - add backing_id to fuse_open_out, add FOPEN_PASSTHROUGH open flag
+
218 * - add FUSE_NO_EXPORT_SUPPORT init flag
+
219 * - add FUSE_NOTIFY_RESEND, add FUSE_HAS_RESEND init flag
+
220 *
+
221 * 7.41
+
222 * - add FUSE_ALLOW_IDMAP
+
223 * 7.42
+
224 * - Add FUSE_OVER_IO_URING and all other io-uring related flags and data
+
225 * structures:
+
226 * - struct fuse_uring_ent_in_out
+
227 * - struct fuse_uring_req_header
+
228 * - struct fuse_uring_cmd_req
+
229 * - FUSE_URING_IN_OUT_HEADER_SZ
+
230 * - FUSE_URING_OP_IN_OUT_SZ
+
231 * - enum fuse_uring_cmd
+
232 *
+
233 * 7.43
+
234 * - add FUSE_REQUEST_TIMEOUT
+
235 *
+
236 * 7.44
+
237 * - add FUSE_NOTIFY_INC_EPOCH
+
238 *
+
239 * 7.45
+
240 * - add FUSE_COPY_FILE_RANGE_64
+
241 * - add struct fuse_copy_file_range_out
+
242 */
+
243
+
244#ifndef _LINUX_FUSE_H
+
245#define _LINUX_FUSE_H
+
246
+
247#ifdef __KERNEL__
+
248#include <linux/types.h>
+
249#else
+
250#include <stdint.h>
+
251#endif
+
252
+
253/*
+
254 * Version negotiation:
+
255 *
+
256 * Both the kernel and userspace send the version they support in the
+
257 * INIT request and reply respectively.
+
258 *
+
259 * If the major versions match then both shall use the smallest
+
260 * of the two minor versions for communication.
+
261 *
+
262 * If the kernel supports a larger major version, then userspace shall
+
263 * reply with the major version it supports, ignore the rest of the
+
264 * INIT message and expect a new INIT message from the kernel with a
+
265 * matching major version.
+
266 *
+
267 * If the library supports a larger major version, then it shall fall
+
268 * back to the major protocol version sent by the kernel for
+
269 * communication and reply with that major version (and an arbitrary
+
270 * supported minor version).
+
271 */
+
272
+
274#define FUSE_KERNEL_VERSION 7
+
275
+
277#define FUSE_KERNEL_MINOR_VERSION 45
+
278
+
280#define FUSE_ROOT_ID 1
+
281
+
282/* Make sure all structures are padded to 64bit boundary, so 32bit
+
283 userspace works under 64bit kernels */
+
284
+
285struct fuse_attr {
+
286 uint64_t ino;
+
287 uint64_t size;
+
288 uint64_t blocks;
+
289 uint64_t atime;
+
290 uint64_t mtime;
+
291 uint64_t ctime;
+
292 uint32_t atimensec;
+
293 uint32_t mtimensec;
+
294 uint32_t ctimensec;
+
295 uint32_t mode;
+
296 uint32_t nlink;
+
297 uint32_t uid;
+
298 uint32_t gid;
+
299 uint32_t rdev;
+
300 uint32_t blksize;
+
301 uint32_t flags;
+
302};
+
303
+
304/*
+
305 * The following structures are bit-for-bit compatible with the statx(2) ABI in
+
306 * Linux.
+
307 */
+
308struct fuse_sx_time {
+
309 int64_t tv_sec;
+
310 uint32_t tv_nsec;
+
311 int32_t __reserved;
+
312};
+
313
+
314struct fuse_statx {
+
315 uint32_t mask;
+
316 uint32_t blksize;
+
317 uint64_t attributes;
+
318 uint32_t nlink;
+
319 uint32_t uid;
+
320 uint32_t gid;
+
321 uint16_t mode;
+
322 uint16_t __spare0[1];
+
323 uint64_t ino;
+
324 uint64_t size;
+
325 uint64_t blocks;
+
326 uint64_t attributes_mask;
+
327 struct fuse_sx_time atime;
+
328 struct fuse_sx_time btime;
+
329 struct fuse_sx_time ctime;
+
330 struct fuse_sx_time mtime;
+
331 uint32_t rdev_major;
+
332 uint32_t rdev_minor;
+
333 uint32_t dev_major;
+
334 uint32_t dev_minor;
+
335 uint64_t __spare2[14];
+
336};
+
337
+
338struct fuse_kstatfs {
+
339 uint64_t blocks;
+
340 uint64_t bfree;
+
341 uint64_t bavail;
+
342 uint64_t files;
+
343 uint64_t ffree;
+
344 uint32_t bsize;
+
345 uint32_t namelen;
+
346 uint32_t frsize;
+
347 uint32_t padding;
+
348 uint32_t spare[6];
+
349};
+
350
+
351struct fuse_file_lock {
+
352 uint64_t start;
+
353 uint64_t end;
+
354 uint32_t type;
+
355 uint32_t pid; /* tgid */
+
356};
+
357
+
361#define FATTR_MODE (1 << 0)
+
362#define FATTR_UID (1 << 1)
+
363#define FATTR_GID (1 << 2)
+
364#define FATTR_SIZE (1 << 3)
+
365#define FATTR_ATIME (1 << 4)
+
366#define FATTR_MTIME (1 << 5)
+
367#define FATTR_FH (1 << 6)
+
368#define FATTR_ATIME_NOW (1 << 7)
+
369#define FATTR_MTIME_NOW (1 << 8)
+
370#define FATTR_LOCKOWNER (1 << 9)
+
371#define FATTR_CTIME (1 << 10)
+
372#define FATTR_KILL_SUIDGID (1 << 11)
+
373
+
386#define FOPEN_DIRECT_IO (1 << 0)
+
387#define FOPEN_KEEP_CACHE (1 << 1)
+
388#define FOPEN_NONSEEKABLE (1 << 2)
+
389#define FOPEN_CACHE_DIR (1 << 3)
+
390#define FOPEN_STREAM (1 << 4)
+
391#define FOPEN_NOFLUSH (1 << 5)
+
392#define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6)
+
393#define FOPEN_PASSTHROUGH (1 << 7)
+
394
+
451#define FUSE_ASYNC_READ (1 << 0)
+
452#define FUSE_POSIX_LOCKS (1 << 1)
+
453#define FUSE_FILE_OPS (1 << 2)
+
454#define FUSE_ATOMIC_O_TRUNC (1 << 3)
+
455#define FUSE_EXPORT_SUPPORT (1 << 4)
+
456#define FUSE_BIG_WRITES (1 << 5)
+
457#define FUSE_DONT_MASK (1 << 6)
+
458#define FUSE_SPLICE_WRITE (1 << 7)
+
459#define FUSE_SPLICE_MOVE (1 << 8)
+
460#define FUSE_SPLICE_READ (1 << 9)
+
461#define FUSE_FLOCK_LOCKS (1 << 10)
+
462#define FUSE_HAS_IOCTL_DIR (1 << 11)
+
463#define FUSE_AUTO_INVAL_DATA (1 << 12)
+
464#define FUSE_DO_READDIRPLUS (1 << 13)
+
465#define FUSE_READDIRPLUS_AUTO (1 << 14)
+
466#define FUSE_ASYNC_DIO (1 << 15)
+
467#define FUSE_WRITEBACK_CACHE (1 << 16)
+
468#define FUSE_NO_OPEN_SUPPORT (1 << 17)
+
469#define FUSE_PARALLEL_DIROPS (1 << 18)
+
470#define FUSE_HANDLE_KILLPRIV (1 << 19)
+
471#define FUSE_POSIX_ACL (1 << 20)
+
472#define FUSE_ABORT_ERROR (1 << 21)
+
473#define FUSE_MAX_PAGES (1 << 22)
+
474#define FUSE_CACHE_SYMLINKS (1 << 23)
+
475#define FUSE_NO_OPENDIR_SUPPORT (1 << 24)
+
476#define FUSE_EXPLICIT_INVAL_DATA (1 << 25)
+
477#define FUSE_MAP_ALIGNMENT (1 << 26)
+
478#define FUSE_SUBMOUNTS (1 << 27)
+
479#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28)
+
480#define FUSE_SETXATTR_EXT (1 << 29)
+
481#define FUSE_INIT_EXT (1 << 30)
+
482#define FUSE_INIT_RESERVED (1 << 31)
+
483/* bits 32..63 get shifted down 32 bits into the flags2 field */
+
484#define FUSE_SECURITY_CTX (1ULL << 32)
+
485#define FUSE_HAS_INODE_DAX (1ULL << 33)
+
486#define FUSE_CREATE_SUPP_GROUP (1ULL << 34)
+
487#define FUSE_HAS_EXPIRE_ONLY (1ULL << 35)
+
488#define FUSE_DIRECT_IO_ALLOW_MMAP (1ULL << 36)
+
489#define FUSE_PASSTHROUGH (1ULL << 37)
+
490#define FUSE_NO_EXPORT_SUPPORT (1ULL << 38)
+
491#define FUSE_HAS_RESEND (1ULL << 39)
+
492/* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */
+
493#define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP
+
494#define FUSE_ALLOW_IDMAP (1ULL << 40)
+
495#define FUSE_OVER_IO_URING (1ULL << 41)
+
496#define FUSE_REQUEST_TIMEOUT (1ULL << 42)
+
497
+
503#define CUSE_UNRESTRICTED_IOCTL (1 << 0)
+
504
+
508#define FUSE_RELEASE_FLUSH (1 << 0)
+
509#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1)
+
510
+
514#define FUSE_GETATTR_FH (1 << 0)
+
515
+
519#define FUSE_LK_FLOCK (1 << 0)
+
520
+
528#define FUSE_WRITE_CACHE (1 << 0)
+
529#define FUSE_WRITE_LOCKOWNER (1 << 1)
+
530#define FUSE_WRITE_KILL_SUIDGID (1 << 2)
+
531
+
532/* Obsolete alias; this flag implies killing suid/sgid only. */
+
533#define FUSE_WRITE_KILL_PRIV FUSE_WRITE_KILL_SUIDGID
+
534
+
538#define FUSE_READ_LOCKOWNER (1 << 1)
+
539
+
552#define FUSE_IOCTL_COMPAT (1 << 0)
+
553#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
554#define FUSE_IOCTL_RETRY (1 << 2)
+
555#define FUSE_IOCTL_32BIT (1 << 3)
+
556#define FUSE_IOCTL_DIR (1 << 4)
+
557#define FUSE_IOCTL_COMPAT_X32 (1 << 5)
+
558
+
559#define FUSE_IOCTL_MAX_IOV 256
+
560
+
566#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
+
567
+
573#define FUSE_FSYNC_FDATASYNC (1 << 0)
+
574
+
581#define FUSE_ATTR_SUBMOUNT (1 << 0)
+
582#define FUSE_ATTR_DAX (1 << 1)
+
583
+
588#define FUSE_OPEN_KILL_SUIDGID (1 << 0)
+
589
+
594#define FUSE_SETXATTR_ACL_KILL_SGID (1 << 0)
+
595
+
600#define FUSE_EXPIRE_ONLY (1 << 0)
+
601
+
607enum fuse_ext_type {
+
608 /* Types 0..31 are reserved for fuse_secctx_header */
+
609 FUSE_MAX_NR_SECCTX = 31,
+
610 FUSE_EXT_GROUPS = 32,
+
611};
+
612
+
613enum fuse_opcode {
+
614 FUSE_LOOKUP = 1,
+
615 FUSE_FORGET = 2, /* no reply */
+
616 FUSE_GETATTR = 3,
+
617 FUSE_SETATTR = 4,
+
618 FUSE_READLINK = 5,
+
619 FUSE_SYMLINK = 6,
+
620 FUSE_MKNOD = 8,
+
621 FUSE_MKDIR = 9,
+
622 FUSE_UNLINK = 10,
+
623 FUSE_RMDIR = 11,
+
624 FUSE_RENAME = 12,
+
625 FUSE_LINK = 13,
+
626 FUSE_OPEN = 14,
+
627 FUSE_READ = 15,
+
628 FUSE_WRITE = 16,
+
629 FUSE_STATFS = 17,
+
630 FUSE_RELEASE = 18,
+
631 FUSE_FSYNC = 20,
+
632 FUSE_SETXATTR = 21,
+
633 FUSE_GETXATTR = 22,
+
634 FUSE_LISTXATTR = 23,
+
635 FUSE_REMOVEXATTR = 24,
+
636 FUSE_FLUSH = 25,
+
637 FUSE_INIT = 26,
+
638 FUSE_OPENDIR = 27,
+
639 FUSE_READDIR = 28,
+
640 FUSE_RELEASEDIR = 29,
+
641 FUSE_FSYNCDIR = 30,
+
642 FUSE_GETLK = 31,
+
643 FUSE_SETLK = 32,
+
644 FUSE_SETLKW = 33,
+
645 FUSE_ACCESS = 34,
+
646 FUSE_CREATE = 35,
+
647 FUSE_INTERRUPT = 36,
+
648 FUSE_BMAP = 37,
+
649 FUSE_DESTROY = 38,
+
650 FUSE_IOCTL = 39,
+
651 FUSE_POLL = 40,
+
652 FUSE_NOTIFY_REPLY = 41,
+
653 FUSE_BATCH_FORGET = 42,
+
654 FUSE_FALLOCATE = 43,
+
655 FUSE_READDIRPLUS = 44,
+
656 FUSE_RENAME2 = 45,
+
657 FUSE_LSEEK = 46,
+
658 FUSE_COPY_FILE_RANGE = 47,
+
659 FUSE_SETUPMAPPING = 48,
+
660 FUSE_REMOVEMAPPING = 49,
+
661 FUSE_SYNCFS = 50,
+
662 FUSE_TMPFILE = 51,
+
663 FUSE_STATX = 52,
+
664 FUSE_COPY_FILE_RANGE_64 = 53,
+
665
+
666 /* CUSE specific operations */
+
667 CUSE_INIT = 4096,
+
668
+
669 /* Reserved opcodes: helpful to detect structure endian-ness */
+
670 CUSE_INIT_BSWAP_RESERVED = 1048576, /* CUSE_INIT << 8 */
+
671 FUSE_INIT_BSWAP_RESERVED = 436207616, /* FUSE_INIT << 24 */
+
672};
+
673
+
674enum fuse_notify_code {
+
675 FUSE_NOTIFY_POLL = 1,
+
676 FUSE_NOTIFY_INVAL_INODE = 2,
+
677 FUSE_NOTIFY_INVAL_ENTRY = 3,
+
678 FUSE_NOTIFY_STORE = 4,
+
679 FUSE_NOTIFY_RETRIEVE = 5,
+
680 FUSE_NOTIFY_DELETE = 6,
+
681 FUSE_NOTIFY_RESEND = 7,
+
682 FUSE_NOTIFY_INC_EPOCH = 8,
+
683 FUSE_NOTIFY_CODE_MAX,
+
684};
+
685
+
686/* The read buffer is required to be at least 8k, but may be much larger */
+
687#define FUSE_MIN_READ_BUFFER 8192
+
688
+
689#define FUSE_COMPAT_ENTRY_OUT_SIZE 120
+
690
+
691struct fuse_entry_out {
+
692 uint64_t nodeid; /* Inode ID */
+
693 uint64_t generation; /* Inode generation: nodeid:gen must
+
694 be unique for the fs's lifetime */
+
695 uint64_t entry_valid; /* Cache timeout for the name */
+
696 uint64_t attr_valid; /* Cache timeout for the attributes */
+
697 uint32_t entry_valid_nsec;
+
698 uint32_t attr_valid_nsec;
+
699 struct fuse_attr attr;
+
700};
+
701
+
702struct fuse_forget_in {
+
703 uint64_t nlookup;
+
704};
+
705
+
706struct fuse_forget_one {
+
707 uint64_t nodeid;
+
708 uint64_t nlookup;
+
709};
+
710
+
711struct fuse_batch_forget_in {
+
712 uint32_t count;
+
713 uint32_t dummy;
+
714};
+
715
+
716struct fuse_getattr_in {
+
717 uint32_t getattr_flags;
+
718 uint32_t dummy;
+
719 uint64_t fh;
+
720};
+
721
+
722#define FUSE_COMPAT_ATTR_OUT_SIZE 96
+
723
+
724struct fuse_attr_out {
+
725 uint64_t attr_valid; /* Cache timeout for the attributes */
+
726 uint32_t attr_valid_nsec;
+
727 uint32_t dummy;
+
728 struct fuse_attr attr;
+
729};
+
730
+
731struct fuse_statx_in {
+
732 uint32_t getattr_flags;
+
733 uint32_t reserved;
+
734 uint64_t fh;
+
735 uint32_t sx_flags;
+
736 uint32_t sx_mask;
+
737};
+
738
+
739struct fuse_statx_out {
+
740 uint64_t attr_valid; /* Cache timeout for the attributes */
+
741 uint32_t attr_valid_nsec;
+
742 uint32_t flags;
+
743 uint64_t spare[2];
+
744 struct fuse_statx stat;
+
745};
+
746
+
747#define FUSE_COMPAT_MKNOD_IN_SIZE 8
+
748
+
749struct fuse_mknod_in {
+
750 uint32_t mode;
+
751 uint32_t rdev;
+
752 uint32_t umask;
+
753 uint32_t padding;
+
754};
+
755
+
756struct fuse_mkdir_in {
+
757 uint32_t mode;
+
758 uint32_t umask;
+
759};
+
760
+
761struct fuse_rename_in {
+
762 uint64_t newdir;
+
763};
+
764
+
765struct fuse_rename2_in {
+
766 uint64_t newdir;
+
767 uint32_t flags;
+
768 uint32_t padding;
+
769};
+
770
+
771struct fuse_link_in {
+
772 uint64_t oldnodeid;
+
773};
+
774
+
775struct fuse_setattr_in {
+
776 uint32_t valid;
+
777 uint32_t padding;
+
778 uint64_t fh;
+
779 uint64_t size;
+
780 uint64_t lock_owner;
+
781 uint64_t atime;
+
782 uint64_t mtime;
+
783 uint64_t ctime;
+
784 uint32_t atimensec;
+
785 uint32_t mtimensec;
+
786 uint32_t ctimensec;
+
787 uint32_t mode;
+
788 uint32_t unused4;
+
789 uint32_t uid;
+
790 uint32_t gid;
+
791 uint32_t unused5;
+
792};
+
793
+
794struct fuse_open_in {
+
795 uint32_t flags;
+
796 uint32_t open_flags; /* FUSE_OPEN_... */
+
797};
+
798
+
799struct fuse_create_in {
+
800 uint32_t flags;
+
801 uint32_t mode;
+
802 uint32_t umask;
+
803 uint32_t open_flags; /* FUSE_OPEN_... */
+
804};
+
805
+
806struct fuse_open_out {
+
807 uint64_t fh;
+
808 uint32_t open_flags;
+
809 int32_t backing_id;
+
810};
+
811
+
812struct fuse_release_in {
+
813 uint64_t fh;
+
814 uint32_t flags;
+
815 uint32_t release_flags;
+
816 uint64_t lock_owner;
+
817};
+
818
+
819struct fuse_flush_in {
+
820 uint64_t fh;
+
821 uint32_t unused;
+
822 uint32_t padding;
+
823 uint64_t lock_owner;
+
824};
+
825
+
826struct fuse_read_in {
+
827 uint64_t fh;
+
828 uint64_t offset;
+
829 uint32_t size;
+
830 uint32_t read_flags;
+
831 uint64_t lock_owner;
+
832 uint32_t flags;
+
833 uint32_t padding;
+
834};
+
835
+
836#define FUSE_COMPAT_WRITE_IN_SIZE 24
+
837
+
838struct fuse_write_in {
+
839 uint64_t fh;
+
840 uint64_t offset;
+
841 uint32_t size;
+
842 uint32_t write_flags;
+
843 uint64_t lock_owner;
+
844 uint32_t flags;
+
845 uint32_t padding;
+
846};
+
847
+
848struct fuse_write_out {
+
849 uint32_t size;
+
850 uint32_t padding;
+
851};
+
852
+
853#define FUSE_COMPAT_STATFS_SIZE 48
+
854
+
855struct fuse_statfs_out {
+
856 struct fuse_kstatfs st;
+
857};
+
858
+
859struct fuse_fsync_in {
+
860 uint64_t fh;
+
861 uint32_t fsync_flags;
+
862 uint32_t padding;
+
863};
+
864
+
865#define FUSE_COMPAT_SETXATTR_IN_SIZE 8
+
866
+
867struct fuse_setxattr_in {
+
868 uint32_t size;
+
869 uint32_t flags;
+
870 uint32_t setxattr_flags;
+
871 uint32_t padding;
+
872};
+
873
+
874struct fuse_getxattr_in {
+
875 uint32_t size;
+
876 uint32_t padding;
+
877};
+
878
+
879struct fuse_getxattr_out {
+
880 uint32_t size;
+
881 uint32_t padding;
+
882};
+
883
+
884struct fuse_lk_in {
+
885 uint64_t fh;
+
886 uint64_t owner;
+
887 struct fuse_file_lock lk;
+
888 uint32_t lk_flags;
+
889 uint32_t padding;
+
890};
+
891
+
892struct fuse_lk_out {
+
893 struct fuse_file_lock lk;
+
894};
+
895
+
896struct fuse_access_in {
+
897 uint32_t mask;
+
898 uint32_t padding;
+
899};
+
900
+
901struct fuse_init_in {
+
902 uint32_t major;
+
903 uint32_t minor;
+
904 uint32_t max_readahead;
+
905 uint32_t flags;
+
906 uint32_t flags2;
+
907 uint32_t unused[11];
+
908};
+
909
+
910#define FUSE_COMPAT_INIT_OUT_SIZE 8
+
911#define FUSE_COMPAT_22_INIT_OUT_SIZE 24
+
912
+
913struct fuse_init_out {
+
914 uint32_t major;
+
915 uint32_t minor;
+
916 uint32_t max_readahead;
+
917 uint32_t flags;
+
918 uint16_t max_background;
+
919 uint16_t congestion_threshold;
+
920 uint32_t max_write;
+
921 uint32_t time_gran;
+
922 uint16_t max_pages;
+
923 uint16_t map_alignment;
+
924 uint32_t flags2;
+
925 uint32_t max_stack_depth;
+
926 uint16_t request_timeout;
+
927 uint16_t unused[11];
+
928};
+
929
+
930#define CUSE_INIT_INFO_MAX 4096
+
931
+
932struct cuse_init_in {
+
933 uint32_t major;
+
934 uint32_t minor;
+
935 uint32_t unused;
+
936 uint32_t flags;
+
937};
+
938
+
939struct cuse_init_out {
+
940 uint32_t major;
+
941 uint32_t minor;
+
942 uint32_t unused;
+
943 uint32_t flags;
+
944 uint32_t max_read;
+
945 uint32_t max_write;
+
946 uint32_t dev_major; /* chardev major */
+
947 uint32_t dev_minor; /* chardev minor */
+
948 uint32_t spare[10];
+
949};
+
950
+
951struct fuse_interrupt_in {
+
952 uint64_t unique;
+
953};
+
954
+
955struct fuse_bmap_in {
+
956 uint64_t block;
+
957 uint32_t blocksize;
+
958 uint32_t padding;
+
959};
+
960
+
961struct fuse_bmap_out {
+
962 uint64_t block;
+
963};
+
964
+
965struct fuse_ioctl_in {
+
966 uint64_t fh;
+
967 uint32_t flags;
+
968 uint32_t cmd;
+
969 uint64_t arg;
+
970 uint32_t in_size;
+
971 uint32_t out_size;
+
972};
+
973
+
974struct fuse_ioctl_iovec {
+
975 uint64_t base;
+
976 uint64_t len;
+
977};
+
978
+
979struct fuse_ioctl_out {
+
980 int32_t result;
+
981 uint32_t flags;
+
982 uint32_t in_iovs;
+
983 uint32_t out_iovs;
+
984};
+
985
+
986struct fuse_poll_in {
+
987 uint64_t fh;
+
988 uint64_t kh;
+
989 uint32_t flags;
+
990 uint32_t events;
+
991};
+
992
+
993struct fuse_poll_out {
+
994 uint32_t revents;
+
995 uint32_t padding;
+
996};
+
997
+
998struct fuse_notify_poll_wakeup_out {
+
999 uint64_t kh;
+
1000};
+
1001
+
1002struct fuse_fallocate_in {
+
1003 uint64_t fh;
+
1004 uint64_t offset;
+
1005 uint64_t length;
+
1006 uint32_t mode;
+
1007 uint32_t padding;
+
1008};
+
1009
+
1016#define FUSE_UNIQUE_RESEND (1ULL << 63)
+
1017
+
1031#define FUSE_INVALID_UIDGID ((uint32_t)(-1))
+
1032
+
1033struct fuse_in_header {
+
1034 uint32_t len;
+
1035 uint32_t opcode;
+
1036 uint64_t unique;
+
1037 uint64_t nodeid;
+
1038 uint32_t uid;
+
1039 uint32_t gid;
+
1040 uint32_t pid;
+
1041 uint16_t total_extlen; /* length of extensions in 8byte units */
+
1042 uint16_t padding;
+
1043};
+
1044
+
1045struct fuse_out_header {
+
1046 uint32_t len;
+
1047 int32_t error;
+
1048 uint64_t unique;
+
1049};
+
1050
+
1051struct fuse_dirent {
+
1052 uint64_t ino;
+
1053 uint64_t off;
+
1054 uint32_t namelen;
+
1055 uint32_t type;
+
1056 char name[];
+
1057};
+
1058
+
1059/* Align variable length records to 64bit boundary */
+
1060#define FUSE_REC_ALIGN(x) \
+
1061 (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1))
+
1062
+
1063#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name)
+
1064#define FUSE_DIRENT_ALIGN(x) FUSE_REC_ALIGN(x)
+
1065#define FUSE_DIRENT_SIZE(d) \
+
1066 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen)
+
1067
+
1068struct fuse_direntplus {
+
1069 struct fuse_entry_out entry_out;
+
1070 struct fuse_dirent dirent;
+
1071};
+
1072
+
1073#define FUSE_NAME_OFFSET_DIRENTPLUS \
+
1074 offsetof(struct fuse_direntplus, dirent.name)
+
1075#define FUSE_DIRENTPLUS_SIZE(d) \
+
1076 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen)
+
1077
+
1078struct fuse_notify_inval_inode_out {
+
1079 uint64_t ino;
+
1080 int64_t off;
+
1081 int64_t len;
+
1082};
+
1083
+
1084struct fuse_notify_inval_entry_out {
+
1085 uint64_t parent;
+
1086 uint32_t namelen;
+
1087 uint32_t flags;
+
1088};
+
1089
+
1090struct fuse_notify_delete_out {
+
1091 uint64_t parent;
+
1092 uint64_t child;
+
1093 uint32_t namelen;
+
1094 uint32_t padding;
+
1095};
+
1096
+
1097struct fuse_notify_store_out {
+
1098 uint64_t nodeid;
+
1099 uint64_t offset;
+
1100 uint32_t size;
+
1101 uint32_t padding;
+
1102};
+
1103
+
1104struct fuse_notify_retrieve_out {
+
1105 uint64_t notify_unique;
+
1106 uint64_t nodeid;
+
1107 uint64_t offset;
+
1108 uint32_t size;
+
1109 uint32_t padding;
+
1110};
+
1111
+
1112/* Matches the size of fuse_write_in */
+
1113struct fuse_notify_retrieve_in {
+
1114 uint64_t dummy1;
+
1115 uint64_t offset;
+
1116 uint32_t size;
+
1117 uint32_t dummy2;
+
1118 uint64_t dummy3;
+
1119 uint64_t dummy4;
+
1120};
+
1121
+
1122struct fuse_backing_map {
+
1123 int32_t fd;
+
1124 uint32_t flags;
+
1125 uint64_t padding;
+
1126};
+
1127
+
1128/* Device ioctls: */
+
1129#define FUSE_DEV_IOC_MAGIC 229
+
1130#define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t)
+
1131#define FUSE_DEV_IOC_BACKING_OPEN _IOW(FUSE_DEV_IOC_MAGIC, 1, \
+
1132 struct fuse_backing_map)
+
1133#define FUSE_DEV_IOC_BACKING_CLOSE _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t)
+
1134
+
1135struct fuse_lseek_in {
+
1136 uint64_t fh;
+
1137 uint64_t offset;
+
1138 uint32_t whence;
+
1139 uint32_t padding;
+
1140};
+
1141
+
1142struct fuse_lseek_out {
+
1143 uint64_t offset;
+
1144};
+
1145
+
1146struct fuse_copy_file_range_in {
+
1147 uint64_t fh_in;
+
1148 uint64_t off_in;
+
1149 uint64_t nodeid_out;
+
1150 uint64_t fh_out;
+
1151 uint64_t off_out;
+
1152 uint64_t len;
+
1153 uint64_t flags;
+
1154};
+
1155
+
1156/* For FUSE_COPY_FILE_RANGE_64 */
+
1157struct fuse_copy_file_range_out {
+
1158 uint64_t bytes_copied;
+
1159};
+
1160
+
1161#define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0)
+
1162#define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1)
+
1163struct fuse_setupmapping_in {
+
1164 /* An already open handle */
+
1165 uint64_t fh;
+
1166 /* Offset into the file to start the mapping */
+
1167 uint64_t foffset;
+
1168 /* Length of mapping required */
+
1169 uint64_t len;
+
1170 /* Flags, FUSE_SETUPMAPPING_FLAG_* */
+
1171 uint64_t flags;
+
1172 /* Offset in Memory Window */
+
1173 uint64_t moffset;
+
1174};
+
1175
+
1176struct fuse_removemapping_in {
+
1177 /* number of fuse_removemapping_one follows */
+
1178 uint32_t count;
+
1179};
+
1180
+
1181struct fuse_removemapping_one {
+
1182 /* Offset into the dax window start the unmapping */
+
1183 uint64_t moffset;
+
1184 /* Length of mapping required */
+
1185 uint64_t len;
+
1186};
+
1187
+
1188#define FUSE_REMOVEMAPPING_MAX_ENTRY \
+
1189 (PAGE_SIZE / sizeof(struct fuse_removemapping_one))
+
1190
+
1191struct fuse_syncfs_in {
+
1192 uint64_t padding;
+
1193};
+
1194
+
1195/*
+
1196 * For each security context, send fuse_secctx with size of security context
+
1197 * fuse_secctx will be followed by security context name and this in turn
+
1198 * will be followed by actual context label.
+
1199 * fuse_secctx, name, context
+
1200 */
+
1201struct fuse_secctx {
+
1202 uint32_t size;
+
1203 uint32_t padding;
+
1204};
+
1205
+
1206/*
+
1207 * Contains the information about how many fuse_secctx structures are being
+
1208 * sent and what's the total size of all security contexts (including
+
1209 * size of fuse_secctx_header).
+
1210 *
+
1211 */
+
1212struct fuse_secctx_header {
+
1213 uint32_t size;
+
1214 uint32_t nr_secctx;
+
1215};
+
1216
+
1225struct fuse_ext_header {
+
1226 uint32_t size;
+
1227 uint32_t type;
+
1228};
+
1229
+
1235struct fuse_supp_groups {
+
1236 uint32_t nr_groups;
+
1237 uint32_t groups[];
+
1238};
+
1239
+
1243#define FUSE_URING_IN_OUT_HEADER_SZ 128
+
1244#define FUSE_URING_OP_IN_OUT_SZ 128
+
1245
+
1246/* Used as part of the fuse_uring_req_header */
+
1247struct fuse_uring_ent_in_out {
+
1248 uint64_t flags;
+
1249
+
1250 /*
+
1251 * commit ID to be used in a reply to a ring request (see also
+
1252 * struct fuse_uring_cmd_req)
+
1253 */
+
1254 uint64_t commit_id;
+
1255
+
1256 /* size of user payload buffer */
+
1257 uint32_t payload_sz;
+
1258 uint32_t padding;
+
1259
+
1260 uint64_t reserved;
+
1261};
+
1262
+
+ +
1267 /* struct fuse_in_header / struct fuse_out_header */
+
1268 char in_out[FUSE_URING_IN_OUT_HEADER_SZ];
+
1269
+
1270 /* per op code header */
+
1271 char op_in[FUSE_URING_OP_IN_OUT_SZ];
+
1272
+
1273 struct fuse_uring_ent_in_out ring_ent_in_out;
+
1274};
+
+
1275
+
1279enum fuse_uring_cmd {
+
1280 FUSE_IO_URING_CMD_INVALID = 0,
+
1281
+
1282 /* register the request buffer and fetch a fuse request */
+
1283 FUSE_IO_URING_CMD_REGISTER = 1,
+
1284
+
1285 /* commit fuse request result and fetch next request */
+
1286 FUSE_IO_URING_CMD_COMMIT_AND_FETCH = 2,
+
1287};
+
1288
+
+ +
1293 uint64_t flags;
+
1294
+
1295 /* entry identifier for commits */
+
1296 uint64_t commit_id;
+
1297
+
1298 /* queue the command is for (queue index) */
+
1299 uint16_t qid;
+
1300 uint8_t padding[6];
+
1301};
+
+
1302
+
1303#endif /* _LINUX_FUSE_H */
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_818_81_2include_2fuse__log_8h.html b/doc/html/fuse-3_818_81_2include_2fuse__log_8h.html new file mode 100644 index 0000000..f8b561a --- /dev/null +++ b/doc/html/fuse-3_818_81_2include_2fuse__log_8h.html @@ -0,0 +1,268 @@ + + + + + + + +libfuse: fuse-3.18.1/include/fuse_log.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_log.h File Reference
+
+
+
#include <stdarg.h>
+
+

Go to the source code of this file.

+ + + + +

+Typedefs

typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
 
+ + + +

+Enumerations

enum  fuse_log_level
 
+ + + + + + + + + +

+Functions

void fuse_set_log_func (fuse_log_func_t func)
 
void fuse_log (enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
 
void void fuse_log_enable_syslog (const char *ident, int option, int facility)
 
void fuse_log_close_syslog (void)
 
+

Detailed Description

+

This file defines the logging interface of FUSE

+ +

Definition in file fuse_log.h.

+

Typedef Documentation

+ +

◆ fuse_log_func_t

+ +
+
+ + + + +
typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
+
+

Log message handler function.

+

This function must be thread-safe. It may be called from any libfuse function, including fuse_parse_cmdline() and other functions invoked before a FUSE filesystem is created.

+

Install a custom log message handler function using fuse_set_log_func().

+
Parameters
+ + + + +
levellog severity level
fmtsprintf-style format string including newline
apformat string arguments
+
+
+ +

Definition at line 52 of file fuse_log.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_log_level

+ +
+
+ + + + +
enum fuse_log_level
+
+

Log severity level

+

These levels correspond to syslog(2) log levels since they are widely used.

+ +

Definition at line 28 of file fuse_log.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_log()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_log (enum fuse_log_level level,
const char * fmt,
 ... 
)
+
+

Emit a log message

+
Parameters
+ + + +
levelseverity level (FUSE_LOG_ERR, FUSE_LOG_DEBUG, etc)
fmtsprintf-style format string including newline
+
+
+ +

Definition at line 76 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_close_syslog()

+ +
+
+ + + + + + + + +
void fuse_log_close_syslog (void )
+
+

To be called at teardown to close syslog.

+ +

Definition at line 93 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_enable_syslog()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void void fuse_log_enable_syslog (const char * ident,
int option,
int facility 
)
+
+

Switch default log handler from stderr to syslog

+

Passed options are according to 'man 3 openlog'

+ +

Definition at line 86 of file fuse_log.c.

+ +
+
+ +

◆ fuse_set_log_func()

+ +
+
+ + + + + + + + +
void fuse_set_log_func (fuse_log_func_t func)
+
+

Install a custom log handler function.

+

Log messages are emitted by libfuse functions to report errors and debug information. Messages are printed to stderr by default but this can be overridden by installing a custom log message handler function.

+

The log message handler function is global and affects all FUSE filesystems created within this process.

+
Parameters
+ + +
funca custom log message handler function or NULL to revert to the default
+
+
+ +

Definition at line 69 of file fuse_log.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2include_2fuse__log_8h_source.html b/doc/html/fuse-3_818_81_2include_2fuse__log_8h_source.html new file mode 100644 index 0000000..efad0d2 --- /dev/null +++ b/doc/html/fuse-3_818_81_2include_2fuse__log_8h_source.html @@ -0,0 +1,113 @@ + + + + + + + +libfuse: fuse-3.18.1/include/fuse_log.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_log.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#ifndef FUSE_LOG_H_
+
10#define FUSE_LOG_H_
+
11
+
17#include <stdarg.h>
+
18
+
19#ifdef __cplusplus
+
20extern "C" {
+
21#endif
+
22
+
+ +
29 FUSE_LOG_EMERG,
+
30 FUSE_LOG_ALERT,
+
31 FUSE_LOG_CRIT,
+
32 FUSE_LOG_ERR,
+
33 FUSE_LOG_WARNING,
+
34 FUSE_LOG_NOTICE,
+
35 FUSE_LOG_INFO,
+
36 FUSE_LOG_DEBUG
+
37};
+
+
38
+
52typedef void (*fuse_log_func_t)(enum fuse_log_level level,
+
53 const char *fmt, va_list ap);
+
54
+ +
69
+
76void fuse_log(enum fuse_log_level level, const char *fmt, ...)
+
77 __attribute__((format(printf, 2, 3)));
+
78
+
84void fuse_log_enable_syslog(const char *ident, int option, int facility);
+
85
+
89void fuse_log_close_syslog(void);
+
90
+
91#ifdef __cplusplus
+
92}
+
93#endif
+
94
+
95#endif /* FUSE_LOG_H_ */
+
void fuse_log_close_syslog(void)
Definition fuse_log.c:93
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:86
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:69
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2include_2fuse__lowlevel_8h.html b/doc/html/fuse-3_818_81_2include_2fuse__lowlevel_8h.html new file mode 100644 index 0000000..1b6ce3a --- /dev/null +++ b/doc/html/fuse-3_818_81_2include_2fuse__lowlevel_8h.html @@ -0,0 +1,2530 @@ + + + + + + + +libfuse: fuse-3.18.1/include/fuse_lowlevel.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_lowlevel.h File Reference
+
+
+
#include "fuse_common.h"
+#include <stddef.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + + + + + +

+Data Structures

struct  fuse_entry_param
 
struct  fuse_ctx
 
struct  fuse_lowlevel_ops
 
struct  fuse_cmdline_opts
 
+ + + +

+Macros

#define FUSE_ROOT_ID   1
 
+ + + + + + + +

+Typedefs

typedef uint64_t fuse_ino_t
 
typedef struct fuse_req * fuse_req_t
 
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
 
+ + + +

+Enumerations

enum  fuse_notify_entry_flags
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

int fuse_reply_err (fuse_req_t req, int err)
 
void fuse_reply_none (fuse_req_t req)
 
int fuse_reply_entry (fuse_req_t req, const struct fuse_entry_param *e)
 
int fuse_reply_create (fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
 
int fuse_reply_attr (fuse_req_t req, const struct stat *attr, double attr_timeout)
 
int fuse_reply_readlink (fuse_req_t req, const char *link)
 
int fuse_passthrough_open (fuse_req_t req, int fd)
 
int fuse_reply_open (fuse_req_t req, const struct fuse_file_info *fi)
 
int fuse_reply_write (fuse_req_t req, size_t count)
 
int fuse_reply_buf (fuse_req_t req, const char *buf, size_t size)
 
int fuse_reply_data (fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
 
int fuse_reply_iov (fuse_req_t req, const struct iovec *iov, int count)
 
int fuse_reply_statfs (fuse_req_t req, const struct statvfs *stbuf)
 
int fuse_reply_xattr (fuse_req_t req, size_t count)
 
int fuse_reply_lock (fuse_req_t req, const struct flock *lock)
 
int fuse_reply_bmap (fuse_req_t req, uint64_t idx)
 
size_t fuse_add_direntry (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
 
size_t fuse_add_direntry_plus (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
 
int fuse_reply_ioctl_retry (fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
 
int fuse_reply_ioctl (fuse_req_t req, int result, const void *buf, size_t size)
 
int fuse_reply_ioctl_iov (fuse_req_t req, int result, const struct iovec *iov, int count)
 
int fuse_reply_poll (fuse_req_t req, unsigned revents)
 
int fuse_reply_lseek (fuse_req_t req, off_t off)
 
int fuse_reply_statx (fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
 
int fuse_lowlevel_notify_poll (struct fuse_pollhandle *ph)
 
int fuse_lowlevel_notify_inval_inode (struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
 
int fuse_lowlevel_notify_increment_epoch (struct fuse_session *se)
 
int fuse_lowlevel_notify_inval_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_expire_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_delete (struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_store (struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
 
int fuse_lowlevel_notify_retrieve (struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
 
void * fuse_req_userdata (fuse_req_t req)
 
const struct fuse_ctxfuse_req_ctx (fuse_req_t req)
 
int fuse_req_getgroups (fuse_req_t req, int size, gid_t list[])
 
void fuse_req_interrupt_func (fuse_req_t req, fuse_interrupt_func_t func, void *data)
 
int fuse_req_interrupted (fuse_req_t req)
 
void fuse_lowlevel_version (void)
 
void fuse_lowlevel_help (void)
 
void fuse_cmdline_help (void)
 
int fuse_parse_cmdline_30 (struct fuse_args *args, struct fuse_cmdline_opts *opts)
 
int fuse_session_mount (struct fuse_session *se, const char *mountpoint)
 
int fuse_session_loop (struct fuse_session *se)
 
void fuse_session_exit (struct fuse_session *se)
 
void fuse_session_reset (struct fuse_session *se)
 
int fuse_session_exited (struct fuse_session *se)
 
void fuse_session_unmount (struct fuse_session *se)
 
void fuse_session_destroy (struct fuse_session *se)
 
int fuse_session_fd (struct fuse_session *se)
 
void fuse_session_process_buf (struct fuse_session *se, const struct fuse_buf *buf)
 
int fuse_session_receive_buf (struct fuse_session *se, struct fuse_buf *buf)
 
bool fuse_req_is_uring (fuse_req_t req)
 
int fuse_req_get_payload (fuse_req_t req, char **payload, size_t *payload_sz, void **mr)
 
+

Detailed Description

+

Low level API

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header. To use the newest API define it to 35 (recommended for any new application).

+ +

Definition in file fuse_lowlevel.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ROOT_ID

+ +
+
+ + + + +
#define FUSE_ROOT_ID   1
+
+

The node ID of the root inode

+ +

Definition at line 44 of file fuse_lowlevel.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_ino_t

+ +
+
+ + + + +
typedef uint64_t fuse_ino_t
+
+

Inode number type

+ +

Definition at line 47 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_interrupt_func_t

+ +
+
+ + + + +
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
+
+

Callback function for an interrupt

+
Parameters
+ + + +
reqinterrupted request
datauser data
+
+
+ +

Definition at line 1992 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_req_t

+ +
+
+ + + + +
typedef struct fuse_req* fuse_req_t
+
+

Request pointer type

+ +

Definition at line 50 of file fuse_lowlevel.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_notify_entry_flags

+ +
+
+ + + + +
enum fuse_notify_entry_flags
+
+

Flags for fuse_lowlevel_notify_entry() 0 = invalidate entry FUSE_LL_EXPIRE_ONLY = expire entry

+ +

Definition at line 151 of file fuse_lowlevel.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_add_direntry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct stat * stbuf,
off_t off 
)
+
+

Add a directory entry to the buffer

+

Buffer needs to be large enough to hold the entry. If it's not, then the entry is not filled in but the size of the entry is still returned. The caller can check this by comparing the bufsize parameter with the returned entry size. If the entry size is larger than the buffer size, the operation failed.

+

From the 'stbuf' argument the st_ino field and bits 12-15 of the st_mode field are used. The other fields are ignored.

+

off should be any non-zero value that the filesystem can use to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to mean "from the beginning", and should therefore never be used (the first call to fuse_add_direntry should be passed the offset of the second directory entry).

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
stbufthe file attributes
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 286 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_add_direntry_plus()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry_plus (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct fuse_entry_parame,
off_t off 
)
+
+

Add a directory entry to the buffer with the attributes

+

See documentation of fuse_add_direntry() for more details.

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
ethe directory entry
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 376 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_cmdline_help()

+ +
+
+ + + + + + + + +
void fuse_cmdline_help (void )
+
+

Print available options for fuse_parse_cmdline().

+ +

Definition at line 130 of file helper.c.

+ +
+
+ +

◆ fuse_lowlevel_help()

+ +
+
+ + + + + + + + +
void fuse_lowlevel_help (void )
+
+

Print available low-level options to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+ +

Definition at line 3000 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_delete()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_delete (struct fuse_session * se,
fuse_ino_t parent,
fuse_ino_t child,
const char * name,
size_t namelen 
)
+
+

This function behaves like fuse_lowlevel_notify_inval_entry() with the following additional effect (at least as of Linux kernel 4.8):

+

If the provided child inode matches the inode that is currently associated with the cached dentry, and if there are any inotify watches registered for the dentry, then the watchers are informed that the dentry has been deleted.

+

To avoid a deadlock this function must not be called while executing a related filesystem operation or while holding a lock that could be needed to execute such an operation (see the description of fuse_lowlevel_notify_inval_entry() for more details).

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.18. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
parentinode number
childinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2562 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_expire_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_expire_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen 
)
+
+

Notify to expire parent attributes and the dentry matching parent/name

+

Same restrictions apply as for fuse_lowlevel_notify_inval_entry()

+

Compared to invalidating an entry, expiring the entry results not in a forceful removal of that entry from kernel cache but instead the next access to it forces a lookup from the filesystem.

+

This makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+

Added in FUSE protocol version 7.38. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure, -enosys if no kernel support
+ +

Definition at line 2549 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_increment_epoch()

+ +
+
+ + + + + + + + +
int fuse_lowlevel_notify_increment_epoch (struct fuse_session * se)
+
+

Notify to increment the epoch for the current

+

Each fuse connection has an 'epoch', which is initialized during INIT. Caching will then be validated against the epoch value: if the current epoch is higher than an object being revalidated, the object is invalid.

+

This function simply increment the current epoch value.

+
Parameters
+ + +
sethe session object
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 3116 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen 
)
+
+

Notify to invalidate parent attributes and the dentry matching parent/name

+

To avoid a deadlock this function must not be called in the execution path of a related filesystem operation or within any code that could hold a lock that could be needed to execute such an operation. As of kernel 4.18, a "related operation" is a lookup(), symlink(), mknod(), mkdir(), unlink(), rename(), link() or create() request for the parent, and a setattr(), unlink(), rmdir(), rename(), setxattr(), removexattr(), readdir() or readdirplus() request for the inode itself.

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2543 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_inode()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_inode (struct fuse_session * se,
fuse_ino_t ino,
off_t off,
off_t len 
)
+
+

Notify to invalidate cache for an inode.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+

If the filesystem has writeback caching enabled, invalidating an inode will first trigger a writeback of all dirty pages. The call will block until all writeback requests have completed and the inode has been invalidated. It will, however, not wait for completion of pending writeback requests that have been issued before.

+

If there are no dirty pages, this function will never block.

+
Parameters
+ + + + + +
sethe session object
inothe inode number
offthe offset in the inode where to start invalidating or negative to invalidate attributes only
lenthe amount of cache to invalidate or 0 for all
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2475 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_poll()

+ +
+
+ + + + + + + + +
int fuse_lowlevel_notify_poll (struct fuse_pollhandle * ph)
+
+

Notify IO readiness event

+

For more information, please read comment for poll operation.

+
Parameters
+ + +
phpoll handle to notify IO readiness event for
+
+
+ +

Definition at line 2458 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_retrieve()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_retrieve (struct fuse_session * se,
fuse_ino_t ino,
size_t size,
off_t offset,
void * cookie 
)
+
+

Retrieve data from the kernel buffers

+

Retrieve data in the kernel buffers belonging to the given inode. If successful then the retrieve_reply() method will be called with the returned data.

+

Only present pages are returned in the retrieve reply. Retrieving stops when it finds a non-present page and only data prior to that is returned.

+

If this function returns an error, then the retrieve will not be completed and no reply will be sent.

+

This function doesn't change the dirty state of pages in the kernel buffer. For dirty pages the write() method will be called regardless of having been retrieved previously.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
sizethe number of bytes to retrieve
offsetthe starting offset into the file to retrieve from
cookieuser data to supply to the reply callback
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2668 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_store()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_store (struct fuse_session * se,
fuse_ino_t ino,
off_t offset,
struct fuse_bufvecbufv,
enum fuse_buf_copy_flags flags 
)
+
+

Store data to the kernel buffers

+

Synchronously store data in the kernel buffers belonging to the given inode. The stored data is marked up-to-date (no read will be performed against it, unless it's invalidated or evicted from the cache).

+

If the stored data overflows the current file size, then the size is extended, similarly to a write(2) on the filesystem.

+

If this function returns an error, then the store wasn't fully completed, but it may have been partially completed.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
offsetthe starting offset into the file to store to
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2588 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_version()

+ +
+
+ + + + + + + + +
void fuse_lowlevel_version (void )
+
+

Print low-level version information to stdout.

+ +

Definition at line 2993 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_parse_cmdline_30()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_parse_cmdline_30 (struct fuse_argsargs,
struct fuse_cmdline_optsopts 
)
+
+

Utility function to parse common options for simple file systems using the low-level API. A help text that describes the available options can be printed with fuse_cmdline_help. A single non-option argument is treated as the mountpoint. Multiple non-option arguments will result in an error.

+

If neither -o subtype= or -o fsname= options are given, a new subtype option will be added and set to the basename of the program (the fsname will remain unset, and then defaults to "fuse").

+

Known options will be removed from args, unknown options will remain.

+
Parameters
+ + + +
argsargument vector (input+output)
optsoutput argument for parsed options
+
+
+
Returns
0 on success, -1 on failure
+

struct fuse_cmdline_opts got extended in libfuse-3.12

+ +

Definition at line 237 of file helper.c.

+ +
+
+ +

◆ fuse_passthrough_open()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_passthrough_open (fuse_req_t req,
int fd 
)
+
+

Setup passthrough backing file for open reply

+

Currently there should be only one backing id per node / backing file.

+

Possible requests: open, opendir, create

+
Parameters
+ + + +
reqrequest handle
fdbacking file descriptor
+
+
+
Returns
positive backing id for success, 0 for failure
+ +

Definition at line 480 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_attr()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_attr (fuse_req_t req,
const struct stat * attr,
double attr_timeout 
)
+
+

Reply with attributes

+

Possible requests: getattr, setattr

+
Parameters
+ + + + +
reqrequest handle
attrthe attributes
attr_timeoutvalidity timeout (in seconds) for the attributes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 460 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_bmap()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_bmap (fuse_req_t req,
uint64_t idx 
)
+
+

Reply with block index

+

Possible requests: bmap

+
Parameters
+ + + +
reqrequest handle
idxblock index within device
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 973 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_buf (fuse_req_t req,
const char * buf,
size_t size 
)
+
+

Reply with data

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
bufbuffer containing data
sizethe size of data in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 524 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_create()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_create (fuse_req_t req,
const struct fuse_entry_parame,
const struct fuse_file_infofi 
)
+
+

Reply with a directory entry and open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes

+

Possible requests: create

+

Side effects: increments the lookup count on success

+
Parameters
+ + + + +
reqrequest handle
ethe entry parameters
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 444 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_data()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_data (fuse_req_t req,
struct fuse_bufvecbufv,
enum fuse_buf_copy_flags flags 
)
+
+

Reply with data copied/moved from buffer(s)

+

Zero copy data transfer ("splicing") will be used under the following circumstances:

+
    +
  1. FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports splicing from the fuse device (FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.capable), and
  4. +
  5. flags does not contain FUSE_BUF_NO_SPLICE
  6. +
  7. The amount of data that is provided in file-descriptor backed buffers (i.e., buffers for which bufv[n].flags == FUSE_BUF_FD) is at least twice the page size.
  8. +
+

In order for SPLICE_F_MOVE to be used, the following additional conditions have to be fulfilled:

+
    +
  1. FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports it (i.e, FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.capable), and
  4. +
  5. flags contains FUSE_BUF_SPLICE_MOVE
  6. +
+

Note that, if splice is used, the data is actually spliced twice: once into a temporary pipe (to prepend header data), and then again into the kernel. If some of the provided buffers are memory-backed, the data in them is copied in step one and spliced in step two.

+

The FUSE_BUF_SPLICE_FORCE_SPLICE and FUSE_BUF_SPLICE_NONBLOCK flags are silently ignored.

+

Possible requests: read, readdir, getxattr, listxattr

+

Side effects: when used to return data from a readdirplus() (but not readdir()) call, increments the lookup count of each returned entry by one on success.

+
Parameters
+ + + + +
reqrequest handle
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 912 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_entry (fuse_req_t req,
const struct fuse_entry_parame 
)
+
+

Reply with a directory entry

+

Possible requests: lookup, mknod, mkdir, symlink, link

+

Side effects: increments the lookup count on success

+
Parameters
+ + + +
reqrequest handle
ethe entry parameters
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 428 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_err()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_err (fuse_req_t req,
int err 
)
+
+

Reply with an error code or success.

+

Possible requests: all except forget, forget_multi, retrieve_reply

+

Wherever possible, error codes should be chosen from the list of documented error conditions in the corresponding system calls manpage.

+

An error code of ENOSYS is sometimes treated specially. This is indicated in the documentation of the affected handler functions.

+

The following requests may be answered with a zero error code: unlink, rmdir, rename, flush, release, fsync, fsyncdir, setxattr, removexattr, setlk.

+
Parameters
+ + + +
reqrequest handle
errthe positive error value, or zero for success
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 331 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl (fuse_req_t req,
int result,
const void * buf,
size_t size 
)
+
+

Reply to finish ioctl

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
bufbuffer containing output data
sizelength of output data
+
+
+ +

Definition at line 1071 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_iov (fuse_req_t req,
int result,
const struct iovec * iov,
int count 
)
+
+

Reply to finish ioctl with iov buffer

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
iovthe vector containing the data
countthe size of vector
+
+
+ +

Definition at line 1092 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_retry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_retry (fuse_req_t req,
const struct iovec * in_iov,
size_t in_count,
const struct iovec * out_iov,
size_t out_count 
)
+
+

Reply to ask for data fetch and output buffer preparation. ioctl will be retried with the specified input data fetched and output buffer prepared.

+

Possible requests: ioctl

+
Parameters
+ + + + + + +
reqrequest handle
in_ioviovec specifying data to fetch from the caller
in_countnumber of entries in in_iov
out_ioviovec specifying addresses to write output to
out_countnumber of entries in out_iov
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1001 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_iov (fuse_req_t req,
const struct iovec * iov,
int count 
)
+
+

Reply with data vector

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
iovthe vector containing the data
countthe size of vector
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 265 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lock()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_lock (fuse_req_t req,
const struct flock * lock 
)
+
+

Reply with file lock information

+

Possible requests: getlk

+
Parameters
+ + + +
reqrequest handle
lockthe lock information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 956 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lseek()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_lseek (fuse_req_t req,
off_t off 
)
+
+

Reply with offset

+

Possible requests: lseek

+
Parameters
+ + + +
reqrequest handle
offoffset of next data or hole
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1126 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_none()

+ +
+
+ + + + + + + + +
void fuse_reply_none (fuse_req_t req)
+
+

Don't send reply

+

Possible requests: forget forget_multi retrieve_reply

+
Parameters
+ + +
reqrequest handle
+
+
+ +

Definition at line 336 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_open()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_open (fuse_req_t req,
const struct fuse_file_infofi 
)
+
+

Reply with open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes,

+

Possible requests: open, opendir

+
Parameters
+ + + +
reqrequest handle
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 505 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_poll()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_poll (fuse_req_t req,
unsigned revents 
)
+
+

Reply with poll result event mask

+
Parameters
+ + + +
reqrequest handle
reventspoll result event mask
+
+
+ +

Definition at line 1116 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_readlink()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_readlink (fuse_req_t req,
const char * link 
)
+
+

Reply with the contents of a symbolic link

+

Possible requests: readlink

+
Parameters
+ + + +
reqrequest handle
linksymbolic link contents
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 475 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_statfs()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_statfs (fuse_req_t req,
const struct statvfs * stbuf 
)
+
+

Reply with filesystem statistics

+

Possible requests: statfs

+
Parameters
+ + + +
reqrequest handle
stbuffilesystem statistics
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 934 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_statx()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_statx (fuse_req_t req,
int flags,
struct statx * statx,
double attr_timeout 
)
+
+

Reply with extended file attributes.

+

Possible requests: statx

+
Parameters
+ + + + + +
reqrequest handle
flagsstatx flags
statxthe attributes
attr_timeoutvalidity timeout (in seconds) for the attributes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1260 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_write()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_write (fuse_req_t req,
size_t count 
)
+
+

Reply with number of bytes written

+

Possible requests: write

+
Parameters
+ + + +
reqrequest handle
countthe number of bytes written
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 514 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_xattr()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_xattr (fuse_req_t req,
size_t count 
)
+
+

Reply with needed buffer size

+

Possible requests: getxattr, listxattr

+
Parameters
+ + + +
reqrequest handle
countthe buffer size needed in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 946 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_ctx()

+ +
+
+ + + + + + + + +
const struct fuse_ctx * fuse_req_ctx (fuse_req_t req)
+
+

Get the context from the request

+

The pointer returned by this function will only be valid for the request's lifetime

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the context structure
+ +

Definition at line 2718 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_get_payload()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_req_get_payload (fuse_req_t req,
char ** payload,
size_t * payload_sz,
void ** mr 
)
+
+

Get the payload of a request (for requests submitted through fuse-io-uring only)

+

This is useful for a file system that wants to write data directly to the request buffer. With io-uring the req is the buffer owner and the file system can write directly to the buffer and avoid extra copying. For example useful for network file systems.

+
Parameters
+ + + + + +
reqthe request
payloadpointer to the payload
payload_szsize of the payload
mrmemory registration handle, currently unused
+
+
+
Returns
0 on success, -errno on failure
+ +

Definition at line 3386 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_req_getgroups (fuse_req_t req,
int size,
gid_t list[] 
)
+
+

Get the current supplementary group IDs for the specified request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + + +
reqrequest handle
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 3614 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupt_func()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_req_interrupt_func (fuse_req_t req,
fuse_interrupt_func_t func,
void * data 
)
+
+

Register/unregister callback for an interrupt

+

If an interrupt has already happened, then the callback function is called from within this function, hence it's not possible for interrupts to be lost.

+
Parameters
+ + + + +
reqrequest handle
functhe callback function or NULL for unregister
datauser data passed to the callback function
+
+
+ +

Definition at line 2723 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupted()

+ +
+
+ + + + + + + + +
int fuse_req_interrupted (fuse_req_t req)
+
+

Check if a request has already been interrupted

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 2736 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_is_uring()

+ +
+
+ + + + + + + + +
bool fuse_req_is_uring (fuse_req_t req)
+
+

Check if the request is submitted through fuse-io-uring

+ +

Definition at line 3380 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_userdata()

+ +
+
+ + + + + + + + +
void * fuse_req_userdata (fuse_req_t req)
+
+

Get the userdata from the request

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the user data passed to fuse_session_new()
+ +

Definition at line 2713 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_destroy()

+ +
+
+ + + + + + + + +
void fuse_session_destroy (struct fuse_session * se)
+
+

Destroy a session

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 3010 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_exit()

+ +
+
+ + + + + + + + +
void fuse_session_exit (struct fuse_session * se)
+
+

Flag a session as terminated.

+

This will cause any running event loops to terminate on the next opportunity. If this function is called by a thread that is not a FUSE worker thread, the next opportunity will be when FUSE a request is received (which may be far in the future if the filesystem is not currently being used by any clients). One way to avoid this delay is to afterwards sent a signal to the main thread (if fuse_set_signal_handlers() is used, SIGPIPE will cause the main thread to wake-up but otherwise be ignored).

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_exited()

+ +
+
+ + + + + + + + +
int fuse_session_exited (struct fuse_session * se)
+
+

Query the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+
Returns
1 if exited, 0 if not exited
+ +
+
+ +

◆ fuse_session_fd()

+ +
+
+ + + + + + + + +
int fuse_session_fd (struct fuse_session * se)
+
+

Return file descriptor for communication with kernel.

+

The file selector can be used to integrate FUSE with a custom event loop. Whenever data is available for reading on the provided fd, the event loop should call fuse_session_receive_buf followed by fuse_session_process_buf to process the request.

+

The returned file descriptor is valid until fuse_session_unmount is called.

+
Parameters
+ + +
sethe session
+
+
+
Returns
a file descriptor
+ +

Definition at line 3534 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_loop()

+ +
+
+ + + + + + + + +
int fuse_session_loop (struct fuse_session * se)
+
+

Enter a single threaded, blocking event loop.

+

When the event loop terminates because the connection to the FUSE kernel module has been closed, this function returns zero. This happens when the filesystem is unmounted regularly (by the filesystem owner or root running the umount(8) or fusermount(1) command), or if connection is explicitly severed by writing 1 to theabort file in /sys/fs/fuse/connections/NNN. The only way to distinguish between these two conditions is to check if the filesystem is still mounted after the session loop returns.

+

When some error occurs during request processing, the function returns a negated errno(3) value.

+

If the loop has been terminated because of a signal handler installed by fuse_set_signal_handlers(), this function returns the (positive) signal value that triggered the exit.

+
Parameters
+ + +
sethe session
+
+
+
Returns
0, -errno, or a signal value
+ +

Definition at line 19 of file fuse_loop.c.

+ +
+
+ +

◆ fuse_session_mount()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_session_mount (struct fuse_session * se,
const char * mountpoint 
)
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
sesession object
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 3473 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_process_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_session_process_buf (struct fuse_session * se,
const struct fuse_bufbuf 
)
+
+

Process a raw request supplied in a generic buffer

+

The fuse_buf may contain a memory buffer or a pipe file descriptor.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf containing the request
+
+
+ +

Definition at line 2830 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_receive_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_session_receive_buf (struct fuse_session * se,
struct fuse_bufbuf 
)
+
+

Read a raw request from the kernel into the supplied buffer.

+

Depending on file system options, system capabilities, and request size the request is either read into a memory buffer or spliced into a temporary pipe.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf to store the request in
+
+
+
Returns
the actual size of the raw request, or -errno on error
+ +

Definition at line 3285 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_reset()

+ +
+
+ + + + + + + + +
void fuse_session_reset (struct fuse_session * se)
+
+

Reset the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_unmount()

+ +
+
+ + + + + + + + +
void fuse_session_unmount (struct fuse_session * se)
+
+

Ensure that file system is unmounted.

+

In regular operation, the file system is typically unmounted by the user calling umount(8) or fusermount(1), which then terminates the FUSE session loop. However, the session loop may also terminate as a result of an explicit call to fuse_session_exit() (e.g. by a signal handler installed by fuse_set_signal_handler()). In this case the filesystem remains mounted, but any attempt to access it will block (while the filesystem process is still running) or give an ESHUTDOWN error (after the filesystem process has terminated).

+

If the communication channel with the FUSE kernel module is still open (i.e., if the session loop was terminated by an explicit call to fuse_session_exit()), this function will close it and unmount the filesystem. If the communication channel has been closed by the kernel, this method will do (almost) nothing.

+

NOTE: The above semantics mean that if the connection to the kernel is terminated via the /sys/fs/fuse/connections/NNN/abort file, this method will not unmount the filesystem.

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 3539 of file fuse_lowlevel.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2include_2fuse__lowlevel_8h_source.html b/doc/html/fuse-3_818_81_2include_2fuse__lowlevel_8h_source.html new file mode 100644 index 0000000..5bdad0d --- /dev/null +++ b/doc/html/fuse-3_818_81_2include_2fuse__lowlevel_8h_source.html @@ -0,0 +1,693 @@ + + + + + + + +libfuse: fuse-3.18.1/include/fuse_lowlevel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_lowlevel.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#ifndef FUSE_LOWLEVEL_H_
+
10#define FUSE_LOWLEVEL_H_
+
11
+
21#ifndef FUSE_USE_VERSION
+
22#error FUSE_USE_VERSION not defined
+
23#endif
+
24
+
25#include "fuse_common.h"
+
26
+
27#include <stddef.h>
+
28#include <utime.h>
+
29#include <fcntl.h>
+
30#include <sys/types.h>
+
31#include <sys/stat.h>
+
32#include <sys/statvfs.h>
+
33#include <sys/uio.h>
+
34
+
35#ifdef __cplusplus
+
36extern "C" {
+
37#endif
+
38
+
39/* ----------------------------------------------------------- *
+
40 * Miscellaneous definitions *
+
41 * ----------------------------------------------------------- */
+
42
+
44#define FUSE_ROOT_ID 1
+
45
+
47typedef uint64_t fuse_ino_t;
+
48
+
50typedef struct fuse_req *fuse_req_t;
+
51
+
52/* Forward declaration */
+
53struct statx;
+
54
+
60struct fuse_session;
+
61
+
63struct fuse_entry_param {
+ +
72
+
83 uint64_t generation;
+
84
+
92 struct stat attr;
+
93
+
98 double attr_timeout;
+
99
+
104 double entry_timeout;
+
105};
+
106
+
115struct fuse_ctx {
+
117 uid_t uid;
+
118
+
120 gid_t gid;
+
121
+
123 pid_t pid;
+
124
+
126 mode_t umask;
+
127};
+
128
+
129struct fuse_forget_data {
+
130 fuse_ino_t ino;
+
131 uint64_t nlookup;
+
132};
+
133
+
134struct fuse_custom_io {
+
135 ssize_t (*writev)(int fd, struct iovec *iov, int count, void *userdata);
+
136 ssize_t (*read)(int fd, void *buf, size_t buf_len, void *userdata);
+
137 ssize_t (*splice_receive)(int fdin, off_t *offin, int fdout,
+
138 off_t *offout, size_t len,
+
139 unsigned int flags, void *userdata);
+
140 ssize_t (*splice_send)(int fdin, off_t *offin, int fdout,
+
141 off_t *offout, size_t len,
+
142 unsigned int flags, void *userdata);
+
143 int (*clone_fd)(int master_fd);
+
144};
+
145
+
+ +
152 FUSE_LL_INVALIDATE = 0,
+
153 FUSE_LL_EXPIRE_ONLY = (1 << 0),
+
154};
+
+
155
+
156/* 'to_set' flags in setattr */
+
157#define FUSE_SET_ATTR_MODE (1 << 0)
+
158#define FUSE_SET_ATTR_UID (1 << 1)
+
159#define FUSE_SET_ATTR_GID (1 << 2)
+
160#define FUSE_SET_ATTR_SIZE (1 << 3)
+
161#define FUSE_SET_ATTR_ATIME (1 << 4)
+
162#define FUSE_SET_ATTR_MTIME (1 << 5)
+
163#define FUSE_SET_ATTR_ATIME_NOW (1 << 7)
+
164#define FUSE_SET_ATTR_MTIME_NOW (1 << 8)
+
165#define FUSE_SET_ATTR_FORCE (1 << 9)
+
166#define FUSE_SET_ATTR_CTIME (1 << 10)
+
167#define FUSE_SET_ATTR_KILL_SUID (1 << 11)
+
168#define FUSE_SET_ATTR_KILL_SGID (1 << 12)
+
169#define FUSE_SET_ATTR_FILE (1 << 13)
+
170#define FUSE_SET_ATTR_KILL_PRIV (1 << 14)
+
171#define FUSE_SET_ATTR_OPEN (1 << 15)
+
172#define FUSE_SET_ATTR_TIMES_SET (1 << 16)
+
173#define FUSE_SET_ATTR_TOUCH (1 << 17)
+
174
+
175/* ----------------------------------------------------------- *
+
176 * Request methods and replies *
+
177 * ----------------------------------------------------------- */
+
178
+
208struct fuse_lowlevel_ops {
+
225 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
226
+
238 void (*destroy) (void *userdata);
+
239
+
251 void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
252
+
289 void (*forget) (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup);
+
290
+
310 void (*getattr) (fuse_req_t req, fuse_ino_t ino,
+
311 struct fuse_file_info *fi);
+
312
+
347 void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
348 int to_set, struct fuse_file_info *fi);
+
349
+
360 void (*readlink) (fuse_req_t req, fuse_ino_t ino);
+
361
+
378 void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
379 mode_t mode, dev_t rdev);
+
380
+
393 void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
394 mode_t mode);
+
395
+
411 void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
412
+
428 void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
429
+
442 void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent,
+
443 const char *name);
+
444
+
474 void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
475 fuse_ino_t newparent, const char *newname,
+
476 unsigned int flags);
+
477
+
490 void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
491 const char *newname);
+
492
+
556 void (*open) (fuse_req_t req, fuse_ino_t ino,
+
557 struct fuse_file_info *fi);
+
558
+
584 void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
585 struct fuse_file_info *fi);
+
586
+
613 void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf,
+
614 size_t size, off_t off, struct fuse_file_info *fi);
+
615
+
654 void (*flush) (fuse_req_t req, fuse_ino_t ino,
+
655 struct fuse_file_info *fi);
+
656
+
682 void (*release) (fuse_req_t req, fuse_ino_t ino,
+
683 struct fuse_file_info *fi);
+
684
+
704 void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
705 struct fuse_file_info *fi);
+
706
+
736 void (*opendir) (fuse_req_t req, fuse_ino_t ino,
+
737 struct fuse_file_info *fi);
+
738
+
782 void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
783 struct fuse_file_info *fi);
+
784
+
801 void (*releasedir) (fuse_req_t req, fuse_ino_t ino,
+
802 struct fuse_file_info *fi);
+
803
+
826 void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
827 struct fuse_file_info *fi);
+
828
+
839 void (*statfs) (fuse_req_t req, fuse_ino_t ino);
+
840
+
852 void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
853 const char *value, size_t size, int flags);
+
854
+
883 void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
884 size_t size);
+
885
+
914 void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size);
+
915
+
931 void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name);
+
932
+
953 void (*access) (fuse_req_t req, fuse_ino_t ino, int mask);
+
954
+
982 void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
983 mode_t mode, struct fuse_file_info *fi);
+
984
+
997 void (*getlk) (fuse_req_t req, fuse_ino_t ino,
+
998 struct fuse_file_info *fi, struct flock *lock);
+
999
+
1022 void (*setlk) (fuse_req_t req, fuse_ino_t ino,
+
1023 struct fuse_file_info *fi,
+
1024 struct flock *lock, int sleep);
+
1025
+
1046 void (*bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
1047 uint64_t idx);
+
1048
+
1049#if FUSE_USE_VERSION < 35
+
1050 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, int cmd,
+
1051 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1052 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1053#else
+
1082 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
1083 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1084 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1085#endif
+
1086
+
1119 void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
1120 struct fuse_pollhandle *ph);
+
1121
+
1149 void (*write_buf) (fuse_req_t req, fuse_ino_t ino,
+
1150 struct fuse_bufvec *bufv, off_t off,
+
1151 struct fuse_file_info *fi);
+
1152
+
1165 void (*retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino,
+
1166 off_t offset, struct fuse_bufvec *bufv);
+
1167
+
1179 void (*forget_multi) (fuse_req_t req, size_t count,
+
1180 struct fuse_forget_data *forgets);
+
1181
+
1197 void (*flock) (fuse_req_t req, fuse_ino_t ino,
+
1198 struct fuse_file_info *fi, int op);
+
1199
+
1220 void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode,
+
1221 off_t offset, off_t length, struct fuse_file_info *fi);
+
1222
+
1248 void (*readdirplus) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
1249 struct fuse_file_info *fi);
+
1250
+
1281 void (*copy_file_range) (fuse_req_t req, fuse_ino_t ino_in,
+
1282 off_t off_in, struct fuse_file_info *fi_in,
+
1283 fuse_ino_t ino_out, off_t off_out,
+
1284 struct fuse_file_info *fi_out, size_t len,
+
1285 int flags);
+
1286
+
1305 void (*lseek) (fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
1306 struct fuse_file_info *fi);
+
1307
+
1326 void (*tmpfile) (fuse_req_t req, fuse_ino_t parent,
+
1327 mode_t mode, struct fuse_file_info *fi);
+
1328
+
1342 void (*statx)(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
+
1343 struct fuse_file_info *fi);
+
1344};
+
1345
+
1367int fuse_reply_err(fuse_req_t req, int err);
+
1368
+
1379void fuse_reply_none(fuse_req_t req);
+
1380
+
1394int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e);
+
1395
+
1414int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+
1415 const struct fuse_file_info *fi);
+
1416
+
1428int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
1429 double attr_timeout);
+
1430
+
1441int fuse_reply_readlink(fuse_req_t req, const char *link);
+
1442
+
1455int fuse_passthrough_open(fuse_req_t req, int fd);
+
1456int fuse_passthrough_close(fuse_req_t req, int backing_id);
+
1457
+
1472int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi);
+
1473
+
1484int fuse_reply_write(fuse_req_t req, size_t count);
+
1485
+
1497int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size);
+
1498
+
1542int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+ +
1544
+
1556int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count);
+
1557
+
1568int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf);
+
1569
+
1580int fuse_reply_xattr(fuse_req_t req, size_t count);
+
1581
+
1592int fuse_reply_lock(fuse_req_t req, const struct flock *lock);
+
1593
+
1604int fuse_reply_bmap(fuse_req_t req, uint64_t idx);
+
1605
+
1606/* ----------------------------------------------------------- *
+
1607 * Filling a buffer in readdir *
+
1608 * ----------------------------------------------------------- */
+
1609
+
1637size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
1638 const char *name, const struct stat *stbuf,
+
1639 off_t off);
+
1640
+
1654size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
1655 const char *name,
+
1656 const struct fuse_entry_param *e, off_t off);
+
1657
+ +
1674 const struct iovec *in_iov, size_t in_count,
+
1675 const struct iovec *out_iov, size_t out_count);
+
1676
+
1688int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size);
+
1689
+
1701int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1702 int count);
+
1703
+
1710int fuse_reply_poll(fuse_req_t req, unsigned revents);
+
1711
+
1722int fuse_reply_lseek(fuse_req_t req, off_t off);
+
1723
+
1736int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout);
+
1737
+
1738/* ----------------------------------------------------------- *
+
1739 * Notification *
+
1740 * ----------------------------------------------------------- */
+
1741
+
1749int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph);
+
1750
+
1774int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
1775 off_t off, off_t len);
+
1776
+
1789int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se);
+
1790
+
1815int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
1816 const char *name, size_t namelen);
+
1817
+
1846int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
1847 const char *name, size_t namelen);
+
1848
+
1877int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
1878 fuse_ino_t parent, fuse_ino_t child,
+
1879 const char *name, size_t namelen);
+
1880
+
1906int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
1907 off_t offset, struct fuse_bufvec *bufv,
+ +
1938int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
1939 size_t size, off_t offset, void *cookie);
+
1940
+
1941
+
1942/* ----------------------------------------------------------- *
+
1943 * Utility functions *
+
1944 * ----------------------------------------------------------- */
+
1945
+
1952void *fuse_req_userdata(fuse_req_t req);
+
1953
+
1963const struct fuse_ctx *fuse_req_ctx(fuse_req_t req);
+
1964
+
1984int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]);
+
1985
+
1992typedef void (*fuse_interrupt_func_t)(fuse_req_t req, void *data);
+
1993
+ +
2006 void *data);
+
2007
+ +
2015
+
2016
+
2017/* ----------------------------------------------------------- *
+
2018 * Inquiry functions *
+
2019 * ----------------------------------------------------------- */
+
2020
+
2024void fuse_lowlevel_version(void);
+
2025
+
2031void fuse_lowlevel_help(void);
+
2032
+
2036void fuse_cmdline_help(void);
+
2037
+
2038/* ----------------------------------------------------------- *
+
2039 * Filesystem setup & teardown *
+
2040 * ----------------------------------------------------------- */
+
2041
+
2047struct fuse_cmdline_opts {
+
2048 int singlethread;
+
2049 int foreground;
+
2050 int debug;
+
2051 int nodefault_subtype;
+
2052 char *mountpoint;
+
2053 int show_version;
+
2054 int show_help;
+
2055 int clone_fd;
+
2056 unsigned int max_idle_threads; /* discouraged, due to thread
+
2057 * destruct overhead */
+
2058
+
2059 /* Added in libfuse-3.12 */
+
2060 unsigned int max_threads;
+
2061};
+
2062
+
2081#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2082int fuse_parse_cmdline(struct fuse_args *args,
+
2083 struct fuse_cmdline_opts *opts);
+
2084#else
+
2085#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2086int fuse_parse_cmdline_30(struct fuse_args *args,
+
2087 struct fuse_cmdline_opts *opts);
+
2088#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_30(args, opts)
+
2089#else
+
2090int fuse_parse_cmdline_312(struct fuse_args *args,
+
2091 struct fuse_cmdline_opts *opts);
+
2092#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_312(args, opts)
+
2093#endif
+
2094#endif
+
2095
+
2096/* Do not call this directly, use fuse_session_new() instead */
+
2097struct fuse_session *
+
2098fuse_session_new_versioned(struct fuse_args *args,
+
2099 const struct fuse_lowlevel_ops *op, size_t op_size,
+
2100 struct libfuse_version *version, void *userdata);
+
2101
+
2132static inline struct fuse_session *
+
2133fuse_session_new_fn(struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
2134 size_t op_size, void *userdata)
+
2135{
+
2136 struct libfuse_version version = {
+
2137 .major = FUSE_MAJOR_VERSION,
+
2138 .minor = FUSE_MINOR_VERSION,
+
2139 .hotfix = FUSE_HOTFIX_VERSION,
+
2140 .padding = 0
+
2141 };
+
2142
+
2143 return fuse_session_new_versioned(args, op, op_size, &version,
+
2144 userdata);
+
2145}
+
2146#define fuse_session_new(args, op, op_size, userdata) \
+
2147 fuse_session_new_fn(args, op, op_size, userdata)
+
2148
+
2149/*
+
2150 * This should mostly not be called directly, but instead the
+
2151 * fuse_session_custom_io() should be used.
+
2152 */
+
2153int fuse_session_custom_io_317(struct fuse_session *se,
+
2154 const struct fuse_custom_io *io, size_t op_size, int fd);
+
2155
+
2183#if FUSE_MAKE_VERSION(3, 17) <= FUSE_USE_VERSION
+
2184static inline int fuse_session_custom_io(struct fuse_session *se,
+
2185 const struct fuse_custom_io *io, size_t op_size, int fd)
+
2186{
+
2187 return fuse_session_custom_io_317(se, io, op_size, fd);
+
2188}
+
2189#else
+
2190static inline int fuse_session_custom_io(struct fuse_session *se,
+
2191 const struct fuse_custom_io *io, int fd)
+
2192{
+
2193 return fuse_session_custom_io_317(se, io,
+
2194 offsetof(struct fuse_custom_io, clone_fd), fd);
+
2195}
+
2196#endif
+
2197
+
2206int fuse_session_mount(struct fuse_session *se, const char *mountpoint);
+
2207
+
2230int fuse_session_loop(struct fuse_session *se);
+
2231
+
2232#if FUSE_USE_VERSION < 32
+
2233 int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
2234 #define fuse_session_loop_mt(se, clone_fd) fuse_session_loop_mt_31(se, clone_fd)
+
2235#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2236 int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config *config);
+
2237 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_32(se, config)
+
2238#else
+
2239 #if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2251 int fuse_session_loop_mt(struct fuse_session *se, struct fuse_loop_config *config);
+
2252 #else
+
2253 int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
2254 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_312(se, config)
+
2255 #endif
+
2256#endif
+
2257
+
2270void fuse_session_exit(struct fuse_session *se);
+
2271
+
2277void fuse_session_reset(struct fuse_session *se);
+
2278
+
2285int fuse_session_exited(struct fuse_session *se);
+
2286
+
2311void fuse_session_unmount(struct fuse_session *se);
+
2312
+
2318void fuse_session_destroy(struct fuse_session *se);
+
2319
+
2320/* ----------------------------------------------------------- *
+
2321 * Custom event loop support *
+
2322 * ----------------------------------------------------------- */
+
2323
+
2338int fuse_session_fd(struct fuse_session *se);
+
2339
+
2348void fuse_session_process_buf(struct fuse_session *se,
+
2349 const struct fuse_buf *buf);
+
2350
+
2362int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf);
+
2363
+ +
2368
+
2384int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz,
+
2385 void **mr);
+
2386
+
2387#ifdef __cplusplus
+
2388}
+
2389#endif
+
2390
+
2391#endif /* FUSE_LOWLEVEL_H_ */
+
fuse_buf_copy_flags
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
bool fuse_req_is_uring(fuse_req_t req)
+
int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, void **mr)
+
int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+ +
struct fuse_req * fuse_req_t
+
uint64_t fuse_ino_t
+ + + + + + + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
int32_t backing_id
+ + + +
void(* flock)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op)
+
void(* write)(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* lseek)(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi)
+
void(* listxattr)(fuse_req_t req, fuse_ino_t ino, size_t size)
+
void(* removexattr)(fuse_req_t req, fuse_ino_t ino, const char *name)
+
void(* forget_multi)(fuse_req_t req, size_t count, struct fuse_forget_data *forgets)
+
void(* retrieve_reply)(fuse_req_t req, void *cookie, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv)
+
void(* create)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi)
+
void(* mkdir)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
+
void(* symlink)(fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
+
void(* write_buf)(fuse_req_t req, fuse_ino_t ino, struct fuse_bufvec *bufv, off_t off, struct fuse_file_info *fi)
+
void(* rmdir)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* link)(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
+
void(* rename)(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
+
void(* copy_file_range)(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags)
+
void(* poll)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
void(* opendir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* mknod)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
+
void(* fallocate)(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi)
+
void(* setattr)(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
+
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* fsync)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* destroy)(void *userdata)
+
void(* forget)(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
void(* getattr)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* ioctl)(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
void(* open)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* getxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
+
void(* fsyncdir)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* read)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* setxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags)
+
void(* release)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* access)(fuse_req_t req, fuse_ino_t ino, int mask)
+
void(* releasedir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* statx)(fuse_req_t req, fuse_ino_t ino, int flags, int mask, struct fuse_file_info *fi)
+
void(* tmpfile)(fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi)
+
void(* bmap)(fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx)
+
void(* readlink)(fuse_req_t req, fuse_ino_t ino)
+
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* statfs)(fuse_req_t req, fuse_ino_t ino)
+
void(* readdir)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+
void(* readdirplus)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* flush)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* unlink)(fuse_req_t req, fuse_ino_t parent, const char *name)
+ +
+ + + + diff --git a/doc/html/fuse-3_818_81_2include_2fuse__mount__compat_8h_source.html b/doc/html/fuse-3_818_81_2include_2fuse__mount__compat_8h_source.html new file mode 100644 index 0000000..017a0ae --- /dev/null +++ b/doc/html/fuse-3_818_81_2include_2fuse__mount__compat_8h_source.html @@ -0,0 +1,109 @@ + + + + + + + +libfuse: fuse-3.18.1/include/fuse_mount_compat.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_mount_compat.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2023 Giulio Benetti <giulio.benetti@benettiengineering.com>
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LICENSE
+
9*/
+
10
+
11#ifndef FUSE_MOUNT_COMPAT_H_
+
12#define FUSE_MOUNT_COMPAT_H_
+
13
+
14#include <sys/mount.h>
+
15
+
16/* Some libc don't define MS_*, so define them manually
+
17 * (values taken from https://elixir.bootlin.com/linux/v6.10/source/include/uapi/linux/mount.h#L13 on)
+
18 */
+
19#ifndef MS_DIRSYNC
+
20#define MS_DIRSYNC 128
+
21#endif
+
22
+
23#ifndef MS_NOSYMFOLLOW
+
24#define MS_NOSYMFOLLOW 256
+
25#endif
+
26
+
27#ifndef MS_REC
+
28#define MS_REC 16384
+
29#endif
+
30
+
31#ifndef MS_PRIVATE
+
32#define MS_PRIVATE (1<<18)
+
33#endif
+
34
+
35#ifndef MS_LAZYTIME
+
36#define MS_LAZYTIME (1<<25)
+
37#endif
+
38
+
39#ifndef UMOUNT_DETACH
+
40#define UMOUNT_DETACH 0x00000002 /* Just detach from the tree */
+
41#endif
+
42#ifndef UMOUNT_NOFOLLOW
+
43#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */
+
44#endif
+
45#ifndef UMOUNT_UNUSED
+
46#define UMOUNT_UNUSED 0x80000000 /* Flag guaranteed to be unused */
+
47#endif
+
48
+
49#endif /* FUSE_MOUNT_COMPAT_H_ */
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2include_2fuse__opt_8h.html b/doc/html/fuse-3_818_81_2include_2fuse__opt_8h.html new file mode 100644 index 0000000..a8376f4 --- /dev/null +++ b/doc/html/fuse-3_818_81_2include_2fuse__opt_8h.html @@ -0,0 +1,587 @@ + + + + + + + +libfuse: fuse-3.18.1/include/fuse_opt.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_opt.h File Reference
+
+
+ +

Go to the source code of this file.

+ + + + + + +

+Data Structures

struct  fuse_opt
 
struct  fuse_args
 
+ + + + + + + + + + + + + + + +

+Macros

#define FUSE_OPT_KEY(templ, key)   { templ, -1U, key }
 
#define FUSE_OPT_END   { NULL, 0, 0 }
 
#define FUSE_ARGS_INIT(argc, argv)   { argc, argv, 0 }
 
#define FUSE_OPT_KEY_OPT   -1
 
#define FUSE_OPT_KEY_NONOPT   -2
 
#define FUSE_OPT_KEY_KEEP   -3
 
#define FUSE_OPT_KEY_DISCARD   -4
 
+ + + +

+Typedefs

typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
 
+ + + + + + + + + + + + + + + +

+Functions

int fuse_opt_parse (struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
 
int fuse_opt_add_opt (char **opts, const char *opt)
 
int fuse_opt_add_opt_escaped (char **opts, const char *opt)
 
int fuse_opt_add_arg (struct fuse_args *args, const char *arg)
 
int fuse_opt_insert_arg (struct fuse_args *args, int pos, const char *arg)
 
void fuse_opt_free_args (struct fuse_args *args)
 
int fuse_opt_match (const struct fuse_opt opts[], const char *opt)
 
+

Detailed Description

+

This file defines the option parsing interface of FUSE

+ +

Definition in file fuse_opt.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ARGS_INIT

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_ARGS_INIT( argc,
 argv 
)   { argc, argv, 0 }
+
+

Initializer for 'struct fuse_args'

+ +

Definition at line 123 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_END

+ +
+
+ + + + +
#define FUSE_OPT_END   { NULL, 0, 0 }
+
+

Last option. An array of 'struct fuse_opt' must end with a NULL template value

+ +

Definition at line 104 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_OPT_KEY( templ,
 key 
)   { templ, -1U, key }
+
+

Key option. In case of a match, the processing function will be called with the specified key.

+ +

Definition at line 98 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_DISCARD

+ +
+
+ + + + +
#define FUSE_OPT_KEY_DISCARD   -4
+
+

Special key value for options to discard

+

Argument is not passed to processing function, but behave as if the processing function returned zero

+ +

Definition at line 153 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_KEEP

+ +
+
+ + + + +
#define FUSE_OPT_KEY_KEEP   -3
+
+

Special key value for options to keep

+

Argument is not passed to processing function, but behave as if the processing function returned 1

+ +

Definition at line 145 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_NONOPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_NONOPT   -2
+
+

Key value passed to the processing function for all non-options

+

Non-options are the arguments beginning with a character other than '-' or all arguments after the special '–' option

+ +

Definition at line 137 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_OPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_OPT   -1
+
+

Key value passed to the processing function if an option did not match any template

+ +

Definition at line 129 of file fuse_opt.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_opt_proc_t

+ +
+
+ + + + +
typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
+
+

Processing function

+

This function is called if

    +
  • option did not match any 'struct fuse_opt'
  • +
  • argument is a non-option
  • +
  • option did match and offset was set to -1
  • +
+

The 'arg' parameter will always contain the whole argument or option including the parameter if exists. A two-argument option ("-x foo") is always converted to single argument option of the form "-xfoo" before this function is called.

+

Options of the form '-ofoo' are passed to this function without the '-o' prefix.

+

The return value of this function determines whether this argument is to be inserted into the output argument vector, or discarded.

+
Parameters
+ + + + + +
datais the user data passed to the fuse_opt_parse() function
argis the whole argument or option
keydetermines why the processing function was called
outargsthe current output argument list
+
+
+
Returns
-1 on error, 0 if arg is to be discarded, 1 if arg should be kept
+ +

Definition at line 180 of file fuse_opt.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_opt_add_arg()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_arg (struct fuse_argsargs,
const char * arg 
)
+
+

Add an argument to a NULL terminated argument vector

+
Parameters
+ + + +
argsis the structure containing the current argument list
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 55 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_opt (char ** opts,
const char * opt 
)
+
+

Add an option to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 139 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt_escaped()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_opt_escaped (char ** opts,
const char * opt 
)
+
+

Add an option, escaping commas, to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 144 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_free_args()

+ +
+
+ + + + + + + + +
void fuse_opt_free_args (struct fuse_argsargs)
+
+

Free the contents of argument list

+

The structure itself is not freed

+
Parameters
+ + +
argsis the structure containing the argument list
+
+
+ +

Definition at line 34 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_insert_arg()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_insert_arg (struct fuse_argsargs,
int pos,
const char * arg 
)
+
+

Add an argument at the specified position in a NULL terminated argument vector

+

Adds the argument to the N-th position. This is useful for adding options at the beginning of the array which must not come after the special '–' option.

+
Parameters
+ + + + +
argsis the structure containing the current argument list
posis the position at which to add the argument
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 95 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_match()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_match (const struct fuse_opt opts[],
const char * opt 
)
+
+

Check if an option matches

+
Parameters
+ + + +
optsis the option description array
optis the option to match
+
+
+
Returns
1 if a match is found, 0 if not
+ +
+
+ +

◆ fuse_opt_parse()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_parse (struct fuse_argsargs,
void * data,
const struct fuse_opt opts[],
fuse_opt_proc_t proc 
)
+
+

Option parsing function

+

If 'args' was returned from a previous call to fuse_opt_parse() or it was constructed from

+

A NULL 'args' is equivalent to an empty argument vector

+

A NULL 'opts' is equivalent to an 'opts' array containing a single end marker

+

A NULL 'proc' is equivalent to a processing function always returning '1'

+
Parameters
+ + + + + +
argsis the input and output argument list
datais the user data
optsis the option description array
procis the processing function
+
+
+
Returns
-1 on error, 0 on success
+ +

Definition at line 398 of file fuse_opt.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2include_2fuse__opt_8h_source.html b/doc/html/fuse-3_818_81_2include_2fuse__opt_8h_source.html new file mode 100644 index 0000000..bdbd5b7 --- /dev/null +++ b/doc/html/fuse-3_818_81_2include_2fuse__opt_8h_source.html @@ -0,0 +1,145 @@ + + + + + + + +libfuse: fuse-3.18.1/include/fuse_opt.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_opt.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#ifndef FUSE_OPT_H_
+
10#define FUSE_OPT_H_
+
11
+
17#ifdef __cplusplus
+
18extern "C" {
+
19#endif
+
20
+
77struct fuse_opt {
+
79 const char *templ;
+
80
+
85 unsigned long offset;
+
86
+
91 int value;
+
92};
+
93
+
98#define FUSE_OPT_KEY(templ, key) { templ, -1U, key }
+
99
+
104#define FUSE_OPT_END { NULL, 0, 0 }
+
105
+
109struct fuse_args {
+
111 int argc;
+
112
+
114 char **argv;
+
115
+
117 int allocated;
+
118};
+
119
+
123#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
+
124
+
129#define FUSE_OPT_KEY_OPT -1
+
130
+
137#define FUSE_OPT_KEY_NONOPT -2
+
138
+
145#define FUSE_OPT_KEY_KEEP -3
+
146
+
153#define FUSE_OPT_KEY_DISCARD -4
+
154
+
180typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key,
+
181 struct fuse_args *outargs);
+
182
+
203int fuse_opt_parse(struct fuse_args *args, void *data,
+
204 const struct fuse_opt opts[], fuse_opt_proc_t proc);
+
205
+
213int fuse_opt_add_opt(char **opts, const char *opt);
+
214
+
222int fuse_opt_add_opt_escaped(char **opts, const char *opt);
+
223
+
231int fuse_opt_add_arg(struct fuse_args *args, const char *arg);
+
232
+
246int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg);
+
247
+
255void fuse_opt_free_args(struct fuse_args *args);
+
256
+
257
+
265int fuse_opt_match(const struct fuse_opt opts[], const char *opt);
+
266
+
267#ifdef __cplusplus
+
268}
+
269#endif
+
270
+
271#endif /* FUSE_OPT_H_ */
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
unsigned long offset
Definition fuse_opt.h:85
+
const char * templ
Definition fuse_opt.h:79
+
int value
Definition fuse_opt.h:91
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2buffer_8c_source.html b/doc/html/fuse-3_818_81_2lib_2buffer_8c_source.html new file mode 100644 index 0000000..e12c387 --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2buffer_8c_source.html @@ -0,0 +1,406 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/buffer.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
buffer.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2010 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Functions for dealing with `struct fuse_buf` and `struct
+
6 fuse_bufvec`.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file LGPL2.txt
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_lowlevel.h"
+
17#include <string.h>
+
18#include <unistd.h>
+
19#include <errno.h>
+
20#include <assert.h>
+
21
+
22size_t fuse_buf_size(const struct fuse_bufvec *bufv)
+
23{
+
24 size_t i;
+
25 size_t size = 0;
+
26
+
27 for (i = 0; i < bufv->count; i++) {
+
28 if (bufv->buf[i].size >= SIZE_MAX - size)
+
29 return SIZE_MAX;
+
30
+
31 size += bufv->buf[i].size;
+
32 }
+
33
+
34 return size;
+
35}
+
36
+
37static size_t min_size(size_t s1, size_t s2)
+
38{
+
39 return s1 < s2 ? s1 : s2;
+
40}
+
41
+
42static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off,
+
43 const struct fuse_buf *src, size_t src_off,
+
44 size_t len)
+
45{
+
46 ssize_t res = 0;
+
47 size_t copied = 0;
+
48
+
49 while (len) {
+
50 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
51 res = pwrite(dst->fd, (char *)src->mem + src_off, len,
+
52 dst->pos + dst_off);
+
53 } else {
+
54 res = write(dst->fd, (char *)src->mem + src_off, len);
+
55 }
+
56 if (res == -1) {
+
57 if (!copied)
+
58 return -errno;
+
59 break;
+
60 }
+
61 if (res == 0)
+
62 break;
+
63
+
64 copied += res;
+
65 if (!(dst->flags & FUSE_BUF_FD_RETRY))
+
66 break;
+
67
+
68 src_off += res;
+
69 dst_off += res;
+
70 len -= res;
+
71 }
+
72
+
73 return copied;
+
74}
+
75
+
76static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off,
+
77 const struct fuse_buf *src, size_t src_off,
+
78 size_t len)
+
79{
+
80 ssize_t res = 0;
+
81 size_t copied = 0;
+
82
+
83 while (len) {
+
84 if (src->flags & FUSE_BUF_FD_SEEK) {
+
85 res = pread(src->fd, (char *)dst->mem + dst_off, len,
+
86 src->pos + src_off);
+
87 } else {
+
88 res = read(src->fd, (char *)dst->mem + dst_off, len);
+
89 }
+
90 if (res == -1) {
+
91 if (!copied)
+
92 return -errno;
+
93 break;
+
94 }
+
95 if (res == 0)
+
96 break;
+
97
+
98 copied += res;
+
99 if (!(src->flags & FUSE_BUF_FD_RETRY))
+
100 break;
+
101
+
102 dst_off += res;
+
103 src_off += res;
+
104 len -= res;
+
105 }
+
106
+
107 return copied;
+
108}
+
109
+
110static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
+
111 const struct fuse_buf *src, size_t src_off,
+
112 size_t len)
+
113{
+
114 char buf[4096];
+
115 struct fuse_buf tmp = {
+
116 .size = sizeof(buf),
+
117 .flags = 0,
+
118 };
+
119 ssize_t res;
+
120 size_t copied = 0;
+
121
+
122 tmp.mem = buf;
+
123
+
124 while (len) {
+
125 size_t this_len = min_size(tmp.size, len);
+
126 size_t read_len;
+
127
+
128 res = fuse_buf_read(&tmp, 0, src, src_off, this_len);
+
129 if (res < 0) {
+
130 if (!copied)
+
131 return res;
+
132 break;
+
133 }
+
134 if (res == 0)
+
135 break;
+
136
+
137 read_len = res;
+
138 res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len);
+
139 if (res < 0) {
+
140 if (!copied)
+
141 return res;
+
142 break;
+
143 }
+
144 if (res == 0)
+
145 break;
+
146
+
147 copied += res;
+
148
+
149 if (res < this_len)
+
150 break;
+
151
+
152 dst_off += res;
+
153 src_off += res;
+
154 len -= res;
+
155 }
+
156
+
157 return copied;
+
158}
+
159
+
160#ifdef HAVE_SPLICE
+
161static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
162 const struct fuse_buf *src, size_t src_off,
+
163 size_t len, enum fuse_buf_copy_flags flags)
+
164{
+
165 int splice_flags = 0;
+
166 off_t *srcpos = NULL;
+
167 off_t *dstpos = NULL;
+
168 off_t srcpos_val;
+
169 off_t dstpos_val;
+
170 ssize_t res;
+
171 size_t copied = 0;
+
172
+ +
174 splice_flags |= SPLICE_F_MOVE;
+ +
176 splice_flags |= SPLICE_F_NONBLOCK;
+
177
+
178 if (src->flags & FUSE_BUF_FD_SEEK) {
+
179 srcpos_val = src->pos + src_off;
+
180 srcpos = &srcpos_val;
+
181 }
+
182 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
183 dstpos_val = dst->pos + dst_off;
+
184 dstpos = &dstpos_val;
+
185 }
+
186
+
187 while (len) {
+
188 res = splice(src->fd, srcpos, dst->fd, dstpos, len,
+
189 splice_flags);
+
190 if (res == -1) {
+
191 if (copied)
+
192 break;
+
193
+
194 if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE))
+
195 return -errno;
+
196
+
197 /* Maybe splice is not supported for this combination */
+
198 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off,
+
199 len);
+
200 }
+
201 if (res == 0)
+
202 break;
+
203
+
204 copied += res;
+
205 if (!(src->flags & FUSE_BUF_FD_RETRY) &&
+
206 !(dst->flags & FUSE_BUF_FD_RETRY)) {
+
207 break;
+
208 }
+
209
+
210 len -= res;
+
211 }
+
212
+
213 return copied;
+
214}
+
215#else
+
216static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
217 const struct fuse_buf *src, size_t src_off,
+
218 size_t len, enum fuse_buf_copy_flags flags)
+
219{
+
220 (void) flags;
+
221
+
222 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
223}
+
224#endif
+
225
+
226
+
227static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
+
228 const struct fuse_buf *src, size_t src_off,
+
229 size_t len, enum fuse_buf_copy_flags flags)
+
230{
+
231 int src_is_fd = src->flags & FUSE_BUF_IS_FD;
+
232 int dst_is_fd = dst->flags & FUSE_BUF_IS_FD;
+
233
+
234 if (!src_is_fd && !dst_is_fd) {
+
235 char *dstmem = (char *)dst->mem + dst_off;
+
236 char *srcmem = (char *)src->mem + src_off;
+
237
+
238 if (dstmem != srcmem) {
+
239 if (dstmem + len <= srcmem || srcmem + len <= dstmem)
+
240 memcpy(dstmem, srcmem, len);
+
241 else
+
242 memmove(dstmem, srcmem, len);
+
243 }
+
244
+
245 return len;
+
246 } else if (!src_is_fd) {
+
247 return fuse_buf_write(dst, dst_off, src, src_off, len);
+
248 } else if (!dst_is_fd) {
+
249 return fuse_buf_read(dst, dst_off, src, src_off, len);
+
250 } else if (flags & FUSE_BUF_NO_SPLICE) {
+
251 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
252 } else {
+
253 return fuse_buf_splice(dst, dst_off, src, src_off, len, flags);
+
254 }
+
255}
+
256
+
257static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv)
+
258{
+
259 if (bufv->idx < bufv->count)
+
260 return &bufv->buf[bufv->idx];
+
261 else
+
262 return NULL;
+
263}
+
264
+
265static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len)
+
266{
+
267 const struct fuse_buf *buf = fuse_bufvec_current(bufv);
+
268
+
269 if (!buf)
+
270 return 0;
+
271
+
272 bufv->off += len;
+
273 assert(bufv->off <= buf->size);
+
274 if (bufv->off == buf->size) {
+
275 assert(bufv->idx < bufv->count);
+
276 bufv->idx++;
+
277 if (bufv->idx == bufv->count)
+
278 return 0;
+
279 bufv->off = 0;
+
280 }
+
281 return 1;
+
282}
+
283
+
284ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
+ +
286{
+
287 size_t copied = 0;
+
288
+
289 if (dstv == srcv)
+
290 return fuse_buf_size(dstv);
+
291
+
292 for (;;) {
+
293 const struct fuse_buf *src = fuse_bufvec_current(srcv);
+
294 const struct fuse_buf *dst = fuse_bufvec_current(dstv);
+
295 size_t src_len;
+
296 size_t dst_len;
+
297 size_t len;
+
298 ssize_t res;
+
299
+
300 if (src == NULL || dst == NULL)
+
301 break;
+
302
+
303 src_len = src->size - srcv->off;
+
304 dst_len = dst->size - dstv->off;
+
305 len = min_size(src_len, dst_len);
+
306
+
307 res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
+
308 if (res < 0) {
+
309 if (!copied)
+
310 return res;
+
311 break;
+
312 }
+
313 copied += res;
+
314
+
315 if (!fuse_bufvec_advance(srcv, res) ||
+
316 !fuse_bufvec_advance(dstv, res))
+
317 break;
+
318
+
319 if (res < len)
+
320 break;
+
321 }
+
322
+
323 return copied;
+
324}
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+ +
enum fuse_buf_flags flags
+ +
off_t pos
+
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2compat_8c_source.html b/doc/html/fuse-3_818_81_2lib_2compat_8c_source.html new file mode 100644 index 0000000..2127a6a --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2compat_8c_source.html @@ -0,0 +1,148 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/compat.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
compat.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file LGPL2.txt.
+
11*/
+
12
+
13/* Description:
+
14 This file has compatibility symbols for platforms that do not
+
15 support version symboling
+
16*/
+
17
+
18#include "libfuse_config.h"
+
19
+
20#include <stddef.h>
+
21#include <stdint.h>
+
22
+
23struct fuse_args;
+ + +
26struct fuse_session;
+
27struct fuse_custom_io;
+
28struct fuse_operations;
+ +
30
+
34#if (!defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
35/* With current libfuse fuse_parse_cmdline is a macro pointing to the
+
36 * versioned function. Here in this file we need to provide the ABI symbol
+
37 * and the redirecting macro is conflicting.
+
38 */
+
39#ifdef fuse_parse_cmdline
+
40#undef fuse_parse_cmdline
+
41#endif
+
42int fuse_parse_cmdline_30(struct fuse_args *args,
+
43 struct fuse_cmdline_opts *opts);
+
44int fuse_parse_cmdline(struct fuse_args *args,
+
45 struct fuse_cmdline_opts *opts);
+
46int fuse_parse_cmdline(struct fuse_args *args,
+
47 struct fuse_cmdline_opts *opts)
+
48{
+
49 return fuse_parse_cmdline_30(args, opts);
+
50}
+
51
+
52int fuse_session_custom_io_30(struct fuse_session *se,
+
53 const struct fuse_custom_io *io, int fd);
+
54int fuse_session_custom_io(struct fuse_session *se,
+
55 const struct fuse_custom_io *io, int fd);
+
56int fuse_session_custom_io(struct fuse_session *se,
+
57 const struct fuse_custom_io *io, int fd)
+
58
+
59{
+
60 return fuse_session_custom_io_30(se, io, fd);
+
61}
+
62
+
63#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
64
+
65int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
66 size_t op_size, void *user_data);
+
67int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
68 size_t op_size, void *user_data);
+
69int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
70 size_t op_size, void *user_data)
+
71{
+
72 return fuse_main_real_30(argc, argv, op, op_size, user_data);
+
73}
+
74
+
75struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
76 const struct fuse_lowlevel_ops *op,
+
77 size_t op_size, void *userdata);
+
78struct fuse_session *fuse_session_new(struct fuse_args *args,
+
79 const struct fuse_lowlevel_ops *op,
+
80 size_t op_size, void *userdata);
+
81struct fuse_session *fuse_session_new(struct fuse_args *args,
+
82 const struct fuse_lowlevel_ops *op,
+
83 size_t op_size, void *userdata)
+
84{
+
85 return fuse_session_new_30(args, op, op_size, userdata);
+
86}
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2cuse__lowlevel_8c_source.html b/doc/html/fuse-3_818_81_2lib_2cuse__lowlevel_8c_source.html new file mode 100644 index 0000000..f3a4fbc --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2cuse__lowlevel_8c_source.html @@ -0,0 +1,458 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/cuse_lowlevel.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_lowlevel.c
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file LGPL2.txt.
+
8*/
+
9
+
10#include "fuse_config.h"
+
11#include "cuse_lowlevel.h"
+
12#include "fuse_kernel.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15
+
16#include <stdio.h>
+
17#include <string.h>
+
18#include <stdlib.h>
+
19#include <stddef.h>
+
20#include <errno.h>
+
21#include <unistd.h>
+
22
+
23struct cuse_data {
+
24 struct cuse_lowlevel_ops clop;
+
25 unsigned max_read;
+
26 unsigned dev_major;
+
27 unsigned dev_minor;
+
28 unsigned flags;
+
29 unsigned dev_info_len;
+
30 char dev_info[];
+
31};
+
32
+
33static struct cuse_lowlevel_ops *req_clop(fuse_req_t req)
+
34{
+
35 return &req->se->cuse_data->clop;
+
36}
+
37
+
38static void cuse_fll_open(fuse_req_t req, fuse_ino_t ino,
+
39 struct fuse_file_info *fi)
+
40{
+
41 (void)ino;
+
42 req_clop(req)->open(req, fi);
+
43}
+
44
+
45static void cuse_fll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
46 off_t off, struct fuse_file_info *fi)
+
47{
+
48 (void)ino;
+
49 req_clop(req)->read(req, size, off, fi);
+
50}
+
51
+
52static void cuse_fll_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+
53 size_t size, off_t off, struct fuse_file_info *fi)
+
54{
+
55 (void)ino;
+
56 req_clop(req)->write(req, buf, size, off, fi);
+
57}
+
58
+
59static void cuse_fll_flush(fuse_req_t req, fuse_ino_t ino,
+
60 struct fuse_file_info *fi)
+
61{
+
62 (void)ino;
+
63 req_clop(req)->flush(req, fi);
+
64}
+
65
+
66static void cuse_fll_release(fuse_req_t req, fuse_ino_t ino,
+
67 struct fuse_file_info *fi)
+
68{
+
69 (void)ino;
+
70 req_clop(req)->release(req, fi);
+
71}
+
72
+
73static void cuse_fll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
74 struct fuse_file_info *fi)
+
75{
+
76 (void)ino;
+
77 req_clop(req)->fsync(req, datasync, fi);
+
78}
+
79
+
80static void cuse_fll_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg,
+
81 struct fuse_file_info *fi, unsigned int flags,
+
82 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
83{
+
84 (void)ino;
+
85 req_clop(req)->ioctl(req, cmd, arg, fi, flags, in_buf, in_bufsz,
+
86 out_bufsz);
+
87}
+
88
+
89static void cuse_fll_poll(fuse_req_t req, fuse_ino_t ino,
+
90 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
91{
+
92 (void)ino;
+
93 req_clop(req)->poll(req, fi, ph);
+
94}
+
95
+
96static size_t cuse_pack_info(int argc, const char **argv, char *buf)
+
97{
+
98 size_t size = 0;
+
99 int i;
+
100
+
101 for (i = 0; i < argc; i++) {
+
102 size_t len;
+
103
+
104 len = strlen(argv[i]) + 1;
+
105 size += len;
+
106 if (buf) {
+
107 memcpy(buf, argv[i], len);
+
108 buf += len;
+
109 }
+
110 }
+
111
+
112 return size;
+
113}
+
114
+
115static struct cuse_data *cuse_prep_data(const struct cuse_info *ci,
+
116 const struct cuse_lowlevel_ops *clop)
+
117{
+
118 struct cuse_data *cd;
+
119 size_t dev_info_len;
+
120
+
121 dev_info_len = cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv,
+
122 NULL);
+
123
+
124 if (dev_info_len > CUSE_INIT_INFO_MAX) {
+
125 fuse_log(FUSE_LOG_ERR, "cuse: dev_info (%zu) too large, limit=%u\n",
+
126 dev_info_len, CUSE_INIT_INFO_MAX);
+
127 return NULL;
+
128 }
+
129
+
130 cd = calloc(1, sizeof(*cd) + dev_info_len);
+
131 if (!cd) {
+
132 fuse_log(FUSE_LOG_ERR, "cuse: failed to allocate cuse_data\n");
+
133 return NULL;
+
134 }
+
135
+
136 memcpy(&cd->clop, clop, sizeof(cd->clop));
+
137 cd->max_read = 131072;
+
138 cd->dev_major = ci->dev_major;
+
139 cd->dev_minor = ci->dev_minor;
+
140 cd->dev_info_len = dev_info_len;
+
141 cd->flags = ci->flags;
+
142 cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info);
+
143
+
144 return cd;
+
145}
+
146
+
147struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
148 const struct cuse_info *ci,
+
149 const struct cuse_lowlevel_ops *clop,
+
150 void *userdata)
+
151{
+
152 struct fuse_lowlevel_ops lop;
+
153 struct cuse_data *cd;
+
154 struct fuse_session *se;
+
155
+
156 cd = cuse_prep_data(ci, clop);
+
157 if (!cd)
+
158 return NULL;
+
159
+
160 memset(&lop, 0, sizeof(lop));
+
161 lop.init = clop->init;
+
162 lop.destroy = clop->destroy;
+
163 lop.open = clop->open ? cuse_fll_open : NULL;
+
164 lop.read = clop->read ? cuse_fll_read : NULL;
+
165 lop.write = clop->write ? cuse_fll_write : NULL;
+
166 lop.flush = clop->flush ? cuse_fll_flush : NULL;
+
167 lop.release = clop->release ? cuse_fll_release : NULL;
+
168 lop.fsync = clop->fsync ? cuse_fll_fsync : NULL;
+
169 lop.ioctl = clop->ioctl ? cuse_fll_ioctl : NULL;
+
170 lop.poll = clop->poll ? cuse_fll_poll : NULL;
+
171
+
172 se = fuse_session_new(args, &lop, sizeof(lop), userdata);
+
173 if (!se) {
+
174 free(cd);
+
175 return NULL;
+
176 }
+
177 se->cuse_data = cd;
+
178
+
179 return se;
+
180}
+
181
+
182static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg,
+
183 char *dev_info, unsigned dev_info_len)
+
184{
+
185 struct iovec iov[3];
+
186
+
187 iov[1].iov_base = arg;
+
188 iov[1].iov_len = sizeof(struct cuse_init_out);
+
189 iov[2].iov_base = dev_info;
+
190 iov[2].iov_len = dev_info_len;
+
191
+
192 return fuse_send_reply_iov_nofree(req, 0, iov, 3);
+
193}
+
194
+
195void _cuse_lowlevel_init(fuse_req_t req, const fuse_ino_t nodeid,
+
196 const void *op_in, const void *req_payload)
+
197{
+
198 const struct fuse_init_in *arg = op_in;
+
199 (void)req_payload;
+
200 struct cuse_init_out outarg;
+
201 struct fuse_session *se = req->se;
+
202 struct cuse_data *cd = se->cuse_data;
+
203 size_t bufsize = se->bufsize;
+
204 struct cuse_lowlevel_ops *clop = req_clop(req);
+
205
+
206 (void) nodeid;
+
207 if (se->debug) {
+
208 fuse_log(FUSE_LOG_DEBUG, "CUSE_INIT: %u.%u\n", arg->major, arg->minor);
+
209 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
210 }
+
211 se->conn.proto_major = arg->major;
+
212 se->conn.proto_minor = arg->minor;
+
213
+
214 /* XXX This is not right.*/
+
215 se->conn.capable_ext = 0;
+
216 se->conn.want_ext = 0;
+
217
+
218 if (arg->major < 7) {
+
219 fuse_log(FUSE_LOG_ERR, "cuse: unsupported protocol version: %u.%u\n",
+
220 arg->major, arg->minor);
+
221 fuse_reply_err(req, EPROTO);
+
222 return;
+
223 }
+
224
+
225 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
226 fuse_log(FUSE_LOG_ERR, "cuse: warning: buffer size too small: %zu\n",
+
227 bufsize);
+
228 bufsize = FUSE_MIN_READ_BUFFER;
+
229 }
+
230
+
231 bufsize -= 4096;
+
232 if (bufsize < se->conn.max_write)
+
233 se->conn.max_write = bufsize;
+
234
+
235 se->got_init = 1;
+
236 if (se->op.init)
+
237 se->op.init(se->userdata, &se->conn);
+
238
+
239 memset(&outarg, 0, sizeof(outarg));
+
240 outarg.major = FUSE_KERNEL_VERSION;
+
241 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
242 outarg.flags = cd->flags;
+
243 outarg.max_read = cd->max_read;
+
244 outarg.max_write = se->conn.max_write;
+
245 outarg.dev_major = cd->dev_major;
+
246 outarg.dev_minor = cd->dev_minor;
+
247
+
248 if (se->debug) {
+
249 fuse_log(FUSE_LOG_DEBUG, " CUSE_INIT: %u.%u\n",
+
250 outarg.major, outarg.minor);
+
251 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
252 fuse_log(FUSE_LOG_DEBUG, " max_read=0x%08x\n", outarg.max_read);
+
253 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
254 fuse_log(FUSE_LOG_DEBUG, " dev_major=%u\n", outarg.dev_major);
+
255 fuse_log(FUSE_LOG_DEBUG, " dev_minor=%u\n", outarg.dev_minor);
+
256 fuse_log(FUSE_LOG_DEBUG, " dev_info: %.*s\n", cd->dev_info_len,
+
257 cd->dev_info);
+
258 }
+
259
+
260 cuse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len);
+
261
+
262 if (clop->init_done)
+
263 clop->init_done(se->userdata);
+
264
+
265 fuse_free_req(req);
+
266}
+
267
+
268void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
269{
+
270 _cuse_lowlevel_init(req, nodeid, inarg, NULL);
+
271}
+
272
+
273struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
274 const struct cuse_info *ci,
+
275 const struct cuse_lowlevel_ops *clop,
+
276 int *multithreaded, void *userdata)
+
277{
+
278 const char *devname = "/dev/cuse";
+
279 static const struct fuse_opt kill_subtype_opts[] = {
+ + +
282 };
+
283 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
284 struct fuse_session *se;
+
285 struct fuse_cmdline_opts opts;
+
286 int fd;
+
287 int res;
+
288
+
289 if (fuse_parse_cmdline(&args, &opts) == -1)
+
290 return NULL;
+
291 *multithreaded = !opts.singlethread;
+
292
+
293 /* Remove subtype= option */
+
294 res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL);
+
295 if (res == -1)
+
296 goto out1;
+
297
+
298 /*
+
299 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
300 * would ensue.
+
301 */
+
302 do {
+
303 fd = open("/dev/null", O_RDWR);
+
304 if (fd > 2)
+
305 close(fd);
+
306 } while (fd >= 0 && fd <= 2);
+
307
+
308 se = cuse_lowlevel_new(&args, ci, clop, userdata);
+
309 if (se == NULL)
+
310 goto out1;
+
311
+
312 fd = open(devname, O_RDWR);
+
313 if (fd == -1) {
+
314 if (errno == ENODEV || errno == ENOENT)
+
315 fuse_log(FUSE_LOG_ERR, "cuse: device not found, try 'modprobe cuse' first\n");
+
316 else
+
317 fuse_log(FUSE_LOG_ERR, "cuse: failed to open %s: %s\n",
+
318 devname, strerror(errno));
+
319 goto err_se;
+
320 }
+
321 se->fd = fd;
+
322
+
323 res = fuse_set_signal_handlers(se);
+
324 if (res == -1)
+
325 goto err_se;
+
326
+
327 res = fuse_daemonize(opts.foreground);
+
328 if (res == -1)
+
329 goto err_sig;
+
330
+
331 fuse_opt_free_args(&args);
+
332 return se;
+
333
+
334err_sig:
+ +
336err_se:
+ +
338out1:
+
339 free(opts.mountpoint);
+
340 fuse_opt_free_args(&args);
+
341 return NULL;
+
342}
+
343
+
344void cuse_lowlevel_teardown(struct fuse_session *se)
+
345{
+ + +
348}
+
349
+
350int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
351 const struct cuse_lowlevel_ops *clop, void *userdata)
+
352{
+
353 struct fuse_session *se;
+
354 int multithreaded;
+
355 int res;
+
356
+
357 se = cuse_lowlevel_setup(argc, argv, ci, clop, &multithreaded,
+
358 userdata);
+
359 if (se == NULL)
+
360 return 1;
+
361
+
362 if (multithreaded) {
+
363 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
364 res = fuse_session_loop_mt(se, config);
+
365 fuse_loop_cfg_destroy(config);
+
366 }
+
367 else
+
368 res = fuse_session_loop(se);
+
369
+
370 cuse_lowlevel_teardown(se);
+
371 if (res == -1)
+
372 return 1;
+
373
+
374 return 0;
+
375}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
uint64_t fuse_ino_t
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + + + +
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2fuse_8c_source.html b/doc/html/fuse-3_818_81_2lib_2fuse_8c_source.html new file mode 100644 index 0000000..01d13e4 --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2fuse_8c_source.html @@ -0,0 +1,5449 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/fuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the high-level FUSE API on top of the low-level
+
6 API.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file LGPL2.txt
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13#include "fuse.h"
+
14#include <pthread.h>
+
15
+
16#include "fuse_config.h"
+
17#include "fuse_i.h"
+
18#include "fuse_lowlevel.h"
+
19#include "fuse_opt.h"
+
20#include "fuse_misc.h"
+
21#include "fuse_kernel.h"
+
22#include "util.h"
+
23
+
24#include <stdint.h>
+
25#include <stdio.h>
+
26#include <string.h>
+
27#include <stdlib.h>
+
28#include <stddef.h>
+
29#include <stdbool.h>
+
30#include <unistd.h>
+
31#include <time.h>
+
32#include <fcntl.h>
+
33#include <limits.h>
+
34#include <errno.h>
+
35#include <signal.h>
+
36#include <dlfcn.h>
+
37#include <assert.h>
+
38#include <poll.h>
+
39#include <sys/param.h>
+
40#include <sys/uio.h>
+
41#include <sys/time.h>
+
42#include <sys/mman.h>
+
43#include <sys/file.h>
+
44
+
45#define FUSE_NODE_SLAB 1
+
46
+
47#ifndef MAP_ANONYMOUS
+
48#undef FUSE_NODE_SLAB
+
49#endif
+
50
+
51#ifndef RENAME_EXCHANGE
+
52#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */
+
53#endif
+
54
+
55#define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1
+
56
+
57#define FUSE_UNKNOWN_INO 0xffffffff
+
58#define OFFSET_MAX 0x7fffffffffffffffLL
+
59
+
60#define NODE_TABLE_MIN_SIZE 8192
+
61
+
62struct fuse_fs {
+
63 struct fuse_operations op;
+
64 void *user_data;
+
65 int debug;
+
66};
+
67
+
68struct fusemod_so {
+
69 void *handle;
+
70 int ctr;
+
71};
+
72
+
73struct lock_queue_element {
+
74 struct lock_queue_element *next;
+
75 pthread_cond_t cond;
+
76 fuse_ino_t nodeid1;
+
77 const char *name1;
+
78 char **path1;
+
79 struct node **wnode1;
+
80 fuse_ino_t nodeid2;
+
81 const char *name2;
+
82 char **path2;
+
83 struct node **wnode2;
+
84 int err;
+
85 bool done : 1;
+
86};
+
87
+
88struct node_table {
+
89 struct node **array;
+
90 size_t use;
+
91 size_t size;
+
92 size_t split;
+
93};
+
94
+
95#define list_entry(ptr, type, member) \
+
96 container_of(ptr, type, member)
+
97
+
98struct list_head {
+
99 struct list_head *next;
+
100 struct list_head *prev;
+
101};
+
102
+
103struct node_slab {
+
104 struct list_head list; /* must be the first member */
+
105 struct list_head freelist;
+
106 int used;
+
107};
+
108
+
109struct fuse {
+
110 struct fuse_session *se;
+
111 struct node_table name_table;
+
112 struct node_table id_table;
+
113 struct list_head lru_table;
+
114 fuse_ino_t ctr;
+
115 unsigned int generation;
+
116 unsigned int hidectr;
+
117 pthread_mutex_t lock;
+
118 struct fuse_config conf;
+
119 int intr_installed;
+
120 struct fuse_fs *fs;
+
121 struct lock_queue_element *lockq;
+
122 int pagesize;
+
123 struct list_head partial_slabs;
+
124 struct list_head full_slabs;
+
125 pthread_t prune_thread;
+
126};
+
127
+
128struct lock {
+
129 int type;
+
130 off_t start;
+
131 off_t end;
+
132 pid_t pid;
+
133 uint64_t owner;
+
134 struct lock *next;
+
135};
+
136
+
137struct node {
+
138 struct node *name_next;
+
139 struct node *id_next;
+
140 fuse_ino_t nodeid;
+
141 unsigned int generation;
+
142 int refctr;
+
143 struct node *parent;
+
144 char *name;
+
145 uint64_t nlookup;
+
146 int open_count;
+
147 struct timespec stat_updated;
+
148 struct timespec mtime;
+
149 off_t size;
+
150 struct lock *locks;
+
151 unsigned int is_hidden : 1;
+
152 unsigned int cache_valid : 1;
+
153 int treelock;
+
154 char inline_name[32];
+
155};
+
156
+
157#define TREELOCK_WRITE -1
+
158#define TREELOCK_WAIT_OFFSET INT_MIN
+
159
+
160struct node_lru {
+
161 struct node node;
+
162 struct list_head lru;
+
163 struct timespec forget_time;
+
164};
+
165
+
166struct fuse_direntry {
+
167 struct stat stat;
+
168 enum fuse_fill_dir_flags flags;
+
169 char *name;
+
170 struct fuse_direntry *next;
+
171};
+
172
+
173struct fuse_dh {
+
174 pthread_mutex_t lock;
+
175 struct fuse *fuse;
+
176 fuse_req_t req;
+
177 char *contents;
+
178 struct fuse_direntry *first;
+
179 struct fuse_direntry **last;
+
180 unsigned len;
+
181 unsigned size;
+
182 unsigned needlen;
+
183 int filled;
+
184 uint64_t fh;
+
185 int error;
+
186 fuse_ino_t nodeid;
+
187};
+
188
+
189struct fuse_context_i {
+
190 struct fuse_context ctx;
+
191 fuse_req_t req;
+
192};
+
193
+
194/* Defined by FUSE_REGISTER_MODULE() in lib/modules/subdir.c and iconv.c. */
+
195extern fuse_module_factory_t fuse_module_subdir_factory;
+
196#ifdef HAVE_ICONV
+
197extern fuse_module_factory_t fuse_module_iconv_factory;
+
198#endif
+
199
+
200static pthread_key_t fuse_context_key;
+
201static pthread_mutex_t fuse_context_lock = PTHREAD_MUTEX_INITIALIZER;
+
202static int fuse_context_ref;
+
203static struct fuse_module *fuse_modules = NULL;
+
204
+
205static int fuse_register_module(const char *name,
+
206 fuse_module_factory_t factory,
+
207 struct fusemod_so *so)
+
208{
+
209 struct fuse_module *mod;
+
210
+
211 mod = calloc(1, sizeof(struct fuse_module));
+
212 if (!mod) {
+
213 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module\n");
+
214 return -1;
+
215 }
+
216 mod->name = strdup(name);
+
217 if (!mod->name) {
+
218 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module name\n");
+
219 free(mod);
+
220 return -1;
+
221 }
+
222 mod->factory = factory;
+
223 mod->ctr = 0;
+
224 mod->so = so;
+
225 if (mod->so)
+
226 mod->so->ctr++;
+
227 mod->next = fuse_modules;
+
228 fuse_modules = mod;
+
229
+
230 return 0;
+
231}
+
232
+
233static void fuse_unregister_module(struct fuse_module *m)
+
234{
+
235 struct fuse_module **mp;
+
236 for (mp = &fuse_modules; *mp; mp = &(*mp)->next) {
+
237 if (*mp == m) {
+
238 *mp = (*mp)->next;
+
239 break;
+
240 }
+
241 }
+
242 free(m->name);
+
243 free(m);
+
244}
+
245
+
246static int fuse_load_so_module(const char *module)
+
247{
+
248 int ret = -1;
+
249 char *tmp;
+
250 struct fusemod_so *so;
+
251 fuse_module_factory_t *factory;
+
252
+
253 tmp = malloc(strlen(module) + 64);
+
254 if (!tmp) {
+
255 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
256 return -1;
+
257 }
+
258 sprintf(tmp, "libfusemod_%s.so", module);
+
259 so = calloc(1, sizeof(struct fusemod_so));
+
260 if (!so) {
+
261 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module so\n");
+
262 goto out;
+
263 }
+
264
+
265 so->handle = dlopen(tmp, RTLD_NOW);
+
266 if (so->handle == NULL) {
+
267 fuse_log(FUSE_LOG_ERR, "fuse: dlopen(%s) failed: %s\n",
+
268 tmp, dlerror());
+
269 goto out_free_so;
+
270 }
+
271
+
272 sprintf(tmp, "fuse_module_%s_factory", module);
+
273 factory = (fuse_module_factory_t*)dlsym(so->handle, tmp);
+
274 if (factory == NULL) {
+
275 fuse_log(FUSE_LOG_ERR, "fuse: symbol <%s> not found in module: %s\n",
+
276 tmp, dlerror());
+
277 goto out_dlclose;
+
278 }
+
279 ret = fuse_register_module(module, *factory, so);
+
280 if (ret)
+
281 goto out_dlclose;
+
282
+
283out:
+
284 free(tmp);
+
285 return ret;
+
286
+
287out_dlclose:
+
288 dlclose(so->handle);
+
289out_free_so:
+
290 free(so);
+
291 goto out;
+
292}
+
293
+
294static struct fuse_module *fuse_find_module(const char *module)
+
295{
+
296 struct fuse_module *m;
+
297 for (m = fuse_modules; m; m = m->next) {
+
298 if (strcmp(module, m->name) == 0) {
+
299 m->ctr++;
+
300 break;
+
301 }
+
302 }
+
303 return m;
+
304}
+
305
+
306static struct fuse_module *fuse_get_module(const char *module)
+
307{
+
308 struct fuse_module *m;
+
309
+
310 pthread_mutex_lock(&fuse_context_lock);
+
311 m = fuse_find_module(module);
+
312 if (!m) {
+
313 int err = fuse_load_so_module(module);
+
314 if (!err)
+
315 m = fuse_find_module(module);
+
316 }
+
317 pthread_mutex_unlock(&fuse_context_lock);
+
318 return m;
+
319}
+
320
+
321static void fuse_put_module(struct fuse_module *m)
+
322{
+
323 pthread_mutex_lock(&fuse_context_lock);
+
324 if (m->so)
+
325 assert(m->ctr > 0);
+
326 /* Builtin modules may already have m->ctr == 0 */
+
327 if (m->ctr > 0)
+
328 m->ctr--;
+
329 if (!m->ctr && m->so) {
+
330 struct fusemod_so *so = m->so;
+
331 assert(so->ctr > 0);
+
332 so->ctr--;
+
333 if (!so->ctr) {
+
334 struct fuse_module **mp;
+
335 for (mp = &fuse_modules; *mp;) {
+
336 if ((*mp)->so == so)
+
337 fuse_unregister_module(*mp);
+
338 else
+
339 mp = &(*mp)->next;
+
340 }
+
341 dlclose(so->handle);
+
342 free(so);
+
343 }
+
344 } else if (!m->ctr) {
+
345 fuse_unregister_module(m);
+
346 }
+
347 pthread_mutex_unlock(&fuse_context_lock);
+
348}
+
349
+
350static void init_list_head(struct list_head *list)
+
351{
+
352 list->next = list;
+
353 list->prev = list;
+
354}
+
355
+
356static int list_empty(const struct list_head *head)
+
357{
+
358 return head->next == head;
+
359}
+
360
+
361static void list_add(struct list_head *new, struct list_head *prev,
+
362 struct list_head *next)
+
363{
+
364 next->prev = new;
+
365 new->next = next;
+
366 new->prev = prev;
+
367 prev->next = new;
+
368}
+
369
+
370static inline void list_add_head(struct list_head *new, struct list_head *head)
+
371{
+
372 list_add(new, head, head->next);
+
373}
+
374
+
375static inline void list_add_tail(struct list_head *new, struct list_head *head)
+
376{
+
377 list_add(new, head->prev, head);
+
378}
+
379
+
380static inline void list_del(struct list_head *entry)
+
381{
+
382 struct list_head *prev = entry->prev;
+
383 struct list_head *next = entry->next;
+
384
+
385 next->prev = prev;
+
386 prev->next = next;
+
387}
+
388
+
389static inline int lru_enabled(struct fuse *f)
+
390{
+
391 return f->conf.remember > 0;
+
392}
+
393
+
394static struct node_lru *node_lru(struct node *node)
+
395{
+
396 return (struct node_lru *) node;
+
397}
+
398
+
399static size_t get_node_size(struct fuse *f)
+
400{
+
401 if (lru_enabled(f))
+
402 return sizeof(struct node_lru);
+
403 else
+
404 return sizeof(struct node);
+
405}
+
406
+
407#ifdef FUSE_NODE_SLAB
+
408static struct node_slab *list_to_slab(struct list_head *head)
+
409{
+
410 return (struct node_slab *) head;
+
411}
+
412
+
413static struct node_slab *node_to_slab(struct fuse *f, struct node *node)
+
414{
+
415 return (struct node_slab *) (((uintptr_t) node) & ~((uintptr_t) f->pagesize - 1));
+
416}
+
417
+
418static int alloc_slab(struct fuse *f)
+
419{
+
420 void *mem;
+
421 struct node_slab *slab;
+
422 char *start;
+
423 size_t num;
+
424 size_t i;
+
425 size_t node_size = get_node_size(f);
+
426
+
427 mem = mmap(NULL, f->pagesize, PROT_READ | PROT_WRITE,
+
428 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
429
+
430 if (mem == MAP_FAILED)
+
431 return -1;
+
432
+
433 slab = mem;
+
434 init_list_head(&slab->freelist);
+
435 slab->used = 0;
+
436 num = (f->pagesize - sizeof(struct node_slab)) / node_size;
+
437
+
438 start = (char *) mem + f->pagesize - num * node_size;
+
439 for (i = 0; i < num; i++) {
+
440 struct list_head *n;
+
441
+
442 n = (struct list_head *) (start + i * node_size);
+
443 list_add_tail(n, &slab->freelist);
+
444 }
+
445 list_add_tail(&slab->list, &f->partial_slabs);
+
446
+
447 return 0;
+
448}
+
449
+
450static struct node *alloc_node(struct fuse *f)
+
451{
+
452 struct node_slab *slab;
+
453 struct list_head *node;
+
454
+
455 if (list_empty(&f->partial_slabs)) {
+
456 int res = alloc_slab(f);
+
457 if (res != 0)
+
458 return NULL;
+
459 }
+
460 slab = list_to_slab(f->partial_slabs.next);
+
461 slab->used++;
+
462 node = slab->freelist.next;
+
463 list_del(node);
+
464 if (list_empty(&slab->freelist)) {
+
465 list_del(&slab->list);
+
466 list_add_tail(&slab->list, &f->full_slabs);
+
467 }
+
468 memset(node, 0, sizeof(struct node));
+
469
+
470 return (struct node *) node;
+
471}
+
472
+
473static void free_slab(struct fuse *f, struct node_slab *slab)
+
474{
+
475 int res;
+
476
+
477 list_del(&slab->list);
+
478 res = munmap(slab, f->pagesize);
+
479 if (res == -1)
+
480 fuse_log(FUSE_LOG_WARNING, "fuse warning: munmap(%p) failed\n",
+
481 slab);
+
482}
+
483
+
484static void free_node_mem(struct fuse *f, struct node *node)
+
485{
+
486 struct node_slab *slab = node_to_slab(f, node);
+
487 struct list_head *n = (struct list_head *) node;
+
488
+
489 slab->used--;
+
490 if (slab->used) {
+
491 if (list_empty(&slab->freelist)) {
+
492 list_del(&slab->list);
+
493 list_add_tail(&slab->list, &f->partial_slabs);
+
494 }
+
495 list_add_head(n, &slab->freelist);
+
496 } else {
+
497 free_slab(f, slab);
+
498 }
+
499}
+
500#else
+
501static struct node *alloc_node(struct fuse *f)
+
502{
+
503 return (struct node *) calloc(1, get_node_size(f));
+
504}
+
505
+
506static void free_node_mem(struct fuse *f, struct node *node)
+
507{
+
508 (void) f;
+
509 free(node);
+
510}
+
511#endif
+
512
+
513static size_t id_hash(struct fuse *f, fuse_ino_t ino)
+
514{
+
515 uint64_t hash = ((uint32_t) ino * 2654435761U) % f->id_table.size;
+
516 uint64_t oldhash = hash % (f->id_table.size / 2);
+
517
+
518 if (oldhash >= f->id_table.split)
+
519 return oldhash;
+
520 else
+
521 return hash;
+
522}
+
523
+
524static struct node *get_node_nocheck(struct fuse *f, fuse_ino_t nodeid)
+
525{
+
526 size_t hash = id_hash(f, nodeid);
+
527 struct node *node;
+
528
+
529 for (node = f->id_table.array[hash]; node != NULL; node = node->id_next)
+
530 if (node->nodeid == nodeid)
+
531 return node;
+
532
+
533 return NULL;
+
534}
+
535
+
536static struct node *get_node(struct fuse *f, fuse_ino_t nodeid)
+
537{
+
538 struct node *node = get_node_nocheck(f, nodeid);
+
539 if (!node) {
+
540 fuse_log(FUSE_LOG_ERR, "fuse internal error: node %llu not found\n",
+
541 (unsigned long long) nodeid);
+
542 abort();
+
543 }
+
544 return node;
+
545}
+
546
+
547static void curr_time(struct timespec *now);
+
548static double diff_timespec(const struct timespec *t1,
+
549 const struct timespec *t2);
+
550
+
551static void remove_node_lru(struct node *node)
+
552{
+
553 struct node_lru *lnode = node_lru(node);
+
554 list_del(&lnode->lru);
+
555 init_list_head(&lnode->lru);
+
556}
+
557
+
558static void set_forget_time(struct fuse *f, struct node *node)
+
559{
+
560 struct node_lru *lnode = node_lru(node);
+
561
+
562 list_del(&lnode->lru);
+
563 list_add_tail(&lnode->lru, &f->lru_table);
+
564 curr_time(&lnode->forget_time);
+
565}
+
566
+
567static void free_node(struct fuse *f, struct node *node)
+
568{
+
569 if (node->name != node->inline_name)
+
570 free(node->name);
+
571 free_node_mem(f, node);
+
572}
+
573
+
574static void node_table_reduce(struct node_table *t)
+
575{
+
576 size_t newsize = t->size / 2;
+
577 void *newarray;
+
578
+
579 if (newsize < NODE_TABLE_MIN_SIZE)
+
580 return;
+
581
+
582 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
583 if (newarray != NULL)
+
584 t->array = newarray;
+
585
+
586 t->size = newsize;
+
587 t->split = t->size / 2;
+
588}
+
589
+
590static void remerge_id(struct fuse *f)
+
591{
+
592 struct node_table *t = &f->id_table;
+
593 int iter;
+
594
+
595 if (t->split == 0)
+
596 node_table_reduce(t);
+
597
+
598 for (iter = 8; t->split > 0 && iter; iter--) {
+
599 struct node **upper;
+
600
+
601 t->split--;
+
602 upper = &t->array[t->split + t->size / 2];
+
603 if (*upper) {
+
604 struct node **nodep;
+
605
+
606 for (nodep = &t->array[t->split]; *nodep;
+
607 nodep = &(*nodep)->id_next);
+
608
+
609 *nodep = *upper;
+
610 *upper = NULL;
+
611 break;
+
612 }
+
613 }
+
614}
+
615
+
616static void unhash_id(struct fuse *f, struct node *node)
+
617{
+
618 struct node **nodep = &f->id_table.array[id_hash(f, node->nodeid)];
+
619
+
620 for (; *nodep != NULL; nodep = &(*nodep)->id_next)
+
621 if (*nodep == node) {
+
622 *nodep = node->id_next;
+
623 f->id_table.use--;
+
624
+
625 if(f->id_table.use < f->id_table.size / 4)
+
626 remerge_id(f);
+
627 return;
+
628 }
+
629}
+
630
+
631static int node_table_resize(struct node_table *t)
+
632{
+
633 size_t newsize = t->size * 2;
+
634 void *newarray;
+
635
+
636 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
637 if (newarray == NULL)
+
638 return -1;
+
639
+
640 t->array = newarray;
+
641 memset(t->array + t->size, 0, t->size * sizeof(struct node *));
+
642 t->size = newsize;
+
643 t->split = 0;
+
644
+
645 return 0;
+
646}
+
647
+
648static void rehash_id(struct fuse *f)
+
649{
+
650 struct node_table *t = &f->id_table;
+
651 struct node **nodep;
+
652 struct node **next;
+
653 size_t hash;
+
654
+
655 if (t->split == t->size / 2)
+
656 return;
+
657
+
658 hash = t->split;
+
659 t->split++;
+
660 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
661 struct node *node = *nodep;
+
662 size_t newhash = id_hash(f, node->nodeid);
+
663
+
664 if (newhash != hash) {
+
665 next = nodep;
+
666 *nodep = node->id_next;
+
667 node->id_next = t->array[newhash];
+
668 t->array[newhash] = node;
+
669 } else {
+
670 next = &node->id_next;
+
671 }
+
672 }
+
673 if (t->split == t->size / 2)
+
674 node_table_resize(t);
+
675}
+
676
+
677static void hash_id(struct fuse *f, struct node *node)
+
678{
+
679 size_t hash = id_hash(f, node->nodeid);
+
680 node->id_next = f->id_table.array[hash];
+
681 f->id_table.array[hash] = node;
+
682 f->id_table.use++;
+
683
+
684 if (f->id_table.use >= f->id_table.size / 2)
+
685 rehash_id(f);
+
686}
+
687
+
688static size_t name_hash(struct fuse *f, fuse_ino_t parent,
+
689 const char *name)
+
690{
+
691 uint64_t hash = parent;
+
692 uint64_t oldhash;
+
693
+
694 for (; *name; name++)
+
695 hash = hash * 31 + (unsigned char) *name;
+
696
+
697 hash %= f->name_table.size;
+
698 oldhash = hash % (f->name_table.size / 2);
+
699 if (oldhash >= f->name_table.split)
+
700 return oldhash;
+
701 else
+
702 return hash;
+
703}
+
704
+
705static void unref_node(struct fuse *f, struct node *node);
+
706
+
707static void remerge_name(struct fuse *f)
+
708{
+
709 struct node_table *t = &f->name_table;
+
710 int iter;
+
711
+
712 if (t->split == 0)
+
713 node_table_reduce(t);
+
714
+
715 for (iter = 8; t->split > 0 && iter; iter--) {
+
716 struct node **upper;
+
717
+
718 t->split--;
+
719 upper = &t->array[t->split + t->size / 2];
+
720 if (*upper) {
+
721 struct node **nodep;
+
722
+
723 for (nodep = &t->array[t->split]; *nodep;
+
724 nodep = &(*nodep)->name_next);
+
725
+
726 *nodep = *upper;
+
727 *upper = NULL;
+
728 break;
+
729 }
+
730 }
+
731}
+
732
+
733static void unhash_name(struct fuse *f, struct node *node)
+
734{
+
735 if (node->name) {
+
736 size_t hash = name_hash(f, node->parent->nodeid, node->name);
+
737 struct node **nodep = &f->name_table.array[hash];
+
738
+
739 for (; *nodep != NULL; nodep = &(*nodep)->name_next)
+
740 if (*nodep == node) {
+
741 *nodep = node->name_next;
+
742 node->name_next = NULL;
+
743 unref_node(f, node->parent);
+
744 if (node->name != node->inline_name)
+
745 free(node->name);
+
746 node->name = NULL;
+
747 node->parent = NULL;
+
748 f->name_table.use--;
+
749
+
750 if (f->name_table.use < f->name_table.size / 4)
+
751 remerge_name(f);
+
752 return;
+
753 }
+
754 fuse_log(FUSE_LOG_ERR,
+
755 "fuse internal error: unable to unhash node: %llu\n",
+
756 (unsigned long long) node->nodeid);
+
757 abort();
+
758 }
+
759}
+
760
+
761static void rehash_name(struct fuse *f)
+
762{
+
763 struct node_table *t = &f->name_table;
+
764 struct node **nodep;
+
765 struct node **next;
+
766 size_t hash;
+
767
+
768 if (t->split == t->size / 2)
+
769 return;
+
770
+
771 hash = t->split;
+
772 t->split++;
+
773 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
774 struct node *node = *nodep;
+
775 size_t newhash = name_hash(f, node->parent->nodeid, node->name);
+
776
+
777 if (newhash != hash) {
+
778 next = nodep;
+
779 *nodep = node->name_next;
+
780 node->name_next = t->array[newhash];
+
781 t->array[newhash] = node;
+
782 } else {
+
783 next = &node->name_next;
+
784 }
+
785 }
+
786 if (t->split == t->size / 2)
+
787 node_table_resize(t);
+
788}
+
789
+
790static int hash_name(struct fuse *f, struct node *node, fuse_ino_t parentid,
+
791 const char *name)
+
792{
+
793 size_t hash = name_hash(f, parentid, name);
+
794 struct node *parent = get_node(f, parentid);
+
795 if (strlen(name) < sizeof(node->inline_name)) {
+
796 strcpy(node->inline_name, name);
+
797 node->name = node->inline_name;
+
798 } else {
+
799 node->name = strdup(name);
+
800 if (node->name == NULL)
+
801 return -1;
+
802 }
+
803
+
804 parent->refctr ++;
+
805 node->parent = parent;
+
806 node->name_next = f->name_table.array[hash];
+
807 f->name_table.array[hash] = node;
+
808 f->name_table.use++;
+
809
+
810 if (f->name_table.use >= f->name_table.size / 2)
+
811 rehash_name(f);
+
812
+
813 return 0;
+
814}
+
815
+
816static void delete_node(struct fuse *f, struct node *node)
+
817{
+
818 if (f->conf.debug)
+
819 fuse_log(FUSE_LOG_DEBUG, "DELETE: %llu\n",
+
820 (unsigned long long) node->nodeid);
+
821
+
822 assert(node->treelock == 0);
+
823 unhash_name(f, node);
+
824 if (lru_enabled(f))
+
825 remove_node_lru(node);
+
826 unhash_id(f, node);
+
827 free_node(f, node);
+
828}
+
829
+
830static void unref_node(struct fuse *f, struct node *node)
+
831{
+
832 assert(node->refctr > 0);
+
833 node->refctr --;
+
834 if (!node->refctr)
+
835 delete_node(f, node);
+
836}
+
837
+
838static fuse_ino_t next_id(struct fuse *f)
+
839{
+
840 do {
+
841 f->ctr = (f->ctr + 1) & 0xffffffff;
+
842 if (!f->ctr)
+
843 f->generation ++;
+
844 } while (f->ctr == 0 || f->ctr == FUSE_UNKNOWN_INO ||
+
845 get_node_nocheck(f, f->ctr) != NULL);
+
846 return f->ctr;
+
847}
+
848
+
849static struct node *lookup_node(struct fuse *f, fuse_ino_t parent,
+
850 const char *name)
+
851{
+
852 size_t hash = name_hash(f, parent, name);
+
853 struct node *node;
+
854
+
855 for (node = f->name_table.array[hash]; node != NULL; node = node->name_next)
+
856 if (node->parent->nodeid == parent &&
+
857 strcmp(node->name, name) == 0)
+
858 return node;
+
859
+
860 return NULL;
+
861}
+
862
+
863static void inc_nlookup(struct node *node)
+
864{
+
865 if (!node->nlookup)
+
866 node->refctr++;
+
867 node->nlookup++;
+
868}
+
869
+
870static struct node *find_node(struct fuse *f, fuse_ino_t parent,
+
871 const char *name)
+
872{
+
873 struct node *node;
+
874
+
875 pthread_mutex_lock(&f->lock);
+
876 if (!name)
+
877 node = get_node(f, parent);
+
878 else
+
879 node = lookup_node(f, parent, name);
+
880 if (node == NULL) {
+
881 node = alloc_node(f);
+
882 if (node == NULL)
+
883 goto out_err;
+
884
+
885 node->nodeid = next_id(f);
+
886 node->generation = f->generation;
+
887 if (f->conf.remember)
+
888 inc_nlookup(node);
+
889
+
890 if (hash_name(f, node, parent, name) == -1) {
+
891 free_node(f, node);
+
892 node = NULL;
+
893 goto out_err;
+
894 }
+
895 hash_id(f, node);
+
896 if (lru_enabled(f)) {
+
897 struct node_lru *lnode = node_lru(node);
+
898 init_list_head(&lnode->lru);
+
899 }
+
900 } else if (lru_enabled(f) && node->nlookup == 1) {
+
901 remove_node_lru(node);
+
902 }
+
903 inc_nlookup(node);
+
904out_err:
+
905 pthread_mutex_unlock(&f->lock);
+
906 return node;
+
907}
+
908
+
909static int lookup_path_in_cache(struct fuse *f,
+
910 const char *path, fuse_ino_t *inop)
+
911{
+
912 char *tmp = strdup(path);
+
913 if (!tmp)
+
914 return -ENOMEM;
+
915
+
916 pthread_mutex_lock(&f->lock);
+
917 fuse_ino_t ino = FUSE_ROOT_ID;
+
918
+
919 int err = 0;
+
920 char *save_ptr;
+
921 char *path_element = strtok_r(tmp, "/", &save_ptr);
+
922 while (path_element != NULL) {
+
923 struct node *node = lookup_node(f, ino, path_element);
+
924 if (node == NULL) {
+
925 err = -ENOENT;
+
926 break;
+
927 }
+
928 ino = node->nodeid;
+
929 path_element = strtok_r(NULL, "/", &save_ptr);
+
930 }
+
931 pthread_mutex_unlock(&f->lock);
+
932 free(tmp);
+
933
+
934 if (!err)
+
935 *inop = ino;
+
936 return err;
+
937}
+
938
+
939static char *add_name(char **buf, unsigned *bufsize, char *s, const char *name)
+
940{
+
941 size_t len = strlen(name);
+
942
+
943 if (s - len <= *buf) {
+
944 unsigned pathlen = *bufsize - (s - *buf);
+
945 unsigned newbufsize = *bufsize;
+
946 char *newbuf;
+
947
+
948 while (newbufsize < pathlen + len + 1) {
+
949 if (newbufsize >= 0x80000000)
+
950 newbufsize = 0xffffffff;
+
951 else
+
952 newbufsize *= 2;
+
953 }
+
954
+
955 newbuf = realloc(*buf, newbufsize);
+
956 if (newbuf == NULL)
+
957 return NULL;
+
958
+
959 *buf = newbuf;
+
960 s = newbuf + newbufsize - pathlen;
+
961 memmove(s, newbuf + *bufsize - pathlen, pathlen);
+
962 *bufsize = newbufsize;
+
963 }
+
964 s -= len;
+
965 memcpy(s, name, len);
+
966 s--;
+
967 *s = '/';
+
968
+
969 return s;
+
970}
+
971
+
972static void unlock_path(struct fuse *f, fuse_ino_t nodeid, struct node *wnode,
+
973 struct node *end)
+
974{
+
975 struct node *node;
+
976
+
977 if (wnode) {
+
978 assert(wnode->treelock == TREELOCK_WRITE);
+
979 wnode->treelock = 0;
+
980 }
+
981
+
982 for (node = get_node(f, nodeid);
+
983 node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) {
+
984 assert(node->treelock != 0);
+
985 assert(node->treelock != TREELOCK_WAIT_OFFSET);
+
986 assert(node->treelock != TREELOCK_WRITE);
+
987 node->treelock--;
+
988 if (node->treelock == TREELOCK_WAIT_OFFSET)
+
989 node->treelock = 0;
+
990 }
+
991}
+
992
+
993static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
994 char **path, struct node **wnodep, bool need_lock)
+
995{
+
996 unsigned bufsize = 256;
+
997 char *buf;
+
998 char *s;
+
999 struct node *node;
+
1000 struct node *wnode = NULL;
+
1001 int err;
+
1002
+
1003 *path = NULL;
+
1004
+
1005 err = -ENOMEM;
+
1006 buf = malloc(bufsize);
+
1007 if (buf == NULL)
+
1008 goto out_err;
+
1009
+
1010 s = buf + bufsize - 1;
+
1011 *s = '\0';
+
1012
+
1013 if (name != NULL) {
+
1014 s = add_name(&buf, &bufsize, s, name);
+
1015 err = -ENOMEM;
+
1016 if (s == NULL)
+
1017 goto out_free;
+
1018 }
+
1019
+
1020 if (wnodep) {
+
1021 assert(need_lock);
+
1022 wnode = lookup_node(f, nodeid, name);
+
1023 if (wnode) {
+
1024 if (wnode->treelock != 0) {
+
1025 if (wnode->treelock > 0)
+
1026 wnode->treelock += TREELOCK_WAIT_OFFSET;
+
1027 err = -EAGAIN;
+
1028 goto out_free;
+
1029 }
+
1030 wnode->treelock = TREELOCK_WRITE;
+
1031 }
+
1032 }
+
1033
+
1034 for (node = get_node(f, nodeid); node->nodeid != FUSE_ROOT_ID;
+
1035 node = node->parent) {
+
1036 err = -ESTALE;
+
1037 if (node->name == NULL || node->parent == NULL)
+
1038 goto out_unlock;
+
1039
+
1040 err = -ENOMEM;
+
1041 s = add_name(&buf, &bufsize, s, node->name);
+
1042 if (s == NULL)
+
1043 goto out_unlock;
+
1044
+
1045 if (need_lock) {
+
1046 err = -EAGAIN;
+
1047 if (node->treelock < 0)
+
1048 goto out_unlock;
+
1049
+
1050 node->treelock++;
+
1051 }
+
1052 }
+
1053
+
1054 if (s[0])
+
1055 memmove(buf, s, bufsize - (s - buf));
+
1056 else
+
1057 strcpy(buf, "/");
+
1058
+
1059 *path = buf;
+
1060 if (wnodep)
+
1061 *wnodep = wnode;
+
1062
+
1063 return 0;
+
1064
+
1065 out_unlock:
+
1066 if (need_lock)
+
1067 unlock_path(f, nodeid, wnode, node);
+
1068 out_free:
+
1069 free(buf);
+
1070
+
1071 out_err:
+
1072 return err;
+
1073}
+
1074
+
1075static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1076 fuse_ino_t nodeid2, const char *name2,
+
1077 char **path1, char **path2,
+
1078 struct node **wnode1, struct node **wnode2)
+
1079{
+
1080 int err;
+
1081
+
1082 /* FIXME: locking two paths needs deadlock checking */
+
1083 err = try_get_path(f, nodeid1, name1, path1, wnode1, true);
+
1084 if (!err) {
+
1085 err = try_get_path(f, nodeid2, name2, path2, wnode2, true);
+
1086 if (err) {
+
1087 struct node *wn1 = wnode1 ? *wnode1 : NULL;
+
1088
+
1089 unlock_path(f, nodeid1, wn1, NULL);
+
1090 free(*path1);
+
1091 }
+
1092 }
+
1093 return err;
+
1094}
+
1095
+
1096static void queue_element_wakeup(struct fuse *f, struct lock_queue_element *qe)
+
1097{
+
1098 int err;
+
1099
+
1100 if (!qe->path1) {
+
1101 /* Just waiting for it to be unlocked */
+
1102 if (get_node(f, qe->nodeid1)->treelock == 0)
+
1103 pthread_cond_signal(&qe->cond);
+
1104
+
1105 return;
+
1106 }
+
1107
+
1108 if (qe->done)
+
1109 return; // Don't try to double-lock the element
+
1110
+
1111 if (!qe->path2) {
+
1112 err = try_get_path(f, qe->nodeid1, qe->name1, qe->path1,
+
1113 qe->wnode1, true);
+
1114 } else {
+
1115 err = try_get_path2(f, qe->nodeid1, qe->name1, qe->nodeid2,
+
1116 qe->name2, qe->path1, qe->path2, qe->wnode1,
+
1117 qe->wnode2);
+
1118 }
+
1119
+
1120 if (err == -EAGAIN)
+
1121 return; /* keep trying */
+
1122
+
1123 qe->err = err;
+
1124 qe->done = true;
+
1125 pthread_cond_signal(&qe->cond);
+
1126}
+
1127
+
1128static void wake_up_queued(struct fuse *f)
+
1129{
+
1130 struct lock_queue_element *qe;
+
1131
+
1132 for (qe = f->lockq; qe != NULL; qe = qe->next)
+
1133 queue_element_wakeup(f, qe);
+
1134}
+
1135
+
1136static void debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid,
+
1137 const char *name, bool wr)
+
1138{
+
1139 if (f->conf.debug) {
+
1140 struct node *wnode = NULL;
+
1141
+
1142 if (wr)
+
1143 wnode = lookup_node(f, nodeid, name);
+
1144
+
1145 if (wnode) {
+
1146 fuse_log(FUSE_LOG_DEBUG, "%s %llu (w)\n",
+
1147 msg, (unsigned long long) wnode->nodeid);
+
1148 } else {
+
1149 fuse_log(FUSE_LOG_DEBUG, "%s %llu\n",
+
1150 msg, (unsigned long long) nodeid);
+
1151 }
+
1152 }
+
1153}
+
1154
+
1155static void queue_path(struct fuse *f, struct lock_queue_element *qe)
+
1156{
+
1157 struct lock_queue_element **qp;
+
1158
+
1159 qe->done = false;
+
1160 pthread_cond_init(&qe->cond, NULL);
+
1161 qe->next = NULL;
+
1162 for (qp = &f->lockq; *qp != NULL; qp = &(*qp)->next);
+
1163 *qp = qe;
+
1164}
+
1165
+
1166static void dequeue_path(struct fuse *f, struct lock_queue_element *qe)
+
1167{
+
1168 struct lock_queue_element **qp;
+
1169
+
1170 pthread_cond_destroy(&qe->cond);
+
1171 for (qp = &f->lockq; *qp != qe; qp = &(*qp)->next);
+
1172 *qp = qe->next;
+
1173}
+
1174
+
1175static int wait_path(struct fuse *f, struct lock_queue_element *qe)
+
1176{
+
1177 queue_path(f, qe);
+
1178
+
1179 do {
+
1180 pthread_cond_wait(&qe->cond, &f->lock);
+
1181 } while (!qe->done);
+
1182
+
1183 dequeue_path(f, qe);
+
1184
+
1185 return qe->err;
+
1186}
+
1187
+
1188static int get_path_common(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1189 char **path, struct node **wnode)
+
1190{
+
1191 int err;
+
1192
+
1193 pthread_mutex_lock(&f->lock);
+
1194 err = try_get_path(f, nodeid, name, path, wnode, true);
+
1195 if (err == -EAGAIN) {
+
1196 struct lock_queue_element qe = {
+
1197 .nodeid1 = nodeid,
+
1198 .name1 = name,
+
1199 .path1 = path,
+
1200 .wnode1 = wnode,
+
1201 };
+
1202 debug_path(f, "QUEUE PATH", nodeid, name, !!wnode);
+
1203 err = wait_path(f, &qe);
+
1204 debug_path(f, "DEQUEUE PATH", nodeid, name, !!wnode);
+
1205 }
+
1206 pthread_mutex_unlock(&f->lock);
+
1207
+
1208 return err;
+
1209}
+
1210
+
1211static int get_path(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1212{
+
1213 return get_path_common(f, nodeid, NULL, path, NULL);
+
1214}
+
1215
+
1216static int get_path_nullok(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1217{
+
1218 int err = 0;
+
1219
+
1220 if (f->conf.nullpath_ok) {
+
1221 *path = NULL;
+
1222 } else {
+
1223 err = get_path_common(f, nodeid, NULL, path, NULL);
+
1224 if (err == -ESTALE)
+
1225 err = 0;
+
1226 }
+
1227
+
1228 return err;
+
1229}
+
1230
+
1231static int get_path_name(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1232 char **path)
+
1233{
+
1234 return get_path_common(f, nodeid, name, path, NULL);
+
1235}
+
1236
+
1237static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1238 char **path, struct node **wnode)
+
1239{
+
1240 return get_path_common(f, nodeid, name, path, wnode);
+
1241}
+
1242
+
1243#if defined(__FreeBSD__)
+
1244#define CHECK_DIR_LOOP
+
1245#endif
+
1246
+
1247#if defined(CHECK_DIR_LOOP)
+
1248static int check_dir_loop(struct fuse *f,
+
1249 fuse_ino_t nodeid1, const char *name1,
+
1250 fuse_ino_t nodeid2, const char *name2)
+
1251{
+
1252 struct node *node, *node1, *node2;
+
1253 fuse_ino_t id1, id2;
+
1254
+
1255 node1 = lookup_node(f, nodeid1, name1);
+
1256 id1 = node1 ? node1->nodeid : nodeid1;
+
1257
+
1258 node2 = lookup_node(f, nodeid2, name2);
+
1259 id2 = node2 ? node2->nodeid : nodeid2;
+
1260
+
1261 for (node = get_node(f, id2); node->nodeid != FUSE_ROOT_ID;
+
1262 node = node->parent) {
+
1263 if (node->name == NULL || node->parent == NULL)
+
1264 break;
+
1265
+
1266 if (node->nodeid != id2 && node->nodeid == id1)
+
1267 return -EINVAL;
+
1268 }
+
1269
+
1270 if (node2)
+
1271 {
+
1272 for (node = get_node(f, id1); node->nodeid != FUSE_ROOT_ID;
+
1273 node = node->parent) {
+
1274 if (node->name == NULL || node->parent == NULL)
+
1275 break;
+
1276
+
1277 if (node->nodeid != id1 && node->nodeid == id2)
+
1278 return -ENOTEMPTY;
+
1279 }
+
1280 }
+
1281
+
1282 return 0;
+
1283}
+
1284#endif
+
1285
+
1286static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1287 fuse_ino_t nodeid2, const char *name2,
+
1288 char **path1, char **path2,
+
1289 struct node **wnode1, struct node **wnode2)
+
1290{
+
1291 int err;
+
1292
+
1293 pthread_mutex_lock(&f->lock);
+
1294
+
1295#if defined(CHECK_DIR_LOOP)
+
1296 if (name1)
+
1297 {
+
1298 // called during rename; perform dir loop check
+
1299 err = check_dir_loop(f, nodeid1, name1, nodeid2, name2);
+
1300 if (err)
+
1301 goto out_unlock;
+
1302 }
+
1303#endif
+
1304
+
1305 err = try_get_path2(f, nodeid1, name1, nodeid2, name2,
+
1306 path1, path2, wnode1, wnode2);
+
1307 if (err == -EAGAIN) {
+
1308 struct lock_queue_element qe = {
+
1309 .nodeid1 = nodeid1,
+
1310 .name1 = name1,
+
1311 .path1 = path1,
+
1312 .wnode1 = wnode1,
+
1313 .nodeid2 = nodeid2,
+
1314 .name2 = name2,
+
1315 .path2 = path2,
+
1316 .wnode2 = wnode2,
+
1317 };
+
1318
+
1319 debug_path(f, "QUEUE PATH1", nodeid1, name1, !!wnode1);
+
1320 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1321 err = wait_path(f, &qe);
+
1322 debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1);
+
1323 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1324 }
+
1325
+
1326#if defined(CHECK_DIR_LOOP)
+
1327out_unlock:
+
1328#endif
+
1329 pthread_mutex_unlock(&f->lock);
+
1330
+
1331 return err;
+
1332}
+
1333
+
1334static void free_path_wrlock(struct fuse *f, fuse_ino_t nodeid,
+
1335 struct node *wnode, char *path)
+
1336{
+
1337 pthread_mutex_lock(&f->lock);
+
1338 unlock_path(f, nodeid, wnode, NULL);
+
1339 if (f->lockq)
+
1340 wake_up_queued(f);
+
1341 pthread_mutex_unlock(&f->lock);
+
1342 free(path);
+
1343}
+
1344
+
1345static void free_path(struct fuse *f, fuse_ino_t nodeid, char *path)
+
1346{
+
1347 if (path)
+
1348 free_path_wrlock(f, nodeid, NULL, path);
+
1349}
+
1350
+
1351static void free_path2(struct fuse *f, fuse_ino_t nodeid1, fuse_ino_t nodeid2,
+
1352 struct node *wnode1, struct node *wnode2,
+
1353 char *path1, char *path2)
+
1354{
+
1355 pthread_mutex_lock(&f->lock);
+
1356 unlock_path(f, nodeid1, wnode1, NULL);
+
1357 unlock_path(f, nodeid2, wnode2, NULL);
+
1358 wake_up_queued(f);
+
1359 pthread_mutex_unlock(&f->lock);
+
1360 free(path1);
+
1361 free(path2);
+
1362}
+
1363
+
1364static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup)
+
1365{
+
1366 struct node *node;
+
1367 if (nodeid == FUSE_ROOT_ID)
+
1368 return;
+
1369 pthread_mutex_lock(&f->lock);
+
1370 node = get_node(f, nodeid);
+
1371
+
1372 /*
+
1373 * Node may still be locked due to interrupt idiocy in open,
+
1374 * create and opendir
+
1375 */
+
1376 while (node->nlookup == nlookup && node->treelock) {
+
1377 struct lock_queue_element qe = {
+
1378 .nodeid1 = nodeid,
+
1379 };
+
1380
+
1381 debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false);
+
1382 queue_path(f, &qe);
+
1383
+
1384 do {
+
1385 pthread_cond_wait(&qe.cond, &f->lock);
+
1386 } while (node->nlookup == nlookup && node->treelock);
+
1387
+
1388 dequeue_path(f, &qe);
+
1389 debug_path(f, "DEQUEUE_PATH (forget)", nodeid, NULL, false);
+
1390 }
+
1391
+
1392 assert(node->nlookup >= nlookup);
+
1393 node->nlookup -= nlookup;
+
1394 if (!node->nlookup) {
+
1395 unref_node(f, node);
+
1396 } else if (lru_enabled(f) && node->nlookup == 1) {
+
1397 set_forget_time(f, node);
+
1398 }
+
1399 pthread_mutex_unlock(&f->lock);
+
1400}
+
1401
+
1402static void unlink_node(struct fuse *f, struct node *node)
+
1403{
+
1404 if (f->conf.remember) {
+
1405 assert(node->nlookup > 1);
+
1406 node->nlookup--;
+
1407 }
+
1408 unhash_name(f, node);
+
1409}
+
1410
+
1411static void remove_node(struct fuse *f, fuse_ino_t dir, const char *name)
+
1412{
+
1413 struct node *node;
+
1414
+
1415 pthread_mutex_lock(&f->lock);
+
1416 node = lookup_node(f, dir, name);
+
1417 if (node != NULL)
+
1418 unlink_node(f, node);
+
1419 pthread_mutex_unlock(&f->lock);
+
1420}
+
1421
+
1422static int rename_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1423 fuse_ino_t newdir, const char *newname, int hide)
+
1424{
+
1425 struct node *node;
+
1426 struct node *newnode;
+
1427 int err = 0;
+
1428
+
1429 pthread_mutex_lock(&f->lock);
+
1430 node = lookup_node(f, olddir, oldname);
+
1431 newnode = lookup_node(f, newdir, newname);
+
1432 if (node == NULL)
+
1433 goto out;
+
1434
+
1435 if (newnode != NULL) {
+
1436 if (hide) {
+
1437 fuse_log(FUSE_LOG_ERR, "fuse: hidden file got created during hiding\n");
+
1438 err = -EBUSY;
+
1439 goto out;
+
1440 }
+
1441 unlink_node(f, newnode);
+
1442 }
+
1443
+
1444 unhash_name(f, node);
+
1445 if (hash_name(f, node, newdir, newname) == -1) {
+
1446 err = -ENOMEM;
+
1447 goto out;
+
1448 }
+
1449
+
1450 if (hide)
+
1451 node->is_hidden = 1;
+
1452
+
1453out:
+
1454 pthread_mutex_unlock(&f->lock);
+
1455 return err;
+
1456}
+
1457
+
1458static int exchange_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1459 fuse_ino_t newdir, const char *newname)
+
1460{
+
1461 struct node *oldnode;
+
1462 struct node *newnode;
+
1463 int err;
+
1464
+
1465 pthread_mutex_lock(&f->lock);
+
1466 oldnode = lookup_node(f, olddir, oldname);
+
1467 newnode = lookup_node(f, newdir, newname);
+
1468
+
1469 if (oldnode)
+
1470 unhash_name(f, oldnode);
+
1471 if (newnode)
+
1472 unhash_name(f, newnode);
+
1473
+
1474 err = -ENOMEM;
+
1475 if (oldnode) {
+
1476 if (hash_name(f, oldnode, newdir, newname) == -1)
+
1477 goto out;
+
1478 }
+
1479 if (newnode) {
+
1480 if (hash_name(f, newnode, olddir, oldname) == -1)
+
1481 goto out;
+
1482 }
+
1483 err = 0;
+
1484out:
+
1485 pthread_mutex_unlock(&f->lock);
+
1486 return err;
+
1487}
+
1488
+
1489static void set_stat(struct fuse *f, fuse_ino_t nodeid, struct stat *stbuf)
+
1490{
+
1491 if (!f->conf.use_ino)
+
1492 stbuf->st_ino = nodeid;
+
1493 if (f->conf.set_mode) {
+
1494 if (f->conf.dmask && S_ISDIR(stbuf->st_mode))
+
1495 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1496 (0777 & ~f->conf.dmask);
+
1497 else if (f->conf.fmask)
+
1498 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1499 (0777 & ~f->conf.fmask);
+
1500 else
+
1501 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1502 (0777 & ~f->conf.umask);
+
1503 }
+
1504 if (f->conf.set_uid)
+
1505 stbuf->st_uid = f->conf.uid;
+
1506 if (f->conf.set_gid)
+
1507 stbuf->st_gid = f->conf.gid;
+
1508}
+
1509
+
1510#ifdef HAVE_STATX
+
1511static void set_statx(struct fuse *f, fuse_ino_t nodeid, struct statx *stxbuf)
+
1512{
+
1513 if (!f->conf.use_ino)
+
1514 stxbuf->stx_ino = nodeid;
+
1515 if (f->conf.set_mode) {
+
1516 if (f->conf.dmask && S_ISDIR(stxbuf->stx_mode))
+
1517 stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) |
+
1518 (0777 & ~f->conf.dmask);
+
1519 else if (f->conf.fmask)
+
1520 stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) |
+
1521 (0777 & ~f->conf.fmask);
+
1522 else
+
1523 stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) |
+
1524 (0777 & ~f->conf.umask);
+
1525 }
+
1526 if (f->conf.set_uid)
+
1527 stxbuf->stx_uid = f->conf.uid;
+
1528 if (f->conf.set_gid)
+
1529 stxbuf->stx_gid = f->conf.gid;
+
1530}
+
1531#endif
+
1532
+
1533static struct fuse *req_fuse(fuse_req_t req)
+
1534{
+
1535 return (struct fuse *) fuse_req_userdata(req);
+
1536}
+
1537
+
1538static void fuse_intr_sighandler(int sig)
+
1539{
+
1540 (void) sig;
+
1541 /* Nothing to do */
+
1542}
+
1543
+
1544struct fuse_intr_data {
+
1545 pthread_t id;
+
1546 pthread_cond_t cond;
+
1547 int finished;
+
1548};
+
1549
+
1550static void fuse_interrupt(fuse_req_t req, void *d_)
+
1551{
+
1552 struct fuse_intr_data *d = d_;
+
1553 struct fuse *f = req_fuse(req);
+
1554
+
1555 if (d->id == pthread_self())
+
1556 return;
+
1557
+
1558 pthread_mutex_lock(&f->lock);
+
1559 while (!d->finished) {
+
1560 struct timeval now;
+
1561 struct timespec timeout;
+
1562
+
1563 pthread_kill(d->id, f->conf.intr_signal);
+
1564 gettimeofday(&now, NULL);
+
1565 timeout.tv_sec = now.tv_sec + 1;
+
1566 timeout.tv_nsec = now.tv_usec * 1000;
+
1567 pthread_cond_timedwait(&d->cond, &f->lock, &timeout);
+
1568 }
+
1569 pthread_mutex_unlock(&f->lock);
+
1570}
+
1571
+
1572static void fuse_do_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1573 struct fuse_intr_data *d)
+
1574{
+
1575 pthread_mutex_lock(&f->lock);
+
1576 d->finished = 1;
+
1577 pthread_cond_broadcast(&d->cond);
+
1578 pthread_mutex_unlock(&f->lock);
+
1579 fuse_req_interrupt_func(req, NULL, NULL);
+
1580 pthread_cond_destroy(&d->cond);
+
1581}
+
1582
+
1583static void fuse_do_prepare_interrupt(fuse_req_t req, struct fuse_intr_data *d)
+
1584{
+
1585 d->id = pthread_self();
+
1586 pthread_cond_init(&d->cond, NULL);
+
1587 d->finished = 0;
+
1588 fuse_req_interrupt_func(req, fuse_interrupt, d);
+
1589}
+
1590
+
1591static inline void fuse_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1592 struct fuse_intr_data *d)
+
1593{
+
1594 if (f->conf.intr)
+
1595 fuse_do_finish_interrupt(f, req, d);
+
1596}
+
1597
+
1598static inline void fuse_prepare_interrupt(struct fuse *f, fuse_req_t req,
+
1599 struct fuse_intr_data *d)
+
1600{
+
1601 if (f->conf.intr)
+
1602 fuse_do_prepare_interrupt(req, d);
+
1603}
+
1604
+
1605static const char* file_info_string(struct fuse_file_info *fi,
+
1606 char* buf, size_t len)
+
1607{
+
1608 if(fi == NULL)
+
1609 return "NULL";
+
1610 snprintf(buf, len, "%llu", (unsigned long long) fi->fh);
+
1611 return buf;
+
1612}
+
1613
+
1614int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1615 struct fuse_file_info *fi)
+
1616{
+
1617 fuse_get_context()->private_data = fs->user_data;
+
1618 if (!fs->op.getattr)
+
1619 return -ENOSYS;
+
1620
+
1621 if (fs->debug) {
+
1622 char buf[10];
+
1623
+
1624 fuse_log(FUSE_LOG_DEBUG, "getattr[%s] %s\n",
+
1625 file_info_string(fi, buf, sizeof(buf)),
+
1626 path);
+
1627 }
+
1628 return fs->op.getattr(path, buf, fi);
+
1629}
+
1630
+
1631int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1632 const char *newpath, unsigned int flags)
+
1633{
+
1634 fuse_get_context()->private_data = fs->user_data;
+
1635 if (!fs->op.rename)
+
1636 return -ENOSYS;
+
1637 if (fs->debug)
+
1638 fuse_log(FUSE_LOG_DEBUG, "rename %s %s 0x%x\n", oldpath, newpath,
+
1639 flags);
+
1640
+
1641 return fs->op.rename(oldpath, newpath, flags);
+
1642}
+
1643
+
1644int fuse_fs_unlink(struct fuse_fs *fs, const char *path)
+
1645{
+
1646 fuse_get_context()->private_data = fs->user_data;
+
1647 if (!fs->op.unlink)
+
1648 return -ENOSYS;
+
1649 if (fs->debug)
+
1650 fuse_log(FUSE_LOG_DEBUG, "unlink %s\n", path);
+
1651
+
1652 return fs->op.unlink(path);
+
1653}
+
1654
+
1655int fuse_fs_rmdir(struct fuse_fs *fs, const char *path)
+
1656{
+
1657 fuse_get_context()->private_data = fs->user_data;
+
1658 if (!fs->op.rmdir)
+
1659 return -ENOSYS;
+
1660 if (fs->debug)
+
1661 fuse_log(FUSE_LOG_DEBUG, "rmdir %s\n", path);
+
1662
+
1663 return fs->op.rmdir(path);
+
1664}
+
1665
+
1666int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path)
+
1667{
+
1668 fuse_get_context()->private_data = fs->user_data;
+
1669 if (!fs->op.symlink)
+
1670 return -ENOSYS;
+
1671 if (fs->debug)
+
1672 fuse_log(FUSE_LOG_DEBUG, "symlink %s %s\n", linkname, path);
+
1673
+
1674 return fs->op.symlink(linkname, path);
+
1675}
+
1676
+
1677int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath)
+
1678{
+
1679 fuse_get_context()->private_data = fs->user_data;
+
1680 if (!fs->op.link)
+
1681 return -ENOSYS;
+
1682 if (fs->debug)
+
1683 fuse_log(FUSE_LOG_DEBUG, "link %s %s\n", oldpath, newpath);
+
1684
+
1685 return fs->op.link(oldpath, newpath);
+
1686}
+
1687
+
1688int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1689 struct fuse_file_info *fi)
+
1690{
+
1691 fuse_get_context()->private_data = fs->user_data;
+
1692 if (!fs->op.release)
+
1693 return 0;
+
1694
+
1695 if (fs->debug)
+
1696 fuse_log(FUSE_LOG_DEBUG, "release%s[%llu] flags: 0x%x\n",
+
1697 fi->flush ? "+flush" : "",
+
1698 (unsigned long long) fi->fh, fi->flags);
+
1699
+
1700 return fs->op.release(path, fi);
+
1701}
+
1702
+
1703int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1704 struct fuse_file_info *fi)
+
1705{
+
1706 int err;
+
1707
+
1708 fuse_get_context()->private_data = fs->user_data;
+
1709 if (!fs->op.opendir)
+
1710 return 0;
+
1711
+
1712 if (fs->debug)
+
1713 fuse_log(FUSE_LOG_DEBUG, "opendir flags: 0x%x %s\n", fi->flags,
+
1714 path);
+
1715
+
1716 err = fs->op.opendir(path, fi);
+
1717
+
1718 if (fs->debug && !err)
+
1719 fuse_log(FUSE_LOG_DEBUG, " opendir[%llu] flags: 0x%x %s\n",
+
1720 (unsigned long long) fi->fh, fi->flags, path);
+
1721
+
1722 return err;
+
1723}
+
1724
+
1725int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1726 struct fuse_file_info *fi)
+
1727{
+
1728 int err;
+
1729
+
1730 fuse_get_context()->private_data = fs->user_data;
+
1731 if (!fs->op.open)
+
1732 return 0;
+
1733
+
1734 if (fs->debug)
+
1735 fuse_log(FUSE_LOG_DEBUG, "open flags: 0x%x %s\n", fi->flags,
+
1736 path);
+
1737
+
1738 err = fs->op.open(path, fi);
+
1739
+
1740 if (fs->debug && !err)
+
1741 fuse_log(FUSE_LOG_DEBUG, " open[%llu] flags: 0x%x %s\n",
+
1742 (unsigned long long) fi->fh, fi->flags, path);
+
1743
+
1744 return err;
+
1745}
+
1746
+
1747static void fuse_free_buf(struct fuse_bufvec *buf)
+
1748{
+
1749 if (buf != NULL) {
+
1750 size_t i;
+
1751
+
1752 for (i = 0; i < buf->count; i++)
+
1753 if (!(buf->buf[i].flags & FUSE_BUF_IS_FD))
+
1754 free(buf->buf[i].mem);
+
1755 free(buf);
+
1756 }
+
1757}
+
1758
+
1759int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1760 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1761 struct fuse_file_info *fi)
+
1762{
+
1763 int res;
+
1764
+
1765 fuse_get_context()->private_data = fs->user_data;
+
1766 if (!fs->op.read && !fs->op.read_buf)
+
1767 return -ENOSYS;
+
1768
+
1769 if (fs->debug)
+
1770 fuse_log(FUSE_LOG_DEBUG,
+
1771 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1772 (unsigned long long) fi->fh,
+
1773 size, (unsigned long long) off, fi->flags);
+
1774
+
1775 if (fs->op.read_buf) {
+
1776 res = fs->op.read_buf(path, bufp, size, off, fi);
+
1777 } else {
+
1778 struct fuse_bufvec *buf;
+
1779 void *mem;
+
1780
+
1781 buf = malloc(sizeof(struct fuse_bufvec));
+
1782 if (buf == NULL)
+
1783 return -ENOMEM;
+
1784
+
1785 mem = malloc(size);
+
1786 if (mem == NULL) {
+
1787 free(buf);
+
1788 return -ENOMEM;
+
1789 }
+
1790 *buf = FUSE_BUFVEC_INIT(size);
+
1791 buf->buf[0].mem = mem;
+
1792 *bufp = buf;
+
1793
+
1794 res = fs->op.read(path, mem, size, off, fi);
+
1795 if (res >= 0)
+
1796 buf->buf[0].size = res;
+
1797 }
+
1798
+
1799 if (fs->debug && res >= 0)
+
1800 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %zu bytes from %llu\n",
+
1801 (unsigned long long) fi->fh,
+
1802 fuse_buf_size(*bufp),
+
1803 (unsigned long long) off);
+
1804 if (res >= 0 && fuse_buf_size(*bufp) > size)
+
1805 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1806
+
1807 if (res < 0)
+
1808 return res;
+
1809
+
1810 return 0;
+
1811}
+
1812
+
1813int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size,
+
1814 off_t off, struct fuse_file_info *fi)
+
1815{
+
1816 int res;
+
1817
+
1818 fuse_get_context()->private_data = fs->user_data;
+
1819 if (!fs->op.read && !fs->op.read_buf)
+
1820 return -ENOSYS;
+
1821
+
1822 if (fs->debug)
+
1823 fuse_log(FUSE_LOG_DEBUG,
+
1824 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1825 (unsigned long long) fi->fh,
+
1826 size, (unsigned long long) off, fi->flags);
+
1827
+
1828 if (fs->op.read_buf) {
+
1829 struct fuse_bufvec *buf = NULL;
+
1830
+
1831 res = fs->op.read_buf(path, &buf, size, off, fi);
+
1832 if (res == 0) {
+
1833 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size);
+
1834
+
1835 dst.buf[0].mem = mem;
+
1836 res = fuse_buf_copy(&dst, buf, 0);
+
1837 }
+
1838 fuse_free_buf(buf);
+
1839 } else {
+
1840 res = fs->op.read(path, mem, size, off, fi);
+
1841 }
+
1842
+
1843 if (fs->debug && res >= 0)
+
1844 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %u bytes from %llu\n",
+
1845 (unsigned long long) fi->fh,
+
1846 res,
+
1847 (unsigned long long) off);
+
1848 if (res >= 0 && res > (int) size)
+
1849 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1850
+
1851 return res;
+
1852}
+
1853
+
1854int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1855 struct fuse_bufvec *buf, off_t off,
+
1856 struct fuse_file_info *fi)
+
1857{
+
1858 int res;
+
1859 size_t size;
+
1860
+
1861 fuse_get_context()->private_data = fs->user_data;
+
1862 if (!fs->op.write_buf && !fs->op.write)
+
1863 return -ENOSYS;
+
1864
+
1865 size = fuse_buf_size(buf);
+
1866 assert(buf->idx == 0 && buf->off == 0);
+
1867 if (fs->debug)
+
1868 fuse_log(FUSE_LOG_DEBUG,
+
1869 "write%s[%llu] %zu bytes to %llu flags: 0x%x\n",
+
1870 fi->writepage ? "page" : "",
+
1871 (unsigned long long) fi->fh,
+
1872 size,
+
1873 (unsigned long long) off,
+
1874 fi->flags);
+
1875
+
1876 if (fs->op.write_buf) {
+
1877 res = fs->op.write_buf(path, buf, off, fi);
+
1878 } else {
+
1879 void *mem = NULL;
+
1880 struct fuse_buf *flatbuf;
+
1881 struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size);
+
1882
+
1883 if (buf->count == 1 &&
+
1884 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
1885 flatbuf = &buf->buf[0];
+
1886 } else {
+
1887 res = -ENOMEM;
+
1888 mem = malloc(size);
+
1889 if (mem == NULL)
+
1890 goto out;
+
1891
+
1892 tmp.buf[0].mem = mem;
+
1893 res = fuse_buf_copy(&tmp, buf, 0);
+
1894 if (res <= 0)
+
1895 goto out_free;
+
1896
+
1897 tmp.buf[0].size = res;
+
1898 flatbuf = &tmp.buf[0];
+
1899 }
+
1900
+
1901 res = fs->op.write(path, flatbuf->mem, flatbuf->size,
+
1902 off, fi);
+
1903out_free:
+
1904 free(mem);
+
1905 }
+
1906out:
+
1907 if (fs->debug && res >= 0)
+
1908 fuse_log(FUSE_LOG_DEBUG, " write%s[%llu] %u bytes to %llu\n",
+
1909 fi->writepage ? "page" : "",
+
1910 (unsigned long long) fi->fh, res,
+
1911 (unsigned long long) off);
+
1912 if (res > (int) size)
+
1913 fuse_log(FUSE_LOG_ERR, "fuse: wrote too many bytes\n");
+
1914
+
1915 return res;
+
1916}
+
1917
+
1918int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem,
+
1919 size_t size, off_t off, struct fuse_file_info *fi)
+
1920{
+
1921 struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(size);
+
1922
+
1923 bufv.buf[0].mem = (void *) mem;
+
1924
+
1925 return fuse_fs_write_buf(fs, path, &bufv, off, fi);
+
1926}
+
1927
+
1928int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1929 struct fuse_file_info *fi)
+
1930{
+
1931 fuse_get_context()->private_data = fs->user_data;
+
1932 if (!fs->op.fsync)
+
1933 return -ENOSYS;
+
1934
+
1935 if (fs->debug)
+
1936 fuse_log(FUSE_LOG_DEBUG, "fsync[%llu] datasync: %i\n",
+
1937 (unsigned long long) fi->fh, datasync);
+
1938
+
1939 return fs->op.fsync(path, datasync, fi);
+
1940}
+
1941
+
1942int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1943 struct fuse_file_info *fi)
+
1944{
+
1945 fuse_get_context()->private_data = fs->user_data;
+
1946 if (!fs->op.fsyncdir)
+
1947 return -ENOSYS;
+
1948
+
1949 if (fs->debug)
+
1950 fuse_log(FUSE_LOG_DEBUG, "fsyncdir[%llu] datasync: %i\n",
+
1951 (unsigned long long) fi->fh, datasync);
+
1952
+
1953 return fs->op.fsyncdir(path, datasync, fi);
+
1954}
+
1955
+
1956int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1957 struct fuse_file_info *fi)
+
1958{
+
1959 fuse_get_context()->private_data = fs->user_data;
+
1960 if (!fs->op.flush)
+
1961 return -ENOSYS;
+
1962 if (fs->debug)
+
1963 fuse_log(FUSE_LOG_DEBUG, "flush[%llu]\n",
+
1964 (unsigned long long) fi->fh);
+
1965
+
1966 return fs->op.flush(path, fi);
+
1967}
+
1968
+
1969int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf)
+
1970{
+
1971 fuse_get_context()->private_data = fs->user_data;
+
1972 if (fs->op.statfs) {
+
1973 if (fs->debug)
+
1974 fuse_log(FUSE_LOG_DEBUG, "statfs %s\n", path);
+
1975
+
1976 return fs->op.statfs(path, buf);
+
1977 } else {
+
1978 buf->f_namemax = 255;
+
1979 buf->f_bsize = 512;
+
1980 return 0;
+
1981 }
+
1982}
+
1983
+
1984int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1985 struct fuse_file_info *fi)
+
1986{
+
1987 fuse_get_context()->private_data = fs->user_data;
+
1988 if (!fs->op.releasedir)
+
1989 return 0;
+
1990
+
1991 if (fs->debug)
+
1992 fuse_log(FUSE_LOG_DEBUG, "releasedir[%llu] flags: 0x%x\n",
+
1993 (unsigned long long) fi->fh, fi->flags);
+
1994
+
1995 return fs->op.releasedir(path, fi);
+
1996}
+
1997
+
1998int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
1999 fuse_fill_dir_t filler, off_t off,
+
2000 struct fuse_file_info *fi,
+
2001 enum fuse_readdir_flags flags)
+
2002{
+
2003 fuse_get_context()->private_data = fs->user_data;
+
2004 if (!fs->op.readdir)
+
2005 return -ENOSYS;
+
2006 if (fs->debug) {
+
2007 fuse_log(FUSE_LOG_DEBUG, "readdir%s[%llu] from %llu\n",
+
2008 (flags & FUSE_READDIR_PLUS) ? "plus" : "",
+
2009 (unsigned long long) fi->fh,
+
2010 (unsigned long long) off);
+
2011 }
+
2012
+
2013 return fs->op.readdir(path, buf, filler, off, fi, flags);
+
2014}
+
2015
+
2016int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
2017 struct fuse_file_info *fi)
+
2018{
+
2019 int err;
+
2020
+
2021 fuse_get_context()->private_data = fs->user_data;
+
2022 if (!fs->op.create)
+
2023 return -ENOSYS;
+
2024
+
2025 if (fs->debug)
+
2026 fuse_log(FUSE_LOG_DEBUG,
+
2027 "create flags: 0x%x %s 0%o umask=0%03o\n",
+
2028 fi->flags, path, mode,
+
2029 fuse_get_context()->umask);
+
2030
+
2031 err = fs->op.create(path, mode, fi);
+
2032
+
2033 if (fs->debug && !err)
+
2034 fuse_log(FUSE_LOG_DEBUG, " create[%llu] flags: 0x%x %s\n",
+
2035 (unsigned long long) fi->fh, fi->flags, path);
+
2036
+
2037 return err;
+
2038}
+
2039
+
2040int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
2041 struct fuse_file_info *fi, int cmd, struct flock *lock)
+
2042{
+
2043 fuse_get_context()->private_data = fs->user_data;
+
2044 if (!fs->op.lock)
+
2045 return -ENOSYS;
+
2046
+
2047 if (fs->debug)
+
2048 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n",
+
2049 (unsigned long long) fi->fh,
+
2050 (cmd == F_GETLK ? "F_GETLK" :
+
2051 (cmd == F_SETLK ? "F_SETLK" :
+
2052 (cmd == F_SETLKW ? "F_SETLKW" : "???"))),
+
2053 (lock->l_type == F_RDLCK ? "F_RDLCK" :
+
2054 (lock->l_type == F_WRLCK ? "F_WRLCK" :
+
2055 (lock->l_type == F_UNLCK ? "F_UNLCK" :
+
2056 "???"))),
+
2057 (unsigned long long) lock->l_start,
+
2058 (unsigned long long) lock->l_len,
+
2059 (unsigned long long) lock->l_pid);
+
2060
+
2061 return fs->op.lock(path, fi, cmd, lock);
+
2062}
+
2063
+
2064int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
2065 struct fuse_file_info *fi, int op)
+
2066{
+
2067 fuse_get_context()->private_data = fs->user_data;
+
2068 if (!fs->op.flock)
+
2069 return -ENOSYS;
+
2070
+
2071 if (fs->debug) {
+
2072 int xop = op & ~LOCK_NB;
+
2073
+
2074 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s%s\n",
+
2075 (unsigned long long) fi->fh,
+
2076 xop == LOCK_SH ? "LOCK_SH" :
+
2077 (xop == LOCK_EX ? "LOCK_EX" :
+
2078 (xop == LOCK_UN ? "LOCK_UN" : "???")),
+
2079 (op & LOCK_NB) ? "|LOCK_NB" : "");
+
2080 }
+
2081 return fs->op.flock(path, fi, op);
+
2082}
+
2083
+
2084int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid,
+
2085 gid_t gid, struct fuse_file_info *fi)
+
2086{
+
2087 fuse_get_context()->private_data = fs->user_data;
+
2088 if (!fs->op.chown)
+
2089 return -ENOSYS;
+
2090 if (fs->debug) {
+
2091 char buf[10];
+
2092
+
2093 fuse_log(FUSE_LOG_DEBUG, "chown[%s] %s %lu %lu\n",
+
2094 file_info_string(fi, buf, sizeof(buf)),
+
2095 path, (unsigned long) uid, (unsigned long) gid);
+
2096 }
+
2097 return fs->op.chown(path, uid, gid, fi);
+
2098}
+
2099
+
2100int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
2101 struct fuse_file_info *fi)
+
2102{
+
2103 fuse_get_context()->private_data = fs->user_data;
+
2104 if (!fs->op.truncate)
+
2105 return -ENOSYS;
+
2106 if (fs->debug) {
+
2107 char buf[10];
+
2108
+
2109 fuse_log(FUSE_LOG_DEBUG, "truncate[%s] %llu\n",
+
2110 file_info_string(fi, buf, sizeof(buf)),
+
2111 (unsigned long long) size);
+
2112 }
+
2113 return fs->op.truncate(path, size, fi);
+
2114}
+
2115
+
2116int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
2117 const struct timespec tv[2], struct fuse_file_info *fi)
+
2118{
+
2119 fuse_get_context()->private_data = fs->user_data;
+
2120 if (!fs->op.utimens)
+
2121 return -ENOSYS;
+
2122 if (fs->debug) {
+
2123 char buf[10];
+
2124
+
2125 fuse_log(FUSE_LOG_DEBUG, "utimens[%s] %s %jd.%09ld %jd.%09ld\n",
+
2126 file_info_string(fi, buf, sizeof(buf)),
+
2127 path, (intmax_t)tv[0].tv_sec, tv[0].tv_nsec,
+
2128 (intmax_t)tv[1].tv_sec, tv[1].tv_nsec);
+
2129 }
+
2130 return fs->op.utimens(path, tv, fi);
+
2131}
+
2132
+
2133int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask)
+
2134{
+
2135 fuse_get_context()->private_data = fs->user_data;
+
2136 if (!fs->op.access)
+
2137 return -ENOSYS;
+
2138 if (fs->debug)
+
2139 fuse_log(FUSE_LOG_DEBUG, "access %s 0%o\n", path, mask);
+
2140
+
2141 return fs->op.access(path, mask);
+
2142}
+
2143
+
2144int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
2145 size_t len)
+
2146{
+
2147 fuse_get_context()->private_data = fs->user_data;
+
2148 if (!fs->op.readlink)
+
2149 return -ENOSYS;
+
2150 if (fs->debug)
+
2151 fuse_log(FUSE_LOG_DEBUG, "readlink %s %lu\n", path,
+
2152 (unsigned long) len);
+
2153
+
2154 return fs->op.readlink(path, buf, len);
+
2155}
+
2156
+
2157int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2158 dev_t rdev)
+
2159{
+
2160 fuse_get_context()->private_data = fs->user_data;
+
2161 if (!fs->op.mknod)
+
2162 return -ENOSYS;
+
2163 if (fs->debug)
+
2164 fuse_log(FUSE_LOG_DEBUG, "mknod %s 0%o 0x%llx umask=0%03o\n",
+
2165 path, mode, (unsigned long long) rdev,
+
2166 fuse_get_context()->umask);
+
2167
+
2168 return fs->op.mknod(path, mode, rdev);
+
2169}
+
2170
+
2171int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode)
+
2172{
+
2173 fuse_get_context()->private_data = fs->user_data;
+
2174 if (!fs->op.mkdir)
+
2175 return -ENOSYS;
+
2176 if (fs->debug)
+
2177 fuse_log(FUSE_LOG_DEBUG, "mkdir %s 0%o umask=0%03o\n",
+
2178 path, mode, fuse_get_context()->umask);
+
2179
+
2180 return fs->op.mkdir(path, mode);
+
2181}
+
2182
+
2183int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2184 const char *value, size_t size, int flags)
+
2185{
+
2186 fuse_get_context()->private_data = fs->user_data;
+
2187 if (!fs->op.setxattr)
+
2188 return -ENOSYS;
+
2189 if (fs->debug)
+
2190 fuse_log(FUSE_LOG_DEBUG, "setxattr %s %s %lu 0x%x\n",
+
2191 path, name, (unsigned long) size, flags);
+
2192
+
2193 return fs->op.setxattr(path, name, value, size, flags);
+
2194}
+
2195
+
2196int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2197 char *value, size_t size)
+
2198{
+
2199 fuse_get_context()->private_data = fs->user_data;
+
2200 if (!fs->op.getxattr)
+
2201 return -ENOSYS;
+
2202 if (fs->debug)
+
2203 fuse_log(FUSE_LOG_DEBUG, "getxattr %s %s %lu\n",
+
2204 path, name, (unsigned long) size);
+
2205
+
2206 return fs->op.getxattr(path, name, value, size);
+
2207}
+
2208
+
2209int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
2210 size_t size)
+
2211{
+
2212 fuse_get_context()->private_data = fs->user_data;
+
2213 if (!fs->op.listxattr)
+
2214 return -ENOSYS;
+
2215 if (fs->debug)
+
2216 fuse_log(FUSE_LOG_DEBUG, "listxattr %s %lu\n",
+
2217 path, (unsigned long) size);
+
2218
+
2219 return fs->op.listxattr(path, list, size);
+
2220}
+
2221
+
2222int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
2223 uint64_t *idx)
+
2224{
+
2225 fuse_get_context()->private_data = fs->user_data;
+
2226 if (!fs->op.bmap)
+
2227 return -ENOSYS;
+
2228 if (fs->debug)
+
2229 fuse_log(FUSE_LOG_DEBUG, "bmap %s blocksize: %lu index: %llu\n",
+
2230 path, (unsigned long) blocksize,
+
2231 (unsigned long long) *idx);
+
2232
+
2233 return fs->op.bmap(path, blocksize, idx);
+
2234}
+
2235
+
2236int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name)
+
2237{
+
2238 fuse_get_context()->private_data = fs->user_data;
+
2239 if (!fs->op.removexattr)
+
2240 return -ENOSYS;
+
2241 if (fs->debug)
+
2242 fuse_log(FUSE_LOG_DEBUG, "removexattr %s %s\n", path, name);
+
2243
+
2244 return fs->op.removexattr(path, name);
+
2245}
+
2246
+
2247int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
2248 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
2249 void *data)
+
2250{
+
2251 fuse_get_context()->private_data = fs->user_data;
+
2252 if (!fs->op.ioctl)
+
2253 return -ENOSYS;
+
2254 if (fs->debug)
+
2255 fuse_log(FUSE_LOG_DEBUG, "ioctl[%llu] 0x%x flags: 0x%x\n",
+
2256 (unsigned long long) fi->fh, cmd, flags);
+
2257
+
2258 return fs->op.ioctl(path, cmd, arg, fi, flags, data);
+
2259}
+
2260
+
2261int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
2262 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
2263 unsigned *reventsp)
+
2264{
+
2265 int res;
+
2266
+
2267 fuse_get_context()->private_data = fs->user_data;
+
2268 if (!fs->op.poll)
+
2269 return -ENOSYS;
+
2270 if (fs->debug)
+
2271 fuse_log(FUSE_LOG_DEBUG, "poll[%llu] ph: %p, events 0x%x\n",
+
2272 (unsigned long long) fi->fh, ph,
+
2273 fi->poll_events);
+
2274
+
2275 res = fs->op.poll(path, fi, ph, reventsp);
+
2276
+
2277 if (fs->debug && !res)
+
2278 fuse_log(FUSE_LOG_DEBUG, " poll[%llu] revents: 0x%x\n",
+
2279 (unsigned long long) fi->fh, *reventsp);
+
2280
+
2281 return res;
+
2282}
+
2283
+
2284int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
2285 off_t offset, off_t length, struct fuse_file_info *fi)
+
2286{
+
2287 fuse_get_context()->private_data = fs->user_data;
+
2288 if (!fs->op.fallocate)
+
2289 return -ENOSYS;
+
2290 if (fs->debug)
+
2291 fuse_log(FUSE_LOG_DEBUG, "fallocate %s mode %x, offset: %llu, length: %llu\n",
+
2292 path,
+
2293 mode,
+
2294 (unsigned long long) offset,
+
2295 (unsigned long long) length);
+
2296
+
2297 return fs->op.fallocate(path, mode, offset, length, fi);
+
2298}
+
2299
+
2300ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
2301 struct fuse_file_info *fi_in, off_t off_in,
+
2302 const char *path_out,
+
2303 struct fuse_file_info *fi_out, off_t off_out,
+
2304 size_t len, int flags)
+
2305{
+
2306 fuse_get_context()->private_data = fs->user_data;
+
2307 if (!fs->op.copy_file_range)
+
2308 return -ENOSYS;
+
2309 if (fs->debug)
+
2310 fuse_log(FUSE_LOG_DEBUG, "copy_file_range from %s:%llu to "
+
2311 "%s:%llu, length: %llu\n",
+
2312 path_in,
+
2313 (unsigned long long) off_in,
+
2314 path_out,
+
2315 (unsigned long long) off_out,
+
2316 (unsigned long long) len);
+
2317
+
2318 return fs->op.copy_file_range(path_in, fi_in, off_in, path_out,
+
2319 fi_out, off_out, len, flags);
+
2320}
+
2321
+
2322off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
2323 struct fuse_file_info *fi)
+
2324{
+
2325 fuse_get_context()->private_data = fs->user_data;
+
2326 if (!fs->op.lseek)
+
2327 return -ENOSYS;
+
2328 if (fs->debug) {
+
2329 char buf[10];
+
2330
+
2331 fuse_log(FUSE_LOG_DEBUG, "lseek[%s] %llu %d\n",
+
2332 file_info_string(fi, buf, sizeof(buf)),
+
2333 (unsigned long long) off, whence);
+
2334 }
+
2335 return fs->op.lseek(path, off, whence, fi);
+
2336}
+
2337
+
2338#ifdef HAVE_STATX
+
2339int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask,
+
2340 struct statx *stxbuf, struct fuse_file_info *fi)
+
2341{
+
2342 fuse_get_context()->private_data = fs->user_data;
+
2343 if (fs->op.statx) {
+
2344 if (fs->debug) {
+
2345 char buf[10];
+
2346
+
2347 fuse_log(FUSE_LOG_DEBUG, "statx[%s] %s %d %d\n",
+
2348 file_info_string(fi, buf, sizeof(buf)), path,
+
2349 flags, mask);
+
2350 }
+
2351 return fs->op.statx(path, flags, mask, stxbuf, fi);
+
2352 }
+
2353
+
2354 return -ENOSYS;
+
2355}
+
2356#else
+
2357int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask,
+
2358 struct statx *stxbuf, struct fuse_file_info *fi)
+
2359{
+
2360 (void)fs;
+
2361 (void)path;
+
2362 (void)flags;
+
2363 (void)mask;
+
2364 (void)stxbuf;
+
2365 (void)fi;
+
2366
+
2367 return -ENOSYS;
+
2368}
+
2369#endif
+
2370
+
2371static int is_open(struct fuse *f, fuse_ino_t dir, const char *name)
+
2372{
+
2373 struct node *node;
+
2374 int isopen = 0;
+
2375 pthread_mutex_lock(&f->lock);
+
2376 node = lookup_node(f, dir, name);
+
2377 if (node && node->open_count > 0)
+
2378 isopen = 1;
+
2379 pthread_mutex_unlock(&f->lock);
+
2380 return isopen;
+
2381}
+
2382
+
2383static char *hidden_name(struct fuse *f, fuse_ino_t dir, const char *oldname,
+
2384 char *newname, size_t bufsize)
+
2385{
+
2386 struct stat buf;
+
2387 struct node *node;
+
2388 struct node *newnode;
+
2389 char *newpath;
+
2390 int res;
+
2391 int failctr = 10;
+
2392
+
2393 do {
+
2394 pthread_mutex_lock(&f->lock);
+
2395 node = lookup_node(f, dir, oldname);
+
2396 if (node == NULL) {
+
2397 pthread_mutex_unlock(&f->lock);
+
2398 return NULL;
+
2399 }
+
2400 do {
+
2401 f->hidectr ++;
+
2402 snprintf(newname, bufsize, ".fuse_hidden%08x%08x",
+
2403 (unsigned int) node->nodeid, f->hidectr);
+
2404 newnode = lookup_node(f, dir, newname);
+
2405 } while(newnode);
+
2406
+
2407 res = try_get_path(f, dir, newname, &newpath, NULL, false);
+
2408 pthread_mutex_unlock(&f->lock);
+
2409 if (res)
+
2410 break;
+
2411
+
2412 memset(&buf, 0, sizeof(buf));
+
2413 res = fuse_fs_getattr(f->fs, newpath, &buf, NULL);
+
2414 if (res == -ENOENT)
+
2415 break;
+
2416 free(newpath);
+
2417 newpath = NULL;
+
2418 } while(res == 0 && --failctr);
+
2419
+
2420 return newpath;
+
2421}
+
2422
+
2423static int hide_node(struct fuse *f, const char *oldpath,
+
2424 fuse_ino_t dir, const char *oldname)
+
2425{
+
2426 char newname[64];
+
2427 char *newpath;
+
2428 int err = -EBUSY;
+
2429
+
2430 newpath = hidden_name(f, dir, oldname, newname, sizeof(newname));
+
2431 if (newpath) {
+
2432 err = fuse_fs_rename(f->fs, oldpath, newpath, 0);
+
2433 if (!err)
+
2434 err = rename_node(f, dir, oldname, dir, newname, 1);
+
2435 free(newpath);
+
2436 }
+
2437 return err;
+
2438}
+
2439
+
2440static int mtime_eq(const struct stat *stbuf, const struct timespec *ts)
+
2441{
+
2442 return stbuf->st_mtime == ts->tv_sec &&
+
2443 ST_MTIM_NSEC(stbuf) == ts->tv_nsec;
+
2444}
+
2445
+
2446#ifndef CLOCK_MONOTONIC
+
2447#define CLOCK_MONOTONIC CLOCK_REALTIME
+
2448#endif
+
2449
+
2450static void curr_time(struct timespec *now)
+
2451{
+
2452 static clockid_t clockid = CLOCK_MONOTONIC;
+
2453 int res = clock_gettime(clockid, now);
+
2454 if (res == -1 && errno == EINVAL) {
+
2455 clockid = CLOCK_REALTIME;
+
2456 res = clock_gettime(clockid, now);
+
2457 }
+
2458 if (res == -1) {
+
2459 perror("fuse: clock_gettime");
+
2460 abort();
+
2461 }
+
2462}
+
2463
+
2464static void update_stat(struct node *node, const struct stat *stbuf)
+
2465{
+
2466 if (node->cache_valid && (!mtime_eq(stbuf, &node->mtime) ||
+
2467 stbuf->st_size != node->size))
+
2468 node->cache_valid = 0;
+
2469 node->mtime.tv_sec = stbuf->st_mtime;
+
2470 node->mtime.tv_nsec = ST_MTIM_NSEC(stbuf);
+
2471 node->size = stbuf->st_size;
+
2472 curr_time(&node->stat_updated);
+
2473}
+
2474
+
2475static int do_lookup(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
2476 struct fuse_entry_param *e)
+
2477{
+
2478 struct node *node;
+
2479
+
2480 node = find_node(f, nodeid, name);
+
2481 if (node == NULL)
+
2482 return -ENOMEM;
+
2483
+
2484 e->ino = node->nodeid;
+
2485 e->generation = node->generation;
+
2486 e->entry_timeout = f->conf.entry_timeout;
+
2487 e->attr_timeout = f->conf.attr_timeout;
+
2488 if (f->conf.auto_cache) {
+
2489 pthread_mutex_lock(&f->lock);
+
2490 update_stat(node, &e->attr);
+
2491 pthread_mutex_unlock(&f->lock);
+
2492 }
+
2493 set_stat(f, e->ino, &e->attr);
+
2494 return 0;
+
2495}
+
2496
+
2497static int lookup_path(struct fuse *f, fuse_ino_t nodeid,
+
2498 const char *name, const char *path,
+
2499 struct fuse_entry_param *e, struct fuse_file_info *fi)
+
2500{
+
2501 int res;
+
2502
+
2503 memset(e, 0, sizeof(struct fuse_entry_param));
+
2504 res = fuse_fs_getattr(f->fs, path, &e->attr, fi);
+
2505 if (res == 0) {
+
2506 res = do_lookup(f, nodeid, name, e);
+
2507 if (res == 0 && f->conf.debug) {
+
2508 fuse_log(FUSE_LOG_DEBUG, " NODEID: %llu\n",
+
2509 (unsigned long long) e->ino);
+
2510 }
+
2511 }
+
2512 return res;
+
2513}
+
2514
+
2515static struct fuse_context_i *fuse_get_context_internal(void)
+
2516{
+
2517 return (struct fuse_context_i *) pthread_getspecific(fuse_context_key);
+
2518}
+
2519
+
2520static struct fuse_context_i *fuse_create_context(struct fuse *f)
+
2521{
+
2522 struct fuse_context_i *c = fuse_get_context_internal();
+
2523 if (c == NULL) {
+
2524 c = (struct fuse_context_i *)
+
2525 calloc(1, sizeof(struct fuse_context_i));
+
2526 if (c == NULL) {
+
2527 /* This is hard to deal with properly, so just
+
2528 abort. If memory is so low that the
+
2529 context cannot be allocated, there's not
+
2530 much hope for the filesystem anyway */
+
2531 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate thread specific data\n");
+
2532 abort();
+
2533 }
+
2534 pthread_setspecific(fuse_context_key, c);
+
2535 } else {
+
2536 memset(c, 0, sizeof(*c));
+
2537 }
+
2538 c->ctx.fuse = f;
+
2539
+
2540 return c;
+
2541}
+
2542
+
2543static void fuse_freecontext(void *data)
+
2544{
+
2545 free(data);
+
2546}
+
2547
+
2548static int fuse_create_context_key(void)
+
2549{
+
2550 int err = 0;
+
2551 pthread_mutex_lock(&fuse_context_lock);
+
2552 if (!fuse_context_ref) {
+
2553 err = pthread_key_create(&fuse_context_key, fuse_freecontext);
+
2554 if (err) {
+
2555 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
2556 strerror(err));
+
2557 pthread_mutex_unlock(&fuse_context_lock);
+
2558 return -1;
+
2559 }
+
2560 }
+
2561 fuse_context_ref++;
+
2562 pthread_mutex_unlock(&fuse_context_lock);
+
2563 return 0;
+
2564}
+
2565
+
2566static void fuse_delete_context_key(void)
+
2567{
+
2568 pthread_mutex_lock(&fuse_context_lock);
+
2569 fuse_context_ref--;
+
2570 if (!fuse_context_ref) {
+
2571 free(pthread_getspecific(fuse_context_key));
+
2572 pthread_key_delete(fuse_context_key);
+
2573 }
+
2574 pthread_mutex_unlock(&fuse_context_lock);
+
2575}
+
2576
+
2577static struct fuse *req_fuse_prepare(fuse_req_t req)
+
2578{
+
2579 struct fuse_context_i *c = fuse_create_context(req_fuse(req));
+
2580 const struct fuse_ctx *ctx = fuse_req_ctx(req);
+
2581 c->req = req;
+
2582 c->ctx.uid = ctx->uid;
+
2583 c->ctx.gid = ctx->gid;
+
2584 c->ctx.pid = ctx->pid;
+
2585 c->ctx.umask = ctx->umask;
+
2586 return c->ctx.fuse;
+
2587}
+
2588
+
2589static inline void reply_err(fuse_req_t req, int err)
+
2590{
+
2591 /* fuse_reply_err() uses non-negated errno values */
+
2592 fuse_reply_err(req, -err);
+
2593}
+
2594
+
2595static void reply_entry(fuse_req_t req, const struct fuse_entry_param *e,
+
2596 int err)
+
2597{
+
2598 if (!err) {
+
2599 struct fuse *f = req_fuse(req);
+
2600 if (fuse_reply_entry(req, e) == -ENOENT) {
+
2601 /* Skip forget for negative result */
+
2602 if (e->ino != 0)
+
2603 forget_node(f, e->ino, 1);
+
2604 }
+
2605 } else
+
2606 reply_err(req, err);
+
2607}
+
2608
+
2609void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
2610 struct fuse_config *cfg)
+
2611{
+
2612 fuse_get_context()->private_data = fs->user_data;
+
2613 if (!fs->op.write_buf)
+ +
2615 if (!fs->op.lock)
+ +
2617 if (!fs->op.flock)
+ +
2619 if (fs->op.init)
+
2620 fs->user_data = fs->op.init(conn, cfg);
+
2621}
+
2622
+
2623static int fuse_init_intr_signal(int signum, int *installed);
+
2624
+
2625static void fuse_lib_init(void *data, struct fuse_conn_info *conn)
+
2626{
+
2627 struct fuse *f = (struct fuse *) data;
+
2628
+
2629 fuse_create_context(f);
+ +
2631 fuse_fs_init(f->fs, conn, &f->conf);
+
2632
+
2633 if (f->conf.intr) {
+
2634 if (fuse_init_intr_signal(f->conf.intr_signal,
+
2635 &f->intr_installed) == -1)
+
2636 fuse_log(FUSE_LOG_ERR, "fuse: failed to init interrupt signal\n");
+
2637 } else {
+
2638 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
2639 conn->no_interrupt = 1;
+
2640 }
+
2641}
+
2642
+
2643void fuse_fs_destroy(struct fuse_fs *fs)
+
2644{
+
2645 fuse_get_context()->private_data = fs->user_data;
+
2646 if (fs->op.destroy)
+
2647 fs->op.destroy(fs->user_data);
+
2648}
+
2649
+
2650static void fuse_lib_destroy(void *data)
+
2651{
+
2652 struct fuse *f = (struct fuse *) data;
+
2653
+
2654 fuse_create_context(f);
+
2655 fuse_fs_destroy(f->fs);
+
2656}
+
2657
+
2658static void fuse_lib_lookup(fuse_req_t req, fuse_ino_t parent,
+
2659 const char *name)
+
2660{
+
2661 struct fuse *f = req_fuse_prepare(req);
+
2662 struct fuse_entry_param e = { .ino = 0 }; /* invalid ino */
+
2663 char *path;
+
2664 int err;
+
2665 struct node *dot = NULL;
+
2666
+
2667 if (name[0] == '.') {
+
2668 int len = strlen(name);
+
2669
+
2670 if (len == 1 || (name[1] == '.' && len == 2)) {
+
2671 pthread_mutex_lock(&f->lock);
+
2672 if (len == 1) {
+
2673 if (f->conf.debug)
+
2674 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOT\n");
+
2675 dot = get_node_nocheck(f, parent);
+
2676 if (dot == NULL) {
+
2677 pthread_mutex_unlock(&f->lock);
+
2678 reply_entry(req, &e, -ESTALE);
+
2679 return;
+
2680 }
+
2681 dot->refctr++;
+
2682 } else {
+
2683 if (f->conf.debug)
+
2684 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOTDOT\n");
+
2685 parent = get_node(f, parent)->parent->nodeid;
+
2686 }
+
2687 pthread_mutex_unlock(&f->lock);
+
2688 name = NULL;
+
2689 }
+
2690 }
+
2691
+
2692 err = get_path_name(f, parent, name, &path);
+
2693 if (!err) {
+
2694 struct fuse_intr_data d;
+
2695 if (f->conf.debug)
+
2696 fuse_log(FUSE_LOG_DEBUG, "LOOKUP %s\n", path);
+
2697 fuse_prepare_interrupt(f, req, &d);
+
2698 err = lookup_path(f, parent, name, path, &e, NULL);
+
2699 if (err == -ENOENT && f->conf.negative_timeout != 0.0) {
+
2700 e.ino = 0;
+
2701 e.entry_timeout = f->conf.negative_timeout;
+
2702 err = 0;
+
2703 }
+
2704 fuse_finish_interrupt(f, req, &d);
+
2705 free_path(f, parent, path);
+
2706 }
+
2707 if (dot) {
+
2708 pthread_mutex_lock(&f->lock);
+
2709 unref_node(f, dot);
+
2710 pthread_mutex_unlock(&f->lock);
+
2711 }
+
2712 reply_entry(req, &e, err);
+
2713}
+
2714
+
2715static void do_forget(struct fuse *f, fuse_ino_t ino, uint64_t nlookup)
+
2716{
+
2717 if (f->conf.debug)
+
2718 fuse_log(FUSE_LOG_DEBUG, "FORGET %llu/%llu\n", (unsigned long long)ino,
+
2719 (unsigned long long) nlookup);
+
2720 forget_node(f, ino, nlookup);
+
2721}
+
2722
+
2723static void fuse_lib_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
2724{
+
2725 do_forget(req_fuse(req), ino, nlookup);
+
2726 fuse_reply_none(req);
+
2727}
+
2728
+
2729static void fuse_lib_forget_multi(fuse_req_t req, size_t count,
+
2730 struct fuse_forget_data *forgets)
+
2731{
+
2732 struct fuse *f = req_fuse(req);
+
2733 size_t i;
+
2734
+
2735 for (i = 0; i < count; i++)
+
2736 do_forget(f, forgets[i].ino, forgets[i].nlookup);
+
2737
+
2738 fuse_reply_none(req);
+
2739}
+
2740
+
2741
+
2742static void fuse_lib_getattr(fuse_req_t req, fuse_ino_t ino,
+
2743 struct fuse_file_info *fi)
+
2744{
+
2745 struct fuse *f = req_fuse_prepare(req);
+
2746 struct stat buf;
+
2747 char *path;
+
2748 int err;
+
2749
+
2750 memset(&buf, 0, sizeof(buf));
+
2751
+
2752 if (fi != NULL)
+
2753 err = get_path_nullok(f, ino, &path);
+
2754 else
+
2755 err = get_path(f, ino, &path);
+
2756 if (!err) {
+
2757 struct fuse_intr_data d;
+
2758 fuse_prepare_interrupt(f, req, &d);
+
2759 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2760 fuse_finish_interrupt(f, req, &d);
+
2761 free_path(f, ino, path);
+
2762 }
+
2763 if (!err) {
+
2764 struct node *node;
+
2765
+
2766 pthread_mutex_lock(&f->lock);
+
2767 node = get_node(f, ino);
+
2768 if (node->is_hidden && buf.st_nlink > 0)
+
2769 buf.st_nlink--;
+
2770 if (f->conf.auto_cache)
+
2771 update_stat(node, &buf);
+
2772 pthread_mutex_unlock(&f->lock);
+
2773 set_stat(f, ino, &buf);
+
2774 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2775 } else
+
2776 reply_err(req, err);
+
2777}
+
2778
+
2779int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2780 struct fuse_file_info *fi)
+
2781{
+
2782 fuse_get_context()->private_data = fs->user_data;
+
2783 if (!fs->op.chmod)
+
2784 return -ENOSYS;
+
2785
+
2786 if (fs->debug) {
+
2787 char buf[10];
+
2788
+
2789 fuse_log(FUSE_LOG_DEBUG, "chmod[%s] %s %llo\n",
+
2790 file_info_string(fi, buf, sizeof(buf)),
+
2791 path, (unsigned long long) mode);
+
2792 }
+
2793 return fs->op.chmod(path, mode, fi);
+
2794}
+
2795
+
2796static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
2797 int valid, struct fuse_file_info *fi)
+
2798{
+
2799 struct fuse *f = req_fuse_prepare(req);
+
2800 struct stat buf;
+
2801 char *path;
+
2802 int err;
+
2803
+
2804 memset(&buf, 0, sizeof(buf));
+
2805 if (fi != NULL)
+
2806 err = get_path_nullok(f, ino, &path);
+
2807 else
+
2808 err = get_path(f, ino, &path);
+
2809 if (!err) {
+
2810 struct fuse_intr_data d;
+
2811 fuse_prepare_interrupt(f, req, &d);
+
2812 err = 0;
+
2813 if (!err && (valid & FUSE_SET_ATTR_MODE))
+
2814 err = fuse_fs_chmod(f->fs, path, attr->st_mode, fi);
+
2815 if (!err && (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))) {
+
2816 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
2817 attr->st_uid : (uid_t) -1;
+
2818 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
2819 attr->st_gid : (gid_t) -1;
+
2820 err = fuse_fs_chown(f->fs, path, uid, gid, fi);
+
2821 }
+
2822 if (!err && (valid & FUSE_SET_ATTR_SIZE)) {
+
2823 err = fuse_fs_truncate(f->fs, path,
+
2824 attr->st_size, fi);
+
2825 }
+
2826#ifdef HAVE_UTIMENSAT
+
2827 if (!err &&
+
2828 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) {
+
2829 struct timespec tv[2];
+
2830
+
2831 tv[0].tv_sec = 0;
+
2832 tv[1].tv_sec = 0;
+
2833 tv[0].tv_nsec = UTIME_OMIT;
+
2834 tv[1].tv_nsec = UTIME_OMIT;
+
2835
+
2836 if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
2837 tv[0].tv_nsec = UTIME_NOW;
+
2838 else if (valid & FUSE_SET_ATTR_ATIME)
+
2839 tv[0] = attr->st_atim;
+
2840
+
2841 if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
2842 tv[1].tv_nsec = UTIME_NOW;
+
2843 else if (valid & FUSE_SET_ATTR_MTIME)
+
2844 tv[1] = attr->st_mtim;
+
2845
+
2846 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2847 } else
+
2848#endif
+
2849 if (!err &&
+
2850 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) ==
+
2851 (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
2852 struct timespec tv[2];
+
2853 tv[0].tv_sec = attr->st_atime;
+
2854 tv[0].tv_nsec = ST_ATIM_NSEC(attr);
+
2855 tv[1].tv_sec = attr->st_mtime;
+
2856 tv[1].tv_nsec = ST_MTIM_NSEC(attr);
+
2857 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2858 }
+
2859 if (!err) {
+
2860 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2861 }
+
2862 fuse_finish_interrupt(f, req, &d);
+
2863 free_path(f, ino, path);
+
2864 }
+
2865 if (!err) {
+
2866 if (f->conf.auto_cache) {
+
2867 pthread_mutex_lock(&f->lock);
+
2868 update_stat(get_node(f, ino), &buf);
+
2869 pthread_mutex_unlock(&f->lock);
+
2870 }
+
2871 set_stat(f, ino, &buf);
+
2872 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2873 } else
+
2874 reply_err(req, err);
+
2875}
+
2876
+
2877static void fuse_lib_access(fuse_req_t req, fuse_ino_t ino, int mask)
+
2878{
+
2879 struct fuse *f = req_fuse_prepare(req);
+
2880 char *path;
+
2881 int err;
+
2882
+
2883 err = get_path(f, ino, &path);
+
2884 if (!err) {
+
2885 struct fuse_intr_data d;
+
2886
+
2887 fuse_prepare_interrupt(f, req, &d);
+
2888 err = fuse_fs_access(f->fs, path, mask);
+
2889 fuse_finish_interrupt(f, req, &d);
+
2890 free_path(f, ino, path);
+
2891 }
+
2892 reply_err(req, err);
+
2893}
+
2894
+
2895static void fuse_lib_readlink(fuse_req_t req, fuse_ino_t ino)
+
2896{
+
2897 struct fuse *f = req_fuse_prepare(req);
+
2898 char linkname[PATH_MAX + 1];
+
2899 char *path;
+
2900 int err;
+
2901
+
2902 err = get_path(f, ino, &path);
+
2903 if (!err) {
+
2904 struct fuse_intr_data d;
+
2905 fuse_prepare_interrupt(f, req, &d);
+
2906 err = fuse_fs_readlink(f->fs, path, linkname, sizeof(linkname));
+
2907 fuse_finish_interrupt(f, req, &d);
+
2908 free_path(f, ino, path);
+
2909 }
+
2910 if (!err) {
+
2911 linkname[PATH_MAX] = '\0';
+
2912 fuse_reply_readlink(req, linkname);
+
2913 } else
+
2914 reply_err(req, err);
+
2915}
+
2916
+
2917static void fuse_lib_mknod(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2918 mode_t mode, dev_t rdev)
+
2919{
+
2920 struct fuse *f = req_fuse_prepare(req);
+
2921 struct fuse_entry_param e;
+
2922 char *path;
+
2923 int err;
+
2924
+
2925 err = get_path_name(f, parent, name, &path);
+
2926 if (!err) {
+
2927 struct fuse_intr_data d;
+
2928
+
2929 fuse_prepare_interrupt(f, req, &d);
+
2930 err = -ENOSYS;
+
2931 if (S_ISREG(mode)) {
+
2932 struct fuse_file_info fi;
+
2933
+
2934 memset(&fi, 0, sizeof(fi));
+
2935 fi.flags = O_CREAT | O_EXCL | O_WRONLY;
+
2936 err = fuse_fs_create(f->fs, path, mode, &fi);
+
2937 if (!err) {
+
2938 err = lookup_path(f, parent, name, path, &e,
+
2939 &fi);
+
2940 fuse_fs_release(f->fs, path, &fi);
+
2941 }
+
2942 }
+
2943 if (err == -ENOSYS) {
+
2944 err = fuse_fs_mknod(f->fs, path, mode, rdev);
+
2945 if (!err)
+
2946 err = lookup_path(f, parent, name, path, &e,
+
2947 NULL);
+
2948 }
+
2949 fuse_finish_interrupt(f, req, &d);
+
2950 free_path(f, parent, path);
+
2951 }
+
2952 reply_entry(req, &e, err);
+
2953}
+
2954
+
2955static void fuse_lib_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2956 mode_t mode)
+
2957{
+
2958 struct fuse *f = req_fuse_prepare(req);
+
2959 struct fuse_entry_param e;
+
2960 char *path;
+
2961 int err;
+
2962
+
2963 err = get_path_name(f, parent, name, &path);
+
2964 if (!err) {
+
2965 struct fuse_intr_data d;
+
2966
+
2967 fuse_prepare_interrupt(f, req, &d);
+
2968 err = fuse_fs_mkdir(f->fs, path, mode);
+
2969 if (!err)
+
2970 err = lookup_path(f, parent, name, path, &e, NULL);
+
2971 fuse_finish_interrupt(f, req, &d);
+
2972 free_path(f, parent, path);
+
2973 }
+
2974 reply_entry(req, &e, err);
+
2975}
+
2976
+
2977static void fuse_lib_unlink(fuse_req_t req, fuse_ino_t parent,
+
2978 const char *name)
+
2979{
+
2980 struct fuse *f = req_fuse_prepare(req);
+
2981 struct node *wnode;
+
2982 char *path;
+
2983 int err;
+
2984
+
2985 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
2986 if (!err) {
+
2987 struct fuse_intr_data d;
+
2988
+
2989 fuse_prepare_interrupt(f, req, &d);
+
2990 if (!f->conf.hard_remove && is_open(f, parent, name)) {
+
2991 err = hide_node(f, path, parent, name);
+
2992 if (!err) {
+
2993 /* we have hidden the node so now check again under a lock in case it is not used any more */
+
2994 if (!is_open(f, parent, wnode->name)) {
+
2995 char *unlinkpath;
+
2996
+
2997 /* get the hidden file path, to unlink it */
+
2998 if (try_get_path(f, wnode->nodeid, NULL, &unlinkpath, NULL, false) == 0) {
+
2999 err = fuse_fs_unlink(f->fs, unlinkpath);
+
3000 if (!err)
+
3001 remove_node(f, parent, wnode->name);
+
3002 free(unlinkpath);
+
3003 }
+
3004 }
+
3005 }
+
3006 } else {
+
3007 err = fuse_fs_unlink(f->fs, path);
+
3008 if (!err)
+
3009 remove_node(f, parent, name);
+
3010 }
+
3011 fuse_finish_interrupt(f, req, &d);
+
3012 free_path_wrlock(f, parent, wnode, path);
+
3013 }
+
3014 reply_err(req, err);
+
3015}
+
3016
+
3017static void fuse_lib_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
3018{
+
3019 struct fuse *f = req_fuse_prepare(req);
+
3020 struct node *wnode;
+
3021 char *path;
+
3022 int err;
+
3023
+
3024 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
3025 if (!err) {
+
3026 struct fuse_intr_data d;
+
3027
+
3028 fuse_prepare_interrupt(f, req, &d);
+
3029 err = fuse_fs_rmdir(f->fs, path);
+
3030 fuse_finish_interrupt(f, req, &d);
+
3031 if (!err)
+
3032 remove_node(f, parent, name);
+
3033 free_path_wrlock(f, parent, wnode, path);
+
3034 }
+
3035 reply_err(req, err);
+
3036}
+
3037
+
3038static void fuse_lib_symlink(fuse_req_t req, const char *linkname,
+
3039 fuse_ino_t parent, const char *name)
+
3040{
+
3041 struct fuse *f = req_fuse_prepare(req);
+
3042 struct fuse_entry_param e;
+
3043 char *path;
+
3044 int err;
+
3045
+
3046 err = get_path_name(f, parent, name, &path);
+
3047 if (!err) {
+
3048 struct fuse_intr_data d;
+
3049
+
3050 fuse_prepare_interrupt(f, req, &d);
+
3051 err = fuse_fs_symlink(f->fs, linkname, path);
+
3052 if (!err)
+
3053 err = lookup_path(f, parent, name, path, &e, NULL);
+
3054 fuse_finish_interrupt(f, req, &d);
+
3055 free_path(f, parent, path);
+
3056 }
+
3057 reply_entry(req, &e, err);
+
3058}
+
3059
+
3060static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir,
+
3061 const char *oldname, fuse_ino_t newdir,
+
3062 const char *newname, unsigned int flags)
+
3063{
+
3064 struct fuse *f = req_fuse_prepare(req);
+
3065 char *oldpath;
+
3066 char *newpath;
+
3067 struct node *wnode1;
+
3068 struct node *wnode2;
+
3069 int err;
+
3070
+
3071 err = get_path2(f, olddir, oldname, newdir, newname,
+
3072 &oldpath, &newpath, &wnode1, &wnode2);
+
3073 if (!err) {
+
3074 struct fuse_intr_data d;
+
3075 err = 0;
+
3076 fuse_prepare_interrupt(f, req, &d);
+
3077 if (!f->conf.hard_remove && !(flags & RENAME_EXCHANGE) &&
+
3078 is_open(f, newdir, newname))
+
3079 err = hide_node(f, newpath, newdir, newname);
+
3080 if (!err) {
+
3081 err = fuse_fs_rename(f->fs, oldpath, newpath, flags);
+
3082 if (!err) {
+
3083 if (flags & RENAME_EXCHANGE) {
+
3084 err = exchange_node(f, olddir, oldname,
+
3085 newdir, newname);
+
3086 } else {
+
3087 err = rename_node(f, olddir, oldname,
+
3088 newdir, newname, 0);
+
3089 }
+
3090 }
+
3091 }
+
3092 fuse_finish_interrupt(f, req, &d);
+
3093 free_path2(f, olddir, newdir, wnode1, wnode2, oldpath, newpath);
+
3094 }
+
3095 reply_err(req, err);
+
3096}
+
3097
+
3098static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
3099 const char *newname)
+
3100{
+
3101 struct fuse *f = req_fuse_prepare(req);
+
3102 struct fuse_entry_param e;
+
3103 char *oldpath;
+
3104 char *newpath;
+
3105 int err;
+
3106
+
3107 err = get_path2(f, ino, NULL, newparent, newname,
+
3108 &oldpath, &newpath, NULL, NULL);
+
3109 if (!err) {
+
3110 struct fuse_intr_data d;
+
3111
+
3112 fuse_prepare_interrupt(f, req, &d);
+
3113 err = fuse_fs_link(f->fs, oldpath, newpath);
+
3114 if (!err)
+
3115 err = lookup_path(f, newparent, newname, newpath,
+
3116 &e, NULL);
+
3117 fuse_finish_interrupt(f, req, &d);
+
3118 free_path2(f, ino, newparent, NULL, NULL, oldpath, newpath);
+
3119 }
+
3120 reply_entry(req, &e, err);
+
3121}
+
3122
+
3123static void fuse_do_release(struct fuse *f, fuse_ino_t ino, const char *path,
+
3124 struct fuse_file_info *fi)
+
3125{
+
3126 struct node *node;
+
3127 int unlink_hidden = 0;
+
3128
+
3129 fuse_fs_release(f->fs, path, fi);
+
3130
+
3131 pthread_mutex_lock(&f->lock);
+
3132 node = get_node(f, ino);
+
3133 assert(node->open_count > 0);
+
3134 --node->open_count;
+
3135 if (node->is_hidden && !node->open_count) {
+
3136 unlink_hidden = 1;
+
3137 node->is_hidden = 0;
+
3138 }
+
3139 pthread_mutex_unlock(&f->lock);
+
3140
+
3141 if(unlink_hidden) {
+
3142 if (path) {
+
3143 fuse_fs_unlink(f->fs, path);
+
3144 } else if (f->conf.nullpath_ok) {
+
3145 char *unlinkpath;
+
3146
+
3147 if (get_path(f, ino, &unlinkpath) == 0)
+
3148 fuse_fs_unlink(f->fs, unlinkpath);
+
3149
+
3150 free_path(f, ino, unlinkpath);
+
3151 }
+
3152 }
+
3153}
+
3154
+
3155static void fuse_lib_create(fuse_req_t req, fuse_ino_t parent,
+
3156 const char *name, mode_t mode,
+
3157 struct fuse_file_info *fi)
+
3158{
+
3159 struct fuse *f = req_fuse_prepare(req);
+
3160 struct fuse_intr_data d;
+
3161 struct fuse_entry_param e;
+
3162 char *path;
+
3163 int err;
+
3164
+
3165 err = get_path_name(f, parent, name, &path);
+
3166 if (!err) {
+
3167 fuse_prepare_interrupt(f, req, &d);
+
3168 err = fuse_fs_create(f->fs, path, mode, fi);
+
3169 if (!err) {
+
3170 err = lookup_path(f, parent, name, path, &e, fi);
+
3171 if (err)
+
3172 fuse_fs_release(f->fs, path, fi);
+
3173 else if (!S_ISREG(e.attr.st_mode)) {
+
3174 err = -EIO;
+
3175 fuse_fs_release(f->fs, path, fi);
+
3176 forget_node(f, e.ino, 1);
+
3177 } else {
+
3178 if (f->conf.direct_io)
+
3179 fi->direct_io = 1;
+
3180 if (f->conf.kernel_cache)
+
3181 fi->keep_cache = 1;
+
3182 if (fi->direct_io &&
+
3183 f->conf.parallel_direct_writes)
+
3184 fi->parallel_direct_writes = 1;
+
3185 }
+
3186 }
+
3187 fuse_finish_interrupt(f, req, &d);
+
3188 }
+
3189 if (!err) {
+
3190 pthread_mutex_lock(&f->lock);
+
3191 get_node(f, e.ino)->open_count++;
+
3192 pthread_mutex_unlock(&f->lock);
+
3193 if (fuse_reply_create(req, &e, fi) == -ENOENT) {
+
3194 /* The open syscall was interrupted, so it
+
3195 must be cancelled */
+
3196 fuse_do_release(f, e.ino, path, fi);
+
3197 forget_node(f, e.ino, 1);
+
3198 }
+
3199 } else {
+
3200 reply_err(req, err);
+
3201 }
+
3202
+
3203 free_path(f, parent, path);
+
3204}
+
3205
+
3206static double diff_timespec(const struct timespec *t1,
+
3207 const struct timespec *t2)
+
3208{
+
3209 return (t1->tv_sec - t2->tv_sec) +
+
3210 ((double) t1->tv_nsec - (double) t2->tv_nsec) / 1000000000.0;
+
3211}
+
3212
+
3213static void open_auto_cache(struct fuse *f, fuse_ino_t ino, const char *path,
+
3214 struct fuse_file_info *fi)
+
3215{
+
3216 struct node *node;
+
3217
+
3218 pthread_mutex_lock(&f->lock);
+
3219 node = get_node(f, ino);
+
3220 if (node->cache_valid) {
+
3221 struct timespec now;
+
3222
+
3223 curr_time(&now);
+
3224 if (diff_timespec(&now, &node->stat_updated) >
+
3225 f->conf.ac_attr_timeout) {
+
3226 struct stat stbuf;
+
3227 int err;
+
3228 pthread_mutex_unlock(&f->lock);
+
3229 err = fuse_fs_getattr(f->fs, path, &stbuf, fi);
+
3230 pthread_mutex_lock(&f->lock);
+
3231 if (!err)
+
3232 update_stat(node, &stbuf);
+
3233 else
+
3234 node->cache_valid = 0;
+
3235 }
+
3236 }
+
3237 if (node->cache_valid)
+
3238 fi->keep_cache = 1;
+
3239
+
3240 node->cache_valid = 1;
+
3241 pthread_mutex_unlock(&f->lock);
+
3242}
+
3243
+
3244static void fuse_lib_open(fuse_req_t req, fuse_ino_t ino,
+
3245 struct fuse_file_info *fi)
+
3246{
+
3247 struct fuse *f = req_fuse_prepare(req);
+
3248 struct fuse_intr_data d;
+
3249 char *path;
+
3250 int err;
+
3251
+
3252 err = get_path(f, ino, &path);
+
3253 if (!err) {
+
3254 fuse_prepare_interrupt(f, req, &d);
+
3255 err = fuse_fs_open(f->fs, path, fi);
+
3256 if (!err) {
+
3257 if (f->conf.direct_io)
+
3258 fi->direct_io = 1;
+
3259 if (f->conf.kernel_cache)
+
3260 fi->keep_cache = 1;
+
3261
+
3262 if (f->conf.auto_cache)
+
3263 open_auto_cache(f, ino, path, fi);
+
3264
+
3265 if (f->conf.no_rofd_flush &&
+
3266 (fi->flags & O_ACCMODE) == O_RDONLY)
+
3267 fi->noflush = 1;
+
3268
+
3269 if (fi->direct_io && f->conf.parallel_direct_writes)
+
3270 fi->parallel_direct_writes = 1;
+
3271
+
3272 }
+
3273 fuse_finish_interrupt(f, req, &d);
+
3274 }
+
3275 if (!err) {
+
3276 pthread_mutex_lock(&f->lock);
+
3277 get_node(f, ino)->open_count++;
+
3278 pthread_mutex_unlock(&f->lock);
+
3279 if (fuse_reply_open(req, fi) == -ENOENT) {
+
3280 /* The open syscall was interrupted, so it
+
3281 must be cancelled */
+
3282 fuse_do_release(f, ino, path, fi);
+
3283 }
+
3284 } else
+
3285 reply_err(req, err);
+
3286
+
3287 free_path(f, ino, path);
+
3288}
+
3289
+
3290static void fuse_lib_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3291 off_t off, struct fuse_file_info *fi)
+
3292{
+
3293 struct fuse *f = req_fuse_prepare(req);
+
3294 struct fuse_bufvec *buf = NULL;
+
3295 char *path;
+
3296 int res;
+
3297
+
3298 res = get_path_nullok(f, ino, &path);
+
3299 if (res == 0) {
+
3300 struct fuse_intr_data d;
+
3301
+
3302 fuse_prepare_interrupt(f, req, &d);
+
3303 res = fuse_fs_read_buf(f->fs, path, &buf, size, off, fi);
+
3304 fuse_finish_interrupt(f, req, &d);
+
3305 free_path(f, ino, path);
+
3306 }
+
3307
+
3308 if (res == 0)
+ +
3310 else
+
3311 reply_err(req, res);
+
3312
+
3313 fuse_free_buf(buf);
+
3314}
+
3315
+
3316static void fuse_lib_write_buf(fuse_req_t req, fuse_ino_t ino,
+
3317 struct fuse_bufvec *buf, off_t off,
+
3318 struct fuse_file_info *fi)
+
3319{
+
3320 struct fuse *f = req_fuse_prepare(req);
+
3321 char *path;
+
3322 int res;
+
3323
+
3324 res = get_path_nullok(f, ino, &path);
+
3325 if (res == 0) {
+
3326 struct fuse_intr_data d;
+
3327
+
3328 fuse_prepare_interrupt(f, req, &d);
+
3329 res = fuse_fs_write_buf(f->fs, path, buf, off, fi);
+
3330 fuse_finish_interrupt(f, req, &d);
+
3331 free_path(f, ino, path);
+
3332 }
+
3333
+
3334 if (res >= 0)
+
3335 fuse_reply_write(req, res);
+
3336 else
+
3337 reply_err(req, res);
+
3338}
+
3339
+
3340static void fuse_lib_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3341 struct fuse_file_info *fi)
+
3342{
+
3343 struct fuse *f = req_fuse_prepare(req);
+
3344 char *path;
+
3345 int err;
+
3346
+
3347 err = get_path_nullok(f, ino, &path);
+
3348 if (!err) {
+
3349 struct fuse_intr_data d;
+
3350
+
3351 fuse_prepare_interrupt(f, req, &d);
+
3352 err = fuse_fs_fsync(f->fs, path, datasync, fi);
+
3353 fuse_finish_interrupt(f, req, &d);
+
3354 free_path(f, ino, path);
+
3355 }
+
3356 reply_err(req, err);
+
3357}
+
3358
+
3359static struct fuse_dh *get_dirhandle(const struct fuse_file_info *llfi,
+
3360 struct fuse_file_info *fi)
+
3361{
+
3362 struct fuse_dh *dh = (struct fuse_dh *) (uintptr_t) llfi->fh;
+
3363 memset(fi, 0, sizeof(struct fuse_file_info));
+
3364 fi->fh = dh->fh;
+
3365 return dh;
+
3366}
+
3367
+
3368static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino,
+
3369 struct fuse_file_info *llfi)
+
3370{
+
3371 struct fuse *f = req_fuse_prepare(req);
+
3372 struct fuse_intr_data d;
+
3373 struct fuse_dh *dh;
+
3374 struct fuse_file_info fi;
+
3375 char *path;
+
3376 int err;
+
3377
+
3378 dh = (struct fuse_dh *) malloc(sizeof(struct fuse_dh));
+
3379 if (dh == NULL) {
+
3380 reply_err(req, -ENOMEM);
+
3381 return;
+
3382 }
+
3383 memset(dh, 0, sizeof(struct fuse_dh));
+
3384 dh->fuse = f;
+
3385 dh->contents = NULL;
+
3386 dh->first = NULL;
+
3387 dh->len = 0;
+
3388 dh->filled = 0;
+
3389 dh->nodeid = ino;
+
3390 pthread_mutex_init(&dh->lock, NULL);
+
3391
+
3392 llfi->fh = (uintptr_t) dh;
+
3393
+
3394 memset(&fi, 0, sizeof(fi));
+
3395 fi.flags = llfi->flags;
+
3396
+
3397 err = get_path(f, ino, &path);
+
3398 if (!err) {
+
3399 fuse_prepare_interrupt(f, req, &d);
+
3400 err = fuse_fs_opendir(f->fs, path, &fi);
+
3401 fuse_finish_interrupt(f, req, &d);
+
3402 dh->fh = fi.fh;
+
3403 llfi->cache_readdir = fi.cache_readdir;
+
3404 llfi->keep_cache = fi.keep_cache;
+
3405 }
+
3406 if (!err) {
+
3407 if (fuse_reply_open(req, llfi) == -ENOENT) {
+
3408 /* The opendir syscall was interrupted, so it
+
3409 must be cancelled */
+
3410 fuse_fs_releasedir(f->fs, path, &fi);
+
3411 pthread_mutex_destroy(&dh->lock);
+
3412 free(dh);
+
3413 }
+
3414 } else {
+
3415 reply_err(req, err);
+
3416 pthread_mutex_destroy(&dh->lock);
+
3417 free(dh);
+
3418 }
+
3419 free_path(f, ino, path);
+
3420}
+
3421
+
3422static int extend_contents(struct fuse_dh *dh, unsigned minsize)
+
3423{
+
3424 if (minsize > dh->size) {
+
3425 char *newptr;
+
3426 unsigned newsize = dh->size;
+
3427 if (!newsize)
+
3428 newsize = 1024;
+
3429 while (newsize < minsize) {
+
3430 if (newsize >= 0x80000000)
+
3431 newsize = 0xffffffff;
+
3432 else
+
3433 newsize *= 2;
+
3434 }
+
3435
+
3436 newptr = (char *) realloc(dh->contents, newsize);
+
3437 if (!newptr) {
+
3438 dh->error = -ENOMEM;
+
3439 return -1;
+
3440 }
+
3441 dh->contents = newptr;
+
3442 dh->size = newsize;
+
3443 }
+
3444 return 0;
+
3445}
+
3446
+
3447static int fuse_add_direntry_to_dh(struct fuse_dh *dh, const char *name,
+
3448 struct stat *st, enum fuse_fill_dir_flags flags)
+
3449{
+
3450 struct fuse_direntry *de;
+
3451
+
3452 de = malloc(sizeof(struct fuse_direntry));
+
3453 if (!de) {
+
3454 dh->error = -ENOMEM;
+
3455 return -1;
+
3456 }
+
3457 de->name = strdup(name);
+
3458 if (!de->name) {
+
3459 dh->error = -ENOMEM;
+
3460 free(de);
+
3461 return -1;
+
3462 }
+
3463 de->flags = flags;
+
3464 de->stat = *st;
+
3465 de->next = NULL;
+
3466
+
3467 *dh->last = de;
+
3468 dh->last = &de->next;
+
3469
+
3470 return 0;
+
3471}
+
3472
+
3473static fuse_ino_t lookup_nodeid(struct fuse *f, fuse_ino_t parent,
+
3474 const char *name)
+
3475{
+
3476 struct node *node;
+
3477 fuse_ino_t res = FUSE_UNKNOWN_INO;
+
3478
+
3479 pthread_mutex_lock(&f->lock);
+
3480 node = lookup_node(f, parent, name);
+
3481 if (node)
+
3482 res = node->nodeid;
+
3483 pthread_mutex_unlock(&f->lock);
+
3484
+
3485 return res;
+
3486}
+
3487
+
3488static int fill_dir(void *dh_, const char *name, const struct stat *statp,
+
3489 off_t off, enum fuse_fill_dir_flags flags)
+
3490{
+
3491 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3492 struct stat stbuf;
+
3493
+
3494 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3495 dh->error = -EIO;
+
3496 return 1;
+
3497 }
+
3498
+
3499 if (statp)
+
3500 stbuf = *statp;
+
3501 else {
+
3502 memset(&stbuf, 0, sizeof(stbuf));
+
3503 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3504 }
+
3505
+
3506 if (!dh->fuse->conf.use_ino) {
+
3507 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3508 if (dh->fuse->conf.readdir_ino) {
+
3509 stbuf.st_ino = (ino_t)
+
3510 lookup_nodeid(dh->fuse, dh->nodeid, name);
+
3511 }
+
3512 }
+
3513
+
3514 if (off) {
+
3515 size_t newlen;
+
3516
+
3517 if (dh->filled) {
+
3518 dh->error = -EIO;
+
3519 return 1;
+
3520 }
+
3521
+
3522 if (dh->first) {
+
3523 dh->error = -EIO;
+
3524 return 1;
+
3525 }
+
3526
+
3527 if (extend_contents(dh, dh->needlen) == -1)
+
3528 return 1;
+
3529
+
3530 newlen = dh->len +
+
3531 fuse_add_direntry(dh->req, dh->contents + dh->len,
+
3532 dh->needlen - dh->len, name,
+
3533 &stbuf, off);
+
3534 if (newlen > dh->needlen)
+
3535 return 1;
+
3536
+
3537 dh->len = newlen;
+
3538 } else {
+
3539 dh->filled = 1;
+
3540
+
3541 if (fuse_add_direntry_to_dh(dh, name, &stbuf, flags) == -1)
+
3542 return 1;
+
3543 }
+
3544 return 0;
+
3545}
+
3546
+
3547static int is_dot_or_dotdot(const char *name)
+
3548{
+
3549 return name[0] == '.' && (name[1] == '\0' ||
+
3550 (name[1] == '.' && name[2] == '\0'));
+
3551}
+
3552
+
3553static int fill_dir_plus(void *dh_, const char *name, const struct stat *statp,
+
3554 off_t off, enum fuse_fill_dir_flags flags)
+
3555{
+
3556 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3557 struct fuse_entry_param e = {
+
3558 /* ino=0 tells the kernel to ignore readdirplus stat info */
+
3559 .ino = 0,
+
3560 };
+
3561 struct fuse *f = dh->fuse;
+
3562 int res;
+
3563
+
3564 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3565 dh->error = -EIO;
+
3566 return 1;
+
3567 }
+
3568
+
3569 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3570 e.attr = *statp;
+
3571 }
+
3572
+
3573 e.attr.st_ino = FUSE_UNKNOWN_INO;
+
3574 if (statp) {
+
3575 e.attr.st_mode = statp->st_mode;
+
3576 if (f->conf.use_ino)
+
3577 e.attr.st_ino = statp->st_ino;
+
3578 }
+
3579 if (!f->conf.use_ino && f->conf.readdir_ino) {
+
3580 e.attr.st_ino = (ino_t)
+
3581 lookup_nodeid(f, dh->nodeid, name);
+
3582 }
+
3583
+
3584 if (off) {
+
3585 size_t newlen;
+
3586
+
3587 if (dh->filled) {
+
3588 dh->error = -EIO;
+
3589 return 1;
+
3590 }
+
3591
+
3592 if (dh->first) {
+
3593 dh->error = -EIO;
+
3594 return 1;
+
3595 }
+
3596 if (extend_contents(dh, dh->needlen) == -1)
+
3597 return 1;
+
3598
+
3599 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3600 if (!is_dot_or_dotdot(name)) {
+
3601 res = do_lookup(f, dh->nodeid, name, &e);
+
3602 if (res) {
+
3603 dh->error = res;
+
3604 return 1;
+
3605 }
+
3606 }
+
3607 }
+
3608
+
3609 newlen = dh->len +
+
3610 fuse_add_direntry_plus(dh->req, dh->contents + dh->len,
+
3611 dh->needlen - dh->len, name,
+
3612 &e, off);
+
3613 if (newlen > dh->needlen)
+
3614 return 1;
+
3615 dh->len = newlen;
+
3616 } else {
+
3617 dh->filled = 1;
+
3618
+
3619 if (fuse_add_direntry_to_dh(dh, name, &e.attr, flags) == -1)
+
3620 return 1;
+
3621 }
+
3622
+
3623 return 0;
+
3624}
+
3625
+
3626static void free_direntries(struct fuse_direntry *de)
+
3627{
+
3628 while (de) {
+
3629 struct fuse_direntry *next = de->next;
+
3630 free(de->name);
+
3631 free(de);
+
3632 de = next;
+
3633 }
+
3634}
+
3635
+
3636static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3637 size_t size, off_t off, struct fuse_dh *dh,
+
3638 struct fuse_file_info *fi,
+
3639 enum fuse_readdir_flags flags)
+
3640{
+
3641 char *path;
+
3642 int err;
+
3643
+
3644 if (f->fs->op.readdir)
+
3645 err = get_path_nullok(f, ino, &path);
+
3646 else
+
3647 err = get_path(f, ino, &path);
+
3648 if (!err) {
+
3649 struct fuse_intr_data d;
+
3650 fuse_fill_dir_t filler = fill_dir;
+
3651
+
3652 if (flags & FUSE_READDIR_PLUS)
+
3653 filler = fill_dir_plus;
+
3654
+
3655 free_direntries(dh->first);
+
3656 dh->first = NULL;
+
3657 dh->last = &dh->first;
+
3658 dh->len = 0;
+
3659 dh->error = 0;
+
3660 dh->needlen = size;
+
3661 dh->filled = 0;
+
3662 dh->req = req;
+
3663 fuse_prepare_interrupt(f, req, &d);
+
3664 err = fuse_fs_readdir(f->fs, path, dh, filler, off, fi, flags);
+
3665 fuse_finish_interrupt(f, req, &d);
+
3666 dh->req = NULL;
+
3667 if (!err)
+
3668 err = dh->error;
+
3669 if (err)
+
3670 dh->filled = 0;
+
3671 free_path(f, ino, path);
+
3672 }
+
3673 return err;
+
3674}
+
3675
+
3676static int readdir_fill_from_list(fuse_req_t req, struct fuse_dh *dh,
+
3677 off_t off, enum fuse_readdir_flags flags)
+
3678{
+
3679 off_t pos;
+
3680 struct fuse_direntry *de = dh->first;
+
3681 int res;
+
3682
+
3683 dh->len = 0;
+
3684
+
3685 if (extend_contents(dh, dh->needlen) == -1)
+
3686 return dh->error;
+
3687
+
3688 for (pos = 0; pos < off; pos++) {
+
3689 if (!de)
+
3690 break;
+
3691
+
3692 de = de->next;
+
3693 }
+
3694 while (de) {
+
3695 char *p = dh->contents + dh->len;
+
3696 unsigned rem = dh->needlen - dh->len;
+
3697 unsigned thislen;
+
3698 unsigned newlen;
+
3699 pos++;
+
3700
+
3701 if (flags & FUSE_READDIR_PLUS) {
+
3702 struct fuse_entry_param e = {
+
3703 .ino = 0,
+
3704 .attr = de->stat,
+
3705 };
+
3706
+
3707 if (de->flags & FUSE_FILL_DIR_PLUS &&
+
3708 !is_dot_or_dotdot(de->name)) {
+
3709 res = do_lookup(dh->fuse, dh->nodeid,
+
3710 de->name, &e);
+
3711 if (res) {
+
3712 dh->error = res;
+
3713 return 1;
+
3714 }
+
3715 }
+
3716
+
3717 thislen = fuse_add_direntry_plus(req, p, rem,
+
3718 de->name, &e, pos);
+
3719 } else {
+
3720 thislen = fuse_add_direntry(req, p, rem,
+
3721 de->name, &de->stat, pos);
+
3722 }
+
3723 newlen = dh->len + thislen;
+
3724 if (newlen > dh->needlen)
+
3725 break;
+
3726 dh->len = newlen;
+
3727 de = de->next;
+
3728 }
+
3729 return 0;
+
3730}
+
3731
+
3732static void fuse_readdir_common(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3733 off_t off, struct fuse_file_info *llfi,
+
3734 enum fuse_readdir_flags flags)
+
3735{
+
3736 struct fuse *f = req_fuse_prepare(req);
+
3737 struct fuse_file_info fi;
+
3738 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3739 int err;
+
3740
+
3741 pthread_mutex_lock(&dh->lock);
+
3742 /* According to SUS, directory contents need to be refreshed on
+
3743 rewinddir() */
+
3744 if (!off)
+
3745 dh->filled = 0;
+
3746
+
3747 if (!dh->filled) {
+
3748 err = readdir_fill(f, req, ino, size, off, dh, &fi, flags);
+
3749 if (err) {
+
3750 reply_err(req, err);
+
3751 goto out;
+
3752 }
+
3753 }
+
3754 if (dh->filled) {
+
3755 dh->needlen = size;
+
3756 err = readdir_fill_from_list(req, dh, off, flags);
+
3757 if (err) {
+
3758 reply_err(req, err);
+
3759 goto out;
+
3760 }
+
3761 }
+
3762 fuse_reply_buf(req, dh->contents, dh->len);
+
3763out:
+
3764 pthread_mutex_unlock(&dh->lock);
+
3765}
+
3766
+
3767static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3768 off_t off, struct fuse_file_info *llfi)
+
3769{
+
3770 fuse_readdir_common(req, ino, size, off, llfi, 0);
+
3771}
+
3772
+
3773static void fuse_lib_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3774 off_t off, struct fuse_file_info *llfi)
+
3775{
+
3776 fuse_readdir_common(req, ino, size, off, llfi, FUSE_READDIR_PLUS);
+
3777}
+
3778
+
3779static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino,
+
3780 struct fuse_file_info *llfi)
+
3781{
+
3782 struct fuse *f = req_fuse_prepare(req);
+
3783 struct fuse_intr_data d;
+
3784 struct fuse_file_info fi;
+
3785 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3786 char *path;
+
3787
+
3788 get_path_nullok(f, ino, &path);
+
3789
+
3790 fuse_prepare_interrupt(f, req, &d);
+
3791 fuse_fs_releasedir(f->fs, path, &fi);
+
3792 fuse_finish_interrupt(f, req, &d);
+
3793 free_path(f, ino, path);
+
3794
+
3795 pthread_mutex_lock(&dh->lock);
+
3796 pthread_mutex_unlock(&dh->lock);
+
3797 pthread_mutex_destroy(&dh->lock);
+
3798 free_direntries(dh->first);
+
3799 free(dh->contents);
+
3800 free(dh);
+
3801 reply_err(req, 0);
+
3802}
+
3803
+
3804static void fuse_lib_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3805 struct fuse_file_info *llfi)
+
3806{
+
3807 struct fuse *f = req_fuse_prepare(req);
+
3808 struct fuse_file_info fi;
+
3809 char *path;
+
3810 int err;
+
3811
+
3812 get_dirhandle(llfi, &fi);
+
3813
+
3814 err = get_path_nullok(f, ino, &path);
+
3815 if (!err) {
+
3816 struct fuse_intr_data d;
+
3817 fuse_prepare_interrupt(f, req, &d);
+
3818 err = fuse_fs_fsyncdir(f->fs, path, datasync, &fi);
+
3819 fuse_finish_interrupt(f, req, &d);
+
3820 free_path(f, ino, path);
+
3821 }
+
3822 reply_err(req, err);
+
3823}
+
3824
+
3825static void fuse_lib_statfs(fuse_req_t req, fuse_ino_t ino)
+
3826{
+
3827 struct fuse *f = req_fuse_prepare(req);
+
3828 struct statvfs buf;
+
3829 char *path = NULL;
+
3830 int err = 0;
+
3831
+
3832 memset(&buf, 0, sizeof(buf));
+
3833 if (ino)
+
3834 err = get_path(f, ino, &path);
+
3835
+
3836 if (!err) {
+
3837 struct fuse_intr_data d;
+
3838 fuse_prepare_interrupt(f, req, &d);
+
3839 err = fuse_fs_statfs(f->fs, path ? path : "/", &buf);
+
3840 fuse_finish_interrupt(f, req, &d);
+
3841 free_path(f, ino, path);
+
3842 }
+
3843
+
3844 if (!err)
+
3845 fuse_reply_statfs(req, &buf);
+
3846 else
+
3847 reply_err(req, err);
+
3848}
+
3849
+
3850static void fuse_lib_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3851 const char *value, size_t size, int flags)
+
3852{
+
3853 struct fuse *f = req_fuse_prepare(req);
+
3854 char *path;
+
3855 int err;
+
3856
+
3857 err = get_path(f, ino, &path);
+
3858 if (!err) {
+
3859 struct fuse_intr_data d;
+
3860 fuse_prepare_interrupt(f, req, &d);
+
3861 err = fuse_fs_setxattr(f->fs, path, name, value, size, flags);
+
3862 fuse_finish_interrupt(f, req, &d);
+
3863 free_path(f, ino, path);
+
3864 }
+
3865 reply_err(req, err);
+
3866}
+
3867
+
3868static int common_getxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3869 const char *name, char *value, size_t size)
+
3870{
+
3871 int err;
+
3872 char *path;
+
3873
+
3874 err = get_path(f, ino, &path);
+
3875 if (!err) {
+
3876 struct fuse_intr_data d;
+
3877 fuse_prepare_interrupt(f, req, &d);
+
3878 err = fuse_fs_getxattr(f->fs, path, name, value, size);
+
3879 fuse_finish_interrupt(f, req, &d);
+
3880 free_path(f, ino, path);
+
3881 }
+
3882 return err;
+
3883}
+
3884
+
3885static void fuse_lib_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3886 size_t size)
+
3887{
+
3888 struct fuse *f = req_fuse_prepare(req);
+
3889 int res;
+
3890
+
3891 if (size) {
+
3892 char *value = (char *) malloc(size);
+
3893 if (value == NULL) {
+
3894 reply_err(req, -ENOMEM);
+
3895 return;
+
3896 }
+
3897 res = common_getxattr(f, req, ino, name, value, size);
+
3898 if (res > 0)
+
3899 fuse_reply_buf(req, value, res);
+
3900 else
+
3901 reply_err(req, res);
+
3902 free(value);
+
3903 } else {
+
3904 res = common_getxattr(f, req, ino, name, NULL, 0);
+
3905 if (res >= 0)
+
3906 fuse_reply_xattr(req, res);
+
3907 else
+
3908 reply_err(req, res);
+
3909 }
+
3910}
+
3911
+
3912static int common_listxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3913 char *list, size_t size)
+
3914{
+
3915 char *path;
+
3916 int err;
+
3917
+
3918 err = get_path(f, ino, &path);
+
3919 if (!err) {
+
3920 struct fuse_intr_data d;
+
3921 fuse_prepare_interrupt(f, req, &d);
+
3922 err = fuse_fs_listxattr(f->fs, path, list, size);
+
3923 fuse_finish_interrupt(f, req, &d);
+
3924 free_path(f, ino, path);
+
3925 }
+
3926 return err;
+
3927}
+
3928
+
3929static void fuse_lib_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
3930{
+
3931 struct fuse *f = req_fuse_prepare(req);
+
3932 int res;
+
3933
+
3934 if (size) {
+
3935 char *list = (char *) malloc(size);
+
3936 if (list == NULL) {
+
3937 reply_err(req, -ENOMEM);
+
3938 return;
+
3939 }
+
3940 res = common_listxattr(f, req, ino, list, size);
+
3941 if (res > 0)
+
3942 fuse_reply_buf(req, list, res);
+
3943 else
+
3944 reply_err(req, res);
+
3945 free(list);
+
3946 } else {
+
3947 res = common_listxattr(f, req, ino, NULL, 0);
+
3948 if (res >= 0)
+
3949 fuse_reply_xattr(req, res);
+
3950 else
+
3951 reply_err(req, res);
+
3952 }
+
3953}
+
3954
+
3955static void fuse_lib_removexattr(fuse_req_t req, fuse_ino_t ino,
+
3956 const char *name)
+
3957{
+
3958 struct fuse *f = req_fuse_prepare(req);
+
3959 char *path;
+
3960 int err;
+
3961
+
3962 err = get_path(f, ino, &path);
+
3963 if (!err) {
+
3964 struct fuse_intr_data d;
+
3965 fuse_prepare_interrupt(f, req, &d);
+
3966 err = fuse_fs_removexattr(f->fs, path, name);
+
3967 fuse_finish_interrupt(f, req, &d);
+
3968 free_path(f, ino, path);
+
3969 }
+
3970 reply_err(req, err);
+
3971}
+
3972
+
3973static struct lock *locks_conflict(struct node *node, const struct lock *lock)
+
3974{
+
3975 struct lock *l;
+
3976
+
3977 for (l = node->locks; l; l = l->next)
+
3978 if (l->owner != lock->owner &&
+
3979 lock->start <= l->end && l->start <= lock->end &&
+
3980 (l->type == F_WRLCK || lock->type == F_WRLCK))
+
3981 break;
+
3982
+
3983 return l;
+
3984}
+
3985
+
3986static void delete_lock(struct lock **lockp)
+
3987{
+
3988 struct lock *l = *lockp;
+
3989 *lockp = l->next;
+
3990 free(l);
+
3991}
+
3992
+
3993static void insert_lock(struct lock **pos, struct lock *lock)
+
3994{
+
3995 lock->next = *pos;
+
3996 *pos = lock;
+
3997}
+
3998
+
3999static int locks_insert(struct node *node, struct lock *lock)
+
4000{
+
4001 struct lock **lp;
+
4002 struct lock *newl1 = NULL;
+
4003 struct lock *newl2 = NULL;
+
4004
+
4005 if (lock->type != F_UNLCK || lock->start != 0 ||
+
4006 lock->end != OFFSET_MAX) {
+
4007 newl1 = malloc(sizeof(struct lock));
+
4008 newl2 = malloc(sizeof(struct lock));
+
4009
+
4010 if (!newl1 || !newl2) {
+
4011 free(newl1);
+
4012 free(newl2);
+
4013 return -ENOLCK;
+
4014 }
+
4015 }
+
4016
+
4017 for (lp = &node->locks; *lp;) {
+
4018 struct lock *l = *lp;
+
4019 if (l->owner != lock->owner)
+
4020 goto skip;
+
4021
+
4022 if (lock->type == l->type) {
+
4023 if (l->end < lock->start - 1)
+
4024 goto skip;
+
4025 if (lock->end < l->start - 1)
+
4026 break;
+
4027 if (l->start <= lock->start && lock->end <= l->end)
+
4028 goto out;
+
4029 if (l->start < lock->start)
+
4030 lock->start = l->start;
+
4031 if (lock->end < l->end)
+
4032 lock->end = l->end;
+
4033 goto delete;
+
4034 } else {
+
4035 if (l->end < lock->start)
+
4036 goto skip;
+
4037 if (lock->end < l->start)
+
4038 break;
+
4039 if (lock->start <= l->start && l->end <= lock->end)
+
4040 goto delete;
+
4041 if (l->end <= lock->end) {
+
4042 l->end = lock->start - 1;
+
4043 goto skip;
+
4044 }
+
4045 if (lock->start <= l->start) {
+
4046 l->start = lock->end + 1;
+
4047 break;
+
4048 }
+
4049 *newl2 = *l;
+
4050 newl2->start = lock->end + 1;
+
4051 l->end = lock->start - 1;
+
4052 insert_lock(&l->next, newl2);
+
4053 newl2 = NULL;
+
4054 }
+
4055 skip:
+
4056 lp = &l->next;
+
4057 continue;
+
4058
+
4059 delete:
+
4060 delete_lock(lp);
+
4061 }
+
4062 if (lock->type != F_UNLCK) {
+
4063 *newl1 = *lock;
+
4064 insert_lock(lp, newl1);
+
4065 newl1 = NULL;
+
4066 }
+
4067out:
+
4068 free(newl1);
+
4069 free(newl2);
+
4070 return 0;
+
4071}
+
4072
+
4073static void flock_to_lock(struct flock *flock, struct lock *lock)
+
4074{
+
4075 memset(lock, 0, sizeof(struct lock));
+
4076 lock->type = flock->l_type;
+
4077 lock->start = flock->l_start;
+
4078 lock->end =
+
4079 flock->l_len ? flock->l_start + flock->l_len - 1 : OFFSET_MAX;
+
4080 lock->pid = flock->l_pid;
+
4081}
+
4082
+
4083static void lock_to_flock(struct lock *lock, struct flock *flock)
+
4084{
+
4085 flock->l_type = lock->type;
+
4086 flock->l_start = lock->start;
+
4087 flock->l_len =
+
4088 (lock->end == OFFSET_MAX) ? 0 : lock->end - lock->start + 1;
+
4089 flock->l_pid = lock->pid;
+
4090}
+
4091
+
4092static int fuse_flush_common(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
4093 const char *path, struct fuse_file_info *fi)
+
4094{
+
4095 struct fuse_intr_data d;
+
4096 struct flock lock;
+
4097 struct lock l;
+
4098 int err;
+
4099 int errlock;
+
4100
+
4101 fuse_prepare_interrupt(f, req, &d);
+
4102 memset(&lock, 0, sizeof(lock));
+
4103 lock.l_type = F_UNLCK;
+
4104 lock.l_whence = SEEK_SET;
+
4105 err = fuse_fs_flush(f->fs, path, fi);
+
4106 errlock = fuse_fs_lock(f->fs, path, fi, F_SETLK, &lock);
+
4107 fuse_finish_interrupt(f, req, &d);
+
4108
+
4109 if (errlock != -ENOSYS) {
+
4110 flock_to_lock(&lock, &l);
+
4111 l.owner = fi->lock_owner;
+
4112 pthread_mutex_lock(&f->lock);
+
4113 locks_insert(get_node(f, ino), &l);
+
4114 pthread_mutex_unlock(&f->lock);
+
4115
+
4116 /* if op.lock() is defined FLUSH is needed regardless
+
4117 of op.flush() */
+
4118 if (err == -ENOSYS)
+
4119 err = 0;
+
4120 }
+
4121 return err;
+
4122}
+
4123
+
4124static void fuse_lib_release(fuse_req_t req, fuse_ino_t ino,
+
4125 struct fuse_file_info *fi)
+
4126{
+
4127 struct fuse *f = req_fuse_prepare(req);
+
4128 struct fuse_intr_data d;
+
4129 char *path;
+
4130 int err = 0;
+
4131
+
4132 get_path_nullok(f, ino, &path);
+
4133 if (fi->flush) {
+
4134 err = fuse_flush_common(f, req, ino, path, fi);
+
4135 if (err == -ENOSYS)
+
4136 err = 0;
+
4137 }
+
4138
+
4139 fuse_prepare_interrupt(f, req, &d);
+
4140 fuse_do_release(f, ino, path, fi);
+
4141 fuse_finish_interrupt(f, req, &d);
+
4142 free_path(f, ino, path);
+
4143
+
4144 reply_err(req, err);
+
4145}
+
4146
+
4147static void fuse_lib_flush(fuse_req_t req, fuse_ino_t ino,
+
4148 struct fuse_file_info *fi)
+
4149{
+
4150 struct fuse *f = req_fuse_prepare(req);
+
4151 char *path;
+
4152 int err;
+
4153
+
4154 get_path_nullok(f, ino, &path);
+
4155 err = fuse_flush_common(f, req, ino, path, fi);
+
4156 free_path(f, ino, path);
+
4157
+
4158 reply_err(req, err);
+
4159}
+
4160
+
4161static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino,
+
4162 struct fuse_file_info *fi, struct flock *lock,
+
4163 int cmd)
+
4164{
+
4165 struct fuse *f = req_fuse_prepare(req);
+
4166 char *path;
+
4167 int err;
+
4168
+
4169 err = get_path_nullok(f, ino, &path);
+
4170 if (!err) {
+
4171 struct fuse_intr_data d;
+
4172 fuse_prepare_interrupt(f, req, &d);
+
4173 err = fuse_fs_lock(f->fs, path, fi, cmd, lock);
+
4174 fuse_finish_interrupt(f, req, &d);
+
4175 free_path(f, ino, path);
+
4176 }
+
4177 return err;
+
4178}
+
4179
+
4180static void fuse_lib_getlk(fuse_req_t req, fuse_ino_t ino,
+
4181 struct fuse_file_info *fi, struct flock *lock)
+
4182{
+
4183 int err;
+
4184 struct lock l;
+
4185 struct lock *conflict;
+
4186 struct fuse *f = req_fuse(req);
+
4187
+
4188 flock_to_lock(lock, &l);
+
4189 l.owner = fi->lock_owner;
+
4190 pthread_mutex_lock(&f->lock);
+
4191 conflict = locks_conflict(get_node(f, ino), &l);
+
4192 if (conflict)
+
4193 lock_to_flock(conflict, lock);
+
4194 pthread_mutex_unlock(&f->lock);
+
4195 if (!conflict)
+
4196 err = fuse_lock_common(req, ino, fi, lock, F_GETLK);
+
4197 else
+
4198 err = 0;
+
4199
+
4200 if (!err)
+
4201 fuse_reply_lock(req, lock);
+
4202 else
+
4203 reply_err(req, err);
+
4204}
+
4205
+
4206static void fuse_lib_setlk(fuse_req_t req, fuse_ino_t ino,
+
4207 struct fuse_file_info *fi, struct flock *lock,
+
4208 int sleep)
+
4209{
+
4210 int err = fuse_lock_common(req, ino, fi, lock,
+
4211 sleep ? F_SETLKW : F_SETLK);
+
4212 if (!err) {
+
4213 struct fuse *f = req_fuse(req);
+
4214 struct lock l;
+
4215 flock_to_lock(lock, &l);
+
4216 l.owner = fi->lock_owner;
+
4217 pthread_mutex_lock(&f->lock);
+
4218 locks_insert(get_node(f, ino), &l);
+
4219 pthread_mutex_unlock(&f->lock);
+
4220 }
+
4221 reply_err(req, err);
+
4222}
+
4223
+
4224static void fuse_lib_flock(fuse_req_t req, fuse_ino_t ino,
+
4225 struct fuse_file_info *fi, int op)
+
4226{
+
4227 struct fuse *f = req_fuse_prepare(req);
+
4228 char *path;
+
4229 int err;
+
4230
+
4231 err = get_path_nullok(f, ino, &path);
+
4232 if (err == 0) {
+
4233 struct fuse_intr_data d;
+
4234 fuse_prepare_interrupt(f, req, &d);
+
4235 err = fuse_fs_flock(f->fs, path, fi, op);
+
4236 fuse_finish_interrupt(f, req, &d);
+
4237 free_path(f, ino, path);
+
4238 }
+
4239 reply_err(req, err);
+
4240}
+
4241
+
4242static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
4243 uint64_t idx)
+
4244{
+
4245 struct fuse *f = req_fuse_prepare(req);
+
4246 struct fuse_intr_data d;
+
4247 char *path;
+
4248 int err;
+
4249
+
4250 err = get_path(f, ino, &path);
+
4251 if (!err) {
+
4252 fuse_prepare_interrupt(f, req, &d);
+
4253 err = fuse_fs_bmap(f->fs, path, blocksize, &idx);
+
4254 fuse_finish_interrupt(f, req, &d);
+
4255 free_path(f, ino, path);
+
4256 }
+
4257 if (!err)
+
4258 fuse_reply_bmap(req, idx);
+
4259 else
+
4260 reply_err(req, err);
+
4261}
+
4262
+
4263static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
4264 void *arg, struct fuse_file_info *llfi,
+
4265 unsigned int flags, const void *in_buf,
+
4266 size_t in_bufsz, size_t out_bufsz)
+
4267{
+
4268 struct fuse *f = req_fuse_prepare(req);
+
4269 struct fuse_intr_data d;
+
4270 struct fuse_file_info fi;
+
4271 char *path, *out_buf = NULL;
+
4272 int err;
+
4273
+
4274 err = -EPERM;
+
4275 if (flags & FUSE_IOCTL_UNRESTRICTED)
+
4276 goto err;
+
4277
+
4278 if (flags & FUSE_IOCTL_DIR)
+
4279 get_dirhandle(llfi, &fi);
+
4280 else
+
4281 fi = *llfi;
+
4282
+
4283 if (out_bufsz) {
+
4284 err = -ENOMEM;
+
4285 out_buf = malloc(out_bufsz);
+
4286 if (!out_buf)
+
4287 goto err;
+
4288 }
+
4289
+
4290 assert(!in_bufsz || !out_bufsz || in_bufsz == out_bufsz);
+
4291 if (out_buf && in_bufsz)
+
4292 memcpy(out_buf, in_buf, in_bufsz);
+
4293
+
4294 err = get_path_nullok(f, ino, &path);
+
4295 if (err)
+
4296 goto err;
+
4297
+
4298 fuse_prepare_interrupt(f, req, &d);
+
4299
+
4300 err = fuse_fs_ioctl(f->fs, path, cmd, arg, &fi, flags,
+
4301 out_buf ? out_buf : (void *)in_buf);
+
4302
+
4303 fuse_finish_interrupt(f, req, &d);
+
4304 free_path(f, ino, path);
+
4305
+
4306 if (err < 0)
+
4307 goto err;
+
4308 fuse_reply_ioctl(req, err, out_buf, out_bufsz);
+
4309 goto out;
+
4310err:
+
4311 reply_err(req, err);
+
4312out:
+
4313 free(out_buf);
+
4314}
+
4315
+
4316static void fuse_lib_poll(fuse_req_t req, fuse_ino_t ino,
+
4317 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
4318{
+
4319 struct fuse *f = req_fuse_prepare(req);
+
4320 struct fuse_intr_data d;
+
4321 char *path;
+
4322 int err;
+
4323 unsigned revents = 0;
+
4324
+
4325 err = get_path_nullok(f, ino, &path);
+
4326 if (!err) {
+
4327 fuse_prepare_interrupt(f, req, &d);
+
4328 err = fuse_fs_poll(f->fs, path, fi, ph, &revents);
+
4329 fuse_finish_interrupt(f, req, &d);
+
4330 free_path(f, ino, path);
+
4331 }
+
4332 if (!err)
+
4333 fuse_reply_poll(req, revents);
+
4334 else
+
4335 reply_err(req, err);
+
4336}
+
4337
+
4338static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
4339 off_t offset, off_t length, struct fuse_file_info *fi)
+
4340{
+
4341 struct fuse *f = req_fuse_prepare(req);
+
4342 struct fuse_intr_data d;
+
4343 char *path;
+
4344 int err;
+
4345
+
4346 err = get_path_nullok(f, ino, &path);
+
4347 if (!err) {
+
4348 fuse_prepare_interrupt(f, req, &d);
+
4349 err = fuse_fs_fallocate(f->fs, path, mode, offset, length, fi);
+
4350 fuse_finish_interrupt(f, req, &d);
+
4351 free_path(f, ino, path);
+
4352 }
+
4353 reply_err(req, err);
+
4354}
+
4355
+
4356static void fuse_lib_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in,
+
4357 off_t off_in, struct fuse_file_info *fi_in,
+
4358 fuse_ino_t nodeid_out, off_t off_out,
+
4359 struct fuse_file_info *fi_out, size_t len,
+
4360 int flags)
+
4361{
+
4362 struct fuse *f = req_fuse_prepare(req);
+
4363 struct fuse_intr_data d;
+
4364 char *path_in, *path_out;
+
4365 int err;
+
4366 ssize_t res;
+
4367
+
4368 err = get_path_nullok(f, nodeid_in, &path_in);
+
4369 if (err) {
+
4370 reply_err(req, err);
+
4371 return;
+
4372 }
+
4373
+
4374 err = get_path_nullok(f, nodeid_out, &path_out);
+
4375 if (err) {
+
4376 free_path(f, nodeid_in, path_in);
+
4377 reply_err(req, err);
+
4378 return;
+
4379 }
+
4380
+
4381 fuse_prepare_interrupt(f, req, &d);
+
4382 res = fuse_fs_copy_file_range(f->fs, path_in, fi_in, off_in, path_out,
+
4383 fi_out, off_out, len, flags);
+
4384 fuse_finish_interrupt(f, req, &d);
+
4385
+
4386 if (res >= 0)
+
4387 fuse_reply_write(req, res);
+
4388 else
+
4389 reply_err(req, res);
+
4390
+
4391 free_path(f, nodeid_in, path_in);
+
4392 free_path(f, nodeid_out, path_out);
+
4393}
+
4394
+
4395static void fuse_lib_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
4396 struct fuse_file_info *fi)
+
4397{
+
4398 struct fuse *f = req_fuse_prepare(req);
+
4399 struct fuse_intr_data d;
+
4400 char *path;
+
4401 int err;
+
4402 off_t res;
+
4403
+
4404 err = get_path(f, ino, &path);
+
4405 if (err) {
+
4406 reply_err(req, err);
+
4407 return;
+
4408 }
+
4409
+
4410 fuse_prepare_interrupt(f, req, &d);
+
4411 res = fuse_fs_lseek(f->fs, path, off, whence, fi);
+
4412 fuse_finish_interrupt(f, req, &d);
+
4413 free_path(f, ino, path);
+
4414 if (res >= 0)
+
4415 fuse_reply_lseek(req, res);
+
4416 else
+
4417 reply_err(req, res);
+
4418}
+
4419
+
4420#ifdef HAVE_STATX
+
4421static void fuse_lib_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
+
4422 struct fuse_file_info *fi)
+
4423{
+
4424 struct fuse *f = req_fuse_prepare(req);
+
4425 struct statx stxbuf;
+
4426 char *path;
+
4427 int err;
+
4428
+
4429 memset(&stxbuf, 0, sizeof(stxbuf));
+
4430
+
4431 if (fi != NULL)
+
4432 err = get_path_nullok(f, ino, &path);
+
4433 else
+
4434 err = get_path(f, ino, &path);
+
4435
+
4436 if (!err) {
+
4437 struct fuse_intr_data d;
+
4438
+
4439 if (!path)
+
4440 flags |= AT_EMPTY_PATH;
+
4441 fuse_prepare_interrupt(f, req, &d);
+
4442 err = fuse_fs_statx(f->fs, path, flags, mask, &stxbuf, fi);
+
4443 fuse_finish_interrupt(f, req, &d);
+
4444 free_path(f, ino, path);
+
4445 }
+
4446 if (!err) {
+
4447 struct node *node;
+
4448
+
4449 pthread_mutex_lock(&f->lock);
+
4450 node = get_node(f, ino);
+
4451 if (node->is_hidden && stxbuf.stx_nlink > 0)
+
4452 stxbuf.stx_nlink--;
+
4453 if (f->conf.auto_cache) {
+
4454 struct stat stbuf;
+
4455
+
4456 stbuf.st_mtime = stxbuf.stx_mtime.tv_nsec;
+
4457 ST_MTIM_NSEC(&stbuf) = stxbuf.stx_mtime.tv_nsec;
+
4458 stbuf.st_size = stxbuf.stx_size;
+
4459 update_stat(node, &stbuf);
+
4460 }
+
4461 pthread_mutex_unlock(&f->lock);
+
4462 set_statx(f, ino, &stxbuf);
+
4463 fuse_reply_statx(req, 0, &stxbuf, f->conf.attr_timeout);
+
4464 } else
+
4465 reply_err(req, err);
+
4466}
+
4467#endif
+
4468
+
4469static int clean_delay(struct fuse *f)
+
4470{
+
4471 /*
+
4472 * This is calculating the delay between clean runs. To
+
4473 * reduce the number of cleans we are doing them 10 times
+
4474 * within the remember window.
+
4475 */
+
4476 int min_sleep = 60;
+
4477 int max_sleep = 3600;
+
4478 int sleep_time = f->conf.remember / 10;
+
4479
+
4480 if (sleep_time > max_sleep)
+
4481 return max_sleep;
+
4482 if (sleep_time < min_sleep)
+
4483 return min_sleep;
+
4484 return sleep_time;
+
4485}
+
4486
+
4487int fuse_clean_cache(struct fuse *f)
+
4488{
+
4489 struct node_lru *lnode;
+
4490 struct list_head *curr, *next;
+
4491 struct node *node;
+
4492 struct timespec now;
+
4493
+
4494 pthread_mutex_lock(&f->lock);
+
4495
+
4496 curr_time(&now);
+
4497
+
4498 for (curr = f->lru_table.next; curr != &f->lru_table; curr = next) {
+
4499 double age;
+
4500
+
4501 next = curr->next;
+
4502 lnode = list_entry(curr, struct node_lru, lru);
+
4503 node = &lnode->node;
+
4504
+
4505 age = diff_timespec(&now, &lnode->forget_time);
+
4506 if (age <= f->conf.remember)
+
4507 break;
+
4508
+
4509 assert(node->nlookup == 1);
+
4510
+
4511 /* Don't forget active directories */
+
4512 if (node->refctr > 1)
+
4513 continue;
+
4514
+
4515 node->nlookup = 0;
+
4516 unhash_name(f, node);
+
4517 unref_node(f, node);
+
4518 }
+
4519 pthread_mutex_unlock(&f->lock);
+
4520
+
4521 return clean_delay(f);
+
4522}
+
4523
+
4524static struct fuse_lowlevel_ops fuse_path_ops = {
+
4525 .init = fuse_lib_init,
+
4526 .destroy = fuse_lib_destroy,
+
4527 .lookup = fuse_lib_lookup,
+
4528 .forget = fuse_lib_forget,
+
4529 .forget_multi = fuse_lib_forget_multi,
+
4530 .getattr = fuse_lib_getattr,
+
4531 .setattr = fuse_lib_setattr,
+
4532 .access = fuse_lib_access,
+
4533 .readlink = fuse_lib_readlink,
+
4534 .mknod = fuse_lib_mknod,
+
4535 .mkdir = fuse_lib_mkdir,
+
4536 .unlink = fuse_lib_unlink,
+
4537 .rmdir = fuse_lib_rmdir,
+
4538 .symlink = fuse_lib_symlink,
+
4539 .rename = fuse_lib_rename,
+
4540 .link = fuse_lib_link,
+
4541 .create = fuse_lib_create,
+
4542 .open = fuse_lib_open,
+
4543 .read = fuse_lib_read,
+
4544 .write_buf = fuse_lib_write_buf,
+
4545 .flush = fuse_lib_flush,
+
4546 .release = fuse_lib_release,
+
4547 .fsync = fuse_lib_fsync,
+
4548 .opendir = fuse_lib_opendir,
+
4549 .readdir = fuse_lib_readdir,
+
4550 .readdirplus = fuse_lib_readdirplus,
+
4551 .releasedir = fuse_lib_releasedir,
+
4552 .fsyncdir = fuse_lib_fsyncdir,
+
4553 .statfs = fuse_lib_statfs,
+
4554 .setxattr = fuse_lib_setxattr,
+
4555 .getxattr = fuse_lib_getxattr,
+
4556 .listxattr = fuse_lib_listxattr,
+
4557 .removexattr = fuse_lib_removexattr,
+
4558 .getlk = fuse_lib_getlk,
+
4559 .setlk = fuse_lib_setlk,
+
4560 .flock = fuse_lib_flock,
+
4561 .bmap = fuse_lib_bmap,
+
4562 .ioctl = fuse_lib_ioctl,
+
4563 .poll = fuse_lib_poll,
+
4564 .fallocate = fuse_lib_fallocate,
+
4565 .copy_file_range = fuse_lib_copy_file_range,
+
4566 .lseek = fuse_lib_lseek,
+
4567#ifdef HAVE_STATX
+
4568 .statx = fuse_lib_statx,
+
4569#endif
+
4570};
+
4571
+
4572int fuse_notify_poll(struct fuse_pollhandle *ph)
+
4573{
+
4574 return fuse_lowlevel_notify_poll(ph);
+
4575}
+
4576
+
4577struct fuse_session *fuse_get_session(struct fuse *f)
+
4578{
+
4579 return f->se;
+
4580}
+
4581
+
4582static int fuse_session_loop_remember(struct fuse *f)
+
4583{
+
4584 struct fuse_session *se = f->se;
+
4585 int res = 0;
+
4586 struct timespec now;
+
4587 time_t next_clean;
+
4588 struct pollfd fds = {
+
4589 .fd = se->fd,
+
4590 .events = POLLIN
+
4591 };
+
4592 struct fuse_buf fbuf = {
+
4593 .mem = NULL,
+
4594 };
+
4595
+
4596 curr_time(&now);
+
4597 next_clean = now.tv_sec;
+
4598 while (!fuse_session_exited(se)) {
+
4599 unsigned timeout;
+
4600
+
4601 curr_time(&now);
+
4602 if (now.tv_sec < next_clean)
+
4603 timeout = next_clean - now.tv_sec;
+
4604 else
+
4605 timeout = 0;
+
4606
+
4607 res = poll(&fds, 1, timeout * 1000);
+
4608 if (res == -1) {
+
4609 if (errno == EINTR)
+
4610 continue;
+
4611 else
+
4612 break;
+
4613 } else if (res > 0) {
+
4614 res = fuse_session_receive_buf_internal(se, &fbuf,
+
4615 NULL);
+
4616 if (res == -EINTR)
+
4617 continue;
+
4618 if (res <= 0)
+
4619 break;
+
4620
+
4621 fuse_session_process_buf_internal(se, &fbuf, NULL);
+
4622 } else {
+
4623 timeout = fuse_clean_cache(f);
+
4624 curr_time(&now);
+
4625 next_clean = now.tv_sec + timeout;
+
4626 }
+
4627 }
+
4628
+
4629 fuse_buf_free(&fbuf);
+
4630 return res < 0 ? -1 : 0;
+
4631}
+
4632
+
4633int fuse_loop(struct fuse *f)
+
4634{
+
4635 if (!f)
+
4636 return -1;
+
4637
+
4638 if (lru_enabled(f))
+
4639 return fuse_session_loop_remember(f);
+
4640
+
4641 return fuse_session_loop(f->se);
+
4642}
+
4643
+
4644FUSE_SYMVER("fuse_loop_mt_312", "fuse_loop_mt@@FUSE_3.12")
+
4645int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config)
+
4646{
+
4647 if (f == NULL)
+
4648 return -1;
+
4649
+
4650 int res = fuse_start_cleanup_thread(f);
+
4651 if (res)
+
4652 return -1;
+
4653
+
4654 res = fuse_session_loop_mt_312(fuse_get_session(f), config);
+ +
4656 return res;
+
4657}
+
4658
+
4659int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1);
+
4660FUSE_SYMVER("fuse_loop_mt_32", "fuse_loop_mt@FUSE_3.2")
+
4661int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1)
+
4662{
+
4663 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4664 if (config == NULL)
+
4665 return ENOMEM;
+
4666
+
4667 fuse_loop_cfg_convert(config, config_v1);
+
4668
+
4669 int res = fuse_loop_mt_312(f, config);
+
4670
+
4671 fuse_loop_cfg_destroy(config);
+
4672
+
4673 return res;
+
4674}
+
4675
+
4676int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
4677FUSE_SYMVER("fuse_loop_mt_31", "fuse_loop_mt@FUSE_3.0")
+
4678int fuse_loop_mt_31(struct fuse *f, int clone_fd)
+
4679{
+
4680 int err;
+
4681 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4682
+
4683 if (config == NULL)
+
4684 return ENOMEM;
+
4685
+
4686 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
4687
+
4688 err = fuse_loop_mt_312(f, config);
+
4689
+
4690 fuse_loop_cfg_destroy(config);
+
4691
+
4692 return err;
+
4693}
+
4694
+
4695void fuse_exit(struct fuse *f)
+
4696{
+
4697 fuse_session_exit(f->se);
+
4698}
+
4699
+
4700struct fuse_context *fuse_get_context(void)
+
4701{
+
4702 struct fuse_context_i *c = fuse_get_context_internal();
+
4703
+
4704 if (c)
+
4705 return &c->ctx;
+
4706 else
+
4707 return NULL;
+
4708}
+
4709
+
4710int fuse_getgroups(int size, gid_t list[])
+
4711{
+
4712 struct fuse_context_i *c = fuse_get_context_internal();
+
4713 if (!c)
+
4714 return -EINVAL;
+
4715
+
4716 return fuse_req_getgroups(c->req, size, list);
+
4717}
+
4718
+
4719int fuse_interrupted(void)
+
4720{
+
4721 struct fuse_context_i *c = fuse_get_context_internal();
+
4722
+
4723 if (c)
+
4724 return fuse_req_interrupted(c->req);
+
4725 else
+
4726 return 0;
+
4727}
+
4728
+
4729int fuse_invalidate_path(struct fuse *f, const char *path) {
+
4730 fuse_ino_t ino;
+
4731 int err = lookup_path_in_cache(f, path, &ino);
+
4732 if (err) {
+
4733 return err;
+
4734 }
+
4735
+
4736 return fuse_lowlevel_notify_inval_inode(f->se, ino, 0, 0);
+
4737}
+
4738
+
4739#define FUSE_LIB_OPT(t, p, v) { t, offsetof(struct fuse_config, p), v }
+
4740
+
4741static const struct fuse_opt fuse_lib_opts[] = {
+ + +
4744 FUSE_LIB_OPT("debug", debug, 1),
+
4745 FUSE_LIB_OPT("-d", debug, 1),
+
4746 FUSE_LIB_OPT("kernel_cache", kernel_cache, 1),
+
4747 FUSE_LIB_OPT("auto_cache", auto_cache, 1),
+
4748 FUSE_LIB_OPT("noauto_cache", auto_cache, 0),
+
4749 FUSE_LIB_OPT("no_rofd_flush", no_rofd_flush, 1),
+
4750 FUSE_LIB_OPT("umask=", set_mode, 1),
+
4751 FUSE_LIB_OPT("umask=%o", umask, 0),
+
4752 FUSE_LIB_OPT("fmask=", set_mode, 1),
+
4753 FUSE_LIB_OPT("fmask=%o", fmask, 0),
+
4754 FUSE_LIB_OPT("dmask=", set_mode, 1),
+
4755 FUSE_LIB_OPT("dmask=%o", dmask, 0),
+
4756 FUSE_LIB_OPT("uid=", set_uid, 1),
+
4757 FUSE_LIB_OPT("uid=%d", uid, 0),
+
4758 FUSE_LIB_OPT("gid=", set_gid, 1),
+
4759 FUSE_LIB_OPT("gid=%d", gid, 0),
+
4760 FUSE_LIB_OPT("entry_timeout=%lf", entry_timeout, 0),
+
4761 FUSE_LIB_OPT("attr_timeout=%lf", attr_timeout, 0),
+
4762 FUSE_LIB_OPT("ac_attr_timeout=%lf", ac_attr_timeout, 0),
+
4763 FUSE_LIB_OPT("ac_attr_timeout=", ac_attr_timeout_set, 1),
+
4764 FUSE_LIB_OPT("negative_timeout=%lf", negative_timeout, 0),
+
4765 FUSE_LIB_OPT("noforget", remember, -1),
+
4766 FUSE_LIB_OPT("remember=%u", remember, 0),
+
4767 FUSE_LIB_OPT("modules=%s", modules, 0),
+
4768 FUSE_LIB_OPT("parallel_direct_write=%d", parallel_direct_writes, 0),
+ +
4770};
+
4771
+
4772static int fuse_lib_opt_proc(void *data, const char *arg, int key,
+
4773 struct fuse_args *outargs)
+
4774{
+
4775 (void) arg; (void) outargs; (void) data; (void) key;
+
4776
+
4777 /* Pass through unknown options */
+
4778 return 1;
+
4779}
+
4780
+
4781
+
4782static const struct fuse_opt fuse_help_opts[] = {
+
4783 FUSE_LIB_OPT("modules=%s", modules, 1),
+
4784 FUSE_OPT_KEY("modules=%s", FUSE_OPT_KEY_KEEP),
+ +
4786};
+
4787
+
4788static void print_module_help(const char *name,
+ +
4790{
+
4791 struct fuse_args a = FUSE_ARGS_INIT(0, NULL);
+
4792 if (fuse_opt_add_arg(&a, "") == -1 ||
+
4793 fuse_opt_add_arg(&a, "-h") == -1)
+
4794 return;
+
4795 printf("\nOptions for %s module:\n", name);
+
4796 (*fac)(&a, NULL);
+ +
4798}
+
4799
+
4800void fuse_lib_help(struct fuse_args *args)
+
4801{
+
4802 /* These are not all options, but only the ones that
+
4803 may be of interest to an end-user */
+
4804 printf(
+
4805" -o kernel_cache cache files in kernel\n"
+
4806" -o [no]auto_cache enable caching based on modification times (off)\n"
+
4807" -o no_rofd_flush disable flushing of read-only fd on close (off)\n"
+
4808" -o umask=M set file permissions (octal)\n"
+
4809" -o fmask=M set file permissions (octal)\n"
+
4810" -o dmask=M set dir permissions (octal)\n"
+
4811" -o uid=N set file owner\n"
+
4812" -o gid=N set file group\n"
+
4813" -o entry_timeout=T cache timeout for names (1.0s)\n"
+
4814" -o negative_timeout=T cache timeout for deleted names (0.0s)\n"
+
4815" -o attr_timeout=T cache timeout for attributes (1.0s)\n"
+
4816" -o ac_attr_timeout=T auto cache timeout for attributes (attr_timeout)\n"
+
4817" -o noforget never forget cached inodes\n"
+
4818" -o remember=T remember cached inodes for T seconds (0s)\n"
+
4819" -o modules=M1[:M2...] names of modules to push onto filesystem stack\n");
+
4820
+
4821
+
4822 /* Print low-level help */
+ +
4824
+
4825 /* Print help for builtin modules */
+
4826 print_module_help("subdir", &fuse_module_subdir_factory);
+
4827#ifdef HAVE_ICONV
+
4828 print_module_help("iconv", &fuse_module_iconv_factory);
+
4829#endif
+
4830
+
4831 /* Parse command line options in case we need to
+
4832 activate more modules */
+
4833 struct fuse_config conf = { .modules = NULL };
+
4834 if (fuse_opt_parse(args, &conf, fuse_help_opts,
+
4835 fuse_lib_opt_proc) == -1
+
4836 || !conf.modules)
+
4837 return;
+
4838
+
4839 char *module;
+
4840 char *next;
+
4841 struct fuse_module *m;
+
4842
+
4843 // Iterate over all modules
+
4844 for (module = conf.modules; module; module = next) {
+
4845 char *p;
+
4846 for (p = module; *p && *p != ':'; p++);
+
4847 next = *p ? p + 1 : NULL;
+
4848 *p = '\0';
+
4849
+
4850 m = fuse_get_module(module);
+
4851 if (m)
+
4852 print_module_help(module, &m->factory);
+
4853 }
+
4854}
+
4855
+
4856static int fuse_init_intr_signal(int signum, int *installed)
+
4857{
+
4858 struct sigaction old_sa;
+
4859
+
4860 if (sigaction(signum, NULL, &old_sa) == -1) {
+
4861 perror("fuse: cannot get old signal handler");
+
4862 return -1;
+
4863 }
+
4864
+
4865 if (old_sa.sa_handler == SIG_DFL) {
+
4866 struct sigaction sa;
+
4867
+
4868 memset(&sa, 0, sizeof(struct sigaction));
+
4869 sa.sa_handler = fuse_intr_sighandler;
+
4870 sigemptyset(&sa.sa_mask);
+
4871
+
4872 if (sigaction(signum, &sa, NULL) == -1) {
+
4873 perror("fuse: cannot set interrupt signal handler");
+
4874 return -1;
+
4875 }
+
4876 *installed = 1;
+
4877 }
+
4878 return 0;
+
4879}
+
4880
+
4881static void fuse_restore_intr_signal(int signum)
+
4882{
+
4883 struct sigaction sa;
+
4884
+
4885 memset(&sa, 0, sizeof(struct sigaction));
+
4886 sa.sa_handler = SIG_DFL;
+
4887 sigaction(signum, &sa, NULL);
+
4888}
+
4889
+
4890
+
4891static int fuse_push_module(struct fuse *f, const char *module,
+
4892 struct fuse_args *args)
+
4893{
+
4894 struct fuse_fs *fs[2] = { f->fs, NULL };
+
4895 struct fuse_fs *newfs;
+
4896 struct fuse_module *m = fuse_get_module(module);
+
4897
+
4898 if (!m)
+
4899 return -1;
+
4900
+
4901 newfs = m->factory(args, fs);
+
4902 if (!newfs) {
+
4903 fuse_put_module(m);
+
4904 return -1;
+
4905 }
+
4906 f->fs = newfs;
+
4907 return 0;
+
4908}
+
4909
+
4910struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
4911 void *user_data)
+
4912{
+
4913 struct fuse_fs *fs;
+
4914
+
4915 if (sizeof(struct fuse_operations) < op_size) {
+
4916 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not not work\n");
+
4917 op_size = sizeof(struct fuse_operations);
+
4918 }
+
4919
+
4920 fs = (struct fuse_fs *) calloc(1, sizeof(struct fuse_fs));
+
4921 if (!fs) {
+
4922 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse_fs object\n");
+
4923 return NULL;
+
4924 }
+
4925
+
4926 fs->user_data = user_data;
+
4927 if (op)
+
4928 memcpy(&fs->op, op, op_size);
+
4929 return fs;
+
4930}
+
4931
+
4932static int node_table_init(struct node_table *t)
+
4933{
+
4934 t->size = NODE_TABLE_MIN_SIZE;
+
4935 t->array = (struct node **) calloc(1, sizeof(struct node *) * t->size);
+
4936 if (t->array == NULL) {
+
4937 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
4938 return -1;
+
4939 }
+
4940 t->use = 0;
+
4941 t->split = 0;
+
4942
+
4943 return 0;
+
4944}
+
4945
+
4946static void *fuse_prune_nodes(void *fuse)
+
4947{
+
4948 struct fuse *f = fuse;
+
4949 int sleep_time;
+
4950
+
4951 fuse_set_thread_name("fuse_prune_nodes");
+
4952
+
4953 while(1) {
+
4954 sleep_time = fuse_clean_cache(f);
+
4955 sleep(sleep_time);
+
4956 }
+
4957 return NULL;
+
4958}
+
4959
+
4960int fuse_start_cleanup_thread(struct fuse *f)
+
4961{
+
4962 if (lru_enabled(f))
+
4963 return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f);
+
4964
+
4965 return 0;
+
4966}
+
4967
+
4968void fuse_stop_cleanup_thread(struct fuse *f)
+
4969{
+
4970 if (lru_enabled(f)) {
+
4971 pthread_mutex_lock(&f->lock);
+
4972 pthread_cancel(f->prune_thread);
+
4973 pthread_mutex_unlock(&f->lock);
+
4974 pthread_join(f->prune_thread, NULL);
+
4975 }
+
4976}
+
4977
+
4978/*
+
4979 * Not supposed to be called directly, but supposed to be called
+
4980 * through the fuse_new macro
+
4981 */
+
4982struct fuse *_fuse_new_31(struct fuse_args *args,
+
4983 const struct fuse_operations *op, size_t op_size,
+
4984 struct libfuse_version *version, void *user_data)
+
4985{
+
4986 struct fuse *f;
+
4987 struct node *root;
+
4988 struct fuse_fs *fs;
+
4989 struct fuse_lowlevel_ops llop = fuse_path_ops;
+
4990
+
4991 f = (struct fuse *) calloc(1, sizeof(struct fuse));
+
4992 if (f == NULL) {
+
4993 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
4994 goto out;
+
4995 }
+
4996
+
4997 f->conf.entry_timeout = 1.0;
+
4998 f->conf.attr_timeout = 1.0;
+
4999 f->conf.negative_timeout = 0.0;
+
5000 f->conf.intr_signal = FUSE_DEFAULT_INTR_SIGNAL;
+
5001
+
5002 /* Parse options */
+
5003 if (fuse_opt_parse(args, &f->conf, fuse_lib_opts,
+
5004 fuse_lib_opt_proc) == -1)
+
5005 goto out_free;
+
5006
+
5007 pthread_mutex_lock(&fuse_context_lock);
+
5008 static int builtin_modules_registered = 0;
+
5009 /* Have the builtin modules already been registered? */
+
5010 if (builtin_modules_registered == 0) {
+
5011 /* If not, register them. */
+
5012 fuse_register_module("subdir", fuse_module_subdir_factory, NULL);
+
5013#ifdef HAVE_ICONV
+
5014 fuse_register_module("iconv", fuse_module_iconv_factory, NULL);
+
5015#endif
+
5016 builtin_modules_registered= 1;
+
5017 }
+
5018 pthread_mutex_unlock(&fuse_context_lock);
+
5019
+
5020 if (fuse_create_context_key() == -1)
+
5021 goto out_free;
+
5022
+
5023 fs = fuse_fs_new(op, op_size, user_data);
+
5024 if (!fs)
+
5025 goto out_delete_context_key;
+
5026
+
5027 f->fs = fs;
+
5028
+
5029 /* Oh f**k, this is ugly! */
+
5030 if (!fs->op.lock) {
+
5031 llop.getlk = NULL;
+
5032 llop.setlk = NULL;
+
5033 }
+
5034
+
5035 f->pagesize = getpagesize();
+
5036 init_list_head(&f->partial_slabs);
+
5037 init_list_head(&f->full_slabs);
+
5038 init_list_head(&f->lru_table);
+
5039
+
5040 if (f->conf.modules) {
+
5041 char *module;
+
5042 char *next;
+
5043
+
5044 for (module = f->conf.modules; module; module = next) {
+
5045 char *p;
+
5046 for (p = module; *p && *p != ':'; p++);
+
5047 next = *p ? p + 1 : NULL;
+
5048 *p = '\0';
+
5049 if (module[0] &&
+
5050 fuse_push_module(f, module, args) == -1)
+
5051 goto out_free_fs;
+
5052 }
+
5053 }
+
5054
+
5055 if (!f->conf.ac_attr_timeout_set)
+
5056 f->conf.ac_attr_timeout = f->conf.attr_timeout;
+
5057
+
5058#if defined(__FreeBSD__) || defined(__NetBSD__)
+
5059 /*
+
5060 * In FreeBSD, we always use these settings as inode numbers
+
5061 * are needed to make getcwd(3) work.
+
5062 */
+
5063 f->conf.readdir_ino = 1;
+
5064#endif
+
5065
+
5066 /* not declared globally, to restrict usage of this function */
+
5067 struct fuse_session *fuse_session_new_versioned(
+
5068 struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
5069 size_t op_size, struct libfuse_version *version,
+
5070 void *userdata);
+
5071 f->se = fuse_session_new_versioned(args, &llop, sizeof(llop), version,
+
5072 f);
+
5073 if (f->se == NULL)
+
5074 goto out_free_fs;
+
5075
+
5076 /* Trace topmost layer by default */
+
5077 f->fs->debug = f->conf.debug;
+
5078 f->ctr = 0;
+
5079 f->generation = 0;
+
5080 if (node_table_init(&f->name_table) == -1)
+
5081 goto out_free_session;
+
5082
+
5083 if (node_table_init(&f->id_table) == -1)
+
5084 goto out_free_name_table;
+
5085
+
5086 pthread_mutex_init(&f->lock, NULL);
+
5087
+
5088 root = alloc_node(f);
+
5089 if (root == NULL) {
+
5090 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
5091 goto out_free_id_table;
+
5092 }
+
5093 if (lru_enabled(f)) {
+
5094 struct node_lru *lnode = node_lru(root);
+
5095 init_list_head(&lnode->lru);
+
5096 }
+
5097
+
5098 strcpy(root->inline_name, "/");
+
5099 root->name = root->inline_name;
+
5100 root->parent = NULL;
+
5101 root->nodeid = FUSE_ROOT_ID;
+
5102 inc_nlookup(root);
+
5103 hash_id(f, root);
+
5104
+
5105 return f;
+
5106
+
5107out_free_id_table:
+
5108 free(f->id_table.array);
+
5109out_free_name_table:
+
5110 free(f->name_table.array);
+
5111out_free_session:
+
5112 fuse_session_destroy(f->se);
+
5113out_free_fs:
+
5114 free(f->fs);
+
5115 free(f->conf.modules);
+
5116out_delete_context_key:
+
5117 fuse_delete_context_key();
+
5118out_free:
+
5119 free(f);
+
5120out:
+
5121 return NULL;
+
5122}
+
5123
+
5124/* Emulates 3.0-style fuse_new(), which processes --help */
+
5125FUSE_SYMVER("_fuse_new_30", "_fuse_new@FUSE_3.0")
+
5126struct fuse *_fuse_new_30(struct fuse_args *args,
+
5127 const struct fuse_operations *op,
+
5128 size_t op_size,
+
5129 struct libfuse_version *version,
+
5130 void *user_data)
+
5131{
+
5132 struct fuse_config conf = {0};
+
5133
+
5134 const struct fuse_opt opts[] = {
+
5135 FUSE_LIB_OPT("-h", show_help, 1),
+
5136 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5138 };
+
5139
+
5140 if (fuse_opt_parse(args, &conf, opts,
+
5141 fuse_lib_opt_proc) == -1)
+
5142 return NULL;
+
5143
+
5144 if (conf.show_help) {
+
5145 fuse_lib_help(args);
+
5146 return NULL;
+
5147 } else
+
5148 return _fuse_new_31(args, op, op_size, version, user_data);
+
5149}
+
5150
+
5151/* ABI compat version */
+
5152struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
5153 size_t op_size, void *user_data);
+
5154FUSE_SYMVER("fuse_new_31", "fuse_new@FUSE_3.1")
+
5155struct fuse *fuse_new_31(struct fuse_args *args,
+
5156 const struct fuse_operations *op,
+
5157 size_t op_size, void *user_data)
+
5158{
+
5159 /* unknown version */
+
5160 struct libfuse_version version = { 0 };
+
5161
+
5162 return _fuse_new_31(args, op, op_size, &version, user_data);
+
5163}
+
5164
+
5165/*
+
5166 * ABI compat version
+
5167 * Emulates 3.0-style fuse_new(), which processes --help
+
5168 */
+
5169struct fuse *fuse_new_30(struct fuse_args *args, const struct fuse_operations *op,
+
5170 size_t op_size, void *user_data);
+
5171FUSE_SYMVER("fuse_new_30", "fuse_new@FUSE_3.0")
+
5172struct fuse *fuse_new_30(struct fuse_args *args,
+
5173 const struct fuse_operations *op,
+
5174 size_t op_size, void *user_data)
+
5175{
+
5176 struct fuse_config conf = {0};
+
5177
+
5178 const struct fuse_opt opts[] = {
+
5179 FUSE_LIB_OPT("-h", show_help, 1),
+
5180 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5182 };
+
5183
+
5184 if (fuse_opt_parse(args, &conf, opts,
+
5185 fuse_lib_opt_proc) == -1)
+
5186 return NULL;
+
5187
+
5188 if (conf.show_help) {
+
5189 fuse_lib_help(args);
+
5190 return NULL;
+
5191 } else
+
5192 return fuse_new_31(args, op, op_size, user_data);
+
5193}
+
5194
+
5195
+
5196void fuse_destroy(struct fuse *f)
+
5197{
+
5198 size_t i;
+
5199
+
5200 if (f->conf.intr && f->intr_installed)
+
5201 fuse_restore_intr_signal(f->conf.intr_signal);
+
5202
+
5203 if (f->fs) {
+
5204 fuse_create_context(f);
+
5205
+
5206 for (i = 0; i < f->id_table.size; i++) {
+
5207 struct node *node;
+
5208
+
5209 for (node = f->id_table.array[i]; node != NULL;
+
5210 node = node->id_next) {
+
5211 if (node->is_hidden) {
+
5212 char *path;
+
5213 if (try_get_path(f, node->nodeid, NULL, &path, NULL, false) == 0) {
+
5214 fuse_fs_unlink(f->fs, path);
+
5215 free(path);
+
5216 }
+
5217 }
+
5218 }
+
5219 }
+
5220 }
+
5221 for (i = 0; i < f->id_table.size; i++) {
+
5222 struct node *node;
+
5223 struct node *next;
+
5224
+
5225 for (node = f->id_table.array[i]; node != NULL; node = next) {
+
5226 next = node->id_next;
+
5227 free_node(f, node);
+
5228 f->id_table.use--;
+
5229 }
+
5230 }
+
5231 assert(list_empty(&f->partial_slabs));
+
5232 assert(list_empty(&f->full_slabs));
+
5233
+
5234 while (fuse_modules) {
+
5235 fuse_put_module(fuse_modules);
+
5236 }
+
5237 free(f->id_table.array);
+
5238 free(f->name_table.array);
+
5239 pthread_mutex_destroy(&f->lock);
+
5240 fuse_session_destroy(f->se);
+
5241 free(f->fs);
+
5242 free(f->conf.modules);
+
5243 free(f);
+
5244 fuse_delete_context_key();
+
5245}
+
5246
+
5247int fuse_mount(struct fuse *f, const char *mountpoint) {
+
5248 return fuse_session_mount(fuse_get_session(f), mountpoint);
+
5249}
+
5250
+
5251
+
5252void fuse_unmount(struct fuse *f) {
+ +
5254}
+
5255
+
5256int fuse_version(void)
+
5257{
+
5258 return FUSE_VERSION;
+
5259}
+
5260
+
5261const char *fuse_pkgversion(void)
+
5262{
+
5263 return PACKAGE_VERSION;
+
5264}
+
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4654
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5197
+
int fuse_interrupted(void)
Definition fuse.c:4663
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5146
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4906
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1383
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4639
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4433
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5202
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4914
+
fuse_fill_dir_flags
Definition fuse.h:58
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_SPLICE_READ
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_IS_FD
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
int fuse_version(void)
Definition fuse.c:5206
+
@ FUSE_BUF_SPLICE_MOVE
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+ + +
enum fuse_buf_flags flags
+
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + + +
int32_t show_help
Definition fuse.h:279
+ +
uint32_t no_interrupt
+ +
void * private_data
Definition fuse.h:874
+ + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:60
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t noflush
Definition fuse_common.h:93
+
uint32_t flush
Definition fuse_common.h:74
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + + +
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2fuse__i_8h_source.html b/doc/html/fuse-3_818_81_2lib_2fuse__i_8h_source.html new file mode 100644 index 0000000..cc33e66 --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2fuse__i_8h_source.html @@ -0,0 +1,292 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/fuse_i.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_i.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt
+
7*/
+
8
+
9#ifndef LIB_FUSE_I_H_
+
10#define LIB_FUSE_I_H_
+
11
+
12#include "fuse.h"
+
13#include "fuse_lowlevel.h"
+
14#include "util.h"
+
15
+
16#include <pthread.h>
+
17#include <semaphore.h>
+
18#include <stdint.h>
+
19#include <stdbool.h>
+
20#include <errno.h>
+
21#include <stdatomic.h>
+
22
+
23#define MIN(a, b) \
+
24({ \
+
25 typeof(a) _a = (a); \
+
26 typeof(b) _b = (b); \
+
27 _a < _b ? _a : _b; \
+
28})
+
29
+
30struct mount_opts;
+
31struct fuse_ring_pool;
+
32
+
33struct fuse_req {
+
34 struct fuse_session *se;
+
35 uint64_t unique;
+
36 _Atomic int ref_cnt;
+
37 pthread_mutex_t lock;
+
38 struct fuse_ctx ctx;
+
39 struct fuse_chan *ch;
+
40 int interrupted;
+
41 struct {
+
42 unsigned int ioctl_64bit : 1;
+
43 unsigned int is_uring : 1;
+
44 unsigned int is_copy_file_range_64 : 1;
+
45 } flags;
+
46 union {
+
47 struct {
+
48 uint64_t unique;
+
49 } i;
+
50 struct {
+ +
52 void *data;
+
53 } ni;
+
54 } u;
+
55 struct fuse_req *next;
+
56 struct fuse_req *prev;
+
57};
+
58
+
59struct fuse_notify_req {
+
60 uint64_t unique;
+
61 void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t,
+
62 const void *, const struct fuse_buf *);
+
63 struct fuse_notify_req *next;
+
64 struct fuse_notify_req *prev;
+
65};
+
66
+
67struct fuse_session_uring {
+
68 bool enable;
+
69 unsigned int q_depth;
+
70 struct fuse_ring_pool *pool;
+
71};
+
72
+
73struct fuse_session {
+
74 _Atomic(char *)mountpoint;
+
75 int fd;
+
76 struct fuse_custom_io *io;
+
77 struct mount_opts *mo;
+
78 int debug;
+
79 int deny_others;
+
80 struct fuse_lowlevel_ops op;
+
81 int got_init;
+
82 struct cuse_data *cuse_data;
+
83 void *userdata;
+
84 uid_t owner;
+
85 struct fuse_conn_info conn;
+
86 struct fuse_req list;
+
87 struct fuse_req interrupts;
+
88 pthread_mutex_t lock;
+
89 int got_destroy;
+
90 pthread_key_t pipe_key;
+
91 int broken_splice_nonblock;
+
92 uint64_t notify_ctr;
+
93 struct fuse_notify_req notify_list;
+
94 _Atomic size_t bufsize;
+
95 int error;
+
96
+
97 /*
+
98 * This is useful if any kind of ABI incompatibility is found at
+
99 * a later version, to 'fix' it at run time.
+
100 */
+
101 struct libfuse_version version;
+
102
+
103 /* thread synchronization */
+
104 _Atomic bool mt_exited;
+
105 pthread_mutex_t mt_lock;
+
106 sem_t mt_finish;
+
107
+
108 /* true if reading requests from /dev/fuse are handled internally */
+
109 bool buf_reallocable;
+
110
+
111 /* io_uring */
+
112 struct fuse_session_uring uring;
+
113
+
114 /*
+
115 * conn->want and conn_want_ext options set by libfuse , needed
+
116 * to correctly convert want to want_ext
+
117 */
+
118 uint32_t conn_want;
+
119 uint64_t conn_want_ext;
+
120};
+
121
+
122struct fuse_chan {
+
123 pthread_mutex_t lock;
+
124 int ctr;
+
125 int fd;
+
126};
+
127
+
135struct fuse_module {
+
136 char *name;
+
137 fuse_module_factory_t factory;
+
138 struct fuse_module *next;
+
139 struct fusemod_so *so;
+
140 int ctr;
+
141};
+
142
+
151#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
152struct fuse_loop_config
+
153{
+
154 /* verififier that a correct struct was was passed. This is especially
+
155 * needed, as versions below (3, 12) were using a public struct
+
156 * (now called fuse_loop_config_v1), which was hard to extend with
+
157 * additional parameters, without risking that file system implementations
+
158 * would not have noticed and might either pass uninitialized members
+
159 * or even too small structs.
+
160 * fuse_loop_config_v1 has clone_fd at this offset, which should be either 0
+
161 * or 1. v2 or even higher version just need to set a value here
+
162 * which not conflicting and very unlikely as having been set by
+
163 * file system implementation.
+
164 */
+
165 int version_id;
+
166
+
171 int clone_fd;
+ +
184
+
190 unsigned int max_threads;
+
191};
+
192#endif
+
193
+
194/* ----------------------------------------------------------- *
+
195 * Channel interface (when using -o clone_fd) *
+
196 * ----------------------------------------------------------- */
+
197
+
204struct fuse_chan *fuse_chan_get(struct fuse_chan *ch);
+
205
+
211void fuse_chan_put(struct fuse_chan *ch);
+
212
+
213struct mount_opts *parse_mount_opts(struct fuse_args *args);
+
214void destroy_mount_opts(struct mount_opts *mo);
+
215void fuse_mount_version(void);
+
216unsigned get_max_read(struct mount_opts *o);
+
217void fuse_kern_unmount(const char *mountpoint, int fd);
+
218int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo);
+
219
+
220int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
221 int count);
+
222void fuse_free_req(fuse_req_t req);
+
223void list_init_req(struct fuse_req *req);
+
224
+
225void _cuse_lowlevel_init(fuse_req_t req, const fuse_ino_t nodeid,
+
226 const void *req_header, const void *req_payload);
+
227void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg);
+
228
+
229int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg);
+
230
+
231void fuse_buf_free(struct fuse_buf *buf);
+
232
+
233int fuse_session_receive_buf_internal(struct fuse_session *se,
+
234 struct fuse_buf *buf,
+
235 struct fuse_chan *ch);
+
236void fuse_session_process_buf_internal(struct fuse_session *se,
+
237 const struct fuse_buf *buf,
+
238 struct fuse_chan *ch);
+
239
+
240struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
241 size_t op_size, void *private_data);
+
242int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config);
+
243int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
244
+
250int fuse_loop_cfg_verify(struct fuse_loop_config *config);
+
251
+
252
+
253/*
+
254 * This can be changed dynamically on recent kernels through the
+
255 * /proc/sys/fs/fuse/max_pages_limit interface.
+
256 *
+
257 * Older kernels will always use the default value.
+
258 */
+
259#define FUSE_DEFAULT_MAX_PAGES_LIMIT 256
+
260#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32
+
261
+
262/* room needed in buffer to accommodate header */
+
263#define FUSE_BUFFER_HEADER_SIZE 0x1000
+
264
+
265
+
266#endif /* LIB_FUSE_I_H_*/
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1383
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
struct fuse_req * fuse_req_t
+
uint64_t fuse_ino_t
+ + + + + + +
unsigned int max_threads
Definition fuse_i.h:166
+
unsigned int max_idle_threads
+ + + + + +
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2fuse__log_8c_source.html b/doc/html/fuse-3_818_81_2lib_2fuse__log_8c_source.html new file mode 100644 index 0000000..c58ba2f --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2fuse__log_8c_source.html @@ -0,0 +1,125 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/fuse_log.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_log.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt
+
9*/
+
10
+
11#include "fuse_log.h"
+
12
+
13#include <stdio.h>
+
14#include <stdbool.h>
+
15#include <syslog.h>
+
16#include <stdarg.h>
+
17
+
18#define MAX_SYSLOG_LINE_LEN 512
+
19
+
20static bool to_syslog = false;
+
21
+
22static void default_log_func(enum fuse_log_level level, const char *fmt, va_list ap)
+
23{
+
24 if (to_syslog)
+
25 vsyslog(level, fmt, ap);
+
26 else
+
27 vfprintf(stderr, fmt, ap);
+
28}
+
29
+
30static fuse_log_func_t log_func = default_log_func;
+
31
+ +
33{
+
34 if (!func)
+
35 func = default_log_func;
+
36
+
37 log_func = func;
+
38}
+
39
+
40void fuse_log(enum fuse_log_level level, const char *fmt, ...)
+
41{
+
42 va_list ap;
+
43
+
44 va_start(ap, fmt);
+
45 log_func(level, fmt, ap);
+
46 va_end(ap);
+
47}
+
48
+
49void fuse_log_enable_syslog(const char *ident, int option, int facility)
+
50{
+
51 to_syslog = true;
+
52
+
53 openlog(ident, option, facility);
+
54}
+
55
+
56void fuse_log_close_syslog(void)
+
57{
+
58 closelog();
+
59}
+
void fuse_log_close_syslog(void)
Definition fuse_log.c:93
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:86
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:69
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2fuse__loop_8c_source.html b/doc/html/fuse-3_818_81_2lib_2fuse__loop_8c_source.html new file mode 100644 index 0000000..9c418ce --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2fuse__loop_8c_source.html @@ -0,0 +1,113 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/fuse_loop.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_loop.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the single-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14#include "fuse_uring_i.h"
+
15#include <stdio.h>
+
16#include <stdlib.h>
+
17#include <errno.h>
+
18
+
19int fuse_session_loop(struct fuse_session *se)
+
20{
+
21 int res = 0;
+
22 struct fuse_buf fbuf = {
+
23 .mem = NULL,
+
24 };
+
25
+
26 while (!fuse_session_exited(se)) {
+
27 res = fuse_session_receive_buf_internal(se, &fbuf, NULL);
+
28
+
29 if (res == -EINTR)
+
30 continue;
+
31 if (res <= 0)
+
32 break;
+
33
+
34 fuse_session_process_buf(se, &fbuf);
+
35 }
+
36
+
37 fuse_buf_free(&fbuf);
+
38 if(res > 0)
+
39 /* No error, just the length of the most recently read
+
40 request */
+
41 res = 0;
+
42 if(se->error != 0)
+
43 res = se->error;
+
44
+
45 if (se->uring.pool)
+
46 fuse_uring_stop(se);
+
47 return res;
+
48}
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+ +
void * mem
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2fuse__loop__mt_8c_source.html b/doc/html/fuse-3_818_81_2lib_2fuse__loop__mt_8c_source.html new file mode 100644 index 0000000..28528ad --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2fuse__loop__mt_8c_source.html @@ -0,0 +1,604 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/fuse_loop_mt.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_loop_mt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the multi-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt.
+
9*/
+
10
+
11#define _GNU_SOURCE
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_lowlevel.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_kernel.h"
+
17#include "fuse_i.h"
+
18#include "fuse_uring_i.h"
+
19#include "util.h"
+
20
+
21#include <stdio.h>
+
22#include <stdlib.h>
+
23#include <string.h>
+
24#include <unistd.h>
+
25#include <signal.h>
+
26#include <semaphore.h>
+
27#include <errno.h>
+
28#include <sys/time.h>
+
29#include <sys/ioctl.h>
+
30#include <assert.h>
+
31#include <limits.h>
+
32
+
33/* Environment var controlling the thread stack size */
+
34#define ENVNAME_THREAD_STACK "FUSE_THREAD_STACK"
+
35
+
36#define FUSE_LOOP_MT_V2_IDENTIFIER INT_MAX - 2
+
37#define FUSE_LOOP_MT_DEF_CLONE_FD 0
+
38#define FUSE_LOOP_MT_DEF_MAX_THREADS 10
+
39#define FUSE_LOOP_MT_DEF_IDLE_THREADS -1 /* thread destruction is disabled
+
40 * by default */
+
41
+
42/* an arbitrary large value that cannot be valid */
+
43#define FUSE_LOOP_MT_MAX_THREADS (100U * 1000)
+
44
+
45struct fuse_worker {
+
46 struct fuse_worker *prev;
+
47 struct fuse_worker *next;
+
48 pthread_t thread_id;
+
49
+
50 // We need to include fuse_buf so that we can properly free
+
51 // it when a thread is terminated by pthread_cancel().
+
52 struct fuse_buf fbuf;
+
53 struct fuse_chan *ch;
+
54 struct fuse_mt *mt;
+
55};
+
56
+
57/* synchronization via se->mt_lock */
+
58struct fuse_mt {
+
59 int numworker;
+
60 int numavail;
+
61 struct fuse_session *se;
+
62 struct fuse_worker main;
+
63 int error;
+
64 int clone_fd;
+
65 int max_idle;
+
66 int max_threads;
+
67};
+
68
+
69static struct fuse_chan *fuse_chan_new(int fd)
+
70{
+
71 struct fuse_chan *ch = (struct fuse_chan *) malloc(sizeof(*ch));
+
72 if (ch == NULL) {
+
73 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate channel\n");
+
74 return NULL;
+
75 }
+
76
+
77 memset(ch, 0, sizeof(*ch));
+
78 ch->fd = fd;
+
79 ch->ctr = 1;
+
80 pthread_mutex_init(&ch->lock, NULL);
+
81
+
82 return ch;
+
83}
+
84
+
85struct fuse_chan *fuse_chan_get(struct fuse_chan *ch)
+
86{
+
87 assert(ch->ctr > 0);
+
88 pthread_mutex_lock(&ch->lock);
+
89 ch->ctr++;
+
90 pthread_mutex_unlock(&ch->lock);
+
91
+
92 return ch;
+
93}
+
94
+
95void fuse_chan_put(struct fuse_chan *ch)
+
96{
+
97 if (ch == NULL)
+
98 return;
+
99 pthread_mutex_lock(&ch->lock);
+
100 ch->ctr--;
+
101 if (!ch->ctr) {
+
102 pthread_mutex_unlock(&ch->lock);
+
103 close(ch->fd);
+
104 pthread_mutex_destroy(&ch->lock);
+
105 free(ch);
+
106 } else
+
107 pthread_mutex_unlock(&ch->lock);
+
108}
+
109
+
110static void list_add_worker(struct fuse_worker *w, struct fuse_worker *next)
+
111{
+
112 struct fuse_worker *prev = next->prev;
+
113 w->next = next;
+
114 w->prev = prev;
+
115 prev->next = w;
+
116 next->prev = w;
+
117}
+
118
+
119static void list_del_worker(struct fuse_worker *w)
+
120{
+
121 struct fuse_worker *prev = w->prev;
+
122 struct fuse_worker *next = w->next;
+
123 prev->next = next;
+
124 next->prev = prev;
+
125}
+
126
+
127static int fuse_loop_start_thread(struct fuse_mt *mt);
+
128
+
129static void *fuse_do_work(void *data)
+
130{
+
131 struct fuse_worker *w = (struct fuse_worker *) data;
+
132 struct fuse_mt *mt = w->mt;
+
133 struct fuse_session *se = mt->se;
+
134
+
135 fuse_set_thread_name("fuse_worker");
+
136
+
137 while (!fuse_session_exited(se)) {
+
138 int isforget = 0;
+
139 int res;
+
140
+
141 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
142 res = fuse_session_receive_buf_internal(se, &w->fbuf, w->ch);
+
143 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+
144 if (res == -EINTR)
+
145 continue;
+
146 if (res <= 0) {
+
147 if (res < 0) {
+ +
149 mt->error = res;
+
150 }
+
151 break;
+
152 }
+
153
+
154 pthread_mutex_lock(&se->mt_lock);
+
155 if (fuse_session_exited(se)) {
+
156 pthread_mutex_unlock(&se->mt_lock);
+
157 return NULL;
+
158 }
+
159
+
160 /*
+
161 * This disgusting hack is needed so that zillions of threads
+
162 * are not created on a burst of FORGET messages
+
163 */
+
164 if (!(w->fbuf.flags & FUSE_BUF_IS_FD)) {
+
165 struct fuse_in_header *in = w->fbuf.mem;
+
166
+
167 if (in->opcode == FUSE_FORGET ||
+
168 in->opcode == FUSE_BATCH_FORGET)
+
169 isforget = 1;
+
170 }
+
171
+
172 if (!isforget)
+
173 mt->numavail--;
+
174 if (mt->numavail == 0 && mt->numworker < mt->max_threads &&
+
175 likely(se->got_init))
+
176 fuse_loop_start_thread(mt);
+
177 pthread_mutex_unlock(&se->mt_lock);
+
178
+
179 fuse_session_process_buf_internal(se, &w->fbuf, w->ch);
+
180
+
181 pthread_mutex_lock(&se->mt_lock);
+
182 if (!isforget)
+
183 mt->numavail++;
+
184
+
185 /* creating and destroying threads is rather expensive - and there is
+
186 * not much gain from destroying existing threads. It is therefore
+
187 * discouraged to set max_idle to anything else than -1. If there
+
188 * is indeed a good reason to destruct threads it should be done
+
189 * delayed, a moving average might be useful for that.
+
190 */
+
191 if (mt->max_idle != -1 && mt->numavail > mt->max_idle && mt->numworker > 1) {
+
192 if (fuse_session_exited(se)) {
+
193 pthread_mutex_unlock(&se->mt_lock);
+
194 return NULL;
+
195 }
+
196 list_del_worker(w);
+
197 mt->numavail--;
+
198 mt->numworker--;
+
199 pthread_mutex_unlock(&se->mt_lock);
+
200
+
201 pthread_detach(w->thread_id);
+
202 fuse_buf_free(&w->fbuf);
+
203 fuse_chan_put(w->ch);
+
204 free(w);
+
205 return NULL;
+
206 }
+
207 pthread_mutex_unlock(&se->mt_lock);
+
208 }
+
209
+
210 sem_post(&se->mt_finish);
+
211 return NULL;
+
212}
+
213
+
214int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg)
+
215{
+
216 sigset_t oldset;
+
217 sigset_t newset;
+
218 int res;
+
219 pthread_attr_t attr;
+
220 char *stack_size;
+
221
+
222 /* Override default stack size
+
223 * XXX: This should ideally be a parameter option. It is rather
+
224 * well hidden here.
+
225 */
+
226 pthread_attr_init(&attr);
+
227 stack_size = getenv(ENVNAME_THREAD_STACK);
+
228 if (stack_size) {
+
229 long size;
+
230
+
231 res = libfuse_strtol(stack_size, &size);
+
232 if (res)
+
233 fuse_log(FUSE_LOG_ERR, "fuse: invalid stack size: %s\n",
+
234 stack_size);
+
235 else if (pthread_attr_setstacksize(&attr, size))
+
236 fuse_log(FUSE_LOG_ERR, "fuse: could not set stack size: %ld\n",
+
237 size);
+
238 }
+
239
+
240 /* Disallow signal reception in worker threads */
+
241 sigemptyset(&newset);
+
242 sigaddset(&newset, SIGTERM);
+
243 sigaddset(&newset, SIGINT);
+
244 sigaddset(&newset, SIGHUP);
+
245 sigaddset(&newset, SIGQUIT);
+
246 pthread_sigmask(SIG_BLOCK, &newset, &oldset);
+
247 res = pthread_create(thread_id, &attr, func, arg);
+
248 pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+
249 pthread_attr_destroy(&attr);
+
250 if (res != 0) {
+
251 fuse_log(FUSE_LOG_ERR, "fuse: error creating thread: %s\n",
+
252 strerror(res));
+
253 return -1;
+
254 }
+
255
+
256 return 0;
+
257}
+
258
+
259static int fuse_clone_chan_fd_default(struct fuse_session *se)
+
260{
+
261 int res;
+
262 int clonefd;
+
263 uint32_t masterfd;
+
264 const char *devname = "/dev/fuse";
+
265
+
266#ifndef O_CLOEXEC
+
267#define O_CLOEXEC 0
+
268#endif
+
269 clonefd = open(devname, O_RDWR | O_CLOEXEC);
+
270 if (clonefd == -1) {
+
271 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n", devname,
+
272 strerror(errno));
+
273 return -1;
+
274 }
+
275 if (!O_CLOEXEC) {
+
276 res = fcntl(clonefd, F_SETFD, FD_CLOEXEC);
+
277 if (res == -1) {
+
278 fuse_log(FUSE_LOG_ERR, "fuse: failed to set CLOEXEC: %s\n",
+
279 strerror(errno));
+
280 close(clonefd);
+
281 return -1;
+
282 }
+
283 }
+
284
+
285 masterfd = se->fd;
+
286 res = ioctl(clonefd, FUSE_DEV_IOC_CLONE, &masterfd);
+
287 if (res == -1) {
+
288 fuse_log(FUSE_LOG_ERR, "fuse: failed to clone device fd: %s\n",
+
289 strerror(errno));
+
290 close(clonefd);
+
291 return -1;
+
292 }
+
293 return clonefd;
+
294}
+
295
+
296static struct fuse_chan *fuse_clone_chan(struct fuse_mt *mt)
+
297{
+
298 int clonefd;
+
299 struct fuse_session *se = mt->se;
+
300 struct fuse_chan *newch;
+
301
+
302 if (se->io != NULL) {
+
303 if (se->io->clone_fd != NULL)
+
304 clonefd = se->io->clone_fd(se->fd);
+
305 else
+
306 return NULL;
+
307 } else {
+
308 clonefd = fuse_clone_chan_fd_default(se);
+
309 }
+
310 if (clonefd < 0)
+
311 return NULL;
+
312
+
313 newch = fuse_chan_new(clonefd);
+
314 if (newch == NULL)
+
315 close(clonefd);
+
316
+
317 return newch;
+
318}
+
319
+
320static int fuse_loop_start_thread(struct fuse_mt *mt)
+
321{
+
322 int res;
+
323
+
324 struct fuse_worker *w = malloc(sizeof(struct fuse_worker));
+
325 if (!w) {
+
326 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate worker structure\n");
+
327 return -1;
+
328 }
+
329 memset(w, 0, sizeof(struct fuse_worker));
+
330 w->fbuf.mem = NULL;
+
331 w->mt = mt;
+
332
+
333 w->ch = NULL;
+
334 if (mt->clone_fd) {
+
335 w->ch = fuse_clone_chan(mt);
+
336 if(!w->ch) {
+
337 /* Don't attempt this again */
+
338 fuse_log(FUSE_LOG_ERR, "fuse: trying to continue "
+
339 "without -o clone_fd.\n");
+
340 mt->clone_fd = 0;
+
341 }
+
342 }
+
343
+
344 res = fuse_start_thread(&w->thread_id, fuse_do_work, w);
+
345 if (res == -1) {
+
346 fuse_chan_put(w->ch);
+
347 free(w);
+
348 return -1;
+
349 }
+
350 list_add_worker(w, &mt->main);
+
351 mt->numavail ++;
+
352 mt->numworker ++;
+
353
+
354 return 0;
+
355}
+
356
+
357static void fuse_join_worker(struct fuse_mt *mt, struct fuse_worker *w)
+
358{
+
359 pthread_join(w->thread_id, NULL);
+
360 pthread_mutex_lock(&mt->se->mt_lock);
+
361 list_del_worker(w);
+
362 pthread_mutex_unlock(&mt->se->mt_lock);
+
363 fuse_buf_free(&w->fbuf);
+
364 fuse_chan_put(w->ch);
+
365 free(w);
+
366}
+
367
+
368int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
369FUSE_SYMVER("fuse_session_loop_mt_312", "fuse_session_loop_mt@@FUSE_3.12")
+
370int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config)
+
371{
+
372int err;
+
373 struct fuse_mt mt;
+
374 struct fuse_worker *w;
+
375 int created_config = 0;
+
376
+
377 if (config) {
+
378 err = fuse_loop_cfg_verify(config);
+
379 if (err)
+
380 return err;
+
381 } else {
+
382 /* The caller does not care about parameters - use the default */
+
383 config = fuse_loop_cfg_create();
+
384 created_config = 1;
+
385 }
+
386
+
387
+
388 memset(&mt, 0, sizeof(struct fuse_mt));
+
389 mt.se = se;
+
390 mt.clone_fd = config->clone_fd;
+
391 mt.error = 0;
+
392 mt.numworker = 0;
+
393 mt.numavail = 0;
+
394 mt.max_idle = config->max_idle_threads;
+
395 mt.max_threads = config->max_threads;
+
396 mt.main.thread_id = pthread_self();
+
397 mt.main.prev = mt.main.next = &mt.main;
+
398
+
399 pthread_mutex_lock(&se->mt_lock);
+
400 err = fuse_loop_start_thread(&mt);
+
401 pthread_mutex_unlock(&se->mt_lock);
+
402 if (!err) {
+
403 while (!fuse_session_exited(se))
+
404 sem_wait(&se->mt_finish);
+
405 if (se->debug)
+
406 fuse_log(FUSE_LOG_DEBUG,
+
407 "fuse: session exited, terminating workers\n");
+
408
+
409 pthread_mutex_lock(&se->mt_lock);
+
410 for (w = mt.main.next; w != &mt.main; w = w->next)
+
411 pthread_cancel(w->thread_id);
+
412 pthread_mutex_unlock(&se->mt_lock);
+
413
+
414 while (mt.main.next != &mt.main)
+
415 fuse_join_worker(&mt, mt.main.next);
+
416
+
417 err = mt.error;
+
418
+
419 if (se->uring.pool)
+
420 fuse_uring_stop(se);
+
421 }
+
422
+
423 pthread_mutex_destroy(&se->mt_lock);
+
424 if(se->error != 0)
+
425 err = se->error;
+
426
+
427
+
428 if (created_config) {
+
429 fuse_loop_cfg_destroy(config);
+
430 config = NULL;
+
431 }
+
432
+
433 return err;
+
434}
+
435
+
436int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1);
+
437FUSE_SYMVER("fuse_session_loop_mt_32", "fuse_session_loop_mt@FUSE_3.2")
+
438int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1)
+
439{
+
440 int err;
+
441 struct fuse_loop_config *config = NULL;
+
442
+
443 if (config_v1 != NULL) {
+
444 /* convert the given v1 config */
+
445 config = fuse_loop_cfg_create();
+
446 if (config == NULL)
+
447 return ENOMEM;
+
448
+
449 fuse_loop_cfg_convert(config, config_v1);
+
450 }
+
451
+
452 err = fuse_session_loop_mt_312(se, config);
+
453
+
454 fuse_loop_cfg_destroy(config);
+
455
+
456 return err;
+
457}
+
458
+
459
+
460int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
461FUSE_SYMVER("fuse_session_loop_mt_31", "fuse_session_loop_mt@FUSE_3.0")
+
462int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd)
+
463{
+
464 int err;
+
465 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
466 if (clone_fd > 0)
+
467 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
468 err = fuse_session_loop_mt_312(se, config);
+
469
+
470 fuse_loop_cfg_destroy(config);
+
471
+
472 return err;
+
473}
+
474
+
475struct fuse_loop_config *fuse_loop_cfg_create(void)
+
476{
+
477 struct fuse_loop_config *config = calloc(1, sizeof(*config));
+
478 if (config == NULL)
+
479 return NULL;
+
480
+
481 config->version_id = FUSE_LOOP_MT_V2_IDENTIFIER;
+
482 config->max_idle_threads = FUSE_LOOP_MT_DEF_IDLE_THREADS;
+
483 config->max_threads = FUSE_LOOP_MT_DEF_MAX_THREADS;
+
484 config->clone_fd = FUSE_LOOP_MT_DEF_CLONE_FD;
+
485
+
486 return config;
+
487}
+
488
+
489void fuse_loop_cfg_destroy(struct fuse_loop_config *config)
+
490{
+
491 free(config);
+
492}
+
493
+
494int fuse_loop_cfg_verify(struct fuse_loop_config *config)
+
495{
+
496 if (config->version_id != FUSE_LOOP_MT_V2_IDENTIFIER)
+
497 return -EINVAL;
+
498
+
499 return 0;
+
500}
+
501
+
502void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
503 struct fuse_loop_config_v1 *v1_conf)
+
504{
+
505 fuse_loop_cfg_set_idle_threads(config, v1_conf->max_idle_threads);
+
506
+
507 fuse_loop_cfg_set_clone_fd(config, v1_conf->clone_fd);
+
508}
+
509
+
510void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
511 unsigned int value)
+
512{
+
513 if (value > FUSE_LOOP_MT_MAX_THREADS) {
+
514 if (value != UINT_MAX)
+
515 fuse_log(FUSE_LOG_ERR,
+
516 "Ignoring invalid max threads value "
+
517 "%u > max (%u).\n", value,
+
518 FUSE_LOOP_MT_MAX_THREADS);
+
519 return;
+
520 }
+
521 config->max_idle_threads = value;
+
522}
+
523
+
524void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
525 unsigned int value)
+
526{
+
527 config->max_threads = value;
+
528}
+
529
+
530void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
531 unsigned int value)
+
532{
+
533 config->clone_fd = value;
+
534}
+
535
+
@ FUSE_BUF_IS_FD
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_exited(struct fuse_session *se)
+ + + +
unsigned int max_threads
Definition fuse_i.h:166
+
unsigned int max_idle_threads
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2fuse__lowlevel_8c_source.html b/doc/html/fuse-3_818_81_2lib_2fuse__lowlevel_8c_source.html new file mode 100644 index 0000000..d2032bd --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2fuse__lowlevel_8c_source.html @@ -0,0 +1,4675 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/fuse_lowlevel.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_lowlevel.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of (most of) the low-level FUSE API. The session loop
+
6 functions are implemented in separate files.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file LGPL2.txt
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_kernel.h"
+
17#include "fuse_opt.h"
+
18#include "fuse_misc.h"
+
19#include "mount_util.h"
+
20#include "util.h"
+
21#include "fuse_uring_i.h"
+
22
+
23#include <pthread.h>
+
24#include <stdatomic.h>
+
25#include <stdint.h>
+
26#include <inttypes.h>
+
27#include <stdbool.h>
+
28#include <stdio.h>
+
29#include <stdlib.h>
+
30#include <stddef.h>
+
31#include <stdalign.h>
+
32#include <string.h>
+
33#include <unistd.h>
+
34#include <limits.h>
+
35#include <errno.h>
+
36#include <assert.h>
+
37#include <sys/file.h>
+
38#include <sys/ioctl.h>
+
39#include <stdalign.h>
+
40
+
41#ifdef USDT_ENABLED
+
42#include "usdt.h"
+
43#endif
+
44
+
45#ifndef F_LINUX_SPECIFIC_BASE
+
46#define F_LINUX_SPECIFIC_BASE 1024
+
47#endif
+
48#ifndef F_SETPIPE_SZ
+
49#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7)
+
50#endif
+
51
+
52#define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg)))
+
53#define OFFSET_MAX 0x7fffffffffffffffLL
+
54
+
55struct fuse_pollhandle {
+
56 uint64_t kh;
+
57 struct fuse_session *se;
+
58};
+
59
+
60static size_t pagesize;
+
61
+
62static __attribute__((constructor)) void fuse_ll_init_pagesize(void)
+
63{
+
64 pagesize = getpagesize();
+
65}
+
66
+
67#ifdef USDT_ENABLED
+
68/* tracepoints */
+
69static void trace_request_receive(int err)
+
70{
+
71 USDT(libfuse, request_receive, err);
+
72}
+
73
+
74static void trace_request_process(unsigned int opcode, unsigned int unique)
+
75{
+
76 USDT(libfuse, request_process, opcode, unique);
+
77}
+
78
+
79static void trace_request_reply(uint64_t unique, unsigned int len,
+
80 int error, int reply_err)
+
81{
+
82 USDT(libfuse, request_reply, unique, len, error, reply_err);
+
83}
+
84#else
+
85static void trace_request_receive(int err)
+
86{
+
87 (void)err;
+
88}
+
89
+
90static void trace_request_process(unsigned int opcode, unsigned int unique)
+
91{
+
92 (void)opcode;
+
93 (void)unique;
+
94}
+
95
+
96static void trace_request_reply(uint64_t unique, unsigned int len,
+
97 int error, int reply_err)
+
98{
+
99 (void)unique;
+
100 (void)len;
+
101 (void)error;
+
102 (void)reply_err;
+
103}
+
104#endif
+
105
+
106static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr)
+
107{
+
108 attr->ino = stbuf->st_ino;
+
109 attr->mode = stbuf->st_mode;
+
110 attr->nlink = stbuf->st_nlink;
+
111 attr->uid = stbuf->st_uid;
+
112 attr->gid = stbuf->st_gid;
+
113 attr->rdev = stbuf->st_rdev;
+
114 attr->size = stbuf->st_size;
+
115 attr->blksize = stbuf->st_blksize;
+
116 attr->blocks = stbuf->st_blocks;
+
117 attr->atime = stbuf->st_atime;
+
118 attr->mtime = stbuf->st_mtime;
+
119 attr->ctime = stbuf->st_ctime;
+
120 attr->atimensec = ST_ATIM_NSEC(stbuf);
+
121 attr->mtimensec = ST_MTIM_NSEC(stbuf);
+
122 attr->ctimensec = ST_CTIM_NSEC(stbuf);
+
123}
+
124
+
125static void convert_attr(const struct fuse_setattr_in *attr, struct stat *stbuf)
+
126{
+
127 stbuf->st_mode = attr->mode;
+
128 stbuf->st_uid = attr->uid;
+
129 stbuf->st_gid = attr->gid;
+
130 stbuf->st_size = attr->size;
+
131 stbuf->st_atime = attr->atime;
+
132 stbuf->st_mtime = attr->mtime;
+
133 stbuf->st_ctime = attr->ctime;
+
134 ST_ATIM_NSEC_SET(stbuf, attr->atimensec);
+
135 ST_MTIM_NSEC_SET(stbuf, attr->mtimensec);
+
136 ST_CTIM_NSEC_SET(stbuf, attr->ctimensec);
+
137}
+
138
+
139static size_t iov_length(const struct iovec *iov, size_t count)
+
140{
+
141 size_t seg;
+
142 size_t ret = 0;
+
143
+
144 for (seg = 0; seg < count; seg++)
+
145 ret += iov[seg].iov_len;
+
146 return ret;
+
147}
+
148
+
149void list_init_req(struct fuse_req *req)
+
150{
+
151 req->next = req;
+
152 req->prev = req;
+
153}
+
154
+
155static void list_del_req(struct fuse_req *req)
+
156{
+
157 struct fuse_req *prev = req->prev;
+
158 struct fuse_req *next = req->next;
+
159 prev->next = next;
+
160 next->prev = prev;
+
161}
+
162
+
163static void list_add_req(struct fuse_req *req, struct fuse_req *next)
+
164{
+
165 struct fuse_req *prev = next->prev;
+
166 req->next = next;
+
167 req->prev = prev;
+
168 prev->next = req;
+
169 next->prev = req;
+
170}
+
171
+
172static void destroy_req(fuse_req_t req)
+
173{
+
174 if (req->flags.is_uring) {
+
175 fuse_log(FUSE_LOG_ERR, "Refusing to destruct uring req\n");
+
176 return;
+
177 }
+
178 assert(req->ch == NULL);
+
179 pthread_mutex_destroy(&req->lock);
+
180 free(req);
+
181}
+
182
+
183void fuse_free_req(fuse_req_t req)
+
184{
+
185 int ctr;
+
186 struct fuse_session *se = req->se;
+
187
+
188 /* XXX: for now no support for interrupts with io-uring
+
189 * It actually might work already, though. But then would add
+
190 * a lock across ring queues.
+
191 */
+
192 if (se->conn.no_interrupt || req->flags.is_uring) {
+
193 ctr = --req->ref_cnt;
+
194 fuse_chan_put(req->ch);
+
195 req->ch = NULL;
+
196 } else {
+
197 pthread_mutex_lock(&se->lock);
+
198 req->u.ni.func = NULL;
+
199 req->u.ni.data = NULL;
+
200 list_del_req(req);
+
201 ctr = --req->ref_cnt;
+
202 fuse_chan_put(req->ch);
+
203 req->ch = NULL;
+
204 pthread_mutex_unlock(&se->lock);
+
205 }
+
206 if (!ctr)
+
207 destroy_req(req);
+
208}
+
209
+
210static struct fuse_req *fuse_ll_alloc_req(struct fuse_session *se)
+
211{
+
212 struct fuse_req *req;
+
213
+
214 req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req));
+
215 if (req == NULL) {
+
216 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate request\n");
+
217 } else {
+
218 req->se = se;
+
219 req->ref_cnt = 1;
+
220 list_init_req(req);
+
221 pthread_mutex_init(&req->lock, NULL);
+
222 }
+
223
+
224 return req;
+
225}
+
226
+
227/*
+
228 * Send data to fuse-kernel using an fd of the fuse device.
+
229 */
+
230static int fuse_write_msg_dev(struct fuse_session *se, struct fuse_chan *ch,
+
231 struct iovec *iov, int count)
+
232{
+
233 ssize_t res;
+
234 int err;
+
235
+
236 if (se->io != NULL)
+
237
+
238 /* se->io->writev is never NULL if se->io is not NULL as
+
239 * specified by fuse_session_custom_io()
+
240 */
+
241 res = se->io->writev(ch ? ch->fd : se->fd, iov, count,
+
242 se->userdata);
+
243 else
+
244 res = writev(ch ? ch->fd : se->fd, iov, count);
+
245
+
246 if (res == -1) {
+
247 /* ENOENT means the operation was interrupted */
+
248 err = errno;
+
249 if (!fuse_session_exited(se) && err != ENOENT)
+
250 perror("fuse: writing device");
+
251 return -err;
+
252 }
+
253
+
254 return 0;
+
255}
+
256
+
257static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch,
+
258 struct iovec *iov, int count, fuse_req_t req)
+
259{
+
260 struct fuse_out_header *out = iov[0].iov_base;
+
261 int err;
+
262 bool is_uring = req && req->flags.is_uring ? true : false;
+
263
+
264 if (!is_uring)
+
265 assert(se != NULL);
+
266 out->len = iov_length(iov, count);
+
267
+
268 if (se->debug) {
+
269 if (out->unique == 0) {
+
270 fuse_log(FUSE_LOG_DEBUG, "NOTIFY: code=%d length=%u\n",
+
271 out->error, out->len);
+
272 } else if (out->error) {
+
273 fuse_log(FUSE_LOG_DEBUG,
+
274 " unique: %llu, error: %i (%s), outsize: %i\n",
+
275 (unsigned long long) out->unique, out->error,
+
276 strerror(-out->error), out->len);
+
277 } else {
+
278 fuse_log(FUSE_LOG_DEBUG,
+
279 " unique: %llu, success, outsize: %i\n",
+
280 (unsigned long long) out->unique, out->len);
+
281 }
+
282 }
+
283
+
284 if (is_uring)
+
285 err = fuse_send_msg_uring(req, iov, count);
+
286 else
+
287 err = fuse_write_msg_dev(se, ch, iov, count);
+
288
+
289 trace_request_reply(out->unique, out->len, out->error, err);
+
290 return err;
+
291}
+
292
+
293int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
294 int count)
+
295{
+
296 struct fuse_out_header out;
+
297
+
298#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
+
299 const char *str = strerrordesc_np(error * -1);
+
300 if ((str == NULL && error != 0) || error > 0) {
+
301#else
+
302 if (error <= -1000 || error > 0) {
+
303#endif
+
304 fuse_log(FUSE_LOG_ERR, "fuse: bad error value: %i\n", error);
+
305 error = -ERANGE;
+
306 }
+
307
+
308 out.unique = req->unique;
+
309 out.error = error;
+
310
+
311 iov[0].iov_base = &out;
+
312 iov[0].iov_len = sizeof(struct fuse_out_header);
+
313
+
314 return fuse_send_msg(req->se, req->ch, iov, count, req);
+
315}
+
316
+
317static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov,
+
318 int count)
+
319{
+
320 int res;
+
321
+
322 res = fuse_send_reply_iov_nofree(req, error, iov, count);
+
323 fuse_free_req(req);
+
324 return res;
+
325}
+
326
+
327static int send_reply(fuse_req_t req, int error, const void *arg,
+
328 size_t argsize)
+
329{
+
330 if (req->flags.is_uring)
+
331 return send_reply_uring(req, error, arg, argsize);
+
332
+
333 struct iovec iov[2];
+
334 int count = 1;
+
335 if (argsize) {
+
336 iov[1].iov_base = (void *) arg;
+
337 iov[1].iov_len = argsize;
+
338 count++;
+
339 }
+
340 return send_reply_iov(req, error, iov, count);
+
341}
+
342
+
343int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
344{
+
345 int res;
+
346 struct iovec *padded_iov;
+
347
+
348 padded_iov = malloc((count + 1) * sizeof(struct iovec));
+
349 if (padded_iov == NULL)
+
350 return fuse_reply_err(req, ENOMEM);
+
351
+
352 memcpy(padded_iov + 1, iov, count * sizeof(struct iovec));
+
353 count++;
+
354
+
355 res = send_reply_iov(req, 0, padded_iov, count);
+
356 free(padded_iov);
+
357
+
358 return res;
+
359}
+
360
+
361
+
362/* `buf` is allowed to be empty so that the proper size may be
+
363 allocated by the caller */
+
364size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
365 const char *name, const struct stat *stbuf, off_t off)
+
366{
+
367 (void)req;
+
368 size_t namelen;
+
369 size_t entlen;
+
370 size_t entlen_padded;
+
371 struct fuse_dirent *dirent;
+
372
+
373 namelen = strlen(name);
+
374 entlen = FUSE_NAME_OFFSET + namelen;
+
375 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
376
+
377 if ((buf == NULL) || (entlen_padded > bufsize))
+
378 return entlen_padded;
+
379
+
380 dirent = (struct fuse_dirent*) buf;
+
381 dirent->ino = stbuf->st_ino;
+
382 dirent->off = off;
+
383 dirent->namelen = namelen;
+
384 dirent->type = (stbuf->st_mode & S_IFMT) >> 12;
+
385 memcpy(dirent->name, name, namelen);
+
386 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
387
+
388 return entlen_padded;
+
389}
+
390
+
391static void convert_statfs(const struct statvfs *stbuf,
+
392 struct fuse_kstatfs *kstatfs)
+
393{
+
394 kstatfs->bsize = stbuf->f_bsize;
+
395 kstatfs->frsize = stbuf->f_frsize;
+
396 kstatfs->blocks = stbuf->f_blocks;
+
397 kstatfs->bfree = stbuf->f_bfree;
+
398 kstatfs->bavail = stbuf->f_bavail;
+
399 kstatfs->files = stbuf->f_files;
+
400 kstatfs->ffree = stbuf->f_ffree;
+
401 kstatfs->namelen = stbuf->f_namemax;
+
402}
+
403
+
404static int send_reply_ok(fuse_req_t req, const void *arg, size_t argsize)
+
405{
+
406 return send_reply(req, 0, arg, argsize);
+
407}
+
408
+
409int fuse_reply_err(fuse_req_t req, int err)
+
410{
+
411 return send_reply(req, -err, NULL, 0);
+
412}
+
413
+ +
415{
+
416 fuse_free_req(req);
+
417}
+
418
+
419static unsigned long calc_timeout_sec(double t)
+
420{
+
421 if (t > (double) ULONG_MAX)
+
422 return ULONG_MAX;
+
423 else if (t < 0.0)
+
424 return 0;
+
425 else
+
426 return (unsigned long) t;
+
427}
+
428
+
429static unsigned int calc_timeout_nsec(double t)
+
430{
+
431 double f = t - (double) calc_timeout_sec(t);
+
432 if (f < 0.0)
+
433 return 0;
+
434 else if (f >= 0.999999999)
+
435 return 999999999;
+
436 else
+
437 return (unsigned int) (f * 1.0e9);
+
438}
+
439
+
440static void fill_entry(struct fuse_entry_out *arg,
+
441 const struct fuse_entry_param *e)
+
442{
+
443 arg->nodeid = e->ino;
+
444 arg->generation = e->generation;
+
445 arg->entry_valid = calc_timeout_sec(e->entry_timeout);
+
446 arg->entry_valid_nsec = calc_timeout_nsec(e->entry_timeout);
+
447 arg->attr_valid = calc_timeout_sec(e->attr_timeout);
+
448 arg->attr_valid_nsec = calc_timeout_nsec(e->attr_timeout);
+
449 convert_stat(&e->attr, &arg->attr);
+
450}
+
451
+
452/* `buf` is allowed to be empty so that the proper size may be
+
453 allocated by the caller */
+
454size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
455 const char *name,
+
456 const struct fuse_entry_param *e, off_t off)
+
457{
+
458 (void)req;
+
459 size_t namelen;
+
460 size_t entlen;
+
461 size_t entlen_padded;
+
462
+
463 namelen = strlen(name);
+
464 entlen = FUSE_NAME_OFFSET_DIRENTPLUS + namelen;
+
465 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
466 if ((buf == NULL) || (entlen_padded > bufsize))
+
467 return entlen_padded;
+
468
+
469 struct fuse_direntplus *dp = (struct fuse_direntplus *) buf;
+
470 memset(&dp->entry_out, 0, sizeof(dp->entry_out));
+
471 fill_entry(&dp->entry_out, e);
+
472
+
473 struct fuse_dirent *dirent = &dp->dirent;
+
474 dirent->ino = e->attr.st_ino;
+
475 dirent->off = off;
+
476 dirent->namelen = namelen;
+
477 dirent->type = (e->attr.st_mode & S_IFMT) >> 12;
+
478 memcpy(dirent->name, name, namelen);
+
479 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
480
+
481 return entlen_padded;
+
482}
+
483
+
484static void fill_open(struct fuse_open_out *arg,
+
485 const struct fuse_file_info *f)
+
486{
+
487 arg->fh = f->fh;
+
488 if (f->backing_id > 0) {
+
489 arg->backing_id = f->backing_id;
+
490 arg->open_flags |= FOPEN_PASSTHROUGH;
+
491 }
+
492 if (f->direct_io)
+
493 arg->open_flags |= FOPEN_DIRECT_IO;
+
494 if (f->keep_cache)
+
495 arg->open_flags |= FOPEN_KEEP_CACHE;
+
496 if (f->cache_readdir)
+
497 arg->open_flags |= FOPEN_CACHE_DIR;
+
498 if (f->nonseekable)
+
499 arg->open_flags |= FOPEN_NONSEEKABLE;
+
500 if (f->noflush)
+
501 arg->open_flags |= FOPEN_NOFLUSH;
+ +
503 arg->open_flags |= FOPEN_PARALLEL_DIRECT_WRITES;
+
504}
+
505
+
506int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
507{
+
508 struct fuse_entry_out arg;
+
509 size_t size = req->se->conn.proto_minor < 9 ?
+
510 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg);
+
511
+
512 /* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant
+
513 negative entry */
+
514 if (!e->ino && req->se->conn.proto_minor < 4)
+
515 return fuse_reply_err(req, ENOENT);
+
516
+
517 memset(&arg, 0, sizeof(arg));
+
518 fill_entry(&arg, e);
+
519 return send_reply_ok(req, &arg, size);
+
520}
+
521
+
522int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+
523 const struct fuse_file_info *f)
+
524{
+
525 alignas(uint64_t) char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)];
+
526 size_t entrysize = req->se->conn.proto_minor < 9 ?
+
527 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(struct fuse_entry_out);
+
528 struct fuse_entry_out *earg = (struct fuse_entry_out *) buf;
+
529 struct fuse_open_out *oarg = (struct fuse_open_out *) (buf + entrysize);
+
530
+
531 memset(buf, 0, sizeof(buf));
+
532 fill_entry(earg, e);
+
533 fill_open(oarg, f);
+
534 return send_reply_ok(req, buf,
+
535 entrysize + sizeof(struct fuse_open_out));
+
536}
+
537
+
538int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
539 double attr_timeout)
+
540{
+
541 struct fuse_attr_out arg;
+
542 size_t size = req->se->conn.proto_minor < 9 ?
+
543 FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg);
+
544
+
545 memset(&arg, 0, sizeof(arg));
+
546 arg.attr_valid = calc_timeout_sec(attr_timeout);
+
547 arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
+
548 convert_stat(attr, &arg.attr);
+
549
+
550 return send_reply_ok(req, &arg, size);
+
551}
+
552
+
553int fuse_reply_readlink(fuse_req_t req, const char *linkname)
+
554{
+
555 return send_reply_ok(req, linkname, strlen(linkname));
+
556}
+
557
+
558int fuse_passthrough_open(fuse_req_t req, int fd)
+
559{
+
560 struct fuse_backing_map map = { .fd = fd };
+
561 int ret;
+
562
+
563 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_OPEN, &map);
+
564 if (ret <= 0) {
+
565 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_open: %s\n", strerror(errno));
+
566 return 0;
+
567 }
+
568
+
569 return ret;
+
570}
+
571
+
572int fuse_passthrough_close(fuse_req_t req, int backing_id)
+
573{
+
574 int ret;
+
575
+
576 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_CLOSE, &backing_id);
+
577 if (ret < 0)
+
578 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_close: %s\n", strerror(errno));
+
579
+
580 return ret;
+
581}
+
582
+
583int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *f)
+
584{
+
585 struct fuse_open_out arg;
+
586
+
587 memset(&arg, 0, sizeof(arg));
+
588 fill_open(&arg, f);
+
589 return send_reply_ok(req, &arg, sizeof(arg));
+
590}
+
591
+
592static int do_fuse_reply_write(fuse_req_t req, size_t count)
+
593{
+
594 struct fuse_write_out arg;
+
595
+
596 memset(&arg, 0, sizeof(arg));
+
597 arg.size = count;
+
598
+
599 return send_reply_ok(req, &arg, sizeof(arg));
+
600}
+
601
+
602static int do_fuse_reply_copy(fuse_req_t req, size_t count)
+
603{
+
604 struct fuse_copy_file_range_out arg;
+
605
+
606 memset(&arg, 0, sizeof(arg));
+
607 arg.bytes_copied = count;
+
608
+
609 return send_reply_ok(req, &arg, sizeof(arg));
+
610}
+
611
+
612int fuse_reply_write(fuse_req_t req, size_t count)
+
613{
+
614 /*
+
615 * This function is also used by FUSE_COPY_FILE_RANGE and its 64-bit
+
616 * variant.
+
617 */
+
618 if (req->flags.is_copy_file_range_64)
+
619 return do_fuse_reply_copy(req, count);
+
620 else
+
621 return do_fuse_reply_write(req, count);
+
622}
+
623
+
624int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
625{
+
626 return send_reply_ok(req, buf, size);
+
627}
+
628
+
629static int fuse_send_data_iov_fallback(struct fuse_session *se,
+
630 struct fuse_chan *ch,
+
631 struct iovec *iov, int iov_count,
+
632 struct fuse_bufvec *buf,
+
633 size_t len, fuse_req_t req)
+
634{
+
635 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
636 void *mbuf;
+
637 int res;
+
638
+
639 /* Optimize common case */
+
640 if (buf->count == 1 && buf->idx == 0 && buf->off == 0 &&
+
641 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
642 /* FIXME: also avoid memory copy if there are multiple buffers
+
643 but none of them contain an fd */
+
644
+
645 iov[iov_count].iov_base = buf->buf[0].mem;
+
646 iov[iov_count].iov_len = len;
+
647 iov_count++;
+
648 return fuse_send_msg(se, ch, iov, iov_count, req);
+
649 }
+
650
+
651 res = posix_memalign(&mbuf, pagesize, len);
+
652 if (res != 0)
+
653 return res;
+
654
+
655 mem_buf.buf[0].mem = mbuf;
+
656 res = fuse_buf_copy(&mem_buf, buf, 0);
+
657 if (res < 0) {
+
658 free(mbuf);
+
659 return -res;
+
660 }
+
661 len = res;
+
662
+
663 iov[iov_count].iov_base = mbuf;
+
664 iov[iov_count].iov_len = len;
+
665 iov_count++;
+
666 res = fuse_send_msg(se, ch, iov, iov_count, req);
+
667 free(mbuf);
+
668
+
669 return res;
+
670}
+
671
+
672struct fuse_ll_pipe {
+
673 size_t size;
+
674 int can_grow;
+
675 int pipe[2];
+
676};
+
677
+
678static void fuse_ll_pipe_free(struct fuse_ll_pipe *llp)
+
679{
+
680 close(llp->pipe[0]);
+
681 close(llp->pipe[1]);
+
682 free(llp);
+
683}
+
684
+
685#ifdef HAVE_SPLICE
+
686#if !defined(HAVE_PIPE2) || !defined(O_CLOEXEC)
+
687static int fuse_pipe(int fds[2])
+
688{
+
689 int rv = pipe(fds);
+
690
+
691 if (rv == -1)
+
692 return rv;
+
693
+
694 if (fcntl(fds[0], F_SETFL, O_NONBLOCK) == -1 ||
+
695 fcntl(fds[1], F_SETFL, O_NONBLOCK) == -1 ||
+
696 fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 ||
+
697 fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) {
+
698 close(fds[0]);
+
699 close(fds[1]);
+
700 rv = -1;
+
701 }
+
702 return rv;
+
703}
+
704#else
+
705static int fuse_pipe(int fds[2])
+
706{
+
707 return pipe2(fds, O_CLOEXEC | O_NONBLOCK);
+
708}
+
709#endif
+
710
+
711static struct fuse_ll_pipe *fuse_ll_get_pipe(struct fuse_session *se)
+
712{
+
713 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
714 if (llp == NULL) {
+
715 int res;
+
716
+
717 llp = malloc(sizeof(struct fuse_ll_pipe));
+
718 if (llp == NULL)
+
719 return NULL;
+
720
+
721 res = fuse_pipe(llp->pipe);
+
722 if (res == -1) {
+
723 free(llp);
+
724 return NULL;
+
725 }
+
726
+
727 /*
+
728 *the default size is 16 pages on linux
+
729 */
+
730 llp->size = pagesize * 16;
+
731 llp->can_grow = 1;
+
732
+
733 pthread_setspecific(se->pipe_key, llp);
+
734 }
+
735
+
736 return llp;
+
737}
+
738#endif
+
739
+
740static void fuse_ll_clear_pipe(struct fuse_session *se)
+
741{
+
742 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
743 if (llp) {
+
744 pthread_setspecific(se->pipe_key, NULL);
+
745 fuse_ll_pipe_free(llp);
+
746 }
+
747}
+
748
+
749#if defined(HAVE_SPLICE) && defined(HAVE_VMSPLICE)
+
750static int read_back(int fd, char *buf, size_t len)
+
751{
+
752 int res;
+
753
+
754 res = read(fd, buf, len);
+
755 if (res == -1) {
+
756 fuse_log(FUSE_LOG_ERR,
+
757 "fuse: internal error: failed to read back from pipe: %s\n",
+
758 strerror(errno));
+
759 return -EIO;
+
760 }
+
761 if (res != len) {
+
762 fuse_log(FUSE_LOG_ERR,
+
763 "fuse: internal error: short read back from pipe: %i from %zd\n",
+
764 res, len);
+
765 return -EIO;
+
766 }
+
767 return 0;
+
768}
+
769
+
770static int grow_pipe_to_max(int pipefd)
+
771{
+
772 int res;
+
773 long max;
+
774 long maxfd;
+
775 char buf[32];
+
776
+
777 maxfd = open("/proc/sys/fs/pipe-max-size", O_RDONLY);
+
778 if (maxfd < 0)
+
779 return -errno;
+
780
+
781 res = read(maxfd, buf, sizeof(buf) - 1);
+
782 if (res < 0) {
+
783 int saved_errno;
+
784
+
785 saved_errno = errno;
+
786 close(maxfd);
+
787 return -saved_errno;
+
788 }
+
789 close(maxfd);
+
790 buf[res] = '\0';
+
791
+
792 res = libfuse_strtol(buf, &max);
+
793 if (res)
+
794 return res;
+
795 res = fcntl(pipefd, F_SETPIPE_SZ, max);
+
796 if (res < 0)
+
797 return -errno;
+
798 return max;
+
799}
+
800
+
801static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
802 struct iovec *iov, int iov_count,
+
803 struct fuse_bufvec *buf, unsigned int flags,
+
804 fuse_req_t req)
+
805{
+
806 int res;
+
807 size_t len = fuse_buf_size(buf);
+
808 struct fuse_out_header *out = iov[0].iov_base;
+
809 struct fuse_ll_pipe *llp;
+
810 int splice_flags;
+
811 size_t pipesize;
+
812 size_t total_buf_size;
+
813 size_t idx;
+
814 size_t headerlen;
+
815 struct fuse_bufvec pipe_buf = FUSE_BUFVEC_INIT(len);
+
816
+
817 if (se->broken_splice_nonblock)
+
818 goto fallback;
+
819
+
820 if (flags & FUSE_BUF_NO_SPLICE)
+
821 goto fallback;
+
822
+
823 total_buf_size = 0;
+
824 for (idx = buf->idx; idx < buf->count; idx++) {
+
825 total_buf_size += buf->buf[idx].size;
+
826 if (idx == buf->idx)
+
827 total_buf_size -= buf->off;
+
828 }
+
829 if (total_buf_size < 2 * pagesize)
+
830 goto fallback;
+
831
+
832 if (se->conn.proto_minor < 14 ||
+
833 !(se->conn.want_ext & FUSE_CAP_SPLICE_WRITE))
+
834 goto fallback;
+
835
+
836 llp = fuse_ll_get_pipe(se);
+
837 if (llp == NULL)
+
838 goto fallback;
+
839
+
840
+
841 headerlen = iov_length(iov, iov_count);
+
842
+
843 out->len = headerlen + len;
+
844
+
845 /*
+
846 * Heuristic for the required pipe size, does not work if the
+
847 * source contains less than page size fragments
+
848 */
+
849 pipesize = pagesize * (iov_count + buf->count + 1) + out->len;
+
850
+
851 if (llp->size < pipesize) {
+
852 if (llp->can_grow) {
+
853 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, pipesize);
+
854 if (res == -1) {
+
855 res = grow_pipe_to_max(llp->pipe[0]);
+
856 if (res > 0)
+
857 llp->size = res;
+
858 llp->can_grow = 0;
+
859 goto fallback;
+
860 }
+
861 llp->size = res;
+
862 }
+
863 if (llp->size < pipesize)
+
864 goto fallback;
+
865 }
+
866
+
867
+
868 res = vmsplice(llp->pipe[1], iov, iov_count, SPLICE_F_NONBLOCK);
+
869 if (res == -1)
+
870 goto fallback;
+
871
+
872 if (res != headerlen) {
+
873 res = -EIO;
+
874 fuse_log(FUSE_LOG_ERR, "fuse: short vmsplice to pipe: %u/%zu\n", res,
+
875 headerlen);
+
876 goto clear_pipe;
+
877 }
+
878
+
879 pipe_buf.buf[0].flags = FUSE_BUF_IS_FD;
+
880 pipe_buf.buf[0].fd = llp->pipe[1];
+
881
+
882 res = fuse_buf_copy(&pipe_buf, buf,
+ +
884 if (res < 0) {
+
885 if (res == -EAGAIN || res == -EINVAL) {
+
886 /*
+
887 * Should only get EAGAIN on kernels with
+
888 * broken SPLICE_F_NONBLOCK support (<=
+
889 * 2.6.35) where this error or a short read is
+
890 * returned even if the pipe itself is not
+
891 * full
+
892 *
+
893 * EINVAL might mean that splice can't handle
+
894 * this combination of input and output.
+
895 */
+
896 if (res == -EAGAIN)
+
897 se->broken_splice_nonblock = 1;
+
898
+
899 pthread_setspecific(se->pipe_key, NULL);
+
900 fuse_ll_pipe_free(llp);
+
901 goto fallback;
+
902 }
+
903 res = -res;
+
904 goto clear_pipe;
+
905 }
+
906
+
907 if (res != 0 && res < len) {
+
908 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
909 void *mbuf;
+
910 size_t now_len = res;
+
911 /*
+
912 * For regular files a short count is either
+
913 * 1) due to EOF, or
+
914 * 2) because of broken SPLICE_F_NONBLOCK (see above)
+
915 *
+
916 * For other inputs it's possible that we overflowed
+
917 * the pipe because of small buffer fragments.
+
918 */
+
919
+
920 res = posix_memalign(&mbuf, pagesize, len);
+
921 if (res != 0)
+
922 goto clear_pipe;
+
923
+
924 mem_buf.buf[0].mem = mbuf;
+
925 mem_buf.off = now_len;
+
926 res = fuse_buf_copy(&mem_buf, buf, 0);
+
927 if (res > 0) {
+
928 char *tmpbuf;
+
929 size_t extra_len = res;
+
930 /*
+
931 * Trickiest case: got more data. Need to get
+
932 * back the data from the pipe and then fall
+
933 * back to regular write.
+
934 */
+
935 tmpbuf = malloc(headerlen);
+
936 if (tmpbuf == NULL) {
+
937 free(mbuf);
+
938 res = ENOMEM;
+
939 goto clear_pipe;
+
940 }
+
941 res = read_back(llp->pipe[0], tmpbuf, headerlen);
+
942 free(tmpbuf);
+
943 if (res != 0) {
+
944 free(mbuf);
+
945 goto clear_pipe;
+
946 }
+
947 res = read_back(llp->pipe[0], mbuf, now_len);
+
948 if (res != 0) {
+
949 free(mbuf);
+
950 goto clear_pipe;
+
951 }
+
952 len = now_len + extra_len;
+
953 iov[iov_count].iov_base = mbuf;
+
954 iov[iov_count].iov_len = len;
+
955 iov_count++;
+
956 res = fuse_send_msg(se, ch, iov, iov_count, req);
+
957 free(mbuf);
+
958 return res;
+
959 }
+
960 free(mbuf);
+
961 res = now_len;
+
962 }
+
963 len = res;
+
964 out->len = headerlen + len;
+
965
+
966 if (se->debug) {
+
967 fuse_log(FUSE_LOG_DEBUG,
+
968 " unique: %llu, success, outsize: %i (splice)\n",
+
969 (unsigned long long) out->unique, out->len);
+
970 }
+
971
+
972 splice_flags = 0;
+
973 if ((flags & FUSE_BUF_SPLICE_MOVE) &&
+
974 (se->conn.want_ext & FUSE_CAP_SPLICE_MOVE))
+
975 splice_flags |= SPLICE_F_MOVE;
+
976
+
977 if (se->io != NULL && se->io->splice_send != NULL) {
+
978 res = se->io->splice_send(llp->pipe[0], NULL,
+
979 ch ? ch->fd : se->fd, NULL, out->len,
+
980 splice_flags, se->userdata);
+
981 } else {
+
982 res = splice(llp->pipe[0], NULL, ch ? ch->fd : se->fd, NULL,
+
983 out->len, splice_flags);
+
984 }
+
985 if (res == -1) {
+
986 res = -errno;
+
987 perror("fuse: splice from pipe");
+
988 goto clear_pipe;
+
989 }
+
990 if (res != out->len) {
+
991 res = -EIO;
+
992 fuse_log(FUSE_LOG_ERR, "fuse: short splice from pipe: %u/%u\n",
+
993 res, out->len);
+
994 goto clear_pipe;
+
995 }
+
996 return 0;
+
997
+
998clear_pipe:
+
999 fuse_ll_clear_pipe(se);
+
1000 return res;
+
1001
+
1002fallback:
+
1003 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len, req);
+
1004}
+
1005#else
+
1006static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
1007 struct iovec *iov, int iov_count,
+
1008 struct fuse_bufvec *req_data, unsigned int flags,
+
1009 fuse_req_t req)
+
1010{
+
1011 size_t len = fuse_buf_size(req_data);
+
1012 (void) flags;
+
1013
+
1014 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, req_data, len, req);
+
1015}
+
1016#endif
+
1017
+
1018int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+
1019 enum fuse_buf_copy_flags flags)
+
1020{
+
1021 struct iovec iov[2];
+
1022 struct fuse_out_header out;
+
1023 int res;
+
1024
+
1025 if (req->flags.is_uring)
+
1026 return fuse_reply_data_uring(req, bufv, flags);
+
1027
+
1028 iov[0].iov_base = &out;
+
1029 iov[0].iov_len = sizeof(struct fuse_out_header);
+
1030
+
1031 out.unique = req->unique;
+
1032 out.error = 0;
+
1033
+
1034 res = fuse_send_data_iov(req->se, req->ch, iov, 1, bufv, flags, req);
+
1035 if (res <= 0) {
+
1036 fuse_free_req(req);
+
1037 return res;
+
1038 } else {
+
1039 return fuse_reply_err(req, res);
+
1040 }
+
1041}
+
1042
+
1043int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
1044{
+
1045 struct fuse_statfs_out arg;
+
1046 size_t size = req->se->conn.proto_minor < 4 ?
+
1047 FUSE_COMPAT_STATFS_SIZE : sizeof(arg);
+
1048
+
1049 memset(&arg, 0, sizeof(arg));
+
1050 convert_statfs(stbuf, &arg.st);
+
1051
+
1052 return send_reply_ok(req, &arg, size);
+
1053}
+
1054
+
1055int fuse_reply_xattr(fuse_req_t req, size_t count)
+
1056{
+
1057 struct fuse_getxattr_out arg;
+
1058
+
1059 memset(&arg, 0, sizeof(arg));
+
1060 arg.size = count;
+
1061
+
1062 return send_reply_ok(req, &arg, sizeof(arg));
+
1063}
+
1064
+
1065int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
1066{
+
1067 struct fuse_lk_out arg;
+
1068
+
1069 memset(&arg, 0, sizeof(arg));
+
1070 arg.lk.type = lock->l_type;
+
1071 if (lock->l_type != F_UNLCK) {
+
1072 arg.lk.start = lock->l_start;
+
1073 if (lock->l_len == 0)
+
1074 arg.lk.end = OFFSET_MAX;
+
1075 else
+
1076 arg.lk.end = lock->l_start + lock->l_len - 1;
+
1077 }
+
1078 arg.lk.pid = lock->l_pid;
+
1079 return send_reply_ok(req, &arg, sizeof(arg));
+
1080}
+
1081
+
1082int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
1083{
+
1084 struct fuse_bmap_out arg;
+
1085
+
1086 memset(&arg, 0, sizeof(arg));
+
1087 arg.block = idx;
+
1088
+
1089 return send_reply_ok(req, &arg, sizeof(arg));
+
1090}
+
1091
+
1092static struct fuse_ioctl_iovec *fuse_ioctl_iovec_copy(const struct iovec *iov,
+
1093 size_t count)
+
1094{
+
1095 struct fuse_ioctl_iovec *fiov;
+
1096 size_t i;
+
1097
+
1098 fiov = malloc(sizeof(fiov[0]) * count);
+
1099 if (!fiov)
+
1100 return NULL;
+
1101
+
1102 for (i = 0; i < count; i++) {
+
1103 fiov[i].base = (uintptr_t) iov[i].iov_base;
+
1104 fiov[i].len = iov[i].iov_len;
+
1105 }
+
1106
+
1107 return fiov;
+
1108}
+
1109
+ +
1111 const struct iovec *in_iov, size_t in_count,
+
1112 const struct iovec *out_iov, size_t out_count)
+
1113{
+
1114 struct fuse_ioctl_out arg;
+
1115 struct fuse_ioctl_iovec *in_fiov = NULL;
+
1116 struct fuse_ioctl_iovec *out_fiov = NULL;
+
1117 struct iovec iov[4];
+
1118 size_t count = 1;
+
1119 int res;
+
1120
+
1121 memset(&arg, 0, sizeof(arg));
+
1122 arg.flags |= FUSE_IOCTL_RETRY;
+
1123 arg.in_iovs = in_count;
+
1124 arg.out_iovs = out_count;
+
1125 iov[count].iov_base = &arg;
+
1126 iov[count].iov_len = sizeof(arg);
+
1127 count++;
+
1128
+
1129 if (req->se->conn.proto_minor < 16) {
+
1130 if (in_count) {
+
1131 iov[count].iov_base = (void *)in_iov;
+
1132 iov[count].iov_len = sizeof(in_iov[0]) * in_count;
+
1133 count++;
+
1134 }
+
1135
+
1136 if (out_count) {
+
1137 iov[count].iov_base = (void *)out_iov;
+
1138 iov[count].iov_len = sizeof(out_iov[0]) * out_count;
+
1139 count++;
+
1140 }
+
1141 } else {
+
1142 /* Can't handle non-compat 64bit ioctls on 32bit */
+
1143 if (sizeof(void *) == 4 && req->flags.ioctl_64bit) {
+
1144 res = fuse_reply_err(req, EINVAL);
+
1145 goto out;
+
1146 }
+
1147
+
1148 if (in_count) {
+
1149 in_fiov = fuse_ioctl_iovec_copy(in_iov, in_count);
+
1150 if (!in_fiov)
+
1151 goto enomem;
+
1152
+
1153 iov[count].iov_base = (void *)in_fiov;
+
1154 iov[count].iov_len = sizeof(in_fiov[0]) * in_count;
+
1155 count++;
+
1156 }
+
1157 if (out_count) {
+
1158 out_fiov = fuse_ioctl_iovec_copy(out_iov, out_count);
+
1159 if (!out_fiov)
+
1160 goto enomem;
+
1161
+
1162 iov[count].iov_base = (void *)out_fiov;
+
1163 iov[count].iov_len = sizeof(out_fiov[0]) * out_count;
+
1164 count++;
+
1165 }
+
1166 }
+
1167
+
1168 res = send_reply_iov(req, 0, iov, count);
+
1169out:
+
1170 free(in_fiov);
+
1171 free(out_fiov);
+
1172
+
1173 return res;
+
1174
+
1175enomem:
+
1176 res = fuse_reply_err(req, ENOMEM);
+
1177 goto out;
+
1178}
+
1179
+
1180int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
1181{
+
1182 struct fuse_ioctl_out arg;
+
1183 struct iovec iov[3];
+
1184 size_t count = 1;
+
1185
+
1186 memset(&arg, 0, sizeof(arg));
+
1187 arg.result = result;
+
1188 iov[count].iov_base = &arg;
+
1189 iov[count].iov_len = sizeof(arg);
+
1190 count++;
+
1191
+
1192 if (size) {
+
1193 iov[count].iov_base = (char *) buf;
+
1194 iov[count].iov_len = size;
+
1195 count++;
+
1196 }
+
1197
+
1198 return send_reply_iov(req, 0, iov, count);
+
1199}
+
1200
+
1201int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1202 int count)
+
1203{
+
1204 struct iovec *padded_iov;
+
1205 struct fuse_ioctl_out arg;
+
1206 int res;
+
1207
+
1208 padded_iov = malloc((count + 2) * sizeof(struct iovec));
+
1209 if (padded_iov == NULL)
+
1210 return fuse_reply_err(req, ENOMEM);
+
1211
+
1212 memset(&arg, 0, sizeof(arg));
+
1213 arg.result = result;
+
1214 padded_iov[1].iov_base = &arg;
+
1215 padded_iov[1].iov_len = sizeof(arg);
+
1216
+
1217 memcpy(&padded_iov[2], iov, count * sizeof(struct iovec));
+
1218
+
1219 res = send_reply_iov(req, 0, padded_iov, count + 2);
+
1220 free(padded_iov);
+
1221
+
1222 return res;
+
1223}
+
1224
+
1225int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
1226{
+
1227 struct fuse_poll_out arg;
+
1228
+
1229 memset(&arg, 0, sizeof(arg));
+
1230 arg.revents = revents;
+
1231
+
1232 return send_reply_ok(req, &arg, sizeof(arg));
+
1233}
+
1234
+
1235int fuse_reply_lseek(fuse_req_t req, off_t off)
+
1236{
+
1237 struct fuse_lseek_out arg;
+
1238
+
1239 memset(&arg, 0, sizeof(arg));
+
1240 arg.offset = off;
+
1241
+
1242 return send_reply_ok(req, &arg, sizeof(arg));
+
1243}
+
1244
+
1245#ifdef HAVE_STATX
+
1246int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx,
+
1247 double attr_timeout)
+
1248{
+
1249 struct fuse_statx_out arg;
+
1250
+
1251 memset(&arg, 0, sizeof(arg));
+
1252 arg.flags = flags;
+
1253 arg.attr_valid = calc_timeout_sec(attr_timeout);
+
1254 arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
+
1255 memcpy(&arg.stat, statx, sizeof(arg.stat));
+
1256
+
1257 return send_reply_ok(req, &arg, sizeof(arg));
+
1258}
+
1259#else
+
+
1260int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx,
+
1261 double attr_timeout)
+
1262{
+
1263 (void)req;
+
1264 (void)flags;
+
1265 (void)statx;
+
1266 (void)attr_timeout;
+
1267
+
1268 return -ENOSYS;
+
1269}
+
+
1270#endif
+
1271
+
1272static void _do_lookup(fuse_req_t req, const fuse_ino_t nodeid,
+
1273 const void *op_in, const void *in_payload)
+
1274{
+
1275 (void)op_in;
+
1276
+
1277 char *name = (char *)in_payload;
+
1278
+
1279 if (req->se->op.lookup)
+
1280 req->se->op.lookup(req, nodeid, name);
+
1281 else
+
1282 fuse_reply_err(req, ENOSYS);
+
1283}
+
1284
+
1285static void do_lookup(fuse_req_t req, const fuse_ino_t nodeid,
+
1286 const void *inarg)
+
1287{
+
1288 _do_lookup(req, nodeid, NULL, inarg);
+
1289}
+
1290
+
1291static void _do_forget(fuse_req_t req, const fuse_ino_t nodeid,
+
1292 const void *op_in, const void *in_payload)
+
1293{
+
1294 (void)in_payload;
+
1295
+
1296 struct fuse_forget_in *arg = (struct fuse_forget_in *)op_in;
+
1297
+
1298 if (req->se->op.forget)
+
1299 req->se->op.forget(req, nodeid, arg->nlookup);
+
1300 else
+
1301 fuse_reply_none(req);
+
1302}
+
1303
+
1304static void do_forget(fuse_req_t req, const fuse_ino_t nodeid,
+
1305 const void *inarg)
+
1306{
+
1307 _do_forget(req, nodeid, inarg, NULL);
+
1308}
+
1309
+
1310static void _do_batch_forget(fuse_req_t req, const fuse_ino_t nodeid,
+
1311 const void *op_in, const void *in_payload)
+
1312{
+
1313 (void)nodeid;
+
1314 unsigned int i;
+
1315
+
1316 const struct fuse_batch_forget_in *arg = op_in;
+
1317 const struct fuse_forget_one *forgets = in_payload;
+
1318
+
1319 if (req->se->op.forget_multi) {
+
1320 req->se->op.forget_multi(req, arg->count,
+
1321 (struct fuse_forget_data *)in_payload);
+
1322 } else if (req->se->op.forget) {
+
1323 for (i = 0; i < arg->count; i++) {
+
1324 const struct fuse_forget_one *forget = &forgets[i];
+
1325 struct fuse_req *dummy_req;
+
1326
+
1327 dummy_req = fuse_ll_alloc_req(req->se);
+
1328 if (dummy_req == NULL)
+
1329 break;
+
1330
+
1331 dummy_req->unique = req->unique;
+
1332 dummy_req->ctx = req->ctx;
+
1333 dummy_req->ch = NULL;
+
1334
+
1335 req->se->op.forget(dummy_req, forget->nodeid,
+
1336 forget->nlookup);
+
1337 }
+
1338 fuse_reply_none(req);
+
1339 } else {
+
1340 fuse_reply_none(req);
+
1341 }
+
1342}
+
1343
+
1344static void do_batch_forget(fuse_req_t req, const fuse_ino_t nodeid,
+
1345 const void *inarg)
+
1346{
+
1347 struct fuse_batch_forget_in *arg = (void *)inarg;
+
1348 struct fuse_forget_one *param = (void *)PARAM(arg);
+
1349
+
1350 _do_batch_forget(req, nodeid, inarg, param);
+
1351}
+
1352
+
1353static void _do_getattr(fuse_req_t req, const fuse_ino_t nodeid,
+
1354 const void *op_in, const void *in_payload)
+
1355{
+
1356 struct fuse_getattr_in *arg = (struct fuse_getattr_in *)op_in;
+
1357 (void)in_payload;
+
1358
+
1359 struct fuse_file_info *fip = NULL;
+
1360 struct fuse_file_info fi;
+
1361
+
1362 if (req->se->conn.proto_minor >= 9) {
+
1363 if (arg->getattr_flags & FUSE_GETATTR_FH) {
+
1364 memset(&fi, 0, sizeof(fi));
+
1365 fi.fh = arg->fh;
+
1366 fip = &fi;
+
1367 }
+
1368 }
+
1369
+
1370 if (req->se->op.getattr)
+
1371 req->se->op.getattr(req, nodeid, fip);
+
1372 else
+
1373 fuse_reply_err(req, ENOSYS);
+
1374}
+
1375
+
1376static void do_getattr(fuse_req_t req, const fuse_ino_t nodeid,
+
1377 const void *inarg)
+
1378{
+
1379 _do_getattr(req, nodeid, inarg, NULL);
+
1380}
+
1381
+
1382static void _do_setattr(fuse_req_t req, const fuse_ino_t nodeid,
+
1383 const void *op_in, const void *in_payload)
+
1384{
+
1385 (void)in_payload;
+
1386 const struct fuse_setattr_in *arg = op_in;
+
1387 uint32_t valid = arg->valid;
+
1388
+
1389 if (req->se->op.setattr) {
+
1390 struct fuse_file_info *fi = NULL;
+
1391 struct fuse_file_info fi_store;
+
1392 struct stat stbuf;
+
1393 memset(&stbuf, 0, sizeof(stbuf));
+
1394 convert_attr(arg, &stbuf);
+
1395 if (arg->valid & FATTR_FH) {
+
1396 valid &= ~FATTR_FH;
+
1397 memset(&fi_store, 0, sizeof(fi_store));
+
1398 fi = &fi_store;
+
1399 fi->fh = arg->fh;
+
1400 }
+
1401 valid &= FUSE_SET_ATTR_MODE | FUSE_SET_ATTR_UID |
+
1402 FUSE_SET_ATTR_GID | FUSE_SET_ATTR_SIZE |
+
1403 FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME |
+
1404 FUSE_SET_ATTR_KILL_SUID | FUSE_SET_ATTR_KILL_SGID |
+
1405 FUSE_SET_ATTR_ATIME_NOW | FUSE_SET_ATTR_MTIME_NOW |
+
1406 FUSE_SET_ATTR_CTIME;
+
1407
+
1408 req->se->op.setattr(req, nodeid, &stbuf, valid, fi);
+
1409 } else
+
1410 fuse_reply_err(req, ENOSYS);
+
1411}
+
1412
+
1413static void do_setattr(fuse_req_t req, const fuse_ino_t nodeid,
+
1414 const void *inarg)
+
1415{
+
1416 _do_setattr(req, nodeid, inarg, NULL);
+
1417}
+
1418
+
1419static void _do_access(fuse_req_t req, const fuse_ino_t nodeid,
+
1420 const void *op_in, const void *in_payload)
+
1421{
+
1422 (void)in_payload;
+
1423 const struct fuse_access_in *arg = op_in;
+
1424
+
1425 if (req->se->op.access)
+
1426 req->se->op.access(req, nodeid, arg->mask);
+
1427 else
+
1428 fuse_reply_err(req, ENOSYS);
+
1429}
+
1430
+
1431static void do_access(fuse_req_t req, const fuse_ino_t nodeid,
+
1432 const void *inarg)
+
1433{
+
1434 _do_access(req, nodeid, inarg, NULL);
+
1435}
+
1436
+
1437static void _do_readlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1438 const void *op_in, const void *in_payload)
+
1439{
+
1440 (void)op_in;
+
1441 (void)in_payload;
+
1442
+
1443 if (req->se->op.readlink)
+
1444 req->se->op.readlink(req, nodeid);
+
1445 else
+
1446 fuse_reply_err(req, ENOSYS);
+
1447}
+
1448
+
1449static void do_readlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1450 const void *inarg)
+
1451{
+
1452 _do_readlink(req, nodeid, inarg, NULL);
+
1453}
+
1454
+
1455static void _do_mknod(fuse_req_t req, const fuse_ino_t nodeid,
+
1456 const void *op_in, const void *in_payload)
+
1457{
+
1458 const struct fuse_mknod_in *arg = (struct fuse_mknod_in *)op_in;
+
1459 const char *name = in_payload;
+
1460
+
1461 if (req->se->conn.proto_minor >= 12)
+
1462 req->ctx.umask = arg->umask;
+
1463
+
1464 if (req->se->op.mknod)
+
1465 req->se->op.mknod(req, nodeid, name, arg->mode, arg->rdev);
+
1466 else
+
1467 fuse_reply_err(req, ENOSYS);
+
1468}
+
1469
+
1470static void do_mknod(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1471{
+
1472 struct fuse_mknod_in *arg = (struct fuse_mknod_in *)inarg;
+
1473 char *name = PARAM(arg);
+
1474
+
1475 if (req->se->conn.proto_minor < 12)
+
1476 name = (char *)inarg + FUSE_COMPAT_MKNOD_IN_SIZE;
+
1477
+
1478 _do_mknod(req, nodeid, inarg, name);
+
1479}
+
1480
+
1481static void _do_mkdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1482 const void *op_in, const void *in_payload)
+
1483{
+
1484 const char *name = in_payload;
+
1485 const struct fuse_mkdir_in *arg = op_in;
+
1486
+
1487 if (req->se->conn.proto_minor >= 12)
+
1488 req->ctx.umask = arg->umask;
+
1489
+
1490 if (req->se->op.mkdir)
+
1491 req->se->op.mkdir(req, nodeid, name, arg->mode);
+
1492 else
+
1493 fuse_reply_err(req, ENOSYS);
+
1494}
+
1495
+
1496static void do_mkdir(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1497{
+
1498 const struct fuse_mkdir_in *arg = inarg;
+
1499 const char *name = PARAM(arg);
+
1500
+
1501 _do_mkdir(req, nodeid, inarg, name);
+
1502}
+
1503
+
1504static void _do_unlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1505 const void *op_in, const void *in_payload)
+
1506{
+
1507 (void)op_in;
+
1508 const char *name = in_payload;
+
1509
+
1510 if (req->se->op.unlink)
+
1511 req->se->op.unlink(req, nodeid, name);
+
1512 else
+
1513 fuse_reply_err(req, ENOSYS);
+
1514}
+
1515
+
1516static void do_unlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1517 const void *inarg)
+
1518{
+
1519 _do_unlink(req, nodeid, NULL, inarg);
+
1520}
+
1521
+
1522static void _do_rmdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1523 const void *op_in, const void *in_payload)
+
1524{
+
1525 (void)op_in;
+
1526 const char *name = in_payload;
+
1527
+
1528 if (req->se->op.rmdir)
+
1529 req->se->op.rmdir(req, nodeid, name);
+
1530 else
+
1531 fuse_reply_err(req, ENOSYS);
+
1532}
+
1533
+
1534static void do_rmdir(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1535{
+
1536 _do_rmdir(req, nodeid, NULL, inarg);
+
1537}
+
1538
+
1539static void _do_symlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1540 const void *op_in, const void *in_payload)
+
1541{
+
1542 (void)op_in;
+
1543 const char *name = (char *)in_payload;
+
1544 const char *linkname = name + strlen(name) + 1;
+
1545
+
1546 if (req->se->op.symlink)
+
1547 req->se->op.symlink(req, linkname, nodeid, name);
+
1548 else
+
1549 fuse_reply_err(req, ENOSYS);
+
1550}
+
1551
+
1552static void do_symlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1553 const void *inarg)
+
1554{
+
1555 _do_symlink(req, nodeid, NULL, inarg);
+
1556}
+
1557
+
1558static void _do_rename(fuse_req_t req, const fuse_ino_t nodeid,
+
1559 const void *op_in, const void *in_payload)
+
1560{
+
1561 const struct fuse_rename_in *arg = (struct fuse_rename_in *)op_in;
+
1562 const char *oldname = in_payload;
+
1563 const char *newname = oldname + strlen(oldname) + 1;
+
1564
+
1565 if (req->se->op.rename)
+
1566 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1567 0);
+
1568 else
+
1569 fuse_reply_err(req, ENOSYS);
+
1570}
+
1571
+
1572static void do_rename(fuse_req_t req, const fuse_ino_t nodeid,
+
1573 const void *inarg)
+
1574{
+
1575 const struct fuse_rename_in *arg = inarg;
+
1576 const void *payload = PARAM(arg);
+
1577
+
1578 _do_rename(req, nodeid, arg, payload);
+
1579}
+
1580
+
1581static void _do_rename2(fuse_req_t req, const fuse_ino_t nodeid,
+
1582 const void *op_in, const void *in_payload)
+
1583{
+
1584 const struct fuse_rename2_in *arg = op_in;
+
1585 const char *oldname = in_payload;
+
1586 const char *newname = oldname + strlen(oldname) + 1;
+
1587
+
1588 if (req->se->op.rename)
+
1589 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1590 arg->flags);
+
1591 else
+
1592 fuse_reply_err(req, ENOSYS);
+
1593}
+
1594
+
1595static void do_rename2(fuse_req_t req, const fuse_ino_t nodeid,
+
1596 const void *inarg)
+
1597{
+
1598 const struct fuse_rename2_in *arg = inarg;
+
1599 const void *payload = PARAM(arg);
+
1600
+
1601 _do_rename2(req, nodeid, arg, payload);
+
1602}
+
1603
+
1604static void _do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *op_in,
+
1605 const void *in_payload)
+
1606{
+
1607 (void)in_payload;
+
1608 const struct fuse_create_in *arg = op_in;
+
1609
+
1610 if (req->se->op.tmpfile) {
+
1611 struct fuse_file_info fi;
+
1612
+
1613 memset(&fi, 0, sizeof(fi));
+
1614 fi.flags = arg->flags;
+
1615
+
1616 if (req->se->conn.proto_minor >= 12)
+
1617 req->ctx.umask = arg->umask;
+
1618
+
1619 req->se->op.tmpfile(req, nodeid, arg->mode, &fi);
+
1620 } else
+
1621 fuse_reply_err(req, ENOSYS);
+
1622}
+
1623
+
1624static void do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1625{
+
1626 struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
+
1627
+
1628 _do_tmpfile(req, nodeid, arg, NULL);
+
1629}
+
1630
+
1631static void _do_link(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
1632 const void *in_payload)
+
1633{
+
1634 struct fuse_link_in *arg = (struct fuse_link_in *)op_in;
+
1635
+
1636 if (req->se->op.link)
+
1637 req->se->op.link(req, arg->oldnodeid, nodeid, in_payload);
+
1638 else
+
1639 fuse_reply_err(req, ENOSYS);
+
1640}
+
1641
+
1642static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1643{
+
1644 const struct fuse_link_in *arg = inarg;
+
1645 const void *name = PARAM(arg);
+
1646
+
1647 _do_link(req, nodeid, inarg, name);
+
1648}
+
1649
+
1650static void _do_create(fuse_req_t req, const fuse_ino_t nodeid,
+
1651 const void *op_in, const void *in_payload)
+
1652{
+
1653 const struct fuse_create_in *arg = op_in;
+
1654 const char *name = in_payload;
+
1655
+
1656 if (req->se->op.create) {
+
1657 struct fuse_file_info fi;
+
1658
+
1659 memset(&fi, 0, sizeof(fi));
+
1660 fi.flags = arg->flags;
+
1661
+
1662 if (req->se->conn.proto_minor >= 12)
+
1663 req->ctx.umask = arg->umask;
+
1664
+
1665 /* XXX: fuse_create_in::open_flags */
+
1666
+
1667 req->se->op.create(req, nodeid, name, arg->mode, &fi);
+
1668 } else {
+
1669 fuse_reply_err(req, ENOSYS);
+
1670 }
+
1671}
+
1672
+
1673static void do_create(fuse_req_t req, const fuse_ino_t nodeid,
+
1674 const void *inarg)
+
1675{
+
1676 const struct fuse_create_in *arg = (struct fuse_create_in *)inarg;
+
1677 void *payload = PARAM(arg);
+
1678
+
1679 if (req->se->conn.proto_minor < 12)
+
1680 payload = (char *)inarg + sizeof(struct fuse_open_in);
+
1681
+
1682 _do_create(req, nodeid, arg, payload);
+
1683}
+
1684
+
1685static void _do_open(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
1686 const void *in_payload)
+
1687{
+
1688 (void)in_payload;
+
1689 struct fuse_open_in *arg = (struct fuse_open_in *)op_in;
+
1690 struct fuse_file_info fi;
+
1691
+
1692 memset(&fi, 0, sizeof(fi));
+
1693 fi.flags = arg->flags;
+
1694
+
1695 /* XXX: fuse_open_in::open_flags */
+
1696
+
1697 if (req->se->op.open)
+
1698 req->se->op.open(req, nodeid, &fi);
+
1699 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPEN_SUPPORT)
+
1700 fuse_reply_err(req, ENOSYS);
+
1701 else
+
1702 fuse_reply_open(req, &fi);
+
1703}
+
1704
+
1705static void do_open(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1706{
+
1707 _do_open(req, nodeid, inarg, NULL);
+
1708}
+
1709
+
1710static void _do_read(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
1711 const void *in_payload)
+
1712{
+
1713 (void)in_payload;
+
1714 struct fuse_read_in *arg = (struct fuse_read_in *)op_in;
+
1715
+
1716 if (req->se->op.read) {
+
1717 struct fuse_file_info fi;
+
1718
+
1719 memset(&fi, 0, sizeof(fi));
+
1720 fi.fh = arg->fh;
+
1721 if (req->se->conn.proto_minor >= 9) {
+
1722 fi.lock_owner = arg->lock_owner;
+
1723 fi.flags = arg->flags;
+
1724 }
+
1725 req->se->op.read(req, nodeid, arg->size, arg->offset, &fi);
+
1726 } else
+
1727 fuse_reply_err(req, ENOSYS);
+
1728}
+
1729
+
1730static void do_read(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1731{
+
1732 _do_read(req, nodeid, inarg, NULL);
+
1733}
+
1734
+
1735static void _do_write(fuse_req_t req, const fuse_ino_t nodeid,
+
1736 const void *op_in, const void *in_payload)
+
1737{
+
1738 struct fuse_write_in *arg = (struct fuse_write_in *)op_in;
+
1739 const char *buf = in_payload;
+
1740 struct fuse_file_info fi;
+
1741
+
1742 memset(&fi, 0, sizeof(fi));
+
1743 fi.fh = arg->fh;
+
1744 fi.writepage = (arg->write_flags & FUSE_WRITE_CACHE) != 0;
+
1745
+
1746 if (req->se->conn.proto_minor >= 9) {
+
1747 fi.lock_owner = arg->lock_owner;
+
1748 fi.flags = arg->flags;
+
1749 }
+
1750
+
1751 if (req->se->op.write)
+
1752 req->se->op.write(req, nodeid, buf, arg->size, arg->offset,
+
1753 &fi);
+
1754 else
+
1755 fuse_reply_err(req, ENOSYS);
+
1756}
+
1757
+
1758static void do_write(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1759{
+
1760 struct fuse_write_in *arg = (struct fuse_write_in *)inarg;
+
1761 const void *payload;
+
1762
+
1763 if (req->se->conn.proto_minor < 9)
+
1764 payload = ((char *)arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1765 else
+
1766 payload = PARAM(arg);
+
1767
+
1768 _do_write(req, nodeid, arg, payload);
+
1769}
+
1770
+
1771static void _do_write_buf(fuse_req_t req, const fuse_ino_t nodeid,
+
1772 const void *op_in, struct fuse_bufvec *bufv)
+
1773{
+
1774 struct fuse_session *se = req->se;
+
1775 struct fuse_write_in *arg = (struct fuse_write_in *)op_in;
+
1776 struct fuse_file_info fi;
+
1777
+
1778 memset(&fi, 0, sizeof(fi));
+
1779 fi.fh = arg->fh;
+
1780 fi.writepage = arg->write_flags & FUSE_WRITE_CACHE;
+
1781
+
1782 if (se->conn.proto_minor >= 9) {
+
1783 fi.lock_owner = arg->lock_owner;
+
1784 fi.flags = arg->flags;
+
1785 }
+
1786
+
1787 se->op.write_buf(req, nodeid, bufv, arg->offset, &fi);
+
1788}
+
1789
+
1790static void do_write_buf(fuse_req_t req, const fuse_ino_t nodeid,
+
1791 const void *inarg, const struct fuse_buf *ibuf)
+
1792{
+
1793 struct fuse_session *se = req->se;
+
1794 struct fuse_bufvec bufv = {
+
1795 .buf[0] = *ibuf,
+
1796 .count = 1,
+
1797 };
+
1798 struct fuse_write_in *arg = (struct fuse_write_in *)inarg;
+
1799
+
1800 if (se->conn.proto_minor < 9) {
+
1801 bufv.buf[0].mem = ((char *)arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1802 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1803 FUSE_COMPAT_WRITE_IN_SIZE;
+
1804 assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD));
+
1805 } else {
+
1806 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
1807 bufv.buf[0].mem = PARAM(arg);
+
1808
+
1809 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1810 sizeof(struct fuse_write_in);
+
1811 }
+
1812 if (bufv.buf[0].size < arg->size) {
+
1813 fuse_log(FUSE_LOG_ERR,
+
1814 "fuse: %s: buffer size too small\n", __func__);
+
1815 fuse_reply_err(req, EIO);
+
1816 goto out;
+
1817 }
+
1818 bufv.buf[0].size = arg->size;
+
1819
+
1820 _do_write_buf(req, nodeid, inarg, &bufv);
+
1821
+
1822out:
+
1823 /* Need to reset the pipe if ->write_buf() didn't consume all data */
+
1824 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
1825 fuse_ll_clear_pipe(se);
+
1826}
+
1827
+
1828static void _do_flush(fuse_req_t req, const fuse_ino_t nodeid,
+
1829 const void *op_in, const void *in_payload)
+
1830{
+
1831 (void)in_payload;
+
1832 struct fuse_flush_in *arg = (struct fuse_flush_in *)op_in;
+
1833 struct fuse_file_info fi;
+
1834
+
1835 memset(&fi, 0, sizeof(fi));
+
1836 fi.fh = arg->fh;
+
1837 fi.flush = 1;
+
1838 if (req->se->conn.proto_minor >= 7)
+
1839 fi.lock_owner = arg->lock_owner;
+
1840
+
1841 if (req->se->op.flush)
+
1842 req->se->op.flush(req, nodeid, &fi);
+
1843 else
+
1844 fuse_reply_err(req, ENOSYS);
+
1845}
+
1846
+
1847static void do_flush(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1848{
+
1849 _do_flush(req, nodeid, inarg, NULL);
+
1850}
+
1851
+
1852static void _do_release(fuse_req_t req, const fuse_ino_t nodeid,
+
1853 const void *op_in, const void *in_payload)
+
1854{
+
1855 (void)in_payload;
+
1856 const struct fuse_release_in *arg = op_in;
+
1857 struct fuse_file_info fi;
+
1858
+
1859 memset(&fi, 0, sizeof(fi));
+
1860 fi.flags = arg->flags;
+
1861 fi.fh = arg->fh;
+
1862 if (req->se->conn.proto_minor >= 8) {
+
1863 fi.flush = (arg->release_flags & FUSE_RELEASE_FLUSH) ? 1 : 0;
+
1864 fi.lock_owner = arg->lock_owner;
+
1865 }
+
1866 if (arg->release_flags & FUSE_RELEASE_FLOCK_UNLOCK) {
+
1867 fi.flock_release = 1;
+
1868 fi.lock_owner = arg->lock_owner;
+
1869 }
+
1870
+
1871 if (req->se->op.release)
+
1872 req->se->op.release(req, nodeid, &fi);
+
1873 else
+
1874 fuse_reply_err(req, 0);
+
1875}
+
1876
+
1877static void do_release(fuse_req_t req, const fuse_ino_t nodeid,
+
1878 const void *inarg)
+
1879{
+
1880 _do_release(req, nodeid, inarg, NULL);
+
1881}
+
1882
+
1883static void _do_fsync(fuse_req_t req, const fuse_ino_t nodeid,
+
1884 const void *op_in, const void *in_payload)
+
1885{
+
1886 (void)in_payload;
+
1887 const struct fuse_fsync_in *arg = op_in;
+
1888 struct fuse_file_info fi;
+
1889 int datasync = arg->fsync_flags & 1;
+
1890
+
1891 memset(&fi, 0, sizeof(fi));
+
1892 fi.fh = arg->fh;
+
1893
+
1894 if (req->se->op.fsync)
+
1895 req->se->op.fsync(req, nodeid, datasync, &fi);
+
1896 else
+
1897 fuse_reply_err(req, ENOSYS);
+
1898}
+
1899
+
1900static void do_fsync(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1901{
+
1902 _do_fsync(req, nodeid, inarg, NULL);
+
1903}
+
1904
+
1905static void _do_opendir(fuse_req_t req, const fuse_ino_t nodeid,
+
1906 const void *op_in, const void *in_payload)
+
1907{
+
1908 (void)in_payload;
+
1909 const struct fuse_open_in *arg = op_in;
+
1910 struct fuse_file_info fi;
+
1911
+
1912 memset(&fi, 0, sizeof(fi));
+
1913 fi.flags = arg->flags;
+
1914 /* XXX: fuse_open_in::open_flags */
+
1915
+
1916 if (req->se->op.opendir)
+
1917 req->se->op.opendir(req, nodeid, &fi);
+
1918 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPENDIR_SUPPORT)
+
1919 fuse_reply_err(req, ENOSYS);
+
1920 else
+
1921 fuse_reply_open(req, &fi);
+
1922}
+
1923
+
1924static void do_opendir(fuse_req_t req, const fuse_ino_t nodeid,
+
1925 const void *inarg)
+
1926{
+
1927 _do_opendir(req, nodeid, inarg, NULL);
+
1928}
+
1929
+
1930static void _do_readdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1931 const void *op_in, const void *in_payload)
+
1932{
+
1933 (void)in_payload;
+
1934 struct fuse_read_in *arg = (struct fuse_read_in *)op_in;
+
1935 struct fuse_file_info fi;
+
1936
+
1937 memset(&fi, 0, sizeof(fi));
+
1938 fi.fh = arg->fh;
+
1939
+
1940 if (req->se->op.readdir)
+
1941 req->se->op.readdir(req, nodeid, arg->size, arg->offset, &fi);
+
1942 else
+
1943 fuse_reply_err(req, ENOSYS);
+
1944}
+
1945
+
1946static void do_readdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1947 const void *inarg)
+
1948{
+
1949 _do_readdir(req, nodeid, inarg, NULL);
+
1950}
+
1951
+
1952static void _do_readdirplus(fuse_req_t req, const fuse_ino_t nodeid,
+
1953 const void *op_in, const void *in_payload)
+
1954{
+
1955 (void)in_payload;
+
1956 struct fuse_read_in *arg = (struct fuse_read_in *)op_in;
+
1957 struct fuse_file_info fi;
+
1958
+
1959 memset(&fi, 0, sizeof(fi));
+
1960 fi.fh = arg->fh;
+
1961
+
1962 if (req->se->op.readdirplus)
+
1963 req->se->op.readdirplus(req, nodeid, arg->size, arg->offset, &fi);
+
1964 else
+
1965 fuse_reply_err(req, ENOSYS);
+
1966}
+
1967
+
1968static void do_readdirplus(fuse_req_t req, const fuse_ino_t nodeid,
+
1969 const void *inarg)
+
1970{
+
1971 _do_readdirplus(req, nodeid, inarg, NULL);
+
1972}
+
1973
+
1974static void _do_releasedir(fuse_req_t req, const fuse_ino_t nodeid,
+
1975 const void *op_in, const void *in_payload)
+
1976{
+
1977 (void)in_payload;
+
1978 struct fuse_release_in *arg = (struct fuse_release_in *)op_in;
+
1979 struct fuse_file_info fi;
+
1980
+
1981 memset(&fi, 0, sizeof(fi));
+
1982 fi.flags = arg->flags;
+
1983 fi.fh = arg->fh;
+
1984
+
1985 if (req->se->op.releasedir)
+
1986 req->se->op.releasedir(req, nodeid, &fi);
+
1987 else
+
1988 fuse_reply_err(req, 0);
+
1989}
+
1990
+
1991static void do_releasedir(fuse_req_t req, const fuse_ino_t nodeid,
+
1992 const void *inarg)
+
1993{
+
1994 _do_releasedir(req, nodeid, inarg, NULL);
+
1995}
+
1996
+
1997static void _do_fsyncdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1998 const void *op_in, const void *in_payload)
+
1999{
+
2000 (void)in_payload;
+
2001 struct fuse_fsync_in *arg = (struct fuse_fsync_in *)op_in;
+
2002 struct fuse_file_info fi;
+
2003 int datasync = arg->fsync_flags & 1;
+
2004
+
2005 memset(&fi, 0, sizeof(fi));
+
2006 fi.fh = arg->fh;
+
2007
+
2008 if (req->se->op.fsyncdir)
+
2009 req->se->op.fsyncdir(req, nodeid, datasync, &fi);
+
2010 else
+
2011 fuse_reply_err(req, ENOSYS);
+
2012}
+
2013static void do_fsyncdir(fuse_req_t req, const fuse_ino_t nodeid,
+
2014 const void *inarg)
+
2015{
+
2016 _do_fsyncdir(req, nodeid, inarg, NULL);
+
2017}
+
2018
+
2019static void _do_statfs(fuse_req_t req, const fuse_ino_t nodeid,
+
2020 const void *op_in, const void *in_payload)
+
2021{
+
2022 (void) nodeid;
+
2023 (void)op_in;
+
2024 (void)in_payload;
+
2025
+
2026 if (req->se->op.statfs)
+
2027 req->se->op.statfs(req, nodeid);
+
2028 else {
+
2029 struct statvfs buf = {
+
2030 .f_namemax = 255,
+
2031 .f_bsize = 512,
+
2032 };
+
2033 fuse_reply_statfs(req, &buf);
+
2034 }
+
2035}
+
2036static void do_statfs(fuse_req_t req, const fuse_ino_t nodeid,
+
2037 const void *inarg)
+
2038{
+
2039 _do_statfs(req, nodeid, inarg, NULL);
+
2040}
+
2041
+
2042static void _do_setxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2043 const void *op_in, const void *in_payload)
+
2044{
+
2045 struct fuse_setxattr_in *arg = (struct fuse_setxattr_in *)op_in;
+
2046 const char *name = in_payload;
+
2047 const char *value = name + strlen(name) + 1;
+
2048
+
2049 /* XXX:The API should be extended to support extra_flags/setxattr_flags */
+
2050
+
2051 if (req->se->op.setxattr)
+
2052 req->se->op.setxattr(req, nodeid, name, value, arg->size,
+
2053 arg->flags);
+
2054 else
+
2055 fuse_reply_err(req, ENOSYS);
+
2056}
+
2057static void do_setxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2058 const void *inarg)
+
2059{
+
2060 struct fuse_session *se = req->se;
+
2061 unsigned int xattr_ext = !!(se->conn.want & FUSE_CAP_SETXATTR_EXT);
+
2062 const struct fuse_setxattr_in *arg = inarg;
+
2063 char *payload = xattr_ext ? PARAM(arg) :
+
2064 (char *)arg + FUSE_COMPAT_SETXATTR_IN_SIZE;
+
2065
+
2066 _do_setxattr(req, nodeid, arg, payload);
+
2067}
+
2068
+
2069static void _do_getxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2070 const void *op_in, const void *in_payload)
+
2071{
+
2072 const struct fuse_getxattr_in *arg = op_in;
+
2073
+
2074 if (req->se->op.getxattr)
+
2075 req->se->op.getxattr(req, nodeid, in_payload, arg->size);
+
2076 else
+
2077 fuse_reply_err(req, ENOSYS);
+
2078}
+
2079
+
2080static void do_getxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2081 const void *inarg)
+
2082{
+
2083 const struct fuse_getxattr_in *arg = inarg;
+
2084 const void *payload = PARAM(arg);
+
2085
+
2086 _do_getxattr(req, nodeid, arg, payload);
+
2087}
+
2088
+
2089static void _do_listxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2090 const void *inarg, const void *in_payload)
+
2091{
+
2092 (void)in_payload;
+
2093 const struct fuse_getxattr_in *arg = inarg;
+
2094
+
2095 if (req->se->op.listxattr)
+
2096 req->se->op.listxattr(req, nodeid, arg->size);
+
2097 else
+
2098 fuse_reply_err(req, ENOSYS);
+
2099}
+
2100static void do_listxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2101 const void *inarg)
+
2102{
+
2103 _do_listxattr(req, nodeid, inarg, NULL);
+
2104}
+
2105
+
2106static void _do_removexattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2107 const void *inarg, const void *in_payload)
+
2108{
+
2109 (void)inarg;
+
2110 const char *name = in_payload;
+
2111
+
2112 if (req->se->op.removexattr)
+
2113 req->se->op.removexattr(req, nodeid, name);
+
2114 else
+
2115 fuse_reply_err(req, ENOSYS);
+
2116}
+
2117static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2118{
+
2119 _do_removexattr(req, nodeid, NULL, inarg);
+
2120}
+
2121
+
2122static void convert_fuse_file_lock(const struct fuse_file_lock *fl,
+
2123 struct flock *flock)
+
2124{
+
2125 memset(flock, 0, sizeof(struct flock));
+
2126 flock->l_type = fl->type;
+
2127 flock->l_whence = SEEK_SET;
+
2128 flock->l_start = fl->start;
+
2129 if (fl->end == OFFSET_MAX)
+
2130 flock->l_len = 0;
+
2131 else
+
2132 flock->l_len = fl->end - fl->start + 1;
+
2133 flock->l_pid = fl->pid;
+
2134}
+
2135
+
2136static void _do_getlk(fuse_req_t req, const fuse_ino_t nodeid,
+
2137 const void *op_in, const void *in_payload)
+
2138{
+
2139 (void)in_payload;
+
2140 const struct fuse_lk_in *arg = op_in;
+
2141 struct fuse_file_info fi;
+
2142 struct flock flock;
+
2143
+
2144 memset(&fi, 0, sizeof(fi));
+
2145 fi.fh = arg->fh;
+
2146 fi.lock_owner = arg->owner;
+
2147
+
2148 convert_fuse_file_lock(&arg->lk, &flock);
+
2149 if (req->se->op.getlk)
+
2150 req->se->op.getlk(req, nodeid, &fi, &flock);
+
2151 else
+
2152 fuse_reply_err(req, ENOSYS);
+
2153}
+
2154static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2155{
+
2156 _do_getlk(req, nodeid, inarg, NULL);
+
2157}
+
2158
+
2159static void do_setlk_common(fuse_req_t req, const fuse_ino_t nodeid,
+
2160 const void *op_in, int sleep)
+
2161{
+
2162 const struct fuse_lk_in *arg = op_in;
+
2163 struct fuse_file_info fi;
+
2164 struct flock flock;
+
2165
+
2166 memset(&fi, 0, sizeof(fi));
+
2167 fi.fh = arg->fh;
+
2168 fi.lock_owner = arg->owner;
+
2169
+
2170 if (arg->lk_flags & FUSE_LK_FLOCK) {
+
2171 int op = 0;
+
2172
+
2173 switch (arg->lk.type) {
+
2174 case F_RDLCK:
+
2175 op = LOCK_SH;
+
2176 break;
+
2177 case F_WRLCK:
+
2178 op = LOCK_EX;
+
2179 break;
+
2180 case F_UNLCK:
+
2181 op = LOCK_UN;
+
2182 break;
+
2183 }
+
2184 if (!sleep)
+
2185 op |= LOCK_NB;
+
2186
+
2187 if (req->se->op.flock)
+
2188 req->se->op.flock(req, nodeid, &fi, op);
+
2189 else
+
2190 fuse_reply_err(req, ENOSYS);
+
2191 } else {
+
2192 convert_fuse_file_lock(&arg->lk, &flock);
+
2193 if (req->se->op.setlk)
+
2194 req->se->op.setlk(req, nodeid, &fi, &flock, sleep);
+
2195 else
+
2196 fuse_reply_err(req, ENOSYS);
+
2197 }
+
2198}
+
2199
+
2200static void _do_setlk(fuse_req_t req, const fuse_ino_t nodeid,
+
2201 const void *op_in, const void *in_payload)
+
2202{
+
2203 (void)in_payload;
+
2204 do_setlk_common(req, nodeid, op_in, 0);
+
2205}
+
2206
+
2207static void do_setlk(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
2208{
+
2209 _do_setlk(req, nodeid, inarg, NULL);
+
2210}
+
2211
+
2212static void _do_setlkw(fuse_req_t req, const fuse_ino_t nodeid,
+
2213 const void *op_in, const void *in_payload)
+
2214{
+
2215 (void)in_payload;
+
2216 do_setlk_common(req, nodeid, op_in, 1);
+
2217}
+
2218static void do_setlkw(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2219{
+
2220 _do_setlkw(req, nodeid, inarg, NULL);
+
2221}
+
2222
+
2223static int find_interrupted(struct fuse_session *se, struct fuse_req *req)
+
2224{
+
2225 struct fuse_req *curr;
+
2226
+
2227 for (curr = se->list.next; curr != &se->list; curr = curr->next) {
+
2228 if (curr->unique == req->u.i.unique) {
+ +
2230 void *data;
+
2231
+
2232 curr->ref_cnt++;
+
2233 pthread_mutex_unlock(&se->lock);
+
2234
+
2235 /* Ugh, ugly locking */
+
2236 pthread_mutex_lock(&curr->lock);
+
2237 pthread_mutex_lock(&se->lock);
+
2238 curr->interrupted = 1;
+
2239 func = curr->u.ni.func;
+
2240 data = curr->u.ni.data;
+
2241 pthread_mutex_unlock(&se->lock);
+
2242 if (func)
+
2243 func(curr, data);
+
2244 pthread_mutex_unlock(&curr->lock);
+
2245
+
2246 pthread_mutex_lock(&se->lock);
+
2247 curr->ref_cnt--;
+
2248 if (!curr->ref_cnt) {
+
2249 destroy_req(curr);
+
2250 }
+
2251
+
2252 return 1;
+
2253 }
+
2254 }
+
2255 for (curr = se->interrupts.next; curr != &se->interrupts;
+
2256 curr = curr->next) {
+
2257 if (curr->u.i.unique == req->u.i.unique)
+
2258 return 1;
+
2259 }
+
2260 return 0;
+
2261}
+
2262
+
2263static void _do_interrupt(fuse_req_t req, const fuse_ino_t nodeid,
+
2264 const void *op_in, const void *in_payload)
+
2265{
+
2266 (void)in_payload;
+
2267 const struct fuse_interrupt_in *arg = op_in;
+
2268 struct fuse_session *se = req->se;
+
2269
+
2270 (void) nodeid;
+
2271 if (se->debug)
+
2272 fuse_log(FUSE_LOG_DEBUG, "INTERRUPT: %llu\n",
+
2273 (unsigned long long) arg->unique);
+
2274
+
2275 req->u.i.unique = arg->unique;
+
2276
+
2277 pthread_mutex_lock(&se->lock);
+
2278 if (find_interrupted(se, req)) {
+
2279 fuse_chan_put(req->ch);
+
2280 req->ch = NULL;
+
2281 destroy_req(req);
+
2282 } else
+
2283 list_add_req(req, &se->interrupts);
+
2284 pthread_mutex_unlock(&se->lock);
+
2285}
+
2286static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2287{
+
2288 _do_interrupt(req, nodeid, inarg, NULL);
+
2289}
+
2290
+
2291static struct fuse_req *check_interrupt(struct fuse_session *se,
+
2292 struct fuse_req *req)
+
2293{
+
2294 struct fuse_req *curr;
+
2295
+
2296 for (curr = se->interrupts.next; curr != &se->interrupts;
+
2297 curr = curr->next) {
+
2298 if (curr->u.i.unique == req->unique) {
+
2299 req->interrupted = 1;
+
2300 list_del_req(curr);
+
2301 fuse_chan_put(curr->ch);
+
2302 curr->ch = NULL;
+
2303 destroy_req(curr);
+
2304 return NULL;
+
2305 }
+
2306 }
+
2307 curr = se->interrupts.next;
+
2308 if (curr != &se->interrupts) {
+
2309 list_del_req(curr);
+
2310 list_init_req(curr);
+
2311 return curr;
+
2312 } else
+
2313 return NULL;
+
2314}
+
2315
+
2316static void _do_bmap(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
2317 const void *in_payload)
+
2318{
+
2319 (void)in_payload;
+
2320 const struct fuse_bmap_in *arg = op_in;
+
2321
+
2322 if (req->se->op.bmap)
+
2323 req->se->op.bmap(req, nodeid, arg->blocksize, arg->block);
+
2324 else
+
2325 fuse_reply_err(req, ENOSYS);
+
2326}
+
2327static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2328{
+
2329 _do_bmap(req, nodeid, inarg, NULL);
+
2330}
+
2331
+
2332static void _do_ioctl(fuse_req_t req, const fuse_ino_t nodeid,
+
2333 const void *op_in, const void *in_payload)
+
2334{
+
2335 struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *)op_in;
+
2336 unsigned int flags = arg->flags;
+
2337 const void *in_buf = in_payload;
+
2338 struct fuse_file_info fi;
+
2339
+
2340 if (flags & FUSE_IOCTL_DIR &&
+
2341 !(req->se->conn.want_ext & FUSE_CAP_IOCTL_DIR)) {
+
2342 fuse_reply_err(req, ENOTTY);
+
2343 return;
+
2344 }
+
2345
+
2346 memset(&fi, 0, sizeof(fi));
+
2347 fi.fh = arg->fh;
+
2348
+
2349 if (sizeof(void *) == 4 && req->se->conn.proto_minor >= 16 &&
+
2350 !(flags & FUSE_IOCTL_32BIT)) {
+
2351 req->flags.ioctl_64bit = 1;
+
2352 }
+
2353
+
2354 if (req->se->op.ioctl)
+
2355 req->se->op.ioctl(req, nodeid, arg->cmd,
+
2356 (void *)(uintptr_t)arg->arg, &fi, flags,
+
2357 in_buf, arg->in_size, arg->out_size);
+
2358 else
+
2359 fuse_reply_err(req, ENOSYS);
+
2360}
+
2361static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2362{
+
2363 const struct fuse_ioctl_in *arg = inarg;
+
2364 void *in_buf = arg->in_size ? PARAM(arg) : NULL;
+
2365
+
2366 _do_ioctl(req, nodeid, arg, in_buf);
+
2367}
+
2368
+
2369void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
2370{
+
2371 free(ph);
+
2372}
+
2373
+
2374static void _do_poll(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
2375 const void *in_payload)
+
2376{
+
2377 (void)in_payload;
+
2378 struct fuse_poll_in *arg = (struct fuse_poll_in *)op_in;
+
2379 struct fuse_file_info fi;
+
2380
+
2381 memset(&fi, 0, sizeof(fi));
+
2382 fi.fh = arg->fh;
+
2383 fi.poll_events = arg->events;
+
2384
+
2385 if (req->se->op.poll) {
+
2386 struct fuse_pollhandle *ph = NULL;
+
2387
+
2388 if (arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) {
+
2389 ph = malloc(sizeof(struct fuse_pollhandle));
+
2390 if (ph == NULL) {
+
2391 fuse_reply_err(req, ENOMEM);
+
2392 return;
+
2393 }
+
2394 ph->kh = arg->kh;
+
2395 ph->se = req->se;
+
2396 }
+
2397
+
2398 req->se->op.poll(req, nodeid, &fi, ph);
+
2399 } else {
+
2400 fuse_reply_err(req, ENOSYS);
+
2401 }
+
2402}
+
2403
+
2404static void do_poll(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
2405{
+
2406 _do_poll(req, nodeid, inarg, NULL);
+
2407}
+
2408
+
2409static void _do_fallocate(fuse_req_t req, const fuse_ino_t nodeid,
+
2410 const void *op_in, const void *in_payload)
+
2411{
+
2412 (void)in_payload;
+
2413 const struct fuse_fallocate_in *arg = op_in;
+
2414 struct fuse_file_info fi;
+
2415
+
2416 memset(&fi, 0, sizeof(fi));
+
2417 fi.fh = arg->fh;
+
2418
+
2419 if (req->se->op.fallocate)
+
2420 req->se->op.fallocate(req, nodeid, arg->mode, arg->offset,
+
2421 arg->length, &fi);
+
2422 else
+
2423 fuse_reply_err(req, ENOSYS);
+
2424}
+
2425
+
2426static void do_fallocate(fuse_req_t req, const fuse_ino_t nodeid,
+
2427 const void *inarg)
+
2428{
+
2429 _do_fallocate(req, nodeid, inarg, NULL);
+
2430}
+
2431
+
2432static void copy_file_range_common(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2433 const struct fuse_copy_file_range_in *arg)
+
2434{
+
2435 struct fuse_file_info fi_in, fi_out;
+
2436
+
2437 memset(&fi_in, 0, sizeof(fi_in));
+
2438 fi_in.fh = arg->fh_in;
+
2439
+
2440 memset(&fi_out, 0, sizeof(fi_out));
+
2441 fi_out.fh = arg->fh_out;
+
2442
+
2443 if (req->se->op.copy_file_range)
+
2444 req->se->op.copy_file_range(req, nodeid_in, arg->off_in, &fi_in,
+
2445 arg->nodeid_out, arg->off_out,
+
2446 &fi_out, arg->len, arg->flags);
+
2447 else
+
2448 fuse_reply_err(req, ENOSYS);
+
2449}
+
2450
+
2451static void _do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2452 const void *op_in, const void *in_payload)
+
2453{
+
2454 const struct fuse_copy_file_range_in *arg = op_in;
+
2455 struct fuse_copy_file_range_in arg_tmp;
+
2456
+
2457 (void) in_payload;
+
2458 /* fuse_write_out can only handle 32bit copy size */
+
2459 if (arg->len > 0xfffff000) {
+
2460 arg_tmp = *arg;
+
2461 arg_tmp.len = 0xfffff000;
+
2462 arg = &arg_tmp;
+
2463 }
+
2464 copy_file_range_common(req, nodeid_in, arg);
+
2465}
+
2466
+
2467static void do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2468 const void *inarg)
+
2469{
+
2470 _do_copy_file_range(req, nodeid_in, inarg, NULL);
+
2471}
+
2472
+
2473static void _do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2474 const void *op_in, const void *in_payload)
+
2475{
+
2476 (void) in_payload;
+
2477 req->flags.is_copy_file_range_64 = 1;
+
2478 /* Limit size on 32bit userspace to avoid conversion overflow */
+
2479 if (sizeof(size_t) == 4)
+
2480 _do_copy_file_range(req, nodeid_in, op_in, NULL);
+
2481 else
+
2482 copy_file_range_common(req, nodeid_in, op_in);
+
2483}
+
2484
+
2485static void do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2486 const void *inarg)
+
2487{
+
2488 _do_copy_file_range_64(req, nodeid_in, inarg, NULL);
+
2489}
+
2490
+
2491/*
+
2492 * Note that the uint64_t offset in struct fuse_lseek_in is derived from
+
2493 * linux kernel loff_t and is therefore signed.
+
2494 */
+
2495static void _do_lseek(fuse_req_t req, const fuse_ino_t nodeid,
+
2496 const void *op_in, const void *in_payload)
+
2497{
+
2498 (void)in_payload;
+
2499 const struct fuse_lseek_in *arg = op_in;
+
2500 struct fuse_file_info fi;
+
2501
+
2502 memset(&fi, 0, sizeof(fi));
+
2503 fi.fh = arg->fh;
+
2504
+
2505 if (req->se->op.lseek)
+
2506 req->se->op.lseek(req, nodeid, arg->offset, arg->whence, &fi);
+
2507 else
+
2508 fuse_reply_err(req, ENOSYS);
+
2509}
+
2510
+
2511static void do_lseek(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
2512{
+
2513 _do_lseek(req, nodeid, inarg, NULL);
+
2514}
+
2515
+
2516#ifdef HAVE_STATX
+
2517static void _do_statx(fuse_req_t req, const fuse_ino_t nodeid,
+
2518 const void *op_in, const void *in_payload)
+
2519{
+
2520 (void)in_payload;
+
2521 const struct fuse_statx_in *arg = op_in;
+
2522 struct fuse_file_info *fip = NULL;
+
2523 struct fuse_file_info fi;
+
2524
+
2525 if (arg->getattr_flags & FUSE_GETATTR_FH) {
+
2526 memset(&fi, 0, sizeof(fi));
+
2527 fi.fh = arg->fh;
+
2528 fip = &fi;
+
2529 }
+
2530
+
2531 if (req->se->op.statx)
+
2532 req->se->op.statx(req, nodeid, arg->sx_flags, arg->sx_mask, fip);
+
2533 else
+
2534 fuse_reply_err(req, ENOSYS);
+
2535}
+
2536#else
+
2537static void _do_statx(fuse_req_t req, const fuse_ino_t nodeid,
+
2538 const void *op_in, const void *in_payload)
+
2539{
+
2540 (void)in_payload;
+
2541 (void)req;
+
2542 (void)nodeid;
+
2543 (void)op_in;
+
2544}
+
2545#endif
+
2546
+
2547static void do_statx(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2548{
+
2549 _do_statx(req, nodeid, inarg, NULL);
+
2550}
+
2551
+
2552static bool want_flags_valid(uint64_t capable, uint64_t want)
+
2553{
+
2554 uint64_t unknown_flags = want & (~capable);
+
2555 if (unknown_flags != 0) {
+
2556 fuse_log(FUSE_LOG_ERR,
+
2557 "fuse: unknown connection 'want' flags: 0x%08llx\n",
+
2558 (unsigned long long)unknown_flags);
+
2559 return false;
+
2560 }
+
2561 return true;
+
2562}
+
2563
+ +
2568{
+
2569 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
2570
+
2571 /*
+
2572 * Convert want to want_ext if necessary.
+
2573 * For the high level interface this function might be called
+
2574 * twice, once from the high level interface and once from the
+
2575 * low level interface. Both, with different want_ext_default and
+
2576 * want_default values. In order to suppress a failure for the
+
2577 * second call, we check if the lower 32 bits of want_ext are
+
2578 * already set to the value of want.
+
2579 */
+
2580 if (conn->want != se->conn_want &&
+
2581 fuse_lower_32_bits(conn->want_ext) != conn->want) {
+
2582 if (conn->want_ext != se->conn_want_ext) {
+
2583 fuse_log(FUSE_LOG_ERR,
+
2584 "%s: Both conn->want_ext and conn->want are set.\n"
+
2585 "want=%x want_ext=%llx, se->want=%x se->want_ext=%llx\n",
+
2586 __func__, conn->want,
+
2587 (unsigned long long)conn->want_ext,
+
2588 se->conn_want,
+
2589 (unsigned long long)se->conn_want_ext);
+
2590 return -EINVAL;
+
2591 }
+
2592
+
2593 /* high bits from want_ext, low bits from want */
+
2594 conn->want_ext = fuse_higher_32_bits(conn->want_ext) |
+
2595 conn->want;
+
2596 }
+
2597
+
2598 /* ensure there won't be a second conversion */
+
2599 conn->want = fuse_lower_32_bits(conn->want_ext);
+
2600
+
2601 return 0;
+
2602}
+
2603
+
2604bool fuse_set_feature_flag(struct fuse_conn_info *conn,
+
2605 uint64_t flag)
+
2606{
+
2607 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
2608
+
2609 if (conn->capable_ext & flag) {
+
2610 conn->want_ext |= flag;
+
2611 se->conn_want_ext |= flag;
+
2612 conn->want |= flag;
+
2613 se->conn_want |= flag;
+
2614 return true;
+
2615 }
+
2616 return false;
+
2617}
+
2618
+
2619void fuse_unset_feature_flag(struct fuse_conn_info *conn,
+
2620 uint64_t flag)
+
2621{
+
2622 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
2623
+
2624 conn->want_ext &= ~flag;
+
2625 se->conn_want_ext &= ~flag;
+
2626 conn->want &= ~flag;
+
2627 se->conn_want &= ~flag;
+
2628}
+
2629
+
2630bool fuse_get_feature_flag(struct fuse_conn_info *conn,
+
2631 uint64_t flag)
+
2632{
+
2633 return conn->capable_ext & flag ? true : false;
+
2634}
+
2635
+
2636/* Prevent bogus data races (bogus since "init" is called before
+
2637 * multi-threading becomes relevant */
+
2638static __attribute__((no_sanitize("thread"))) void
+
2639_do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
2640 const void *in_payload)
+
2641{
+
2642 (void)in_payload;
+
2643 const struct fuse_init_in *arg = op_in;
+
2644 struct fuse_init_out outarg;
+
2645 struct fuse_session *se = req->se;
+
2646 size_t bufsize = se->bufsize;
+
2647 size_t outargsize = sizeof(outarg);
+
2648 uint64_t inargflags = 0;
+
2649 uint64_t outargflags = 0;
+
2650 bool buf_reallocable = se->buf_reallocable;
+
2651 (void) nodeid;
+
2652 bool enable_io_uring = false;
+
2653
+
2654 if (se->debug) {
+
2655 fuse_log(FUSE_LOG_DEBUG, "INIT: %u.%u\n", arg->major, arg->minor);
+
2656 if (arg->major == 7 && arg->minor >= 6) {
+
2657 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
2658 fuse_log(FUSE_LOG_DEBUG, "max_readahead=0x%08x\n",
+
2659 arg->max_readahead);
+
2660 }
+
2661 }
+
2662 se->conn.proto_major = arg->major;
+
2663 se->conn.proto_minor = arg->minor;
+
2664 se->conn.capable_ext = 0;
+
2665 se->conn.want_ext = 0;
+
2666
+
2667 memset(&outarg, 0, sizeof(outarg));
+
2668 outarg.major = FUSE_KERNEL_VERSION;
+
2669 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
2670
+
2671 if (arg->major < 7) {
+
2672 fuse_log(FUSE_LOG_ERR, "fuse: unsupported protocol version: %u.%u\n",
+
2673 arg->major, arg->minor);
+
2674 fuse_reply_err(req, EPROTO);
+
2675 return;
+
2676 }
+
2677
+
2678 if (arg->major > 7) {
+
2679 /* Wait for a second INIT request with a 7.X version */
+
2680 send_reply_ok(req, &outarg, sizeof(outarg));
+
2681 return;
+
2682 }
+
2683
+
2684 if (arg->minor >= 6) {
+
2685 if (arg->max_readahead < se->conn.max_readahead)
+
2686 se->conn.max_readahead = arg->max_readahead;
+
2687 inargflags = arg->flags;
+
2688 if (inargflags & FUSE_INIT_EXT)
+
2689 inargflags = inargflags | (uint64_t) arg->flags2 << 32;
+
2690 if (inargflags & FUSE_ASYNC_READ)
+
2691 se->conn.capable_ext |= FUSE_CAP_ASYNC_READ;
+
2692 if (inargflags & FUSE_POSIX_LOCKS)
+
2693 se->conn.capable_ext |= FUSE_CAP_POSIX_LOCKS;
+
2694 if (inargflags & FUSE_ATOMIC_O_TRUNC)
+
2695 se->conn.capable_ext |= FUSE_CAP_ATOMIC_O_TRUNC;
+
2696 if (inargflags & FUSE_EXPORT_SUPPORT)
+
2697 se->conn.capable_ext |= FUSE_CAP_EXPORT_SUPPORT;
+
2698 if (inargflags & FUSE_DONT_MASK)
+
2699 se->conn.capable_ext |= FUSE_CAP_DONT_MASK;
+
2700 if (inargflags & FUSE_FLOCK_LOCKS)
+
2701 se->conn.capable_ext |= FUSE_CAP_FLOCK_LOCKS;
+
2702 if (inargflags & FUSE_AUTO_INVAL_DATA)
+
2703 se->conn.capable_ext |= FUSE_CAP_AUTO_INVAL_DATA;
+
2704 if (inargflags & FUSE_DO_READDIRPLUS)
+
2705 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS;
+
2706 if (inargflags & FUSE_READDIRPLUS_AUTO)
+
2707 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS_AUTO;
+
2708 if (inargflags & FUSE_ASYNC_DIO)
+
2709 se->conn.capable_ext |= FUSE_CAP_ASYNC_DIO;
+
2710 if (inargflags & FUSE_WRITEBACK_CACHE)
+
2711 se->conn.capable_ext |= FUSE_CAP_WRITEBACK_CACHE;
+
2712 if (inargflags & FUSE_NO_OPEN_SUPPORT)
+
2713 se->conn.capable_ext |= FUSE_CAP_NO_OPEN_SUPPORT;
+
2714 if (inargflags & FUSE_PARALLEL_DIROPS)
+
2715 se->conn.capable_ext |= FUSE_CAP_PARALLEL_DIROPS;
+
2716 if (inargflags & FUSE_POSIX_ACL)
+
2717 se->conn.capable_ext |= FUSE_CAP_POSIX_ACL;
+
2718 if (inargflags & FUSE_HANDLE_KILLPRIV)
+
2719 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV;
+
2720 if (inargflags & FUSE_HANDLE_KILLPRIV_V2)
+
2721 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV_V2;
+
2722 if (inargflags & FUSE_CACHE_SYMLINKS)
+
2723 se->conn.capable_ext |= FUSE_CAP_CACHE_SYMLINKS;
+
2724 if (inargflags & FUSE_NO_OPENDIR_SUPPORT)
+
2725 se->conn.capable_ext |= FUSE_CAP_NO_OPENDIR_SUPPORT;
+
2726 if (inargflags & FUSE_EXPLICIT_INVAL_DATA)
+
2727 se->conn.capable_ext |= FUSE_CAP_EXPLICIT_INVAL_DATA;
+
2728 if (inargflags & FUSE_SETXATTR_EXT)
+
2729 se->conn.capable_ext |= FUSE_CAP_SETXATTR_EXT;
+
2730 if (!(inargflags & FUSE_MAX_PAGES)) {
+
2731 size_t max_bufsize =
+
2732 FUSE_DEFAULT_MAX_PAGES_PER_REQ * getpagesize()
+
2733 + FUSE_BUFFER_HEADER_SIZE;
+
2734 if (bufsize > max_bufsize) {
+
2735 bufsize = max_bufsize;
+
2736 }
+
2737 buf_reallocable = false;
+
2738 }
+
2739 if (inargflags & FUSE_DIRECT_IO_ALLOW_MMAP)
+
2740 se->conn.capable_ext |= FUSE_CAP_DIRECT_IO_ALLOW_MMAP;
+
2741 if (arg->minor >= 38 || (inargflags & FUSE_HAS_EXPIRE_ONLY))
+
2742 se->conn.capable_ext |= FUSE_CAP_EXPIRE_ONLY;
+
2743 if (inargflags & FUSE_PASSTHROUGH)
+
2744 se->conn.capable_ext |= FUSE_CAP_PASSTHROUGH;
+
2745 if (inargflags & FUSE_NO_EXPORT_SUPPORT)
+
2746 se->conn.capable_ext |= FUSE_CAP_NO_EXPORT_SUPPORT;
+
2747 if (inargflags & FUSE_OVER_IO_URING)
+
2748 se->conn.capable_ext |= FUSE_CAP_OVER_IO_URING;
+
2749
+
2750 } else {
+
2751 se->conn.max_readahead = 0;
+
2752 }
+
2753
+
2754 if (se->conn.proto_minor >= 14) {
+
2755#ifdef HAVE_SPLICE
+
2756#ifdef HAVE_VMSPLICE
+
2757 if ((se->io == NULL) || (se->io->splice_send != NULL)) {
+
2758 se->conn.capable_ext |= FUSE_CAP_SPLICE_WRITE |
+ +
2760 }
+
2761#endif
+
2762 if ((se->io == NULL) || (se->io->splice_receive != NULL)) {
+
2763 se->conn.capable_ext |= FUSE_CAP_SPLICE_READ;
+
2764 }
+
2765#endif
+
2766 }
+
2767 if (se->conn.proto_minor >= 18)
+
2768 se->conn.capable_ext |= FUSE_CAP_IOCTL_DIR;
+
2769
+
2770 /* Default settings for modern filesystems.
+
2771 *
+
2772 * Most of these capabilities were disabled by default in
+
2773 * libfuse2 for backwards compatibility reasons. In libfuse3,
+
2774 * we can finally enable them by default (as long as they're
+
2775 * supported by the kernel).
+
2776 */
+
2777#define LL_SET_DEFAULT(cond, cap) \
+
2778 if ((cond)) \
+
2779 fuse_set_feature_flag(&se->conn, cap)
+
2780
+
2781 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_READ);
+
2782 LL_SET_DEFAULT(1, FUSE_CAP_AUTO_INVAL_DATA);
+
2783 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_DIO);
+
2784 LL_SET_DEFAULT(1, FUSE_CAP_IOCTL_DIR);
+
2785 LL_SET_DEFAULT(1, FUSE_CAP_ATOMIC_O_TRUNC);
+
2786 LL_SET_DEFAULT(se->op.write_buf, FUSE_CAP_SPLICE_READ);
+
2787 LL_SET_DEFAULT(se->op.getlk && se->op.setlk,
+ +
2789 LL_SET_DEFAULT(se->op.flock, FUSE_CAP_FLOCK_LOCKS);
+
2790 LL_SET_DEFAULT(se->op.readdirplus, FUSE_CAP_READDIRPLUS);
+
2791 LL_SET_DEFAULT(se->op.readdirplus && se->op.readdir,
+ +
2793 LL_SET_DEFAULT(1, FUSE_CAP_OVER_IO_URING);
+
2794
+
2795 /* This could safely become default, but libfuse needs an API extension
+
2796 * to support it
+
2797 * LL_SET_DEFAULT(1, FUSE_CAP_SETXATTR_EXT);
+
2798 */
+
2799
+
2800 se->conn.time_gran = 1;
+
2801
+
2802 if (se->op.init) {
+
2803 // Apply the first 32 bits of capable_ext to capable
+
2804 se->conn.capable = fuse_lower_32_bits(se->conn.capable_ext);
+
2805
+
2806 se->op.init(se->userdata, &se->conn);
+
2807
+
2808 /*
+
2809 * se->conn.want is 32-bit value and deprecated in favour of
+
2810 * se->conn.want_ext
+
2811 * Userspace might still use conn.want - we need to convert it
+
2812 */
+ +
2814 }
+
2815
+
2816 if (!want_flags_valid(se->conn.capable_ext, se->conn.want_ext)) {
+
2817 fuse_reply_err(req, EPROTO);
+
2818 se->error = -EPROTO;
+ +
2820 return;
+
2821 }
+
2822
+
2823 unsigned max_read_mo = get_max_read(se->mo);
+
2824 if (se->conn.max_read != max_read_mo) {
+
2825 fuse_log(FUSE_LOG_ERR, "fuse: error: init() and fuse_session_new() "
+
2826 "requested different maximum read size (%u vs %u)\n",
+
2827 se->conn.max_read, max_read_mo);
+
2828 fuse_reply_err(req, EPROTO);
+
2829 se->error = -EPROTO;
+ +
2831 return;
+
2832 }
+
2833
+
2834 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
2835 fuse_log(FUSE_LOG_ERR,
+
2836 "fuse: warning: buffer size too small: %zu\n",
+
2837 bufsize);
+
2838 bufsize = FUSE_MIN_READ_BUFFER;
+
2839 }
+
2840
+
2841 if (buf_reallocable)
+
2842 bufsize = UINT_MAX;
+
2843 se->conn.max_write = MIN(se->conn.max_write, bufsize - FUSE_BUFFER_HEADER_SIZE);
+
2844 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
2845
+
2846 if (arg->flags & FUSE_MAX_PAGES) {
+
2847 outarg.flags |= FUSE_MAX_PAGES;
+
2848 outarg.max_pages = (se->conn.max_write - 1) / getpagesize() + 1;
+
2849 }
+
2850 outargflags = outarg.flags;
+
2851 /* Always enable big writes, this is superseded
+
2852 by the max_write option */
+
2853 outargflags |= FUSE_BIG_WRITES;
+
2854
+
2855 if (se->conn.want_ext & FUSE_CAP_ASYNC_READ)
+
2856 outargflags |= FUSE_ASYNC_READ;
+
2857 if (se->conn.want_ext & FUSE_CAP_POSIX_LOCKS)
+
2858 outargflags |= FUSE_POSIX_LOCKS;
+
2859 if (se->conn.want_ext & FUSE_CAP_ATOMIC_O_TRUNC)
+
2860 outargflags |= FUSE_ATOMIC_O_TRUNC;
+
2861 if (se->conn.want_ext & FUSE_CAP_EXPORT_SUPPORT)
+
2862 outargflags |= FUSE_EXPORT_SUPPORT;
+
2863 if (se->conn.want_ext & FUSE_CAP_DONT_MASK)
+
2864 outargflags |= FUSE_DONT_MASK;
+
2865 if (se->conn.want_ext & FUSE_CAP_FLOCK_LOCKS)
+
2866 outargflags |= FUSE_FLOCK_LOCKS;
+
2867 if (se->conn.want_ext & FUSE_CAP_AUTO_INVAL_DATA)
+
2868 outargflags |= FUSE_AUTO_INVAL_DATA;
+
2869 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS)
+
2870 outargflags |= FUSE_DO_READDIRPLUS;
+
2871 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS_AUTO)
+
2872 outargflags |= FUSE_READDIRPLUS_AUTO;
+
2873 if (se->conn.want_ext & FUSE_CAP_ASYNC_DIO)
+
2874 outargflags |= FUSE_ASYNC_DIO;
+
2875 if (se->conn.want_ext & FUSE_CAP_WRITEBACK_CACHE)
+
2876 outargflags |= FUSE_WRITEBACK_CACHE;
+
2877 if (se->conn.want_ext & FUSE_CAP_PARALLEL_DIROPS)
+
2878 outargflags |= FUSE_PARALLEL_DIROPS;
+
2879 if (se->conn.want_ext & FUSE_CAP_POSIX_ACL)
+
2880 outargflags |= FUSE_POSIX_ACL;
+
2881 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV)
+
2882 outargflags |= FUSE_HANDLE_KILLPRIV;
+
2883 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2)
+
2884 outargflags |= FUSE_HANDLE_KILLPRIV_V2;
+
2885 if (se->conn.want_ext & FUSE_CAP_CACHE_SYMLINKS)
+
2886 outargflags |= FUSE_CACHE_SYMLINKS;
+
2887 if (se->conn.want_ext & FUSE_CAP_EXPLICIT_INVAL_DATA)
+
2888 outargflags |= FUSE_EXPLICIT_INVAL_DATA;
+
2889 if (se->conn.want_ext & FUSE_CAP_SETXATTR_EXT)
+
2890 outargflags |= FUSE_SETXATTR_EXT;
+
2891 if (se->conn.want_ext & FUSE_CAP_DIRECT_IO_ALLOW_MMAP)
+
2892 outargflags |= FUSE_DIRECT_IO_ALLOW_MMAP;
+
2893 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH) {
+
2894 outargflags |= FUSE_PASSTHROUGH;
+
2895 /*
+
2896 * outarg.max_stack_depth includes the fuse stack layer,
+
2897 * so it is one more than max_backing_stack_depth.
+
2898 */
+
2899 outarg.max_stack_depth = se->conn.max_backing_stack_depth + 1;
+
2900 }
+
2901 if (se->conn.want_ext & FUSE_CAP_NO_EXPORT_SUPPORT)
+
2902 outargflags |= FUSE_NO_EXPORT_SUPPORT;
+
2903 if (se->uring.enable && se->conn.want_ext & FUSE_CAP_OVER_IO_URING) {
+
2904 outargflags |= FUSE_OVER_IO_URING;
+
2905 enable_io_uring = true;
+
2906 }
+
2907
+
2908 if ((inargflags & FUSE_REQUEST_TIMEOUT) && se->conn.request_timeout) {
+
2909 outargflags |= FUSE_REQUEST_TIMEOUT;
+
2910 outarg.request_timeout = se->conn.request_timeout;
+
2911 }
+
2912
+
2913 outarg.max_readahead = se->conn.max_readahead;
+
2914 outarg.max_write = se->conn.max_write;
+
2915 if (se->conn.proto_minor >= 13) {
+
2916 if (se->conn.max_background >= (1 << 16))
+
2917 se->conn.max_background = (1 << 16) - 1;
+
2918 if (se->conn.congestion_threshold > se->conn.max_background)
+
2919 se->conn.congestion_threshold = se->conn.max_background;
+
2920 if (!se->conn.congestion_threshold) {
+
2921 se->conn.congestion_threshold =
+
2922 se->conn.max_background * 3 / 4;
+
2923 }
+
2924
+
2925 outarg.max_background = se->conn.max_background;
+
2926 outarg.congestion_threshold = se->conn.congestion_threshold;
+
2927 }
+
2928 if (se->conn.proto_minor >= 23)
+
2929 outarg.time_gran = se->conn.time_gran;
+
2930
+
2931 if (se->debug) {
+
2932 fuse_log(FUSE_LOG_DEBUG, " INIT: %u.%u\n", outarg.major, outarg.minor);
+
2933 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
2934 fuse_log(FUSE_LOG_DEBUG, " max_readahead=0x%08x\n",
+
2935 outarg.max_readahead);
+
2936 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
2937 fuse_log(FUSE_LOG_DEBUG, " max_background=%i\n",
+
2938 outarg.max_background);
+
2939 fuse_log(FUSE_LOG_DEBUG, " congestion_threshold=%i\n",
+
2940 outarg.congestion_threshold);
+
2941 fuse_log(FUSE_LOG_DEBUG, " time_gran=%u\n",
+
2942 outarg.time_gran);
+
2943 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH)
+
2944 fuse_log(FUSE_LOG_DEBUG, " max_stack_depth=%u\n",
+
2945 outarg.max_stack_depth);
+
2946 }
+
2947 if (arg->minor < 5)
+
2948 outargsize = FUSE_COMPAT_INIT_OUT_SIZE;
+
2949 else if (arg->minor < 23)
+
2950 outargsize = FUSE_COMPAT_22_INIT_OUT_SIZE;
+
2951
+
2952 /* XXX: Add an option to make non-available io-uring fatal */
+
2953 if (enable_io_uring) {
+
2954 int ring_rc = fuse_uring_start(se);
+
2955
+
2956 if (ring_rc != 0) {
+
2957 fuse_log(FUSE_LOG_INFO,
+
2958 "fuse: failed to start io-uring: %s\n",
+
2959 strerror(ring_rc));
+
2960 outargflags &= ~FUSE_OVER_IO_URING;
+
2961 enable_io_uring = false;
+
2962 }
+
2963 }
+
2964
+
2965 if (inargflags & FUSE_INIT_EXT) {
+
2966 outargflags |= FUSE_INIT_EXT;
+
2967 outarg.flags2 = outargflags >> 32;
+
2968 }
+
2969 outarg.flags = outargflags;
+
2970
+
2971 /*
+
2972 * Has to be set before replying, as new kernel requests might
+
2973 * immediately arrive and got_init is used for op-code sanity.
+
2974 * Especially with external handlers, where we have no control
+
2975 * over the thread scheduling.
+
2976 */
+
2977 se->got_init = 1;
+
2978 send_reply_ok(req, &outarg, outargsize);
+
2979 if (enable_io_uring)
+
2980 fuse_uring_wake_ring_threads(se);
+
2981}
+
2982
+
2983static __attribute__((no_sanitize("thread"))) void
+
2984do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2985{
+
2986 _do_init(req, nodeid, inarg, NULL);
+
2987}
+
2988
+
2989static void _do_destroy(fuse_req_t req, const fuse_ino_t nodeid,
+
2990 const void *op_in, const void *in_payload)
+
2991{
+
2992 struct fuse_session *se = req->se;
+
2993 char *mountpoint;
+
2994
+
2995 (void) nodeid;
+
2996 (void)op_in;
+
2997 (void)in_payload;
+
2998
+
2999 mountpoint = atomic_exchange(&se->mountpoint, NULL);
+
3000 free(mountpoint);
+
3001
+
3002 se->got_destroy = 1;
+
3003 se->got_init = 0;
+
3004 if (se->op.destroy)
+
3005 se->op.destroy(se->userdata);
+
3006
+
3007 send_reply_ok(req, NULL, 0);
+
3008}
+
3009
+
3010static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
3011{
+
3012 _do_destroy(req, nodeid, inarg, NULL);
+
3013}
+
3014
+
3015static void list_del_nreq(struct fuse_notify_req *nreq)
+
3016{
+
3017 struct fuse_notify_req *prev = nreq->prev;
+
3018 struct fuse_notify_req *next = nreq->next;
+
3019 prev->next = next;
+
3020 next->prev = prev;
+
3021}
+
3022
+
3023static void list_add_nreq(struct fuse_notify_req *nreq,
+
3024 struct fuse_notify_req *next)
+
3025{
+
3026 struct fuse_notify_req *prev = next->prev;
+
3027 nreq->next = next;
+
3028 nreq->prev = prev;
+
3029 prev->next = nreq;
+
3030 next->prev = nreq;
+
3031}
+
3032
+
3033static void list_init_nreq(struct fuse_notify_req *nreq)
+
3034{
+
3035 nreq->next = nreq;
+
3036 nreq->prev = nreq;
+
3037}
+
3038
+
3039static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid,
+
3040 const void *inarg, const struct fuse_buf *buf)
+
3041{
+
3042 struct fuse_session *se = req->se;
+
3043 struct fuse_notify_req *nreq;
+
3044 struct fuse_notify_req *head;
+
3045
+
3046 pthread_mutex_lock(&se->lock);
+
3047 head = &se->notify_list;
+
3048 for (nreq = head->next; nreq != head; nreq = nreq->next) {
+
3049 if (nreq->unique == req->unique) {
+
3050 list_del_nreq(nreq);
+
3051 break;
+
3052 }
+
3053 }
+
3054 pthread_mutex_unlock(&se->lock);
+
3055
+
3056 if (nreq != head)
+
3057 nreq->reply(nreq, req, nodeid, inarg, buf);
+
3058}
+
3059
+
3060static int send_notify_iov(struct fuse_session *se, int notify_code,
+
3061 struct iovec *iov, int count)
+
3062{
+
3063 struct fuse_out_header out;
+
3064 struct fuse_req *req = NULL;
+
3065
+
3066 if (!se->got_init)
+
3067 return -ENOTCONN;
+
3068
+
3069 out.unique = 0;
+
3070 out.error = notify_code;
+
3071 iov[0].iov_base = &out;
+
3072 iov[0].iov_len = sizeof(struct fuse_out_header);
+
3073
+
3074 return fuse_send_msg(se, NULL, iov, count, req);
+
3075}
+
3076
+
3077int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
3078{
+
3079 if (ph != NULL) {
+
3080 struct fuse_notify_poll_wakeup_out outarg;
+
3081 struct iovec iov[2];
+
3082
+
3083 outarg.kh = ph->kh;
+
3084
+
3085 iov[1].iov_base = &outarg;
+
3086 iov[1].iov_len = sizeof(outarg);
+
3087
+
3088 return send_notify_iov(ph->se, FUSE_NOTIFY_POLL, iov, 2);
+
3089 } else {
+
3090 return 0;
+
3091 }
+
3092}
+
3093
+
3094int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
3095 off_t off, off_t len)
+
3096{
+
3097 struct fuse_notify_inval_inode_out outarg;
+
3098 struct iovec iov[2];
+
3099
+
3100 if (!se)
+
3101 return -EINVAL;
+
3102
+
3103 if (se->conn.proto_minor < 12)
+
3104 return -ENOSYS;
+
3105
+
3106 outarg.ino = ino;
+
3107 outarg.off = off;
+
3108 outarg.len = len;
+
3109
+
3110 iov[1].iov_base = &outarg;
+
3111 iov[1].iov_len = sizeof(outarg);
+
3112
+
3113 return send_notify_iov(se, FUSE_NOTIFY_INVAL_INODE, iov, 2);
+
3114}
+
3115
+
+
3116int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
+
3117{
+
3118 struct iovec iov[1];
+
3119
+
3120 if (!se)
+
3121 return -EINVAL;
+
3122
+
3123 if (se->conn.proto_minor < 44)
+
3124 return -ENOSYS;
+
3125
+
3126 return send_notify_iov(se, FUSE_NOTIFY_INC_EPOCH, iov, 1);
+
3127}
+
+
3128
+
3148static int fuse_lowlevel_notify_entry(struct fuse_session *se, fuse_ino_t parent,
+
3149 const char *name, size_t namelen,
+
3150 enum fuse_notify_entry_flags flags)
+
3151{
+
3152 struct fuse_notify_inval_entry_out outarg;
+
3153 struct iovec iov[3];
+
3154
+
3155 if (!se)
+
3156 return -EINVAL;
+
3157
+
3158 if (se->conn.proto_minor < 12)
+
3159 return -ENOSYS;
+
3160
+
3161 outarg.parent = parent;
+
3162 outarg.namelen = namelen;
+
3163 outarg.flags = 0;
+
3164 if (flags & FUSE_LL_EXPIRE_ONLY)
+
3165 outarg.flags |= FUSE_EXPIRE_ONLY;
+
3166
+
3167 iov[1].iov_base = &outarg;
+
3168 iov[1].iov_len = sizeof(outarg);
+
3169 iov[2].iov_base = (void *)name;
+
3170 iov[2].iov_len = namelen + 1;
+
3171
+
3172 return send_notify_iov(se, FUSE_NOTIFY_INVAL_ENTRY, iov, 3);
+
3173}
+
3174
+
3175int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
3176 const char *name, size_t namelen)
+
3177{
+
3178 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_INVALIDATE);
+
3179}
+
3180
+
3181int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
3182 const char *name, size_t namelen)
+
3183{
+
3184 if (!se)
+
3185 return -EINVAL;
+
3186
+
3187 if (!(se->conn.capable_ext & FUSE_CAP_EXPIRE_ONLY))
+
3188 return -ENOSYS;
+
3189
+
3190 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_EXPIRE_ONLY);
+
3191}
+
3192
+
3193
+
3194int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
3195 fuse_ino_t parent, fuse_ino_t child,
+
3196 const char *name, size_t namelen)
+
3197{
+
3198 struct fuse_notify_delete_out outarg;
+
3199 struct iovec iov[3];
+
3200
+
3201 if (!se)
+
3202 return -EINVAL;
+
3203
+
3204 if (se->conn.proto_minor < 18)
+
3205 return -ENOSYS;
+
3206
+
3207 outarg.parent = parent;
+
3208 outarg.child = child;
+
3209 outarg.namelen = namelen;
+
3210 outarg.padding = 0;
+
3211
+
3212 iov[1].iov_base = &outarg;
+
3213 iov[1].iov_len = sizeof(outarg);
+
3214 iov[2].iov_base = (void *)name;
+
3215 iov[2].iov_len = namelen + 1;
+
3216
+
3217 return send_notify_iov(se, FUSE_NOTIFY_DELETE, iov, 3);
+
3218}
+
3219
+
3220int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
3221 off_t offset, struct fuse_bufvec *bufv,
+
3222 enum fuse_buf_copy_flags flags)
+
3223{
+
3224 struct fuse_out_header out;
+
3225 struct fuse_notify_store_out outarg;
+
3226 struct iovec iov[3];
+
3227 size_t size = fuse_buf_size(bufv);
+
3228 int res;
+
3229 struct fuse_req *req = NULL;
+
3230
+
3231 if (!se)
+
3232 return -EINVAL;
+
3233
+
3234 if (se->conn.proto_minor < 15)
+
3235 return -ENOSYS;
+
3236
+
3237 out.unique = 0;
+
3238 out.error = FUSE_NOTIFY_STORE;
+
3239
+
3240 outarg.nodeid = ino;
+
3241 outarg.offset = offset;
+
3242 outarg.size = size;
+
3243 outarg.padding = 0;
+
3244
+
3245 iov[0].iov_base = &out;
+
3246 iov[0].iov_len = sizeof(out);
+
3247 iov[1].iov_base = &outarg;
+
3248 iov[1].iov_len = sizeof(outarg);
+
3249
+
3250 res = fuse_send_data_iov(se, NULL, iov, 2, bufv, flags, req);
+
3251 if (res > 0)
+
3252 res = -res;
+
3253
+
3254 return res;
+
3255}
+
3256
+
3257struct fuse_retrieve_req {
+
3258 struct fuse_notify_req nreq;
+
3259 void *cookie;
+
3260};
+
3261
+
3262static void fuse_ll_retrieve_reply(struct fuse_notify_req *nreq,
+
3263 fuse_req_t req, fuse_ino_t ino,
+
3264 const void *inarg,
+
3265 const struct fuse_buf *ibuf)
+
3266{
+
3267 struct fuse_session *se = req->se;
+
3268 struct fuse_retrieve_req *rreq =
+
3269 container_of(nreq, struct fuse_retrieve_req, nreq);
+
3270 const struct fuse_notify_retrieve_in *arg = inarg;
+
3271 struct fuse_bufvec bufv = {
+
3272 .buf[0] = *ibuf,
+
3273 .count = 1,
+
3274 };
+
3275
+
3276 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
3277 bufv.buf[0].mem = PARAM(arg);
+
3278
+
3279 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
3280 sizeof(struct fuse_notify_retrieve_in);
+
3281
+
3282 if (bufv.buf[0].size < arg->size) {
+
3283 fuse_log(FUSE_LOG_ERR, "fuse: retrieve reply: buffer size too small\n");
+
3284 fuse_reply_none(req);
+
3285 goto out;
+
3286 }
+
3287 bufv.buf[0].size = arg->size;
+
3288
+
3289 if (se->op.retrieve_reply) {
+
3290 se->op.retrieve_reply(req, rreq->cookie, ino,
+
3291 arg->offset, &bufv);
+
3292 } else {
+
3293 fuse_reply_none(req);
+
3294 }
+
3295out:
+
3296 free(rreq);
+
3297 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
3298 fuse_ll_clear_pipe(se);
+
3299}
+
3300
+
3301int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
3302 size_t size, off_t offset, void *cookie)
+
3303{
+
3304 struct fuse_notify_retrieve_out outarg;
+
3305 struct iovec iov[2];
+
3306 struct fuse_retrieve_req *rreq;
+
3307 int err;
+
3308
+
3309 if (!se)
+
3310 return -EINVAL;
+
3311
+
3312 if (se->conn.proto_minor < 15)
+
3313 return -ENOSYS;
+
3314
+
3315 rreq = malloc(sizeof(*rreq));
+
3316 if (rreq == NULL)
+
3317 return -ENOMEM;
+
3318
+
3319 pthread_mutex_lock(&se->lock);
+
3320 rreq->cookie = cookie;
+
3321 rreq->nreq.unique = se->notify_ctr++;
+
3322 rreq->nreq.reply = fuse_ll_retrieve_reply;
+
3323 list_add_nreq(&rreq->nreq, &se->notify_list);
+
3324 pthread_mutex_unlock(&se->lock);
+
3325
+
3326 outarg.notify_unique = rreq->nreq.unique;
+
3327 outarg.nodeid = ino;
+
3328 outarg.offset = offset;
+
3329 outarg.size = size;
+
3330 outarg.padding = 0;
+
3331
+
3332 iov[1].iov_base = &outarg;
+
3333 iov[1].iov_len = sizeof(outarg);
+
3334
+
3335 err = send_notify_iov(se, FUSE_NOTIFY_RETRIEVE, iov, 2);
+
3336 if (err) {
+
3337 pthread_mutex_lock(&se->lock);
+
3338 list_del_nreq(&rreq->nreq);
+
3339 pthread_mutex_unlock(&se->lock);
+
3340 free(rreq);
+
3341 }
+
3342
+
3343 return err;
+
3344}
+
3345
+ +
3347{
+
3348 return req->se->userdata;
+
3349}
+
3350
+
3351const struct fuse_ctx *fuse_req_ctx(fuse_req_t req)
+
3352{
+
3353 return &req->ctx;
+
3354}
+
3355
+ +
3357 void *data)
+
3358{
+
3359 pthread_mutex_lock(&req->lock);
+
3360 pthread_mutex_lock(&req->se->lock);
+
3361 req->u.ni.func = func;
+
3362 req->u.ni.data = data;
+
3363 pthread_mutex_unlock(&req->se->lock);
+
3364 if (req->interrupted && func)
+
3365 func(req, data);
+
3366 pthread_mutex_unlock(&req->lock);
+
3367}
+
3368
+ +
3370{
+
3371 int interrupted;
+
3372
+
3373 pthread_mutex_lock(&req->se->lock);
+
3374 interrupted = req->interrupted;
+
3375 pthread_mutex_unlock(&req->se->lock);
+
3376
+
3377 return interrupted;
+
3378}
+
3379
+
+ +
3381{
+
3382 return req->flags.is_uring;
+
3383}
+
+
3384
+
3385#ifndef HAVE_URING
+
+
3386int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz,
+
3387 void **mr)
+
3388{
+
3389 (void)req;
+
3390 (void)payload;
+
3391 (void)payload_sz;
+
3392 (void)mr;
+
3393 return -ENOTSUP;
+
3394}
+
+
3395#endif
+
3396
+
3397static struct {
+
3398 void (*func)(fuse_req_t req, const fuse_ino_t node, const void *arg);
+
3399 const char *name;
+
3400} fuse_ll_ops[] = {
+
3401 [FUSE_LOOKUP] = { do_lookup, "LOOKUP" },
+
3402 [FUSE_FORGET] = { do_forget, "FORGET" },
+
3403 [FUSE_GETATTR] = { do_getattr, "GETATTR" },
+
3404 [FUSE_SETATTR] = { do_setattr, "SETATTR" },
+
3405 [FUSE_READLINK] = { do_readlink, "READLINK" },
+
3406 [FUSE_SYMLINK] = { do_symlink, "SYMLINK" },
+
3407 [FUSE_MKNOD] = { do_mknod, "MKNOD" },
+
3408 [FUSE_MKDIR] = { do_mkdir, "MKDIR" },
+
3409 [FUSE_UNLINK] = { do_unlink, "UNLINK" },
+
3410 [FUSE_RMDIR] = { do_rmdir, "RMDIR" },
+
3411 [FUSE_RENAME] = { do_rename, "RENAME" },
+
3412 [FUSE_LINK] = { do_link, "LINK" },
+
3413 [FUSE_OPEN] = { do_open, "OPEN" },
+
3414 [FUSE_READ] = { do_read, "READ" },
+
3415 [FUSE_WRITE] = { do_write, "WRITE" },
+
3416 [FUSE_STATFS] = { do_statfs, "STATFS" },
+
3417 [FUSE_RELEASE] = { do_release, "RELEASE" },
+
3418 [FUSE_FSYNC] = { do_fsync, "FSYNC" },
+
3419 [FUSE_SETXATTR] = { do_setxattr, "SETXATTR" },
+
3420 [FUSE_GETXATTR] = { do_getxattr, "GETXATTR" },
+
3421 [FUSE_LISTXATTR] = { do_listxattr, "LISTXATTR" },
+
3422 [FUSE_REMOVEXATTR] = { do_removexattr, "REMOVEXATTR" },
+
3423 [FUSE_FLUSH] = { do_flush, "FLUSH" },
+
3424 [FUSE_INIT] = { do_init, "INIT" },
+
3425 [FUSE_OPENDIR] = { do_opendir, "OPENDIR" },
+
3426 [FUSE_READDIR] = { do_readdir, "READDIR" },
+
3427 [FUSE_RELEASEDIR] = { do_releasedir, "RELEASEDIR" },
+
3428 [FUSE_FSYNCDIR] = { do_fsyncdir, "FSYNCDIR" },
+
3429 [FUSE_GETLK] = { do_getlk, "GETLK" },
+
3430 [FUSE_SETLK] = { do_setlk, "SETLK" },
+
3431 [FUSE_SETLKW] = { do_setlkw, "SETLKW" },
+
3432 [FUSE_ACCESS] = { do_access, "ACCESS" },
+
3433 [FUSE_CREATE] = { do_create, "CREATE" },
+
3434 [FUSE_TMPFILE] = { do_tmpfile, "TMPFILE" },
+
3435 [FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" },
+
3436 [FUSE_BMAP] = { do_bmap, "BMAP" },
+
3437 [FUSE_IOCTL] = { do_ioctl, "IOCTL" },
+
3438 [FUSE_POLL] = { do_poll, "POLL" },
+
3439 [FUSE_FALLOCATE] = { do_fallocate, "FALLOCATE" },
+
3440 [FUSE_DESTROY] = { do_destroy, "DESTROY" },
+
3441 [FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" },
+
3442 [FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" },
+
3443 [FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"},
+
3444 [FUSE_RENAME2] = { do_rename2, "RENAME2" },
+
3445 [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" },
+
3446 [FUSE_COPY_FILE_RANGE_64] = { do_copy_file_range_64, "COPY_FILE_RANGE_64" },
+
3447 [FUSE_LSEEK] = { do_lseek, "LSEEK" },
+
3448 [FUSE_STATX] = { do_statx, "STATX" },
+
3449 [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" },
+
3450};
+
3451
+
3452static struct {
+
3453 void (*func)(fuse_req_t req, const fuse_ino_t ino, const void *op_in,
+
3454 const void *op_payload);
+
3455 const char *name;
+
3456} fuse_ll_ops2[] __attribute__((unused)) = {
+
3457 [FUSE_LOOKUP] = { _do_lookup, "LOOKUP" },
+
3458 [FUSE_FORGET] = { _do_forget, "FORGET" },
+
3459 [FUSE_GETATTR] = { _do_getattr, "GETATTR" },
+
3460 [FUSE_SETATTR] = { _do_setattr, "SETATTR" },
+
3461 [FUSE_READLINK] = { _do_readlink, "READLINK" },
+
3462 [FUSE_SYMLINK] = { _do_symlink, "SYMLINK" },
+
3463 [FUSE_MKNOD] = { _do_mknod, "MKNOD" },
+
3464 [FUSE_MKDIR] = { _do_mkdir, "MKDIR" },
+
3465 [FUSE_UNLINK] = { _do_unlink, "UNLINK" },
+
3466 [FUSE_RMDIR] = { _do_rmdir, "RMDIR" },
+
3467 [FUSE_RENAME] = { _do_rename, "RENAME" },
+
3468 [FUSE_LINK] = { _do_link, "LINK" },
+
3469 [FUSE_OPEN] = { _do_open, "OPEN" },
+
3470 [FUSE_READ] = { _do_read, "READ" },
+
3471 [FUSE_WRITE] = { _do_write, "WRITE" },
+
3472 [FUSE_STATFS] = { _do_statfs, "STATFS" },
+
3473 [FUSE_RELEASE] = { _do_release, "RELEASE" },
+
3474 [FUSE_FSYNC] = { _do_fsync, "FSYNC" },
+
3475 [FUSE_SETXATTR] = { _do_setxattr, "SETXATTR" },
+
3476 [FUSE_GETXATTR] = { _do_getxattr, "GETXATTR" },
+
3477 [FUSE_LISTXATTR] = { _do_listxattr, "LISTXATTR" },
+
3478 [FUSE_REMOVEXATTR] = { _do_removexattr, "REMOVEXATTR" },
+
3479 [FUSE_FLUSH] = { _do_flush, "FLUSH" },
+
3480 [FUSE_INIT] = { _do_init, "INIT" },
+
3481 [FUSE_OPENDIR] = { _do_opendir, "OPENDIR" },
+
3482 [FUSE_READDIR] = { _do_readdir, "READDIR" },
+
3483 [FUSE_RELEASEDIR] = { _do_releasedir, "RELEASEDIR" },
+
3484 [FUSE_FSYNCDIR] = { _do_fsyncdir, "FSYNCDIR" },
+
3485 [FUSE_GETLK] = { _do_getlk, "GETLK" },
+
3486 [FUSE_SETLK] = { _do_setlk, "SETLK" },
+
3487 [FUSE_SETLKW] = { _do_setlkw, "SETLKW" },
+
3488 [FUSE_ACCESS] = { _do_access, "ACCESS" },
+
3489 [FUSE_CREATE] = { _do_create, "CREATE" },
+
3490 [FUSE_TMPFILE] = { _do_tmpfile, "TMPFILE" },
+
3491 [FUSE_INTERRUPT] = { _do_interrupt, "INTERRUPT" },
+
3492 [FUSE_BMAP] = { _do_bmap, "BMAP" },
+
3493 [FUSE_IOCTL] = { _do_ioctl, "IOCTL" },
+
3494 [FUSE_POLL] = { _do_poll, "POLL" },
+
3495 [FUSE_FALLOCATE] = { _do_fallocate, "FALLOCATE" },
+
3496 [FUSE_DESTROY] = { _do_destroy, "DESTROY" },
+
3497 [FUSE_NOTIFY_REPLY] = { (void *)1, "NOTIFY_REPLY" },
+
3498 [FUSE_BATCH_FORGET] = { _do_batch_forget, "BATCH_FORGET" },
+
3499 [FUSE_READDIRPLUS] = { _do_readdirplus, "READDIRPLUS" },
+
3500 [FUSE_RENAME2] = { _do_rename2, "RENAME2" },
+
3501 [FUSE_COPY_FILE_RANGE] = { _do_copy_file_range, "COPY_FILE_RANGE" },
+
3502 [FUSE_COPY_FILE_RANGE_64] = { _do_copy_file_range_64, "COPY_FILE_RANGE_64" },
+
3503 [FUSE_LSEEK] = { _do_lseek, "LSEEK" },
+
3504 [FUSE_STATX] = { _do_statx, "STATX" },
+
3505 [CUSE_INIT] = { _cuse_lowlevel_init, "CUSE_INIT" },
+
3506};
+
3507
+
3508/*
+
3509 * For ABI compatibility we cannot allow higher values than CUSE_INIT.
+
3510 * Without ABI compatibility we could use the size of the array.
+
3511 * #define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
+
3512 */
+
3513#define FUSE_MAXOP (CUSE_INIT + 1)
+
3514
+
3515
+
3520static inline int
+
3521fuse_req_opcode_sanity_ok(struct fuse_session *se, enum fuse_opcode in_op)
+
3522{
+
3523 int err = EIO;
+
3524
+
3525 if (!se->got_init) {
+
3526 enum fuse_opcode expected;
+
3527
+
3528 expected = se->cuse_data ? CUSE_INIT : FUSE_INIT;
+
3529 if (in_op != expected)
+
3530 return err;
+
3531 } else if (in_op == FUSE_INIT || in_op == CUSE_INIT)
+
3532 return err;
+
3533
+
3534 return 0;
+
3535}
+
3536
+
3537static inline void
+
3538fuse_session_in2req(struct fuse_req *req, struct fuse_in_header *in)
+
3539{
+
3540 req->unique = in->unique;
+
3541 req->ctx.uid = in->uid;
+
3542 req->ctx.gid = in->gid;
+
3543 req->ctx.pid = in->pid;
+
3544}
+
3545
+
3549static inline int
+
3550fuse_req_check_allow_root(struct fuse_session *se, enum fuse_opcode in_op,
+
3551 uid_t in_uid)
+
3552{
+
3553 int err = EACCES;
+
3554
+
3555 if (se->deny_others && in_uid != se->owner && in_uid != 0 &&
+
3556 in_op != FUSE_INIT && in_op != FUSE_READ &&
+
3557 in_op != FUSE_WRITE && in_op != FUSE_FSYNC &&
+
3558 in_op != FUSE_RELEASE && in_op != FUSE_READDIR &&
+
3559 in_op != FUSE_FSYNCDIR && in_op != FUSE_RELEASEDIR &&
+
3560 in_op != FUSE_NOTIFY_REPLY &&
+
3561 in_op != FUSE_READDIRPLUS)
+
3562 return err;
+
3563
+
3564 return 0;
+
3565}
+
3566
+
3567static const char *opname(enum fuse_opcode opcode)
+
3568{
+
3569 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
+
3570 return "???";
+
3571 else
+
3572 return fuse_ll_ops[opcode].name;
+
3573}
+
3574
+
3575static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst,
+
3576 struct fuse_bufvec *src)
+
3577{
+
3578 ssize_t res = fuse_buf_copy(dst, src, 0);
+
3579 if (res < 0) {
+
3580 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n", strerror(-res));
+
3581 return res;
+
3582 }
+
3583 if ((size_t)res < fuse_buf_size(dst)) {
+
3584 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: short read\n");
+
3585 return -1;
+
3586 }
+
3587 return 0;
+
3588}
+
3589
+
3590void fuse_session_process_buf(struct fuse_session *se,
+
3591 const struct fuse_buf *buf)
+
3592{
+
3593 fuse_session_process_buf_internal(se, buf, NULL);
+
3594}
+
3595
+
3596/* libfuse internal handler */
+
3597void fuse_session_process_buf_internal(struct fuse_session *se,
+
3598 const struct fuse_buf *buf, struct fuse_chan *ch)
+
3599{
+
3600 const size_t write_header_size = sizeof(struct fuse_in_header) +
+
3601 sizeof(struct fuse_write_in);
+
3602 struct fuse_bufvec bufv = { .buf[0] = *buf, .count = 1 };
+
3603 struct fuse_bufvec tmpbuf = FUSE_BUFVEC_INIT(write_header_size);
+
3604 struct fuse_in_header *in;
+
3605 const void *inarg;
+
3606 struct fuse_req *req;
+
3607 void *mbuf = NULL;
+
3608 int err;
+
3609 int res;
+
3610
+
3611 if (buf->flags & FUSE_BUF_IS_FD) {
+
3612 if (buf->size < tmpbuf.buf[0].size)
+
3613 tmpbuf.buf[0].size = buf->size;
+
3614
+
3615 mbuf = malloc(tmpbuf.buf[0].size);
+
3616 if (mbuf == NULL) {
+
3617 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate header\n");
+
3618 goto clear_pipe;
+
3619 }
+
3620 tmpbuf.buf[0].mem = mbuf;
+
3621
+
3622 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
3623 if (res < 0)
+
3624 goto clear_pipe;
+
3625
+
3626 in = mbuf;
+
3627 } else {
+
3628 in = buf->mem;
+
3629 }
+
3630
+
3631 trace_request_process(in->opcode, in->unique);
+
3632
+
3633 if (se->debug) {
+
3634 fuse_log(FUSE_LOG_DEBUG,
+
3635 "dev unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n",
+
3636 (unsigned long long) in->unique,
+
3637 opname((enum fuse_opcode) in->opcode), in->opcode,
+
3638 (unsigned long long) in->nodeid, buf->size, in->pid);
+
3639 }
+
3640
+
3641 req = fuse_ll_alloc_req(se);
+
3642 if (req == NULL) {
+
3643 struct fuse_out_header out = {
+
3644 .unique = in->unique,
+
3645 .error = -ENOMEM,
+
3646 };
+
3647 struct iovec iov = {
+
3648 .iov_base = &out,
+
3649 .iov_len = sizeof(struct fuse_out_header),
+
3650 };
+
3651
+
3652 fuse_send_msg(se, ch, &iov, 1, NULL);
+
3653 goto clear_pipe;
+
3654 }
+
3655
+
3656 fuse_session_in2req(req, in);
+
3657 req->ch = ch ? fuse_chan_get(ch) : NULL;
+
3658
+
3659 err = fuse_req_opcode_sanity_ok(se, in->opcode);
+
3660 if (err)
+
3661 goto reply_err;
+
3662
+
3663 err = fuse_req_check_allow_root(se, in->opcode, in->uid);
+
3664 if (err)
+
3665 goto reply_err;
+
3666
+
3667 err = ENOSYS;
+
3668 if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func)
+
3669 goto reply_err;
+
3670 /* Do not process interrupt request */
+
3671 if (se->conn.no_interrupt && in->opcode == FUSE_INTERRUPT) {
+
3672 if (se->debug)
+
3673 fuse_log(FUSE_LOG_DEBUG, "FUSE_INTERRUPT: reply to kernel to disable interrupt\n");
+
3674 goto reply_err;
+
3675 }
+
3676 if (!se->conn.no_interrupt && in->opcode != FUSE_INTERRUPT) {
+
3677 struct fuse_req *intr;
+
3678 pthread_mutex_lock(&se->lock);
+
3679 intr = check_interrupt(se, req);
+
3680 list_add_req(req, &se->list);
+
3681 pthread_mutex_unlock(&se->lock);
+
3682 if (intr)
+
3683 fuse_reply_err(intr, EAGAIN);
+
3684 }
+
3685
+
3686 if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size &&
+
3687 (in->opcode != FUSE_WRITE || !se->op.write_buf) &&
+
3688 in->opcode != FUSE_NOTIFY_REPLY) {
+
3689 void *newmbuf;
+
3690
+
3691 err = ENOMEM;
+
3692 newmbuf = realloc(mbuf, buf->size);
+
3693 if (newmbuf == NULL)
+
3694 goto reply_err;
+
3695 mbuf = newmbuf;
+
3696
+
3697 tmpbuf = FUSE_BUFVEC_INIT(buf->size - write_header_size);
+
3698 tmpbuf.buf[0].mem = (char *)mbuf + write_header_size;
+
3699
+
3700 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
3701 err = -res;
+
3702 if (res < 0)
+
3703 goto reply_err;
+
3704
+
3705 in = mbuf;
+
3706 }
+
3707
+
3708 inarg = (void *) &in[1];
+
3709 if (in->opcode == FUSE_WRITE && se->op.write_buf)
+
3710 do_write_buf(req, in->nodeid, inarg, buf);
+
3711 else if (in->opcode == FUSE_NOTIFY_REPLY)
+
3712 do_notify_reply(req, in->nodeid, inarg, buf);
+
3713 else
+
3714 fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg);
+
3715
+
3716out_free:
+
3717 free(mbuf);
+
3718 return;
+
3719
+
3720reply_err:
+
3721 fuse_reply_err(req, err);
+
3722clear_pipe:
+
3723 if (buf->flags & FUSE_BUF_IS_FD)
+
3724 fuse_ll_clear_pipe(se);
+
3725 goto out_free;
+
3726}
+
3727
+
3728void fuse_session_process_uring_cqe(struct fuse_session *se,
+
3729 struct fuse_req *req,
+
3730 struct fuse_in_header *in, void *op_in,
+
3731 void *op_payload, size_t payload_len)
+
3732{
+
3733 int err;
+
3734
+
3735 fuse_session_in2req(req, in);
+
3736
+
3737 err = fuse_req_opcode_sanity_ok(se, in->opcode);
+
3738 if (err)
+
3739 goto reply_err;
+
3740
+
3741 err = fuse_req_check_allow_root(se, in->opcode, in->uid);
+
3742 if (err)
+
3743 goto reply_err;
+
3744
+
3745 err = ENOSYS;
+
3746 if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func)
+
3747 goto reply_err;
+
3748
+
3749 if (se->debug) {
+
3750 fuse_log(
+
3751 FUSE_LOG_DEBUG,
+
3752 "cqe unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n",
+
3753 (unsigned long long)in->unique,
+
3754 opname((enum fuse_opcode)in->opcode), in->opcode,
+
3755 (unsigned long long)in->nodeid, payload_len, in->pid);
+
3756 }
+
3757
+
3758 if (in->opcode == FUSE_WRITE && se->op.write_buf) {
+
3759 struct fuse_bufvec bufv = {
+
3760 .buf[0] = { .size = payload_len,
+
3761 .flags = 0,
+
3762 .mem = op_payload },
+
3763 .count = 1,
+
3764 };
+
3765 _do_write_buf(req, in->nodeid, op_in, &bufv);
+
3766 } else if (in->opcode == FUSE_NOTIFY_REPLY) {
+
3767 struct fuse_buf buf = { .size = payload_len,
+
3768 .mem = op_payload };
+
3769 do_notify_reply(req, in->nodeid, op_in, &buf);
+
3770 } else {
+
3771 fuse_ll_ops2[in->opcode].func(req, in->nodeid, op_in,
+
3772 op_payload);
+
3773 }
+
3774
+
3775 return;
+
3776
+
3777reply_err:
+
3778 fuse_reply_err(req, err);
+
3779}
+
3780
+
3781#define LL_OPTION(n,o,v) \
+
3782 { n, offsetof(struct fuse_session, o), v }
+
3783
+
3784static const struct fuse_opt fuse_ll_opts[] = {
+
3785 LL_OPTION("debug", debug, 1),
+
3786 LL_OPTION("-d", debug, 1),
+
3787 LL_OPTION("--debug", debug, 1),
+
3788 LL_OPTION("allow_root", deny_others, 1),
+
3789 LL_OPTION("io_uring", uring.enable, 1),
+
3790 LL_OPTION("io_uring_q_depth=%u", uring.q_depth, -1),
+ +
3792};
+
3793
+
3794void fuse_lowlevel_version(void)
+
3795{
+
3796 printf("using FUSE kernel interface version %i.%i\n",
+
3797 FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION);
+
3798 fuse_mount_version();
+
3799}
+
3800
+
3801void fuse_lowlevel_help(void)
+
3802{
+
3803 /* These are not all options, but the ones that are
+
3804 potentially of interest to an end-user */
+
3805 printf(
+
3806" -o allow_other allow access by all users\n"
+
3807" -o allow_root allow access by root\n"
+
3808" -o auto_unmount auto unmount on process termination\n"
+
3809" -o io_uring enable io-uring\n"
+
3810" -o io_uring_q_depth=<n> io-uring queue depth\n"
+
3811);
+
3812}
+
3813
+
3814void fuse_session_destroy(struct fuse_session *se)
+
3815{
+
3816 struct fuse_ll_pipe *llp;
+
3817
+
3818 if (se->got_init && !se->got_destroy) {
+
3819 if (se->op.destroy)
+
3820 se->op.destroy(se->userdata);
+
3821 }
+
3822 llp = pthread_getspecific(se->pipe_key);
+
3823 if (llp != NULL)
+
3824 fuse_ll_pipe_free(llp);
+
3825 pthread_key_delete(se->pipe_key);
+
3826 sem_destroy(&se->mt_finish);
+
3827 pthread_mutex_destroy(&se->mt_lock);
+
3828 pthread_mutex_destroy(&se->lock);
+
3829 free(se->cuse_data);
+
3830 if (se->fd != -1)
+
3831 close(se->fd);
+
3832 if (se->io != NULL)
+
3833 free(se->io);
+
3834 destroy_mount_opts(se->mo);
+
3835 free(se);
+
3836}
+
3837
+
3838
+
3839static void fuse_ll_pipe_destructor(void *data)
+
3840{
+
3841 struct fuse_ll_pipe *llp = data;
+
3842 fuse_ll_pipe_free(llp);
+
3843}
+
3844
+
3845void fuse_buf_free(struct fuse_buf *buf)
+
3846{
+
3847 if (buf->mem == NULL)
+
3848 return;
+
3849
+
3850 size_t write_header_sz =
+
3851 sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in);
+
3852
+
3853 char *ptr = (char *)buf->mem - pagesize + write_header_sz;
+
3854 free(ptr);
+
3855 buf->mem = NULL;
+
3856}
+
3857
+
3858/*
+
3859 * This is used to allocate buffers that hold fuse requests
+
3860 */
+
3861static void *buf_alloc(size_t size, bool internal)
+
3862{
+
3863 /*
+
3864 * For libfuse internal caller add in alignment. That cannot be done
+
3865 * for an external caller, as it is not guaranteed that the external
+
3866 * caller frees the raw pointer.
+
3867 */
+
3868 if (internal) {
+
3869 size_t write_header_sz = sizeof(struct fuse_in_header) +
+
3870 sizeof(struct fuse_write_in);
+
3871 size_t new_size = ROUND_UP(size + write_header_sz, pagesize);
+
3872
+
3873 char *buf = aligned_alloc(pagesize, new_size);
+
3874 if (buf == NULL)
+
3875 return NULL;
+
3876
+
3877 buf += pagesize - write_header_sz;
+
3878
+
3879 return buf;
+
3880 } else {
+
3881 return malloc(size);
+
3882 }
+
3883}
+
3884
+
3885/*
+
3886 *@param internal true if called from libfuse internal code
+
3887 */
+
3888static int _fuse_session_receive_buf(struct fuse_session *se,
+
3889 struct fuse_buf *buf, struct fuse_chan *ch,
+
3890 bool internal)
+
3891{
+
3892 int err;
+
3893 ssize_t res;
+
3894 size_t bufsize;
+
3895#ifdef HAVE_SPLICE
+
3896 struct fuse_ll_pipe *llp;
+
3897 struct fuse_buf tmpbuf;
+
3898
+
3899pipe_retry:
+
3900 bufsize = se->bufsize;
+
3901
+
3902 if (se->conn.proto_minor < 14 ||
+
3903 !(se->conn.want_ext & FUSE_CAP_SPLICE_READ))
+
3904 goto fallback;
+
3905
+
3906 llp = fuse_ll_get_pipe(se);
+
3907 if (llp == NULL)
+
3908 goto fallback;
+
3909
+
3910 if (llp->size < bufsize) {
+
3911 if (llp->can_grow) {
+
3912 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, bufsize);
+
3913 if (res == -1) {
+
3914 llp->can_grow = 0;
+
3915 res = grow_pipe_to_max(llp->pipe[0]);
+
3916 if (res > 0)
+
3917 llp->size = res;
+
3918 goto fallback;
+
3919 }
+
3920 llp->size = res;
+
3921 }
+
3922 if (llp->size < bufsize)
+
3923 goto fallback;
+
3924 }
+
3925
+
3926 if (se->io != NULL && se->io->splice_receive != NULL) {
+
3927 res = se->io->splice_receive(ch ? ch->fd : se->fd, NULL,
+
3928 llp->pipe[1], NULL, bufsize, 0,
+
3929 se->userdata);
+
3930 } else {
+
3931 res = splice(ch ? ch->fd : se->fd, NULL, llp->pipe[1], NULL,
+
3932 bufsize, 0);
+
3933 }
+
3934 err = errno;
+
3935 trace_request_receive(err);
+
3936
+
3937 if (fuse_session_exited(se))
+
3938 return 0;
+
3939
+
3940 if (res == -1) {
+
3941 if (err == ENODEV) {
+
3942 /* Filesystem was unmounted, or connection was aborted
+
3943 via /sys/fs/fuse/connections */
+ +
3945 return 0;
+
3946 }
+
3947
+
3948 /* FUSE_INIT might have increased the required bufsize */
+
3949 if (err == EINVAL && bufsize < se->bufsize) {
+
3950 fuse_ll_clear_pipe(se);
+
3951 goto pipe_retry;
+
3952 }
+
3953
+
3954 if (err != EINTR && err != EAGAIN)
+
3955 perror("fuse: splice from device");
+
3956 return -err;
+
3957 }
+
3958
+
3959 if (res < sizeof(struct fuse_in_header)) {
+
3960 fuse_log(FUSE_LOG_ERR, "short splice from fuse device\n");
+
3961 return -EIO;
+
3962 }
+
3963
+
3964 tmpbuf = (struct fuse_buf){
+
3965 .size = res,
+
3966 .flags = FUSE_BUF_IS_FD,
+
3967 .fd = llp->pipe[0],
+
3968 };
+
3969
+
3970 /*
+
3971 * Don't bother with zero copy for small requests.
+
3972 * fuse_loop_mt() needs to check for FORGET so this more than
+
3973 * just an optimization.
+
3974 */
+
3975 if (res < sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) +
+
3976 pagesize) {
+
3977 struct fuse_bufvec src = { .buf[0] = tmpbuf, .count = 1 };
+
3978 struct fuse_bufvec dst = { .count = 1 };
+
3979
+
3980 if (!buf->mem) {
+
3981 buf->mem = buf_alloc(bufsize, internal);
+
3982 if (!buf->mem) {
+
3983 fuse_log(
+
3984 FUSE_LOG_ERR,
+
3985 "fuse: failed to allocate read buffer\n");
+
3986 return -ENOMEM;
+
3987 }
+
3988 buf->mem_size = bufsize;
+
3989 }
+
3990 buf->size = bufsize;
+
3991 buf->flags = 0;
+
3992 dst.buf[0] = *buf;
+
3993
+
3994 res = fuse_buf_copy(&dst, &src, 0);
+
3995 if (res < 0) {
+
3996 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n",
+
3997 strerror(-res));
+
3998 fuse_ll_clear_pipe(se);
+
3999 return res;
+
4000 }
+
4001 if (res < tmpbuf.size) {
+
4002 fuse_log(FUSE_LOG_ERR,
+
4003 "fuse: copy from pipe: short read\n");
+
4004 fuse_ll_clear_pipe(se);
+
4005 return -EIO;
+
4006 }
+
4007 assert(res == tmpbuf.size);
+
4008
+
4009 } else {
+
4010 /* Don't overwrite buf->mem, as that would cause a leak */
+
4011 buf->fd = tmpbuf.fd;
+
4012 buf->flags = tmpbuf.flags;
+
4013 }
+
4014 buf->size = tmpbuf.size;
+
4015
+
4016 return res;
+
4017
+
4018fallback:
+
4019#endif
+
4020 bufsize = internal ? buf->mem_size : se->bufsize;
+
4021 if (!buf->mem) {
+
4022 bufsize = se->bufsize; /* might have changed */
+
4023 buf->mem = buf_alloc(bufsize, internal);
+
4024 if (!buf->mem) {
+
4025 fuse_log(FUSE_LOG_ERR,
+
4026 "fuse: failed to allocate read buffer\n");
+
4027 return -ENOMEM;
+
4028 }
+
4029
+
4030 if (internal)
+
4031 buf->mem_size = bufsize;
+
4032 }
+
4033
+
4034restart:
+
4035 if (se->io != NULL) {
+
4036 /* se->io->read is never NULL if se->io is not NULL as
+
4037 specified by fuse_session_custom_io()*/
+
4038 res = se->io->read(ch ? ch->fd : se->fd, buf->mem, bufsize,
+
4039 se->userdata);
+
4040 } else {
+
4041 res = read(ch ? ch->fd : se->fd, buf->mem, bufsize);
+
4042 }
+
4043 err = errno;
+
4044 trace_request_receive(err);
+
4045
+
4046 if (fuse_session_exited(se))
+
4047 return 0;
+
4048 if (res == -1) {
+
4049 if (err == EINVAL && internal && se->bufsize > bufsize) {
+
4050 /* FUSE_INIT might have increased the required bufsize */
+
4051 bufsize = se->bufsize;
+
4052 void *newbuf = buf_alloc(bufsize, internal);
+
4053 if (!newbuf) {
+
4054 fuse_log(
+
4055 FUSE_LOG_ERR,
+
4056 "fuse: failed to (re)allocate read buffer\n");
+
4057 return -ENOMEM;
+
4058 }
+
4059 fuse_buf_free(buf);
+
4060 buf->mem = newbuf;
+
4061 buf->mem_size = bufsize;
+
4062 goto restart;
+
4063 }
+
4064
+
4065 /* ENOENT means the operation was interrupted, it's safe
+
4066 to restart */
+
4067 if (err == ENOENT)
+
4068 goto restart;
+
4069
+
4070 if (err == ENODEV) {
+
4071 /* Filesystem was unmounted, or connection was aborted
+
4072 via /sys/fs/fuse/connections */
+ +
4074 return 0;
+
4075 }
+
4076 /* Errors occurring during normal operation: EINTR (read
+
4077 interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem
+
4078 umounted) */
+
4079 if (err != EINTR && err != EAGAIN)
+
4080 perror("fuse: reading device");
+
4081 return -err;
+
4082 }
+
4083 if ((size_t)res < sizeof(struct fuse_in_header)) {
+
4084 fuse_log(FUSE_LOG_ERR, "short read on fuse device\n");
+
4085 return -EIO;
+
4086 }
+
4087
+
4088 buf->size = res;
+
4089
+
4090 return res;
+
4091}
+
4092
+
4093int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
4094{
+
4095 return _fuse_session_receive_buf(se, buf, NULL, false);
+
4096}
+
4097
+
4098/* libfuse internal handler */
+
4099int fuse_session_receive_buf_internal(struct fuse_session *se,
+
4100 struct fuse_buf *buf,
+
4101 struct fuse_chan *ch)
+
4102{
+
4103 /*
+
4104 * if run internally thread buffers are from libfuse - we can
+
4105 * reallocate them
+
4106 */
+
4107 if (unlikely(!se->got_init) && !se->buf_reallocable)
+
4108 se->buf_reallocable = true;
+
4109
+
4110 return _fuse_session_receive_buf(se, buf, ch, true);
+
4111}
+
4112
+
4113struct fuse_session *
+
4114fuse_session_new_versioned(struct fuse_args *args,
+
4115 const struct fuse_lowlevel_ops *op, size_t op_size,
+
4116 struct libfuse_version *version, void *userdata)
+
4117{
+
4118 int err;
+
4119 struct fuse_session *se;
+
4120 struct mount_opts *mo;
+
4121
+
4122 if (op == NULL || op_size == 0) {
+
4123 fuse_log(FUSE_LOG_ERR,
+
4124 "fuse: warning: empty op list passed to fuse_session_new()\n");
+
4125 return NULL;
+
4126 }
+
4127
+
4128 if (version == NULL) {
+
4129 fuse_log(FUSE_LOG_ERR, "fuse: warning: version not passed to fuse_session_new()\n");
+
4130 return NULL;
+
4131 }
+
4132
+
4133 if (sizeof(struct fuse_lowlevel_ops) < op_size) {
+
4134 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
4135 op_size = sizeof(struct fuse_lowlevel_ops);
+
4136 }
+
4137
+
4138 if (args == NULL || args->argc == 0) {
+
4139 fuse_log(FUSE_LOG_ERR, "fuse: empty argv passed to fuse_session_new().\n");
+
4140 return NULL;
+
4141 }
+
4142
+
4143 se = (struct fuse_session *) calloc(1, sizeof(struct fuse_session));
+
4144 if (se == NULL) {
+
4145 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
4146 goto out1;
+
4147 }
+
4148 se->fd = -1;
+
4149 se->conn.max_write = FUSE_DEFAULT_MAX_PAGES_LIMIT * getpagesize();
+
4150 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
4151 se->conn.max_readahead = UINT_MAX;
+
4152
+
4153 /*
+
4154 * Allow overriding with env, mostly to avoid the need to modify
+
4155 * all tests. I.e. to test with and without io-uring being enabled.
+
4156 */
+
4157 se->uring.enable = getenv("FUSE_URING_ENABLE") ?
+
4158 atoi(getenv("FUSE_URING_ENABLE")) :
+
4159 SESSION_DEF_URING_ENABLE;
+
4160 se->uring.q_depth = getenv("FUSE_URING_QUEUE_DEPTH") ?
+
4161 atoi(getenv("FUSE_URING_QUEUE_DEPTH")) :
+
4162 SESSION_DEF_URING_Q_DEPTH;
+
4163
+
4164 /* Parse options */
+
4165 if(fuse_opt_parse(args, se, fuse_ll_opts, NULL) == -1)
+
4166 goto out2;
+
4167 if(se->deny_others) {
+
4168 /* Allowing access only by root is done by instructing
+
4169 * kernel to allow access by everyone, and then restricting
+
4170 * access to root and mountpoint owner in libfuse.
+
4171 */
+
4172 // We may be adding the option a second time, but
+
4173 // that doesn't hurt.
+
4174 if(fuse_opt_add_arg(args, "-oallow_other") == -1)
+
4175 goto out2;
+
4176 }
+
4177 mo = parse_mount_opts(args);
+
4178 if (mo == NULL)
+
4179 goto out3;
+
4180
+
4181 if(args->argc == 1 &&
+
4182 args->argv[0][0] == '-') {
+
4183 fuse_log(FUSE_LOG_ERR, "fuse: warning: argv[0] looks like an option, but "
+
4184 "will be ignored\n");
+
4185 } else if (args->argc != 1) {
+
4186 int i;
+
4187 fuse_log(FUSE_LOG_ERR, "fuse: unknown option(s): `");
+
4188 for(i = 1; i < args->argc-1; i++)
+
4189 fuse_log(FUSE_LOG_ERR, "%s ", args->argv[i]);
+
4190 fuse_log(FUSE_LOG_ERR, "%s'\n", args->argv[i]);
+
4191 goto out4;
+
4192 }
+
4193
+
4194 if (se->debug)
+
4195 fuse_log(FUSE_LOG_DEBUG, "FUSE library version: %s\n", PACKAGE_VERSION);
+
4196
+
4197 list_init_req(&se->list);
+
4198 list_init_req(&se->interrupts);
+
4199 list_init_nreq(&se->notify_list);
+
4200 se->notify_ctr = 1;
+
4201 pthread_mutex_init(&se->lock, NULL);
+
4202 sem_init(&se->mt_finish, 0, 0);
+
4203 pthread_mutex_init(&se->mt_lock, NULL);
+
4204
+
4205 err = pthread_key_create(&se->pipe_key, fuse_ll_pipe_destructor);
+
4206 if (err) {
+
4207 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
4208 strerror(err));
+
4209 goto out5;
+
4210 }
+
4211
+
4212 memcpy(&se->op, op, op_size);
+
4213 se->owner = getuid();
+
4214 se->userdata = userdata;
+
4215
+
4216 se->mo = mo;
+
4217
+
4218 /* Fuse server application should pass the version it was compiled
+
4219 * against and pass it. If a libfuse version accidentally introduces an
+
4220 * ABI incompatibility, it might be possible to 'fix' that at run time,
+
4221 * by checking the version numbers.
+
4222 */
+
4223 se->version = *version;
+
4224
+
4225 return se;
+
4226
+
4227out5:
+
4228 sem_destroy(&se->mt_finish);
+
4229 pthread_mutex_destroy(&se->mt_lock);
+
4230 pthread_mutex_destroy(&se->lock);
+
4231out4:
+
4232 fuse_opt_free_args(args);
+
4233out3:
+
4234 if (mo != NULL)
+
4235 destroy_mount_opts(mo);
+
4236out2:
+
4237 free(se);
+
4238out1:
+
4239 return NULL;
+
4240}
+
4241
+
4242struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
4243 const struct fuse_lowlevel_ops *op,
+
4244 size_t op_size, void *userdata);
+
4245struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
4246 const struct fuse_lowlevel_ops *op,
+
4247 size_t op_size,
+
4248 void *userdata)
+
4249{
+
4250 struct fuse_lowlevel_ops null_ops = { 0 };
+
4251
+
4252 /* unknown version */
+
4253 struct libfuse_version version = { 0 };
+
4254
+
4255 /*
+
4256 * This function is the ABI interface function from fuse_session_new in
+
4257 * compat.c. External libraries like "fuser" might call fuse_session_new()
+
4258 * with NULL ops and then pass that session to fuse_session_mount().
+
4259 * The actual FUSE operations are handled in their own library.
+
4260 */
+
4261 if (op == NULL) {
+
4262 op = &null_ops;
+
4263 op_size = sizeof(null_ops);
+
4264 }
+
4265
+
4266 return fuse_session_new_versioned(args, op, op_size, &version,
+
4267 userdata);
+
4268}
+
4269
+
4270FUSE_SYMVER("fuse_session_custom_io_317", "fuse_session_custom_io@@FUSE_3.17")
+
4271int fuse_session_custom_io_317(struct fuse_session *se,
+
4272 const struct fuse_custom_io *io, size_t op_size, int fd)
+
4273{
+
4274 if (sizeof(struct fuse_custom_io) < op_size) {
+
4275 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
4276 op_size = sizeof(struct fuse_custom_io);
+
4277 }
+
4278
+
4279 if (fd < 0) {
+
4280 fuse_log(FUSE_LOG_ERR, "Invalid file descriptor value %d passed to "
+
4281 "fuse_session_custom_io()\n", fd);
+
4282 return -EBADF;
+
4283 }
+
4284 if (io == NULL) {
+
4285 fuse_log(FUSE_LOG_ERR, "No custom IO passed to "
+
4286 "fuse_session_custom_io()\n");
+
4287 return -EINVAL;
+
4288 } else if (io->read == NULL || io->writev == NULL) {
+
4289 /* If the user provides their own file descriptor, we can't
+
4290 guarantee that the default behavior of the io operations made
+
4291 in libfuse will function properly. Therefore, we enforce the
+
4292 user to implement these io operations when using custom io. */
+
4293 fuse_log(FUSE_LOG_ERR, "io passed to fuse_session_custom_io() must "
+
4294 "implement both io->read() and io->writev\n");
+
4295 return -EINVAL;
+
4296 }
+
4297
+
4298 se->io = calloc(1, sizeof(struct fuse_custom_io));
+
4299 if (se->io == NULL) {
+
4300 fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for custom io. "
+
4301 "Error: %s\n", strerror(errno));
+
4302 return -errno;
+
4303 }
+
4304
+
4305 se->fd = fd;
+
4306 memcpy(se->io, io, op_size);
+
4307 return 0;
+
4308}
+
4309
+
4310int fuse_session_custom_io_30(struct fuse_session *se,
+
4311 const struct fuse_custom_io *io, int fd);
+
4312FUSE_SYMVER("fuse_session_custom_io_30", "fuse_session_custom_io@FUSE_3.0")
+
4313int fuse_session_custom_io_30(struct fuse_session *se,
+
4314 const struct fuse_custom_io *io, int fd)
+
4315{
+
4316 return fuse_session_custom_io_317(se, io,
+
4317 offsetof(struct fuse_custom_io, clone_fd), fd);
+
4318}
+
4319
+
4320int fuse_session_mount(struct fuse_session *se, const char *_mountpoint)
+
4321{
+
4322 int fd;
+
4323 char *mountpoint;
+
4324
+
4325 if (_mountpoint == NULL) {
+
4326 fuse_log(FUSE_LOG_ERR, "Invalid null-ptr mountpoint!\n");
+
4327 return -1;
+
4328 }
+
4329
+
4330 mountpoint = strdup(_mountpoint);
+
4331 if (mountpoint == NULL) {
+
4332 fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for mountpoint. Error: %s\n",
+
4333 strerror(errno));
+
4334 return -1;
+
4335 }
+
4336
+
4337 /*
+
4338 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
4339 * would ensue.
+
4340 */
+
4341 do {
+
4342 fd = open("/dev/null", O_RDWR);
+
4343 if (fd > 2)
+
4344 close(fd);
+
4345 } while (fd >= 0 && fd <= 2);
+
4346
+
4347 /*
+
4348 * To allow FUSE daemons to run without privileges, the caller may open
+
4349 * /dev/fuse before launching the file system and pass on the file
+
4350 * descriptor by specifying /dev/fd/N as the mount point. Note that the
+
4351 * parent process takes care of performing the mount in this case.
+
4352 */
+
4353 fd = fuse_mnt_parse_fuse_fd(mountpoint);
+
4354 if (fd != -1) {
+
4355 if (fcntl(fd, F_GETFD) == -1) {
+
4356 fuse_log(FUSE_LOG_ERR,
+
4357 "fuse: Invalid file descriptor /dev/fd/%u\n",
+
4358 fd);
+
4359 goto error_out;
+
4360 }
+
4361 se->fd = fd;
+
4362 return 0;
+
4363 }
+
4364
+
4365 /* Open channel */
+
4366 fd = fuse_kern_mount(mountpoint, se->mo);
+
4367 if (fd == -1)
+
4368 goto error_out;
+
4369 se->fd = fd;
+
4370
+
4371 /* Save mountpoint */
+
4372 se->mountpoint = mountpoint;
+
4373
+
4374 return 0;
+
4375
+
4376error_out:
+
4377 free(mountpoint);
+
4378 return -1;
+
4379}
+
4380
+
4381int fuse_session_fd(struct fuse_session *se)
+
4382{
+
4383 return se->fd;
+
4384}
+
4385
+
4386void fuse_session_unmount(struct fuse_session *se)
+
4387{
+
4388 if (se->mountpoint != NULL) {
+
4389 char *mountpoint = atomic_exchange(&se->mountpoint, NULL);
+
4390
+
4391 fuse_kern_unmount(mountpoint, se->fd);
+
4392 se->fd = -1;
+
4393 free(mountpoint);
+
4394 }
+
4395}
+
4396
+
4397#ifdef linux
+
4398int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
4399{
+
4400 char *buf;
+
4401 size_t bufsize = 1024;
+
4402 char path[128];
+
4403 int ret;
+
4404 int fd;
+
4405 unsigned long pid = req->ctx.pid;
+
4406 char *s;
+
4407
+
4408 sprintf(path, "/proc/%lu/task/%lu/status", pid, pid);
+
4409
+
4410retry:
+
4411 buf = malloc(bufsize);
+
4412 if (buf == NULL)
+
4413 return -ENOMEM;
+
4414
+
4415 ret = -EIO;
+
4416 fd = open(path, O_RDONLY);
+
4417 if (fd == -1)
+
4418 goto out_free;
+
4419
+
4420 ret = read(fd, buf, bufsize);
+
4421 close(fd);
+
4422 if (ret < 0) {
+
4423 ret = -EIO;
+
4424 goto out_free;
+
4425 }
+
4426
+
4427 if ((size_t)ret == bufsize) {
+
4428 free(buf);
+
4429 bufsize *= 4;
+
4430 goto retry;
+
4431 }
+
4432
+
4433 buf[ret] = '\0';
+
4434 ret = -EIO;
+
4435 s = strstr(buf, "\nGroups:");
+
4436 if (s == NULL)
+
4437 goto out_free;
+
4438
+
4439 s += 8;
+
4440 ret = 0;
+
4441 while (1) {
+
4442 char *end;
+
4443 unsigned long val = strtoul(s, &end, 0);
+
4444 if (end == s)
+
4445 break;
+
4446
+
4447 s = end;
+
4448 if (ret < size)
+
4449 list[ret] = val;
+
4450 ret++;
+
4451 }
+
4452
+
4453out_free:
+
4454 free(buf);
+
4455 return ret;
+
4456}
+
4457#else /* linux */
+
4458/*
+
4459 * This is currently not implemented on other than Linux...
+
4460 */
+
4461int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
4462{
+
4463 (void) req; (void) size; (void) list;
+
4464 return -ENOSYS;
+
4465}
+
4466#endif
+
4467
+
4468/* Prevent spurious data race warning - we don't care
+
4469 * about races for this flag */
+
4470__attribute__((no_sanitize_thread))
+
4471void fuse_session_exit(struct fuse_session *se)
+
4472{
+
4473 atomic_store_explicit(&se->mt_exited, 1, memory_order_relaxed);
+
4474 sem_post(&se->mt_finish);
+
4475}
+
4476
+
4477__attribute__((no_sanitize_thread))
+
4478void fuse_session_reset(struct fuse_session *se)
+
4479{
+
4480 se->mt_exited = false;
+
4481 se->error = 0;
+
4482}
+
4483
+
4484__attribute__((no_sanitize_thread))
+
4485int fuse_session_exited(struct fuse_session *se)
+
4486{
+
4487 bool exited =
+
4488 atomic_load_explicit(&se->mt_exited, memory_order_relaxed);
+
4489
+
4490 return exited ? 1 : 0;
+
4491}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
@ FUSE_BUF_IS_FD
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_CAP_OVER_IO_URING
+
int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+
int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
+
@ FUSE_BUF_IS_FD
+
bool fuse_req_is_uring(fuse_req_t req)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, void **mr)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
enum fuse_buf_flags flags
+
size_t mem_size
+ +
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + + + +
uint64_t capable_ext
+
uint64_t want_ext
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:60
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t nonseekable
Definition fuse_common.h:78
+
int32_t backing_id
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t noflush
Definition fuse_common.h:93
+
uint32_t flush
Definition fuse_common.h:74
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2fuse__misc_8h_source.html b/doc/html/fuse-3_818_81_2lib_2fuse__misc_8h_source.html new file mode 100644 index 0000000..2431e34 --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2fuse__misc_8h_source.html @@ -0,0 +1,111 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/fuse_misc.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_misc.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt
+
7*/
+
8
+
9#include <pthread.h>
+
10
+
11/*
+
12 Versioned symbols cannot be used in some cases because it
+
13 - not supported on MacOSX (in MachO binary format)
+
14
+
15 Note: "@@" denotes the default symbol, "@" is binary a compat version.
+
16
+
17*/
+
18#ifdef LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS
+
19# if HAVE_SYMVER_ATTRIBUTE
+
20# define FUSE_SYMVER(sym1, sym2) __attribute__ ((symver (sym2)))
+
21# else
+
22# define FUSE_SYMVER(sym1, sym2) __asm__("\t.symver " sym1 "," sym2);
+
23# endif
+
24#else
+
25#define FUSE_SYMVER(sym1, sym2)
+
26#endif
+
27
+
28#ifdef HAVE_STRUCT_STAT_ST_ATIM
+
29/* Linux */
+
30#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec)
+
31#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec)
+
32#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec)
+
33#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atim.tv_nsec = (val)
+
34#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctim.tv_nsec = (val)
+
35#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtim.tv_nsec = (val)
+
36#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC)
+
37/* FreeBSD */
+
38#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimespec.tv_nsec)
+
39#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimespec.tv_nsec)
+
40#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimespec.tv_nsec)
+
41#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atimespec.tv_nsec = (val)
+
42#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctimespec.tv_nsec = (val)
+
43#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtimespec.tv_nsec = (val)
+
44#else
+
45#define ST_ATIM_NSEC(stbuf) 0
+
46#define ST_CTIM_NSEC(stbuf) 0
+
47#define ST_MTIM_NSEC(stbuf) 0
+
48#define ST_ATIM_NSEC_SET(stbuf, val) do { } while (0)
+
49#define ST_CTIM_NSEC_SET(stbuf, val) do { } while (0)
+
50#define ST_MTIM_NSEC_SET(stbuf, val) do { } while (0)
+
51#endif
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2fuse__opt_8c_source.html b/doc/html/fuse-3_818_81_2lib_2fuse__opt_8c_source.html new file mode 100644 index 0000000..d7e20b3 --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2fuse__opt_8c_source.html @@ -0,0 +1,504 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/fuse_opt.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_opt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of option parsing routines (dealing with `struct
+
6 fuse_args`).
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file LGPL2.txt
+
10*/
+
11
+
12#include "fuse_config.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15#include "fuse_misc.h"
+
16
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <assert.h>
+
21
+
22struct fuse_opt_context {
+
23 void *data;
+
24 const struct fuse_opt *opt;
+
25 fuse_opt_proc_t proc;
+
26 int argctr;
+
27 int argc;
+
28 char **argv;
+
29 struct fuse_args outargs;
+
30 char *opts;
+
31 int nonopt;
+
32};
+
33
+
34void fuse_opt_free_args(struct fuse_args *args)
+
35{
+
36 if (args) {
+
37 if (args->argv && args->allocated) {
+
38 int i;
+
39 for (i = 0; i < args->argc; i++)
+
40 free(args->argv[i]);
+
41 free(args->argv);
+
42 }
+
43 args->argc = 0;
+
44 args->argv = NULL;
+
45 args->allocated = 0;
+
46 }
+
47}
+
48
+
49static int alloc_failed(void)
+
50{
+
51 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
52 return -1;
+
53}
+
54
+
55int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
+
56{
+
57 char **newargv;
+
58 char *newarg;
+
59
+
60 assert(!args->argv || args->allocated);
+
61
+
62 newarg = strdup(arg);
+
63 if (!newarg)
+
64 return alloc_failed();
+
65
+
66 newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *));
+
67 if (!newargv) {
+
68 free(newarg);
+
69 return alloc_failed();
+
70 }
+
71
+
72 args->argv = newargv;
+
73 args->allocated = 1;
+
74 args->argv[args->argc++] = newarg;
+
75 args->argv[args->argc] = NULL;
+
76 return 0;
+
77}
+
78
+
79static int fuse_opt_insert_arg_common(struct fuse_args *args, int pos,
+
80 const char *arg)
+
81{
+
82 assert(pos <= args->argc);
+
83 if (fuse_opt_add_arg(args, arg) == -1)
+
84 return -1;
+
85
+
86 if (pos != args->argc - 1) {
+
87 char *newarg = args->argv[args->argc - 1];
+
88 memmove(&args->argv[pos + 1], &args->argv[pos],
+
89 sizeof(char *) * (args->argc - pos - 1));
+
90 args->argv[pos] = newarg;
+
91 }
+
92 return 0;
+
93}
+
94
+
95int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
+
96{
+
97 return fuse_opt_insert_arg_common(args, pos, arg);
+
98}
+
99
+
100static int next_arg(struct fuse_opt_context *ctx, const char *opt)
+
101{
+
102 if (ctx->argctr + 1 >= ctx->argc) {
+
103 fuse_log(FUSE_LOG_ERR, "fuse: missing argument after `%s'\n", opt);
+
104 return -1;
+
105 }
+
106 ctx->argctr++;
+
107 return 0;
+
108}
+
109
+
110static int add_arg(struct fuse_opt_context *ctx, const char *arg)
+
111{
+
112 return fuse_opt_add_arg(&ctx->outargs, arg);
+
113}
+
114
+
115static int add_opt_common(char **opts, const char *opt, int esc)
+
116{
+
117 unsigned oldlen = *opts ? strlen(*opts) : 0;
+
118 char *d = realloc(*opts, oldlen + 1 + strlen(opt) * 2 + 1);
+
119
+
120 if (!d)
+
121 return alloc_failed();
+
122
+
123 *opts = d;
+
124 if (oldlen) {
+
125 d += oldlen;
+
126 *d++ = ',';
+
127 }
+
128
+
129 for (; *opt; opt++) {
+
130 if (esc && (*opt == ',' || *opt == '\\'))
+
131 *d++ = '\\';
+
132 *d++ = *opt;
+
133 }
+
134 *d = '\0';
+
135
+
136 return 0;
+
137}
+
138
+
139int fuse_opt_add_opt(char **opts, const char *opt)
+
140{
+
141 return add_opt_common(opts, opt, 0);
+
142}
+
143
+
144int fuse_opt_add_opt_escaped(char **opts, const char *opt)
+
145{
+
146 return add_opt_common(opts, opt, 1);
+
147}
+
148
+
149static int add_opt(struct fuse_opt_context *ctx, const char *opt)
+
150{
+
151 return add_opt_common(&ctx->opts, opt, 1);
+
152}
+
153
+
154static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key,
+
155 int iso)
+
156{
+
157 if (key == FUSE_OPT_KEY_DISCARD)
+
158 return 0;
+
159
+
160 if (key != FUSE_OPT_KEY_KEEP && ctx->proc) {
+
161 int res = ctx->proc(ctx->data, arg, key, &ctx->outargs);
+
162 if (res == -1 || !res)
+
163 return res;
+
164 }
+
165 if (iso)
+
166 return add_opt(ctx, arg);
+
167 else
+
168 return add_arg(ctx, arg);
+
169}
+
170
+
171static int match_template(const char *t, const char *arg, unsigned *sepp)
+
172{
+
173 int arglen = strlen(arg);
+
174 const char *sep = strchr(t, '=');
+
175 sep = sep ? sep : strchr(t, ' ');
+
176 if (sep && (!sep[1] || sep[1] == '%')) {
+
177 int tlen = sep - t;
+
178 if (sep[0] == '=')
+
179 tlen ++;
+
180 if (arglen >= tlen && strncmp(arg, t, tlen) == 0) {
+
181 *sepp = sep - t;
+
182 return 1;
+
183 }
+
184 }
+
185 if (strcmp(t, arg) == 0) {
+
186 *sepp = 0;
+
187 return 1;
+
188 }
+
189 return 0;
+
190}
+
191
+
192static const struct fuse_opt *find_opt(const struct fuse_opt *opt,
+
193 const char *arg, unsigned *sepp)
+
194{
+
195 for (; opt && opt->templ; opt++)
+
196 if (match_template(opt->templ, arg, sepp))
+
197 return opt;
+
198 return NULL;
+
199}
+
200
+
201int fuse_opt_match(const struct fuse_opt *opts, const char *opt)
+
202{
+
203 unsigned dummy;
+
204 return find_opt(opts, opt, &dummy) ? 1 : 0;
+
205}
+
206
+
207static int process_opt_param(void *var, const char *format, const char *param,
+
208 const char *arg)
+
209{
+
210 assert(format[0] == '%');
+
211 if (format[1] == 's') {
+
212 char **s = var;
+
213 char *copy = strdup(param);
+
214 if (!copy)
+
215 return alloc_failed();
+
216
+
217 free(*s);
+
218 *s = copy;
+
219 } else {
+
220 if (sscanf(param, format, var) != 1) {
+
221 fuse_log(FUSE_LOG_ERR, "fuse: invalid parameter in option `%s'\n", arg);
+
222 return -1;
+
223 }
+
224 }
+
225 return 0;
+
226}
+
227
+
228static int process_opt(struct fuse_opt_context *ctx,
+
229 const struct fuse_opt *opt, unsigned sep,
+
230 const char *arg, int iso)
+
231{
+
232 if (opt->offset == -1U) {
+
233 if (call_proc(ctx, arg, opt->value, iso) == -1)
+
234 return -1;
+
235 } else {
+
236 void *var = (char *)ctx->data + opt->offset;
+
237 if (sep && opt->templ[sep + 1]) {
+
238 const char *param = arg + sep;
+
239 if (opt->templ[sep] == '=')
+
240 param ++;
+
241 if (process_opt_param(var, opt->templ + sep + 1,
+
242 param, arg) == -1)
+
243 return -1;
+
244 } else
+
245 *(int *)var = opt->value;
+
246 }
+
247 return 0;
+
248}
+
249
+
250static int process_opt_sep_arg(struct fuse_opt_context *ctx,
+
251 const struct fuse_opt *opt, unsigned sep,
+
252 const char *arg, int iso)
+
253{
+
254 int res;
+
255 char *newarg;
+
256 char *param;
+
257
+
258 if (next_arg(ctx, arg) == -1)
+
259 return -1;
+
260
+
261 param = ctx->argv[ctx->argctr];
+
262 newarg = malloc(sep + strlen(param) + 1);
+
263 if (!newarg)
+
264 return alloc_failed();
+
265
+
266 memcpy(newarg, arg, sep);
+
267 strcpy(newarg + sep, param);
+
268 res = process_opt(ctx, opt, sep, newarg, iso);
+
269 free(newarg);
+
270
+
271 return res;
+
272}
+
273
+
274static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso)
+
275{
+
276 unsigned sep;
+
277 const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep);
+
278 if (opt) {
+
279 for (; opt; opt = find_opt(opt + 1, arg, &sep)) {
+
280 int res;
+
281 if (sep && opt->templ[sep] == ' ' && !arg[sep])
+
282 res = process_opt_sep_arg(ctx, opt, sep, arg,
+
283 iso);
+
284 else
+
285 res = process_opt(ctx, opt, sep, arg, iso);
+
286 if (res == -1)
+
287 return -1;
+
288 }
+
289 return 0;
+
290 } else
+
291 return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso);
+
292}
+
293
+
294static int process_real_option_group(struct fuse_opt_context *ctx, char *opts)
+
295{
+
296 char *s = opts;
+
297 char *d = s;
+
298 int end = 0;
+
299
+
300 while (!end) {
+
301 if (*s == '\0')
+
302 end = 1;
+
303 if (*s == ',' || end) {
+
304 int res;
+
305
+
306 *d = '\0';
+
307 res = process_gopt(ctx, opts, 1);
+
308 if (res == -1)
+
309 return -1;
+
310 d = opts;
+
311 } else {
+
312 if (s[0] == '\\' && s[1] != '\0') {
+
313 s++;
+
314 if (s[0] >= '0' && s[0] <= '3' &&
+
315 s[1] >= '0' && s[1] <= '7' &&
+
316 s[2] >= '0' && s[2] <= '7') {
+
317 *d++ = (s[0] - '0') * 0100 +
+
318 (s[1] - '0') * 0010 +
+
319 (s[2] - '0');
+
320 s += 2;
+
321 } else {
+
322 *d++ = *s;
+
323 }
+
324 } else {
+
325 *d++ = *s;
+
326 }
+
327 }
+
328 s++;
+
329 }
+
330
+
331 return 0;
+
332}
+
333
+
334static int process_option_group(struct fuse_opt_context *ctx, const char *opts)
+
335{
+
336 int res;
+
337 char *copy = strdup(opts);
+
338
+
339 if (!copy) {
+
340 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
341 return -1;
+
342 }
+
343 res = process_real_option_group(ctx, copy);
+
344 free(copy);
+
345 return res;
+
346}
+
347
+
348static int process_one(struct fuse_opt_context *ctx, const char *arg)
+
349{
+
350 if (ctx->nonopt || arg[0] != '-')
+
351 return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0);
+
352 else if (arg[1] == 'o') {
+
353 if (arg[2])
+
354 return process_option_group(ctx, arg + 2);
+
355 else {
+
356 if (next_arg(ctx, arg) == -1)
+
357 return -1;
+
358
+
359 return process_option_group(ctx,
+
360 ctx->argv[ctx->argctr]);
+
361 }
+
362 } else if (arg[1] == '-' && !arg[2]) {
+
363 if (add_arg(ctx, arg) == -1)
+
364 return -1;
+
365 ctx->nonopt = ctx->outargs.argc;
+
366 return 0;
+
367 } else
+
368 return process_gopt(ctx, arg, 0);
+
369}
+
370
+
371static int opt_parse(struct fuse_opt_context *ctx)
+
372{
+
373 if (ctx->argc) {
+
374 if (add_arg(ctx, ctx->argv[0]) == -1)
+
375 return -1;
+
376 }
+
377
+
378 for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++)
+
379 if (process_one(ctx, ctx->argv[ctx->argctr]) == -1)
+
380 return -1;
+
381
+
382 if (ctx->opts) {
+
383 if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 ||
+
384 fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1)
+
385 return -1;
+
386 }
+
387
+
388 /* If option separator ("--") is the last argument, remove it */
+
389 if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc &&
+
390 strcmp(ctx->outargs.argv[ctx->outargs.argc - 1], "--") == 0) {
+
391 free(ctx->outargs.argv[ctx->outargs.argc - 1]);
+
392 ctx->outargs.argv[--ctx->outargs.argc] = NULL;
+
393 }
+
394
+
395 return 0;
+
396}
+
397
+
398int fuse_opt_parse(struct fuse_args *args, void *data,
+
399 const struct fuse_opt opts[], fuse_opt_proc_t proc)
+
400{
+
401 int res;
+
402 struct fuse_opt_context ctx = {
+
403 .data = data,
+
404 .opt = opts,
+
405 .proc = proc,
+
406 };
+
407
+
408 if (!args || !args->argv || !args->argc)
+
409 return 0;
+
410
+
411 ctx.argc = args->argc;
+
412 ctx.argv = args->argv;
+
413
+
414 res = opt_parse(&ctx);
+
415 if (res != -1) {
+
416 struct fuse_args tmp = *args;
+
417 *args = ctx.outargs;
+
418 ctx.outargs = tmp;
+
419 }
+
420 free(ctx.opts);
+
421 fuse_opt_free_args(&ctx.outargs);
+
422 return res;
+
423}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
#define FUSE_OPT_KEY_OPT
Definition fuse_opt.h:129
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
unsigned long offset
Definition fuse_opt.h:85
+
const char * templ
Definition fuse_opt.h:79
+
int value
Definition fuse_opt.h:91
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2fuse__signals_8c_source.html b/doc/html/fuse-3_818_81_2lib_2fuse__signals_8c_source.html new file mode 100644 index 0000000..fc14c7e --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2fuse__signals_8c_source.html @@ -0,0 +1,281 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/fuse_signals.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_signals.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Utility functions for setting signal handlers.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14
+
15#include <stdio.h>
+
16#include <string.h>
+
17#include <signal.h>
+
18#include <stdlib.h>
+
19#include <errno.h>
+
20
+
21#ifdef HAVE_BACKTRACE
+
22#include <execinfo.h>
+
23#endif
+
24
+
25/*
+
26 * Must not handle SIGCANCEL, as that is used to wake up threads from
+
27 * syscalls reading requests from /dev/fuse
+
28 */
+
29static int teardown_sigs[] = { SIGHUP, SIGINT, SIGTERM };
+
30
+
31static int ignore_sigs[] = { SIGPIPE};
+
32static int fail_sigs[] = { SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGSEGV };
+
33static struct fuse_session *fuse_instance;
+
34
+
35#ifdef HAVE_BACKTRACE
+
36#define BT_STACK_SZ (1024 * 1024)
+
37static void *backtrace_buffer[BT_STACK_SZ];
+
38#endif
+
39
+
40static void dump_stack(void)
+
41{
+
42#ifdef HAVE_BACKTRACE
+
43 char **strings;
+
44
+
45 int nptrs = backtrace(backtrace_buffer, BT_STACK_SZ);
+
46 strings = backtrace_symbols(backtrace_buffer, nptrs);
+
47
+
48 if (strings == NULL) {
+
49 fuse_log(FUSE_LOG_ERR, "Failed to get backtrace symbols: %s\n",
+
50 strerror(errno));
+
51 return;
+
52 }
+
53
+
54 for (int idx = 0; idx < nptrs; idx++)
+
55 fuse_log(FUSE_LOG_ERR, "%s\n", strings[idx]);
+
56
+
57 free(strings);
+
58#endif
+
59}
+
60
+
61static void exit_handler(int sig)
+
62{
+
63 if (fuse_instance == NULL) {
+
64 fuse_log(FUSE_LOG_ERR, "fuse_instance is NULL\n");
+
65 return;
+
66 }
+
67
+
68 if (fuse_instance->debug)
+
69 fuse_log(FUSE_LOG_ERR, "exit_handler called with sig %d\n",
+
70 sig);
+
71
+
72 fuse_session_exit(fuse_instance);
+
73
+
74 if (sig < 0) {
+
75 fuse_log(FUSE_LOG_ERR,
+
76 "assertion error: signal value <= 0\n");
+
77 dump_stack();
+
78 abort();
+
79 fuse_instance->error = sig;
+
80 }
+
81
+
82 fuse_instance->error = sig;
+
83}
+
84
+
85static void exit_backtrace(int sig)
+
86{
+
87 if (fuse_instance == NULL)
+
88 return;
+
89
+
90 fuse_session_exit(fuse_instance);
+
91
+
92 fuse_remove_signal_handlers(fuse_instance);
+
93 fuse_log(FUSE_LOG_ERR, "Got signal: %d\n", sig);
+
94 dump_stack();
+
95 abort();
+
96}
+
97
+
98
+
99static void do_nothing(int sig)
+
100{
+
101 (void) sig;
+
102}
+
103
+
104static int set_one_signal_handler(int sig, void (*handler)(int), int remove)
+
105{
+
106 struct sigaction sa;
+
107 struct sigaction old_sa;
+
108
+
109 memset(&sa, 0, sizeof(struct sigaction));
+
110 sa.sa_handler = remove ? SIG_DFL : handler;
+
111 sigemptyset(&(sa.sa_mask));
+
112 sa.sa_flags = 0;
+
113
+
114 if (sigaction(sig, NULL, &old_sa) == -1) {
+
115 perror("fuse: cannot get old signal handler");
+
116 return -1;
+
117 }
+
118
+
119 if (old_sa.sa_handler == (remove ? handler : SIG_DFL) &&
+
120 sigaction(sig, &sa, NULL) == -1) {
+
121 perror("fuse: cannot set signal handler");
+
122 return -1;
+
123 }
+
124 return 0;
+
125}
+
126
+
127static int _fuse_set_signal_handlers(int signals[], int nr_signals,
+
128 void (*handler)(int))
+
129{
+
130 for (int idx = 0; idx < nr_signals; idx++) {
+
131 int signal = signals[idx];
+
132
+
133 /*
+
134 * If we used SIG_IGN instead of the do_nothing function,
+
135 * then we would be unable to tell if we set SIG_IGN (and
+
136 * thus should reset to SIG_DFL in fuse_remove_signal_handlers)
+
137 * or if it was already set to SIG_IGN (and should be left
+
138 * untouched.
+
139 */
+
140 if (set_one_signal_handler(signal, handler, 0) == -1) {
+
141 fuse_log(FUSE_LOG_ERR,
+
142 "Failed to install signal handler for sig %d\n",
+
143 signal);
+
144 return -1;
+
145 }
+
146 }
+
147
+
148 return 0;
+
149}
+
150
+
151int fuse_set_signal_handlers(struct fuse_session *se)
+
152{
+
153 size_t nr_signals;
+
154 int rc;
+
155
+
156 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
157 rc = _fuse_set_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
158 if (rc < 0)
+
159 return rc;
+
160
+
161 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
162 rc = _fuse_set_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
163 if (rc < 0)
+
164 return rc;
+
165
+
166 /*
+
167 * needs to be set independently if already set, as some applications
+
168 * may have multiple sessions and might rely on traditional behavior
+
169 * that the last session is used.
+
170 */
+
171 fuse_instance = se;
+
172
+
173 return 0;
+
174}
+
175
+
176int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
177{
+
178 size_t nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
179
+
180 int rc = _fuse_set_signal_handlers(fail_sigs, nr_signals,
+
181 exit_backtrace);
+
182 if (rc < 0)
+
183 return rc;
+
184
+
185 /* See fuse_set_signal_handlers, why set unconditionally */
+
186 fuse_instance = se;
+
187
+
188 return 0;
+
189}
+
190
+
191static void _fuse_remove_signal_handlers(int signals[], int nr_signals,
+
192 void (*handler)(int))
+
193{
+
194 for (int idx = 0; idx < nr_signals; idx++)
+
195 set_one_signal_handler(signals[idx], handler, 1);
+
196}
+
197
+
198void fuse_remove_signal_handlers(struct fuse_session *se)
+
199{
+
200 size_t nr_signals;
+
201
+
202 if (fuse_instance != se)
+
203 fuse_log(FUSE_LOG_ERR,
+
204 "fuse: fuse_remove_signal_handlers: unknown session\n");
+
205 else
+
206 fuse_instance = NULL;
+
207
+
208 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
209 _fuse_remove_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
210
+
211 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
212 _fuse_remove_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
213
+
214 nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
215 _fuse_remove_signal_handlers(fail_sigs, nr_signals, exit_backtrace);
+
216}
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2fuse__uring_8c_source.html b/doc/html/fuse-3_818_81_2lib_2fuse__uring_8c_source.html new file mode 100644 index 0000000..89f7385 --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2fuse__uring_8c_source.html @@ -0,0 +1,1015 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/fuse_uring.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_uring.c
+
+
+
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 * Copyright (C) 2025 Bernd Schubert <bschubert@ddn.com>
+
4 *
+
5 * Implementation of (most of) FUSE-over-io-uring.
+
6 *
+
7 * This program can be distributed under the terms of the GNU LGPLv2.
+
8 * See the file LGPL2.txt
+
9 */
+
10
+
11#define _GNU_SOURCE
+
12
+
13#include "fuse_i.h"
+
14#include "fuse_kernel.h"
+
15#include "fuse_uring_i.h"
+
16
+
17#include <stdlib.h>
+
18#include <liburing.h>
+
19#include <sys/sysinfo.h>
+
20#include <stdint.h>
+
21#include <inttypes.h>
+
22#include <stdbool.h>
+
23#include <string.h>
+
24#include <unistd.h>
+
25#include <numa.h>
+
26#include <pthread.h>
+
27#include <stdio.h>
+
28#include <linux/sched.h>
+
29#include <poll.h>
+
30#include <sys/eventfd.h>
+
31
+
32/* Size of command data area in SQE when IORING_SETUP_SQE128 is used */
+
33#define FUSE_URING_MAX_SQE128_CMD_DATA 80
+
34
+
35struct fuse_ring_ent {
+
36 struct fuse_ring_queue *ring_queue; /* back pointer */
+
37 struct fuse_req req;
+
38
+
39 struct fuse_uring_req_header *req_header;
+
40 void *op_payload;
+
41 size_t req_payload_sz;
+
42
+
43 /* commit id of a fuse request */
+
44 uint64_t req_commit_id;
+
45
+
46 enum fuse_uring_cmd last_cmd;
+
47
+
48 /* header and payload */
+
49 struct iovec iov[2];
+
50};
+
51
+
52struct fuse_ring_queue {
+
53 /* back pointer */
+
54 struct fuse_ring_pool *ring_pool;
+
55 int qid;
+
56 int numa_node;
+
57 pthread_t tid;
+
58 int eventfd;
+
59 size_t req_header_sz;
+
60 struct io_uring ring;
+
61
+
62 pthread_mutex_t ring_lock;
+
63 bool cqe_processing;
+
64
+
65 /* size depends on queue depth */
+
66 struct fuse_ring_ent ent[];
+
67};
+
68
+
+ +
73 struct fuse_session *se;
+
74
+
75 /* number of queues */
+
76 size_t nr_queues;
+
77
+
78 /* number of per queue entries */
+
79 size_t queue_depth;
+
80
+
81 /* max payload size for fuse requests*/
+
82 size_t max_req_payload_sz;
+
83
+
84 /* size of a single queue */
+
85 size_t queue_mem_size;
+
86
+
87 unsigned int started_threads;
+
88 unsigned int failed_threads;
+
89
+
90 /* Avoid sending queue entries before FUSE_INIT reply*/
+
91 sem_t init_sem;
+
92
+
93 pthread_cond_t thread_start_cond;
+
94 pthread_mutex_t thread_start_mutex;
+
95
+
96 /* pointer to the first queue */
+
97 struct fuse_ring_queue *queues;
+
98};
+
+
99
+
100static size_t
+
101fuse_ring_queue_size(const size_t q_depth)
+
102{
+
103 const size_t req_size = sizeof(struct fuse_ring_ent) * q_depth;
+
104
+
105 return sizeof(struct fuse_ring_queue) + req_size;
+
106}
+
107
+
108static struct fuse_ring_queue *
+
109fuse_uring_get_queue(struct fuse_ring_pool *fuse_ring, int qid)
+
110{
+
111 void *ptr =
+
112 ((char *)fuse_ring->queues) + (qid * fuse_ring->queue_mem_size);
+
113
+
114 return ptr;
+
115}
+
116
+
120static void *fuse_uring_get_sqe_cmd(struct io_uring_sqe *sqe)
+
121{
+
122 return (void *)&sqe->cmd[0];
+
123}
+
124
+
125static void fuse_uring_sqe_set_req_data(struct fuse_uring_cmd_req *req,
+
126 const unsigned int qid,
+
127 const uint64_t commit_id)
+
128{
+
129 req->qid = qid;
+
130 req->commit_id = commit_id;
+
131 req->flags = 0;
+
132}
+
133
+
134static void
+
135fuse_uring_sqe_prepare(struct io_uring_sqe *sqe, struct fuse_ring_ent *req,
+
136 __u32 cmd_op)
+
137{
+
138 /* These fields should be written once, never change */
+
139 sqe->opcode = IORING_OP_URING_CMD;
+
140
+
141 /*
+
142 * IOSQE_FIXED_FILE: fd is the index to the fd *array*
+
143 * given to io_uring_register_files()
+
144 */
+
145 sqe->flags = IOSQE_FIXED_FILE;
+
146 sqe->fd = 0;
+
147
+
148 sqe->rw_flags = 0;
+
149 sqe->ioprio = 0;
+
150 sqe->off = 0;
+
151
+
152 io_uring_sqe_set_data(sqe, req);
+
153
+
154 sqe->cmd_op = cmd_op;
+
155 sqe->__pad1 = 0;
+
156}
+
157
+
158static int fuse_uring_commit_sqe(struct fuse_ring_pool *ring_pool,
+
159 struct fuse_ring_queue *queue,
+
160 struct fuse_ring_ent *ring_ent)
+
161{
+
162 bool locked = false;
+
163 struct fuse_session *se = ring_pool->se;
+
164 struct fuse_uring_req_header *rrh = ring_ent->req_header;
+
165 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
+
166 struct fuse_uring_ent_in_out *ent_in_out =
+
167 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
+
168 struct io_uring_sqe *sqe;
+
169
+
170 if (pthread_self() != queue->tid) {
+
171 pthread_mutex_lock(&queue->ring_lock);
+
172 locked = true;
+
173 }
+
174
+
175 sqe = io_uring_get_sqe(&queue->ring);
+
176
+
177 if (sqe == NULL) {
+
178 /* This is an impossible condition, unless there is a bug.
+
179 * The kernel sent back an SQEs, which is assigned to a request.
+
180 * There is no way to get out of SQEs, as the number of
+
181 * SQEs matches the number tof requests.
+
182 */
+
183
+
184 se->error = -EIO;
+
185 fuse_log(FUSE_LOG_ERR, "Failed to get a ring SQEs\n");
+
186
+
187 return -EIO;
+
188 }
+
189
+
190 ring_ent->last_cmd = FUSE_IO_URING_CMD_COMMIT_AND_FETCH;
+
191 fuse_uring_sqe_prepare(sqe, ring_ent, ring_ent->last_cmd);
+
192 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), queue->qid,
+
193 ring_ent->req_commit_id);
+
194
+
195 if (se->debug) {
+
196 fuse_log(FUSE_LOG_DEBUG, " unique: %" PRIu64 ", result=%d\n",
+
197 out->unique, ent_in_out->payload_sz);
+
198 }
+
199
+
200 if (!queue->cqe_processing)
+
201 io_uring_submit(&queue->ring);
+
202
+
203 if (locked)
+
204 pthread_mutex_unlock(&queue->ring_lock);
+
205
+
206 return 0;
+
207}
+
208
+
209int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz,
+
210 void **mr)
+
211{
+
212 struct fuse_ring_ent *ring_ent;
+
213
+
214 /* Not possible without io-uring interface */
+
215 if (!req->flags.is_uring)
+
216 return -EINVAL;
+
217
+
218 ring_ent = container_of(req, struct fuse_ring_ent, req);
+
219
+
220 *payload = ring_ent->op_payload;
+
221 *payload_sz = ring_ent->req_payload_sz;
+
222
+
223 /*
+
224 * For now unused, but will be used later when the application can
+
225 * allocate the buffers itself and register them for rdma.
+
226 */
+
227 if (mr)
+
228 *mr = NULL;
+
229
+
230 return 0;
+
231}
+
232
+
233int send_reply_uring(fuse_req_t req, int error, const void *arg, size_t argsize)
+
234{
+
235 int res;
+
236 struct fuse_ring_ent *ring_ent =
+
237 container_of(req, struct fuse_ring_ent, req);
+
238 struct fuse_uring_req_header *rrh = ring_ent->req_header;
+
239 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
+
240 struct fuse_uring_ent_in_out *ent_in_out =
+
241 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
+
242
+
243 struct fuse_ring_queue *queue = ring_ent->ring_queue;
+
244 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
245 size_t max_payload_sz = ring_pool->max_req_payload_sz;
+
246
+
247 if (argsize > max_payload_sz) {
+
248 fuse_log(FUSE_LOG_ERR, "argsize %zu exceeds buffer size %zu",
+
249 argsize, max_payload_sz);
+
250 error = -EINVAL;
+
251 } else if (argsize) {
+
252 if (arg != ring_ent->op_payload)
+
253 memcpy(ring_ent->op_payload, arg, argsize);
+
254 }
+
255 ent_in_out->payload_sz = argsize;
+
256
+
257 out->error = error;
+
258 out->unique = req->unique;
+
259
+
260 res = fuse_uring_commit_sqe(ring_pool, queue, ring_ent);
+
261
+
262 fuse_free_req(req);
+
263
+
264 return res;
+
265}
+
266
+
267int fuse_reply_data_uring(fuse_req_t req, struct fuse_bufvec *bufv,
+
268 enum fuse_buf_copy_flags flags)
+
269{
+
270 struct fuse_ring_ent *ring_ent =
+
271 container_of(req, struct fuse_ring_ent, req);
+
272
+
273 struct fuse_ring_queue *queue = ring_ent->ring_queue;
+
274 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
275 struct fuse_uring_req_header *rrh = ring_ent->req_header;
+
276 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
+
277 struct fuse_uring_ent_in_out *ent_in_out =
+
278 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
+
279 size_t max_payload_sz = ring_ent->req_payload_sz;
+
280 struct fuse_bufvec dest_vec = FUSE_BUFVEC_INIT(max_payload_sz);
+
281 int res;
+
282
+
283 dest_vec.buf[0].mem = ring_ent->op_payload;
+
284 dest_vec.buf[0].size = max_payload_sz;
+
285
+
286 res = fuse_buf_copy(&dest_vec, bufv, flags);
+
287
+
288 out->error = res < 0 ? res : 0;
+
289 out->unique = req->unique;
+
290
+
291 ent_in_out->payload_sz = res > 0 ? res : 0;
+
292
+
293 res = fuse_uring_commit_sqe(ring_pool, queue, ring_ent);
+
294
+
295 fuse_free_req(req);
+
296
+
297 return res;
+
298}
+
299
+
303int fuse_send_msg_uring(fuse_req_t req, struct iovec *iov, int count)
+
304{
+
305 struct fuse_ring_ent *ring_ent =
+
306 container_of(req, struct fuse_ring_ent, req);
+
307
+
308 struct fuse_ring_queue *queue = ring_ent->ring_queue;
+
309 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
310 struct fuse_uring_req_header *rrh = ring_ent->req_header;
+
311 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
+
312 struct fuse_uring_ent_in_out *ent_in_out =
+
313 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
+
314 size_t max_buf = ring_pool->max_req_payload_sz;
+
315 size_t len = 0;
+
316 int res = 0;
+
317
+
318 /* copy iov into the payload, idx=0 is the header section */
+
319 for (int idx = 1; idx < count; idx++) {
+
320 struct iovec *cur = &iov[idx];
+
321
+
322 if (len + cur->iov_len > max_buf) {
+
323 fuse_log(FUSE_LOG_ERR,
+
324 "iov[%d] exceeds buffer size %zu",
+
325 idx, max_buf);
+
326 res = -EINVAL; /* Gracefully handle this? */
+
327 break;
+
328 }
+
329
+
330 memcpy(ring_ent->op_payload + len, cur->iov_base, cur->iov_len);
+
331 len += cur->iov_len;
+
332 }
+
333
+
334 ent_in_out->payload_sz = len;
+
335
+
336 out->error = res;
+
337 out->unique = req->unique;
+
338 out->len = len;
+
339
+
340 return fuse_uring_commit_sqe(ring_pool, queue, ring_ent);
+
341}
+
342
+
343static int fuse_queue_setup_io_uring(struct io_uring *ring, size_t qid,
+
344 size_t depth, int fd, int evfd)
+
345{
+
346 int rc;
+
347 struct io_uring_params params = {0};
+
348 int files[2] = { fd, evfd };
+
349
+
350 depth += 1; /* for the eventfd poll SQE */
+
351
+
352 params.flags = IORING_SETUP_SQE128;
+
353
+
354 /* Avoid cq overflow */
+
355 params.flags |= IORING_SETUP_CQSIZE;
+
356 params.cq_entries = depth * 2;
+
357
+
358 /* These flags should help to increase performance, but actually
+
359 * make it a bit slower - reason should get investigated.
+
360 */
+
361 if (0) {
+
362 /* Has the main slow down effect */
+
363 params.flags |= IORING_SETUP_SINGLE_ISSUER;
+
364
+
365 // params.flags |= IORING_SETUP_DEFER_TASKRUN;
+
366 params.flags |= IORING_SETUP_TASKRUN_FLAG;
+
367
+
368 /* Second main effect to make it slower */
+
369 params.flags |= IORING_SETUP_COOP_TASKRUN;
+
370 }
+
371
+
372 rc = io_uring_queue_init_params(depth, ring, &params);
+
373 if (rc != 0) {
+
374 fuse_log(FUSE_LOG_ERR, "Failed to setup qid %zu: %d (%s)\n",
+
375 qid, rc, strerror(-rc));
+
376 return rc;
+
377 }
+
378
+
379 rc = io_uring_register_files(ring, files, 1);
+
380 if (rc != 0) {
+
381 rc = -errno;
+
382 fuse_log(FUSE_LOG_ERR,
+
383 "Failed to register files for ring idx %zu: %s",
+
384 qid, strerror(errno));
+
385 return rc;
+
386 }
+
387
+
388 return 0;
+
389}
+
390
+
391static void fuse_session_destruct_uring(struct fuse_ring_pool *fuse_ring)
+
392{
+
393 for (size_t qid = 0; qid < fuse_ring->nr_queues; qid++) {
+
394 struct fuse_ring_queue *queue =
+
395 fuse_uring_get_queue(fuse_ring, qid);
+
396
+
397 if (queue->tid != 0) {
+
398 uint64_t value = 1ULL;
+
399 int rc;
+
400
+
401 rc = write(queue->eventfd, &value, sizeof(value));
+
402 if (rc != sizeof(value))
+
403 fprintf(stderr,
+
404 "Wrote to eventfd=%d err=%s: rc=%d\n",
+
405 queue->eventfd, strerror(errno), rc);
+
406 pthread_cancel(queue->tid);
+
407 pthread_join(queue->tid, NULL);
+
408 queue->tid = 0;
+
409 }
+
410
+
411 if (queue->eventfd >= 0) {
+
412 close(queue->eventfd);
+
413 queue->eventfd = -1;
+
414 }
+
415
+
416 if (queue->ring.ring_fd != -1)
+
417 io_uring_queue_exit(&queue->ring);
+
418
+
419 for (size_t idx = 0; idx < fuse_ring->queue_depth; idx++) {
+
420 struct fuse_ring_ent *ent = &queue->ent[idx];
+
421
+
422 numa_free(ent->op_payload, ent->req_payload_sz);
+
423 numa_free(ent->req_header, queue->req_header_sz);
+
424 }
+
425
+
426 pthread_mutex_destroy(&queue->ring_lock);
+
427 }
+
428
+
429 free(fuse_ring->queues);
+
430 pthread_cond_destroy(&fuse_ring->thread_start_cond);
+
431 pthread_mutex_destroy(&fuse_ring->thread_start_mutex);
+
432 free(fuse_ring);
+
433}
+
434
+
435static int fuse_uring_register_ent(struct fuse_ring_queue *queue,
+
436 struct fuse_ring_ent *ent)
+
437{
+
438 struct io_uring_sqe *sqe;
+
439
+
440 sqe = io_uring_get_sqe(&queue->ring);
+
441 if (sqe == NULL) {
+
442 /*
+
443 * All SQEs are idle here - no good reason this
+
444 * could fail
+
445 */
+
446 fuse_log(FUSE_LOG_ERR, "Failed to get all ring SQEs");
+
447 return -EIO;
+
448 }
+
449
+
450 ent->last_cmd = FUSE_IO_URING_CMD_REGISTER;
+
451 fuse_uring_sqe_prepare(sqe, ent, ent->last_cmd);
+
452
+
453 /* only needed for fetch */
+
454 ent->iov[0].iov_base = ent->req_header;
+
455 ent->iov[0].iov_len = queue->req_header_sz;
+
456
+
457 ent->iov[1].iov_base = ent->op_payload;
+
458 ent->iov[1].iov_len = ent->req_payload_sz;
+
459
+
460 sqe->addr = (uint64_t)(ent->iov);
+
461 sqe->len = 2;
+
462
+
463 /* this is a fetch, kernel does not read commit id */
+
464 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), queue->qid, 0);
+
465
+
466 return 0;
+
467
+
468}
+
469
+
470static int fuse_uring_register_queue(struct fuse_ring_queue *queue)
+
471{
+
472 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
473 unsigned int sq_ready;
+
474 struct io_uring_sqe *sqe;
+
475 int res;
+
476
+
477 for (size_t idx = 0; idx < ring_pool->queue_depth; idx++) {
+
478 struct fuse_ring_ent *ent = &queue->ent[idx];
+
479
+
480 res = fuse_uring_register_ent(queue, ent);
+
481 if (res != 0)
+
482 return res;
+
483 }
+
484
+
485 sq_ready = io_uring_sq_ready(&queue->ring);
+
486 if (sq_ready != ring_pool->queue_depth) {
+
487 fuse_log(FUSE_LOG_ERR,
+
488 "SQE ready mismatch, expected %zu got %u\n",
+
489 ring_pool->queue_depth, sq_ready);
+
490 return -EINVAL;
+
491 }
+
492
+
493 /* Poll SQE for the eventfd to wake up on teardown */
+
494 sqe = io_uring_get_sqe(&queue->ring);
+
495 if (sqe == NULL) {
+
496 fuse_log(FUSE_LOG_ERR, "Failed to get eventfd SQE");
+
497 return -EIO;
+
498 }
+
499
+
500 io_uring_prep_poll_add(sqe, queue->eventfd, POLLIN);
+
501 io_uring_sqe_set_data(sqe, (void *)(uintptr_t)queue->eventfd);
+
502
+
503 /* Only preparation until here, no submission yet */
+
504
+
505 return 0;
+
506}
+
507
+
508static struct fuse_ring_pool *fuse_create_ring(struct fuse_session *se)
+
509{
+
510 struct fuse_ring_pool *fuse_ring = NULL;
+
511 const size_t nr_queues = get_nprocs_conf();
+
512 size_t payload_sz = se->bufsize - FUSE_BUFFER_HEADER_SIZE;
+
513 size_t queue_sz;
+
514
+
515 if (se->debug)
+
516 fuse_log(FUSE_LOG_DEBUG, "starting io-uring q-depth=%d\n",
+
517 se->uring.q_depth);
+
518
+
519 fuse_ring = calloc(1, sizeof(*fuse_ring));
+
520 if (fuse_ring == NULL) {
+
521 fuse_log(FUSE_LOG_ERR, "Allocating the ring failed\n");
+
522 goto err;
+
523 }
+
524
+
525 queue_sz = fuse_ring_queue_size(se->uring.q_depth);
+
526 fuse_ring->queues = calloc(1, queue_sz * nr_queues);
+
527 if (fuse_ring->queues == NULL) {
+
528 fuse_log(FUSE_LOG_ERR, "Allocating the queues failed\n");
+
529 goto err;
+
530 }
+
531
+
532 fuse_ring->se = se;
+
533 fuse_ring->nr_queues = nr_queues;
+
534 fuse_ring->queue_depth = se->uring.q_depth;
+
535 fuse_ring->max_req_payload_sz = payload_sz;
+
536 fuse_ring->queue_mem_size = queue_sz;
+
537
+
538 /*
+
539 * very basic queue initialization, that cannot fail and will
+
540 * allow easy cleanup if something (like mmap) fails in the middle
+
541 * below
+
542 */
+
543 for (size_t qid = 0; qid < nr_queues; qid++) {
+
544 struct fuse_ring_queue *queue =
+
545 fuse_uring_get_queue(fuse_ring, qid);
+
546
+
547 queue->ring.ring_fd = -1;
+
548 queue->numa_node = numa_node_of_cpu(qid);
+
549 queue->qid = qid;
+
550 queue->ring_pool = fuse_ring;
+
551 queue->eventfd = -1;
+
552 pthread_mutex_init(&queue->ring_lock, NULL);
+
553 }
+
554
+
555 pthread_cond_init(&fuse_ring->thread_start_cond, NULL);
+
556 pthread_mutex_init(&fuse_ring->thread_start_mutex, NULL);
+
557 sem_init(&fuse_ring->init_sem, 0, 0);
+
558
+
559 return fuse_ring;
+
560
+
561err:
+
562 if (fuse_ring)
+
563 fuse_session_destruct_uring(fuse_ring);
+
564
+
565 return NULL;
+
566}
+
567
+
568static void fuse_uring_resubmit(struct fuse_ring_queue *queue,
+
569 struct fuse_ring_ent *ent)
+
570{
+
571 struct io_uring_sqe *sqe;
+
572
+
573 sqe = io_uring_get_sqe(&queue->ring);
+
574 if (sqe == NULL) {
+
575 /* This is an impossible condition, unless there is a bug.
+
576 * The kernel sent back an SQEs, which is assigned to a request.
+
577 * There is no way to get out of SQEs, as the number of
+
578 * SQEs matches the number tof requests.
+
579 */
+
580
+
581 queue->ring_pool->se->error = -EIO;
+
582 fuse_log(FUSE_LOG_ERR, "Failed to get a ring SQEs\n");
+
583
+
584 return;
+
585 }
+
586
+
587 fuse_uring_sqe_prepare(sqe, ent, ent->last_cmd);
+
588
+
589 switch (ent->last_cmd) {
+
590 case FUSE_IO_URING_CMD_REGISTER:
+
591 sqe->addr = (uint64_t)(ent->iov);
+
592 sqe->len = 2;
+
593 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe),
+
594 queue->qid, 0);
+
595 break;
+
596 case FUSE_IO_URING_CMD_COMMIT_AND_FETCH:
+
597 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe),
+
598 queue->qid, ent->req_commit_id);
+
599 break;
+
600 default:
+
601 fuse_log(FUSE_LOG_ERR, "Unknown command type: %d\n",
+
602 ent->last_cmd);
+
603 queue->ring_pool->se->error = -EINVAL;
+
604 break;
+
605 }
+
606
+
607 /* caller submits */
+
608}
+
609
+
610static void fuse_uring_handle_cqe(struct fuse_ring_queue *queue,
+
611 struct io_uring_cqe *cqe)
+
612{
+
613 struct fuse_ring_ent *ent = io_uring_cqe_get_data(cqe);
+
614
+
615 if (!ent) {
+
616 fuse_log(FUSE_LOG_ERR,
+
617 "cqe=%p io_uring_cqe_get_data returned NULL\n", cqe);
+
618 return;
+
619 }
+
620
+
621 struct fuse_req *req = &ent->req;
+
622 struct fuse_ring_pool *fuse_ring = queue->ring_pool;
+
623 struct fuse_uring_req_header *rrh = ent->req_header;
+
624
+
625 struct fuse_in_header *in = (struct fuse_in_header *)&rrh->in_out;
+
626 struct fuse_uring_ent_in_out *ent_in_out = &rrh->ring_ent_in_out;
+
627
+
628 ent->req_commit_id = ent_in_out->commit_id;
+
629 if (unlikely(ent->req_commit_id == 0)) {
+
630 /*
+
631 * If this happens kernel will not find the response - it will
+
632 * be stuck forever - better to abort immediately.
+
633 */
+
634 fuse_log(FUSE_LOG_ERR, "Received invalid commit_id=0\n");
+
635 abort();
+
636 }
+
637
+
638 memset(&req->flags, 0, sizeof(req->flags));
+
639 memset(&req->u, 0, sizeof(req->u));
+
640 req->flags.is_uring = 1;
+
641 req->ref_cnt++;
+
642 req->ch = NULL; /* not needed for uring */
+
643 req->interrupted = 0;
+
644 list_init_req(req);
+
645
+
646 fuse_session_process_uring_cqe(fuse_ring->se, req, in, &rrh->op_in,
+
647 ent->op_payload, ent_in_out->payload_sz);
+
648}
+
649
+
650static int fuse_uring_queue_handle_cqes(struct fuse_ring_queue *queue)
+
651{
+
652 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
653 struct fuse_session *se = ring_pool->se;
+
654 size_t num_completed = 0;
+
655 struct io_uring_cqe *cqe;
+
656 unsigned int head;
+
657 struct fuse_ring_ent *ent;
+
658 int ret = 0;
+
659
+
660 io_uring_for_each_cqe(&queue->ring, head, cqe) {
+
661 int err = 0;
+
662
+
663 num_completed++;
+
664
+
665 err = cqe->res;
+
666 if (unlikely(err != 0)) {
+
667 if (err > 0 && ((uintptr_t)io_uring_cqe_get_data(cqe) ==
+
668 (unsigned int)queue->eventfd)) {
+
669 /* teardown from eventfd */
+
670 return -ENOTCONN;
+
671 }
+
672
+
673
+
674 switch (err) {
+
675 case -EAGAIN:
+
676 fallthrough;
+
677 case -EINTR:
+
678 ent = io_uring_cqe_get_data(cqe);
+
679 fuse_uring_resubmit(queue, ent);
+
680 continue;
+
681 default:
+
682 break;
+
683 }
+
684
+
685 /* -ENOTCONN is ok on umount */
+
686 if (err != -ENOTCONN) {
+
687 se->error = cqe->res;
+
688
+
689 /* return first error */
+
690 if (ret == 0)
+
691 ret = err;
+
692 }
+
693
+
694 } else {
+
695 fuse_uring_handle_cqe(queue, cqe);
+
696 }
+
697 }
+
698
+
699 if (num_completed)
+
700 io_uring_cq_advance(&queue->ring, num_completed);
+
701
+
702 return ret == 0 ? 0 : num_completed;
+
703}
+
704
+
709static void fuse_uring_set_thread_core(int qid)
+
710{
+
711 cpu_set_t mask;
+
712 int rc;
+
713
+
714 CPU_ZERO(&mask);
+
715 CPU_SET(qid, &mask);
+
716 rc = sched_setaffinity(0, sizeof(cpu_set_t), &mask);
+
717 if (rc != 0)
+
718 fuse_log(FUSE_LOG_ERR, "Failed to bind qid=%d to its core: %s\n",
+
719 qid, strerror(errno));
+
720
+
721 if (0) {
+
722 const int policy = SCHED_IDLE;
+
723 const struct sched_param param = {
+
724 .sched_priority = sched_get_priority_min(policy),
+
725 };
+
726
+
727 /* Set the lowest possible priority, so that the application
+
728 * submitting requests is not moved away from the current core.
+
729 */
+
730 rc = sched_setscheduler(0, policy, &param);
+
731 if (rc != 0)
+
732 fuse_log(FUSE_LOG_ERR, "Failed to set scheduler: %s\n",
+
733 strerror(errno));
+
734 }
+
735}
+
736
+
737/*
+
738 * @return negative error code or io-uring file descriptor
+
739 */
+
740static int fuse_uring_init_queue(struct fuse_ring_queue *queue)
+
741{
+
742 struct fuse_ring_pool *ring = queue->ring_pool;
+
743 struct fuse_session *se = ring->se;
+
744 int res;
+
745 size_t page_sz = sysconf(_SC_PAGESIZE);
+
746
+
747 queue->eventfd = eventfd(0, EFD_CLOEXEC);
+
748 if (queue->eventfd < 0) {
+
749 res = -errno;
+
750 fuse_log(FUSE_LOG_ERR,
+
751 "Failed to create eventfd for qid %d: %s\n",
+
752 queue->qid, strerror(errno));
+
753 return res;
+
754 }
+
755
+
756 res = fuse_queue_setup_io_uring(&queue->ring, queue->qid,
+
757 ring->queue_depth, se->fd,
+
758 queue->eventfd);
+
759 if (res != 0) {
+
760 fuse_log(FUSE_LOG_ERR, "qid=%d io_uring init failed\n",
+
761 queue->qid);
+
762 goto err;
+
763 }
+
764
+
765 queue->req_header_sz = ROUND_UP(sizeof(struct fuse_ring_ent),
+
766 page_sz);
+
767
+
768 for (size_t idx = 0; idx < ring->queue_depth; idx++) {
+
769 struct fuse_ring_ent *ring_ent = &queue->ent[idx];
+
770 struct fuse_req *req = &ring_ent->req;
+
771
+
772 ring_ent->ring_queue = queue;
+
773
+
774 /*
+
775 * Also allocate the header to have it page aligned, which
+
776 * is a requirement for page pinning
+
777 */
+
778 ring_ent->req_header =
+
779 numa_alloc_local(queue->req_header_sz);
+
780 ring_ent->req_payload_sz = ring->max_req_payload_sz;
+
781
+
782 ring_ent->op_payload =
+
783 numa_alloc_local(ring_ent->req_payload_sz);
+
784
+
785 req->se = se;
+
786 pthread_mutex_init(&req->lock, NULL);
+
787 req->flags.is_uring = 1;
+
788 req->ref_cnt = 1; /* extra ref to avoid destruction */
+
789 list_init_req(req);
+
790 }
+
791
+
792 res = fuse_uring_register_queue(queue);
+
793 if (res != 0) {
+
794 fuse_log(
+
795 FUSE_LOG_ERR,
+
796 "Grave fuse-uring error on preparing SQEs, aborting\n");
+
797 se->error = -EIO;
+ +
799 }
+
800
+
801 return queue->ring.ring_fd;
+
802
+
803err:
+
804 close(queue->eventfd);
+
805 return res;
+
806}
+
807
+
808static void *fuse_uring_thread(void *arg)
+
809{
+
810 struct fuse_ring_queue *queue = arg;
+
811 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
812 struct fuse_session *se = ring_pool->se;
+
813 int err;
+
814 char thread_name[16] = { 0 };
+
815
+
816 snprintf(thread_name, 16, "fuse-ring-%d", queue->qid);
+
817 thread_name[15] = '\0';
+
818 fuse_set_thread_name(thread_name);
+
819
+
820 fuse_uring_set_thread_core(queue->qid);
+
821
+
822 err = fuse_uring_init_queue(queue);
+
823 pthread_mutex_lock(&ring_pool->thread_start_mutex);
+
824 if (err < 0)
+
825 ring_pool->failed_threads++;
+
826 ring_pool->started_threads++;
+
827 pthread_cond_broadcast(&ring_pool->thread_start_cond);
+
828 pthread_mutex_unlock(&ring_pool->thread_start_mutex);
+
829
+
830 if (err < 0) {
+
831 fuse_log(FUSE_LOG_ERR, "qid=%d queue setup failed\n",
+
832 queue->qid);
+
833 goto err_non_fatal;
+
834 }
+
835
+
836 sem_wait(&ring_pool->init_sem);
+
837
+
838 /* Not using fuse_session_exited(se), as that cannot be inlined */
+
839 while (!atomic_load_explicit(&se->mt_exited, memory_order_relaxed)) {
+
840 io_uring_submit_and_wait(&queue->ring, 1);
+
841
+
842 pthread_mutex_lock(&queue->ring_lock);
+
843 queue->cqe_processing = true;
+
844 err = fuse_uring_queue_handle_cqes(queue);
+
845 queue->cqe_processing = false;
+
846 pthread_mutex_unlock(&queue->ring_lock);
+
847 if (err < 0)
+
848 goto err;
+
849 }
+
850
+
851 return NULL;
+
852
+
853err:
+ +
855err_non_fatal:
+
856 return NULL;
+
857}
+
858
+
859static int fuse_uring_start_ring_threads(struct fuse_ring_pool *ring)
+
860{
+
861 int rc = 0;
+
862
+
863 for (size_t qid = 0; qid < ring->nr_queues; qid++) {
+
864 struct fuse_ring_queue *queue = fuse_uring_get_queue(ring, qid);
+
865
+
866 rc = pthread_create(&queue->tid, NULL, fuse_uring_thread, queue);
+
867 if (rc != 0)
+
868 break;
+
869 }
+
870
+
871 return rc;
+
872}
+
873
+
874static int fuse_uring_sanity_check(struct fuse_session *se)
+
875{
+
876 if (se->uring.q_depth == 0) {
+
877 fuse_log(FUSE_LOG_ERR, "io-uring queue depth must be > 0\n");
+
878 return -EINVAL;
+
879 }
+
880
+
881 _Static_assert(sizeof(struct fuse_uring_cmd_req) <=
+
882 FUSE_URING_MAX_SQE128_CMD_DATA,
+
883 "SQE128_CMD_DATA has 80B cmd data");
+
884
+
885 return 0;
+
886}
+
887
+
888int fuse_uring_start(struct fuse_session *se)
+
889{
+
890 int err = 0;
+
891 struct fuse_ring_pool *fuse_ring;
+
892
+
893 fuse_uring_sanity_check(se);
+
894
+
895 fuse_ring = fuse_create_ring(se);
+
896 if (fuse_ring == NULL) {
+
897 err = -EADDRNOTAVAIL;
+
898 goto err;
+
899 }
+
900
+
901 se->uring.pool = fuse_ring;
+
902
+
903 /* Hold off threads from send fuse ring entries (SQEs) */
+
904 sem_init(&fuse_ring->init_sem, 0, 0);
+
905 pthread_cond_init(&fuse_ring->thread_start_cond, NULL);
+
906 pthread_mutex_init(&fuse_ring->thread_start_mutex, NULL);
+
907
+
908 err = fuse_uring_start_ring_threads(fuse_ring);
+
909 if (err)
+
910 goto err;
+
911
+
912 /*
+
913 * Wait for all threads to start or to fail
+
914 */
+
915 pthread_mutex_lock(&fuse_ring->thread_start_mutex);
+
916 while (fuse_ring->started_threads < fuse_ring->nr_queues)
+
917 pthread_cond_wait(&fuse_ring->thread_start_cond,
+
918 &fuse_ring->thread_start_mutex);
+
919
+
920 if (fuse_ring->failed_threads != 0)
+
921 err = -EADDRNOTAVAIL;
+
922 pthread_mutex_unlock(&fuse_ring->thread_start_mutex);
+
923
+
924err:
+
925 if (err) {
+
926 /* Note all threads need to have been started */
+
927 fuse_session_destruct_uring(fuse_ring);
+
928 se->uring.pool = fuse_ring;
+
929 }
+
930 return err;
+
931}
+
932
+
933int fuse_uring_stop(struct fuse_session *se)
+
934{
+
935 struct fuse_ring_pool *ring = se->uring.pool;
+
936
+
937 if (ring == NULL)
+
938 return 0;
+
939
+
940 fuse_session_destruct_uring(ring);
+
941
+
942 return 0;
+
943}
+
944
+
945void fuse_uring_wake_ring_threads(struct fuse_session *se)
+
946{
+
947 struct fuse_ring_pool *ring = se->uring.pool;
+
948
+
949 /* Wake up the threads to let them send SQEs */
+
950 for (size_t qid = 0; qid < ring->nr_queues; qid++)
+
951 sem_post(&ring->init_sem);
+
952}
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
fuse_buf_copy_flags
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
struct fuse_req * fuse_req_t
+
int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, void **mr)
+
void * mem
+
size_t size
+ +
struct fuse_buf buf[1]
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2fuse__uring__i_8h_source.html b/doc/html/fuse-3_818_81_2lib_2fuse__uring__i_8h_source.html new file mode 100644 index 0000000..66f19f1 --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2fuse__uring__i_8h_source.html @@ -0,0 +1,149 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/fuse_uring_i.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_uring_i.h
+
+
+
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 * Copyright (C) 2025 Bernd Schubert <bschubert@ddn.com>
+
4 * This program can be distributed under the terms of the GNU LGPLv2.
+
5 * See the file LGPL2.txt
+
6 */
+
7
+
8#ifndef FUSE_URING_I_H_
+
9#define FUSE_URING_I_H_
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_kernel.h"
+
14
+
15#ifndef HAVE_URING
+
16#include "util.h"
+
17#endif
+
18
+
19#include <errno.h> // IWYU pragma: keep
+
20
+
21/* io-uring defaults */
+
22#define SESSION_DEF_URING_ENABLE (0)
+
23#define SESSION_DEF_URING_Q_DEPTH (8)
+
24
+
25void fuse_session_process_uring_cqe(struct fuse_session *se,
+
26 struct fuse_req *req,
+
27 struct fuse_in_header *in, void *in_header,
+
28 void *in_payload, size_t payload_len);
+
29
+
30#ifdef HAVE_URING
+
31
+
32struct fuse_in_header;
+
33
+
34int fuse_uring_start(struct fuse_session *se);
+
35void fuse_uring_wake_ring_threads(struct fuse_session *se);
+
36int fuse_uring_stop(struct fuse_session *se);
+
37int send_reply_uring(fuse_req_t req, int error, const void *arg,
+
38 size_t argsize);
+
39
+
40int fuse_reply_data_uring(fuse_req_t req, struct fuse_bufvec *bufv,
+
41 enum fuse_buf_copy_flags flags);
+
42int fuse_send_msg_uring(fuse_req_t req, struct iovec *iov, int count);
+
43
+
44#else // HAVE_URING
+
45
+
46static inline int fuse_uring_start(struct fuse_session *se FUSE_VAR_UNUSED)
+
47{
+
48 return -ENOTSUP;
+
49}
+
50
+
51static inline void
+
52fuse_uring_wake_ring_threads(struct fuse_session *se FUSE_VAR_UNUSED)
+
53{
+
54}
+
55
+
56static inline int fuse_uring_stop(struct fuse_session *se FUSE_VAR_UNUSED)
+
57{
+
58 return -ENOTSUP;
+
59}
+
60
+
61static inline int send_reply_uring(fuse_req_t req FUSE_VAR_UNUSED,
+
62 int error FUSE_VAR_UNUSED,
+
63 const void *arg FUSE_VAR_UNUSED,
+
64 size_t argsize FUSE_VAR_UNUSED)
+
65{
+
66 return -ENOTSUP;
+
67}
+
68
+
69static inline int
+
70fuse_reply_data_uring(fuse_req_t req FUSE_VAR_UNUSED,
+
71 struct fuse_bufvec *bufv FUSE_VAR_UNUSED,
+
72 enum fuse_buf_copy_flags flags FUSE_VAR_UNUSED)
+
73{
+
74 return -ENOTSUP;
+
75}
+
76
+
77static inline int fuse_send_msg_uring(fuse_req_t req FUSE_VAR_UNUSED,
+
78 struct iovec *iov FUSE_VAR_UNUSED,
+
79 int count FUSE_VAR_UNUSED)
+
80{
+
81 return -ENOTSUP;
+
82}
+
83
+
84#endif // HAVE_URING
+
85
+
86#endif // FUSE_URING_I_H_
+
fuse_buf_copy_flags
+
struct fuse_req * fuse_req_t
+ +
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2helper_8c_source.html b/doc/html/fuse-3_818_81_2lib_2helper_8c_source.html new file mode 100644 index 0000000..4598ae8 --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2helper_8c_source.html @@ -0,0 +1,606 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/helper.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
helper.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file LGPL2.txt.
+
11*/
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_i.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_opt.h"
+
17#include "fuse_lowlevel.h"
+
18#include "mount_util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <stddef.h>
+
23#include <unistd.h>
+
24#include <string.h>
+
25#include <limits.h>
+
26#include <errno.h>
+
27#include <sys/param.h>
+
28
+
29#define FUSE_HELPER_OPT(t, p) \
+
30 { t, offsetof(struct fuse_cmdline_opts, p), 1 }
+
31
+
32static const struct fuse_opt fuse_helper_opts[] = {
+
33 FUSE_HELPER_OPT("-h", show_help),
+
34 FUSE_HELPER_OPT("--help", show_help),
+
35 FUSE_HELPER_OPT("-V", show_version),
+
36 FUSE_HELPER_OPT("--version", show_version),
+
37 FUSE_HELPER_OPT("-d", debug),
+
38 FUSE_HELPER_OPT("debug", debug),
+
39 FUSE_HELPER_OPT("-d", foreground),
+
40 FUSE_HELPER_OPT("debug", foreground),
+ + +
43 FUSE_HELPER_OPT("-f", foreground),
+
44 FUSE_HELPER_OPT("-s", singlethread),
+
45 FUSE_HELPER_OPT("fsname=", nodefault_subtype),
+ +
47#ifndef __FreeBSD__
+
48 FUSE_HELPER_OPT("subtype=", nodefault_subtype),
+ +
50#endif
+
51 FUSE_HELPER_OPT("clone_fd", clone_fd),
+
52 FUSE_HELPER_OPT("max_idle_threads=%u", max_idle_threads),
+
53 FUSE_HELPER_OPT("max_threads=%u", max_threads),
+ +
55};
+
56
+
57struct fuse_conn_info_opts {
+
58 int atomic_o_trunc;
+
59 int no_remote_posix_lock;
+
60 int no_remote_flock;
+
61 int splice_write;
+
62 int splice_move;
+
63 int splice_read;
+
64 int no_splice_write;
+
65 int no_splice_move;
+
66 int no_splice_read;
+
67 int auto_inval_data;
+
68 int no_auto_inval_data;
+
69 int no_readdirplus;
+
70 int no_readdirplus_auto;
+
71 int async_dio;
+
72 int no_async_dio;
+
73 int writeback_cache;
+
74 int no_writeback_cache;
+
75 int async_read;
+
76 int sync_read;
+
77 unsigned max_write;
+
78 unsigned max_readahead;
+
79 unsigned max_background;
+
80 unsigned congestion_threshold;
+
81 unsigned time_gran;
+
82 int set_max_write;
+
83 int set_max_readahead;
+
84 int set_max_background;
+
85 int set_congestion_threshold;
+
86 int set_time_gran;
+
87};
+
88
+
89#define CONN_OPTION(t, p, v) \
+
90 { t, offsetof(struct fuse_conn_info_opts, p), v }
+
91static const struct fuse_opt conn_info_opt_spec[] = {
+
92 CONN_OPTION("max_write=%u", max_write, 0),
+
93 CONN_OPTION("max_write=", set_max_write, 1),
+
94 CONN_OPTION("max_readahead=%u", max_readahead, 0),
+
95 CONN_OPTION("max_readahead=", set_max_readahead, 1),
+
96 CONN_OPTION("max_background=%u", max_background, 0),
+
97 CONN_OPTION("max_background=", set_max_background, 1),
+
98 CONN_OPTION("congestion_threshold=%u", congestion_threshold, 0),
+
99 CONN_OPTION("congestion_threshold=", set_congestion_threshold, 1),
+
100 CONN_OPTION("sync_read", sync_read, 1),
+
101 CONN_OPTION("async_read", async_read, 1),
+
102 CONN_OPTION("atomic_o_trunc", atomic_o_trunc, 1),
+
103 CONN_OPTION("no_remote_lock", no_remote_posix_lock, 1),
+
104 CONN_OPTION("no_remote_lock", no_remote_flock, 1),
+
105 CONN_OPTION("no_remote_flock", no_remote_flock, 1),
+
106 CONN_OPTION("no_remote_posix_lock", no_remote_posix_lock, 1),
+
107 CONN_OPTION("splice_write", splice_write, 1),
+
108 CONN_OPTION("no_splice_write", no_splice_write, 1),
+
109 CONN_OPTION("splice_move", splice_move, 1),
+
110 CONN_OPTION("no_splice_move", no_splice_move, 1),
+
111 CONN_OPTION("splice_read", splice_read, 1),
+
112 CONN_OPTION("no_splice_read", no_splice_read, 1),
+
113 CONN_OPTION("auto_inval_data", auto_inval_data, 1),
+
114 CONN_OPTION("no_auto_inval_data", no_auto_inval_data, 1),
+
115 CONN_OPTION("readdirplus=no", no_readdirplus, 1),
+
116 CONN_OPTION("readdirplus=yes", no_readdirplus, 0),
+
117 CONN_OPTION("readdirplus=yes", no_readdirplus_auto, 1),
+
118 CONN_OPTION("readdirplus=auto", no_readdirplus, 0),
+
119 CONN_OPTION("readdirplus=auto", no_readdirplus_auto, 0),
+
120 CONN_OPTION("async_dio", async_dio, 1),
+
121 CONN_OPTION("no_async_dio", no_async_dio, 1),
+
122 CONN_OPTION("writeback_cache", writeback_cache, 1),
+
123 CONN_OPTION("no_writeback_cache", no_writeback_cache, 1),
+
124 CONN_OPTION("time_gran=%u", time_gran, 0),
+
125 CONN_OPTION("time_gran=", set_time_gran, 1),
+ +
127};
+
128
+
129
+
130void fuse_cmdline_help(void)
+
131{
+
132 printf(" -h --help print help\n"
+
133 " -V --version print version\n"
+
134 " -d -o debug enable debug output (implies -f)\n"
+
135 " -f foreground operation\n"
+
136 " -s disable multi-threaded operation\n"
+
137 " -o clone_fd use separate fuse device fd for each thread\n"
+
138 " (may improve performance)\n"
+
139 " -o max_idle_threads the maximum number of idle worker threads\n"
+
140 " allowed (default: -1)\n"
+
141 " -o max_threads the maximum number of worker threads\n"
+
142 " allowed (default: 10)\n");
+
143}
+
144
+
145static int fuse_helper_opt_proc(void *data, const char *arg, int key,
+
146 struct fuse_args *outargs)
+
147{
+
148 (void) outargs;
+
149 struct fuse_cmdline_opts *opts = data;
+
150
+
151 switch (key) {
+ +
153 if (!opts->mountpoint) {
+
154 if (fuse_mnt_parse_fuse_fd(arg) != -1) {
+
155 return fuse_opt_add_opt(&opts->mountpoint, arg);
+
156 }
+
157
+
158 char mountpoint[PATH_MAX] = "";
+
159 if (realpath(arg, mountpoint) == NULL) {
+
160 fuse_log(FUSE_LOG_ERR,
+
161 "fuse: bad mount point `%s': %s\n",
+
162 arg, strerror(errno));
+
163 return -1;
+
164 }
+
165 return fuse_opt_add_opt(&opts->mountpoint, mountpoint);
+
166 } else {
+
167 fuse_log(FUSE_LOG_ERR, "fuse: invalid argument `%s'\n", arg);
+
168 return -1;
+
169 }
+
170
+
171 default:
+
172 /* Pass through unknown options */
+
173 return 1;
+
174 }
+
175}
+
176
+
177/* Under FreeBSD, there is no subtype option so this
+
178 function actually sets the fsname */
+
179static int add_default_subtype(const char *progname, struct fuse_args *args)
+
180{
+
181 int res;
+
182 char *subtype_opt;
+
183
+
184 const char *basename = strrchr(progname, '/');
+
185 if (basename == NULL)
+
186 basename = progname;
+
187 else if (basename[1] != '\0')
+
188 basename++;
+
189
+
190 subtype_opt = (char *) malloc(strlen(basename) + 64);
+
191 if (subtype_opt == NULL) {
+
192 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
193 return -1;
+
194 }
+
195#ifdef __FreeBSD__
+
196 sprintf(subtype_opt, "-ofsname=%s", basename);
+
197#else
+
198 sprintf(subtype_opt, "-osubtype=%s", basename);
+
199#endif
+
200 res = fuse_opt_add_arg(args, subtype_opt);
+
201 free(subtype_opt);
+
202 return res;
+
203}
+
204
+
205int fuse_parse_cmdline_312(struct fuse_args *args,
+
206 struct fuse_cmdline_opts *opts);
+
207FUSE_SYMVER("fuse_parse_cmdline_312", "fuse_parse_cmdline@@FUSE_3.12")
+
208int fuse_parse_cmdline_312(struct fuse_args *args,
+
209 struct fuse_cmdline_opts *opts)
+
210{
+
211 memset(opts, 0, sizeof(struct fuse_cmdline_opts));
+
212
+
213 opts->max_idle_threads = UINT_MAX; /* new default in fuse version 3.12 */
+
214 opts->max_threads = 10;
+
215
+
216 if (fuse_opt_parse(args, opts, fuse_helper_opts,
+
217 fuse_helper_opt_proc) == -1)
+
218 return -1;
+
219
+
220 /* *Linux*: if neither -o subtype nor -o fsname are specified,
+
221 set subtype to program's basename.
+
222 *FreeBSD*: if fsname is not specified, set to program's
+
223 basename. */
+
224 if (!opts->nodefault_subtype)
+
225 if (add_default_subtype(args->argv[0], args) == -1)
+
226 return -1;
+
227
+
228 return 0;
+
229}
+
230
+
234int fuse_parse_cmdline_30(struct fuse_args *args,
+
235 struct fuse_cmdline_opts *opts);
+
236FUSE_SYMVER("fuse_parse_cmdline_30", "fuse_parse_cmdline@FUSE_3.0")
+
237int fuse_parse_cmdline_30(struct fuse_args *args,
+
238 struct fuse_cmdline_opts *out_opts)
+
239{
+
240 struct fuse_cmdline_opts opts;
+
241
+
242 int rc = fuse_parse_cmdline_312(args, &opts);
+
243 if (rc == 0) {
+
244 /* copy up to the size of the old pre 3.12 struct */
+
245 memcpy(out_opts, &opts,
+
246 offsetof(struct fuse_cmdline_opts, max_idle_threads) +
+
247 sizeof(opts.max_idle_threads));
+
248 }
+
249
+
250 return rc;
+
251}
+
252
+
253int fuse_daemonize(int foreground)
+
254{
+
255 if (!foreground) {
+
256 int nullfd;
+
257 int waiter[2];
+
258 char completed;
+
259
+
260 if (pipe(waiter)) {
+
261 perror("fuse_daemonize: pipe");
+
262 return -1;
+
263 }
+
264
+
265 /*
+
266 * demonize current process by forking it and killing the
+
267 * parent. This makes current process as a child of 'init'.
+
268 */
+
269 switch(fork()) {
+
270 case -1:
+
271 perror("fuse_daemonize: fork");
+
272 return -1;
+
273 case 0:
+
274 break;
+
275 default:
+
276 (void) read(waiter[0], &completed, sizeof(completed));
+
277 _exit(0);
+
278 }
+
279
+
280 if (setsid() == -1) {
+
281 perror("fuse_daemonize: setsid");
+
282 return -1;
+
283 }
+
284
+
285 (void) chdir("/");
+
286
+
287 nullfd = open("/dev/null", O_RDWR, 0);
+
288 if (nullfd != -1) {
+
289 (void) dup2(nullfd, 0);
+
290 (void) dup2(nullfd, 1);
+
291 (void) dup2(nullfd, 2);
+
292 if (nullfd > 2)
+
293 close(nullfd);
+
294 }
+
295
+
296 /* Propagate completion of daemon initialization */
+
297 completed = 1;
+
298 (void) write(waiter[1], &completed, sizeof(completed));
+
299 close(waiter[0]);
+
300 close(waiter[1]);
+
301 } else {
+
302 (void) chdir("/");
+
303 }
+
304 return 0;
+
305}
+
306
+
307int fuse_main_real_versioned(int argc, char *argv[],
+
308 const struct fuse_operations *op, size_t op_size,
+
309 struct libfuse_version *version, void *user_data)
+
310{
+
311 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
312 struct fuse *fuse;
+
313 struct fuse_cmdline_opts opts;
+
314 int res;
+
315 struct fuse_loop_config *loop_config = NULL;
+
316
+
317 if (fuse_parse_cmdline(&args, &opts) != 0)
+
318 return 1;
+
319
+
320 if (opts.show_version) {
+
321 printf("FUSE library version %s\n", PACKAGE_VERSION);
+ +
323 res = 0;
+
324 goto out1;
+
325 }
+
326
+
327 if (opts.show_help) {
+
328 if(args.argv[0][0] != '\0')
+
329 printf("usage: %s [options] <mountpoint>\n\n",
+
330 args.argv[0]);
+
331 printf("FUSE options:\n");
+ +
333 fuse_lib_help(&args);
+
334 res = 0;
+
335 goto out1;
+
336 }
+
337
+
338 if (!opts.show_help &&
+
339 !opts.mountpoint) {
+
340 fuse_log(FUSE_LOG_ERR, "error: no mountpoint specified\n");
+
341 res = 2;
+
342 goto out1;
+
343 }
+
344
+
345 struct fuse *_fuse_new_31(struct fuse_args *args,
+
346 const struct fuse_operations *op, size_t op_size,
+
347 struct libfuse_version *version,
+
348 void *user_data);
+
349 fuse = _fuse_new_31(&args, op, op_size, version, user_data);
+
350 if (fuse == NULL) {
+
351 res = 3;
+
352 goto out1;
+
353 }
+
354
+
355 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
356 res = 4;
+
357 goto out2;
+
358 }
+
359
+
360 if (fuse_daemonize(opts.foreground) != 0) {
+
361 res = 5;
+
362 goto out3;
+
363 }
+
364
+
365 struct fuse_session *se = fuse_get_session(fuse);
+
366 if (fuse_set_signal_handlers(se) != 0) {
+
367 res = 6;
+
368 goto out3;
+
369 }
+
370
+
371 if (opts.singlethread)
+
372 res = fuse_loop(fuse);
+
373 else {
+
374 loop_config = fuse_loop_cfg_create();
+
375 if (loop_config == NULL) {
+
376 res = 7;
+
377 goto out3;
+
378 }
+
379
+
380 fuse_loop_cfg_set_clone_fd(loop_config, opts.clone_fd);
+
381
+
382 fuse_loop_cfg_set_idle_threads(loop_config, opts.max_idle_threads);
+
383 fuse_loop_cfg_set_max_threads(loop_config, opts.max_threads);
+
384 res = fuse_loop_mt(fuse, loop_config);
+
385 }
+
386 if (res)
+
387 res = 8;
+
388
+ +
390out3:
+
391 fuse_unmount(fuse);
+
392out2:
+
393 fuse_destroy(fuse);
+
394out1:
+
395 fuse_loop_cfg_destroy(loop_config);
+
396 free(opts.mountpoint);
+
397 fuse_opt_free_args(&args);
+
398 return res;
+
399}
+
400
+
401/* Not symboled, as not part of the official API */
+
402int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
403 size_t op_size, void *user_data);
+
404int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
405 size_t op_size, void *user_data)
+
406{
+
407 struct libfuse_version version = { 0 };
+
408 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
409 user_data);
+
410}
+
411
+
412void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
413 struct fuse_conn_info *conn)
+
414{
+
415 if(opts->set_max_write)
+
416 conn->max_write = opts->max_write;
+
417 if(opts->set_max_background)
+
418 conn->max_background = opts->max_background;
+
419 if(opts->set_congestion_threshold)
+
420 conn->congestion_threshold = opts->congestion_threshold;
+
421 if(opts->set_time_gran)
+
422 conn->time_gran = opts->time_gran;
+
423 if(opts->set_max_readahead)
+
424 conn->max_readahead = opts->max_readahead;
+
425
+
426#define LL_ENABLE(cond, cap) \
+
427 do { \
+
428 if (cond) \
+
429 fuse_set_feature_flag(conn, cap); \
+
430 } while (0)
+
431
+
432#define LL_DISABLE(cond, cap) \
+
433 do { \
+
434 if (cond) \
+
435 fuse_unset_feature_flag(conn, cap); \
+
436 } while (0)
+
437
+
438 LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ);
+
439 LL_DISABLE(opts->no_splice_read, FUSE_CAP_SPLICE_READ);
+
440
+
441 LL_ENABLE(opts->splice_write, FUSE_CAP_SPLICE_WRITE);
+
442 LL_DISABLE(opts->no_splice_write, FUSE_CAP_SPLICE_WRITE);
+
443
+
444 LL_ENABLE(opts->splice_move, FUSE_CAP_SPLICE_MOVE);
+
445 LL_DISABLE(opts->no_splice_move, FUSE_CAP_SPLICE_MOVE);
+
446
+
447 LL_ENABLE(opts->auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
448 LL_DISABLE(opts->no_auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
449
+
450 LL_DISABLE(opts->no_readdirplus, FUSE_CAP_READDIRPLUS);
+
451 LL_DISABLE(opts->no_readdirplus_auto, FUSE_CAP_READDIRPLUS_AUTO);
+
452
+
453 LL_ENABLE(opts->async_dio, FUSE_CAP_ASYNC_DIO);
+
454 LL_DISABLE(opts->no_async_dio, FUSE_CAP_ASYNC_DIO);
+
455
+
456 LL_ENABLE(opts->writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
457 LL_DISABLE(opts->no_writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
458
+
459 LL_ENABLE(opts->async_read, FUSE_CAP_ASYNC_READ);
+
460 LL_DISABLE(opts->sync_read, FUSE_CAP_ASYNC_READ);
+
461
+
462 LL_DISABLE(opts->no_remote_posix_lock, FUSE_CAP_POSIX_LOCKS);
+
463 LL_DISABLE(opts->no_remote_flock, FUSE_CAP_FLOCK_LOCKS);
+
464}
+
465
+
466struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args)
+
467{
+
468 struct fuse_conn_info_opts *opts;
+
469
+
470 opts = calloc(1, sizeof(struct fuse_conn_info_opts));
+
471 if(opts == NULL) {
+
472 fuse_log(FUSE_LOG_ERR, "calloc failed\n");
+
473 return NULL;
+
474 }
+
475 if(fuse_opt_parse(args, opts, conn_info_opt_spec, NULL) == -1) {
+
476 free(opts);
+
477 return NULL;
+
478 }
+
479 return opts;
+
480}
+
481
+
482int fuse_open_channel(const char *mountpoint, const char* options)
+
483{
+
484 struct mount_opts *opts = NULL;
+
485 int fd = -1;
+
486 const char *argv[] = { "", "-o", options };
+
487 int argc = sizeof(argv) / sizeof(argv[0]);
+
488 struct fuse_args args = FUSE_ARGS_INIT(argc, (char**) argv);
+
489
+
490 opts = parse_mount_opts(&args);
+
491 if (opts == NULL)
+
492 return -1;
+
493
+
494 fd = fuse_kern_mount(mountpoint, opts);
+
495 destroy_mount_opts(opts);
+
496
+
497 return fd;
+
498}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5197
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5146
+
int fuse_main_real_versioned(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
Definition helper.c:307
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:482
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5202
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:466
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_READDIRPLUS
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:412
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t time_gran
+
uint32_t congestion_threshold
+
uint32_t max_write
+
uint32_t max_readahead
+
uint32_t max_background
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2modules_2iconv_8c_source.html b/doc/html/fuse-3_818_81_2lib_2modules_2iconv_8c_source.html new file mode 100644 index 0000000..93e9e7c --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2modules_2iconv_8c_source.html @@ -0,0 +1,835 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/modules/iconv.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
iconv.c
+
+
+
1/*
+
2 fuse iconv module: file name charset conversion
+
3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt
+
7*/
+
8
+
9#include <fuse_config.h>
+
10
+
11#include <fuse.h>
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <stddef.h>
+
15#include <string.h>
+
16#include <errno.h>
+
17#include <iconv.h>
+
18#include <pthread.h>
+
19#include <locale.h>
+
20#include <langinfo.h>
+
21
+
22struct iconv {
+
23 struct fuse_fs *next;
+
24 pthread_mutex_t lock;
+
25 char *from_code;
+
26 char *to_code;
+
27 iconv_t tofs;
+
28 iconv_t fromfs;
+
29};
+
30
+
31struct iconv_dh {
+
32 struct iconv *ic;
+
33 void *prev_buf;
+
34 fuse_fill_dir_t prev_filler;
+
35};
+
36
+
37static struct iconv *iconv_get(void)
+
38{
+ +
40}
+
41
+
42static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp,
+
43 int fromfs)
+
44{
+
45 size_t pathlen;
+
46 size_t newpathlen;
+
47 char *newpath;
+
48 size_t plen;
+
49 char *p;
+
50 size_t res;
+
51 int err;
+
52
+
53 if (path == NULL) {
+
54 *newpathp = NULL;
+
55 return 0;
+
56 }
+
57
+
58 pathlen = strlen(path);
+
59 newpathlen = pathlen * 4;
+
60 newpath = malloc(newpathlen + 1);
+
61 if (!newpath)
+
62 return -ENOMEM;
+
63
+
64 plen = newpathlen;
+
65 p = newpath;
+
66 pthread_mutex_lock(&ic->lock);
+
67 do {
+
68 res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path,
+
69 &pathlen, &p, &plen);
+
70 if (res == (size_t) -1) {
+
71 char *tmp;
+
72 size_t inc;
+
73
+
74 err = -EILSEQ;
+
75 if (errno != E2BIG)
+
76 goto err;
+
77
+
78 inc = (pathlen + 1) * 4;
+
79 newpathlen += inc;
+
80 int dp = p - newpath;
+
81 tmp = realloc(newpath, newpathlen + 1);
+
82 err = -ENOMEM;
+
83 if (!tmp)
+
84 goto err;
+
85
+
86 p = tmp + dp;
+
87 plen += inc;
+
88 newpath = tmp;
+
89 }
+
90 } while (res == (size_t) -1);
+
91 pthread_mutex_unlock(&ic->lock);
+
92 *p = '\0';
+
93 *newpathp = newpath;
+
94 return 0;
+
95
+
96err:
+
97 iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL);
+
98 pthread_mutex_unlock(&ic->lock);
+
99 free(newpath);
+
100 return err;
+
101}
+
102
+
103static int iconv_getattr(const char *path, struct stat *stbuf,
+
104 struct fuse_file_info *fi)
+
105{
+
106 struct iconv *ic = iconv_get();
+
107 char *newpath;
+
108 int err = iconv_convpath(ic, path, &newpath, 0);
+
109 if (!err) {
+
110 err = fuse_fs_getattr(ic->next, newpath, stbuf, fi);
+
111 free(newpath);
+
112 }
+
113 return err;
+
114}
+
115
+
116static int iconv_access(const char *path, int mask)
+
117{
+
118 struct iconv *ic = iconv_get();
+
119 char *newpath;
+
120 int err = iconv_convpath(ic, path, &newpath, 0);
+
121 if (!err) {
+
122 err = fuse_fs_access(ic->next, newpath, mask);
+
123 free(newpath);
+
124 }
+
125 return err;
+
126}
+
127
+
128static int iconv_readlink(const char *path, char *buf, size_t size)
+
129{
+
130 struct iconv *ic = iconv_get();
+
131 char *newpath;
+
132 int err = iconv_convpath(ic, path, &newpath, 0);
+
133 if (!err) {
+
134 err = fuse_fs_readlink(ic->next, newpath, buf, size);
+
135 if (!err) {
+
136 char *newlink;
+
137 err = iconv_convpath(ic, buf, &newlink, 1);
+
138 if (!err) {
+
139 strncpy(buf, newlink, size - 1);
+
140 buf[size - 1] = '\0';
+
141 free(newlink);
+
142 }
+
143 }
+
144 free(newpath);
+
145 }
+
146 return err;
+
147}
+
148
+
149static int iconv_opendir(const char *path, struct fuse_file_info *fi)
+
150{
+
151 struct iconv *ic = iconv_get();
+
152 char *newpath;
+
153 int err = iconv_convpath(ic, path, &newpath, 0);
+
154 if (!err) {
+
155 err = fuse_fs_opendir(ic->next, newpath, fi);
+
156 free(newpath);
+
157 }
+
158 return err;
+
159}
+
160
+
161static int iconv_dir_fill(void *buf, const char *name,
+
162 const struct stat *stbuf, off_t off,
+
163 enum fuse_fill_dir_flags flags)
+
164{
+
165 struct iconv_dh *dh = buf;
+
166 char *newname;
+
167 int res = 0;
+
168 if (iconv_convpath(dh->ic, name, &newname, 1) == 0) {
+
169 res = dh->prev_filler(dh->prev_buf, newname, stbuf, off, flags);
+
170 free(newname);
+
171 }
+
172 return res;
+
173}
+
174
+
175static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
176 off_t offset, struct fuse_file_info *fi,
+
177 enum fuse_readdir_flags flags)
+
178{
+
179 struct iconv *ic = iconv_get();
+
180 char *newpath;
+
181 int err = iconv_convpath(ic, path, &newpath, 0);
+
182 if (!err) {
+
183 struct iconv_dh dh;
+
184 dh.ic = ic;
+
185 dh.prev_buf = buf;
+
186 dh.prev_filler = filler;
+
187 err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill,
+
188 offset, fi, flags);
+
189 free(newpath);
+
190 }
+
191 return err;
+
192}
+
193
+
194static int iconv_releasedir(const char *path, struct fuse_file_info *fi)
+
195{
+
196 struct iconv *ic = iconv_get();
+
197 char *newpath;
+
198 int err = iconv_convpath(ic, path, &newpath, 0);
+
199 if (!err) {
+
200 err = fuse_fs_releasedir(ic->next, newpath, fi);
+
201 free(newpath);
+
202 }
+
203 return err;
+
204}
+
205
+
206static int iconv_mknod(const char *path, mode_t mode, dev_t rdev)
+
207{
+
208 struct iconv *ic = iconv_get();
+
209 char *newpath;
+
210 int err = iconv_convpath(ic, path, &newpath, 0);
+
211 if (!err) {
+
212 err = fuse_fs_mknod(ic->next, newpath, mode, rdev);
+
213 free(newpath);
+
214 }
+
215 return err;
+
216}
+
217
+
218static int iconv_mkdir(const char *path, mode_t mode)
+
219{
+
220 struct iconv *ic = iconv_get();
+
221 char *newpath;
+
222 int err = iconv_convpath(ic, path, &newpath, 0);
+
223 if (!err) {
+
224 err = fuse_fs_mkdir(ic->next, newpath, mode);
+
225 free(newpath);
+
226 }
+
227 return err;
+
228}
+
229
+
230static int iconv_unlink(const char *path)
+
231{
+
232 struct iconv *ic = iconv_get();
+
233 char *newpath;
+
234 int err = iconv_convpath(ic, path, &newpath, 0);
+
235 if (!err) {
+
236 err = fuse_fs_unlink(ic->next, newpath);
+
237 free(newpath);
+
238 }
+
239 return err;
+
240}
+
241
+
242static int iconv_rmdir(const char *path)
+
243{
+
244 struct iconv *ic = iconv_get();
+
245 char *newpath;
+
246 int err = iconv_convpath(ic, path, &newpath, 0);
+
247 if (!err) {
+
248 err = fuse_fs_rmdir(ic->next, newpath);
+
249 free(newpath);
+
250 }
+
251 return err;
+
252}
+
253
+
254static int iconv_symlink(const char *from, const char *to)
+
255{
+
256 struct iconv *ic = iconv_get();
+
257 char *newfrom;
+
258 char *newto;
+
259 int err = iconv_convpath(ic, from, &newfrom, 0);
+
260 if (!err) {
+
261 err = iconv_convpath(ic, to, &newto, 0);
+
262 if (!err) {
+
263 err = fuse_fs_symlink(ic->next, newfrom, newto);
+
264 free(newto);
+
265 }
+
266 free(newfrom);
+
267 }
+
268 return err;
+
269}
+
270
+
271static int iconv_rename(const char *from, const char *to, unsigned int flags)
+
272{
+
273 struct iconv *ic = iconv_get();
+
274 char *newfrom;
+
275 char *newto;
+
276 int err = iconv_convpath(ic, from, &newfrom, 0);
+
277 if (!err) {
+
278 err = iconv_convpath(ic, to, &newto, 0);
+
279 if (!err) {
+
280 err = fuse_fs_rename(ic->next, newfrom, newto, flags);
+
281 free(newto);
+
282 }
+
283 free(newfrom);
+
284 }
+
285 return err;
+
286}
+
287
+
288static int iconv_link(const char *from, const char *to)
+
289{
+
290 struct iconv *ic = iconv_get();
+
291 char *newfrom;
+
292 char *newto;
+
293 int err = iconv_convpath(ic, from, &newfrom, 0);
+
294 if (!err) {
+
295 err = iconv_convpath(ic, to, &newto, 0);
+
296 if (!err) {
+
297 err = fuse_fs_link(ic->next, newfrom, newto);
+
298 free(newto);
+
299 }
+
300 free(newfrom);
+
301 }
+
302 return err;
+
303}
+
304
+
305static int iconv_chmod(const char *path, mode_t mode,
+
306 struct fuse_file_info *fi)
+
307{
+
308 struct iconv *ic = iconv_get();
+
309 char *newpath;
+
310 int err = iconv_convpath(ic, path, &newpath, 0);
+
311 if (!err) {
+
312 err = fuse_fs_chmod(ic->next, newpath, mode, fi);
+
313 free(newpath);
+
314 }
+
315 return err;
+
316}
+
317
+
318static int iconv_chown(const char *path, uid_t uid, gid_t gid,
+
319 struct fuse_file_info *fi)
+
320{
+
321 struct iconv *ic = iconv_get();
+
322 char *newpath;
+
323 int err = iconv_convpath(ic, path, &newpath, 0);
+
324 if (!err) {
+
325 err = fuse_fs_chown(ic->next, newpath, uid, gid, fi);
+
326 free(newpath);
+
327 }
+
328 return err;
+
329}
+
330
+
331static int iconv_truncate(const char *path, off_t size,
+
332 struct fuse_file_info *fi)
+
333{
+
334 struct iconv *ic = iconv_get();
+
335 char *newpath;
+
336 int err = iconv_convpath(ic, path, &newpath, 0);
+
337 if (!err) {
+
338 err = fuse_fs_truncate(ic->next, newpath, size, fi);
+
339 free(newpath);
+
340 }
+
341 return err;
+
342}
+
343
+
344static int iconv_utimens(const char *path, const struct timespec ts[2],
+
345 struct fuse_file_info *fi)
+
346{
+
347 struct iconv *ic = iconv_get();
+
348 char *newpath;
+
349 int err = iconv_convpath(ic, path, &newpath, 0);
+
350 if (!err) {
+
351 err = fuse_fs_utimens(ic->next, newpath, ts, fi);
+
352 free(newpath);
+
353 }
+
354 return err;
+
355}
+
356
+
357static int iconv_create(const char *path, mode_t mode,
+
358 struct fuse_file_info *fi)
+
359{
+
360 struct iconv *ic = iconv_get();
+
361 char *newpath;
+
362 int err = iconv_convpath(ic, path, &newpath, 0);
+
363 if (!err) {
+
364 err = fuse_fs_create(ic->next, newpath, mode, fi);
+
365 free(newpath);
+
366 }
+
367 return err;
+
368}
+
369
+
370static int iconv_open_file(const char *path, struct fuse_file_info *fi)
+
371{
+
372 struct iconv *ic = iconv_get();
+
373 char *newpath;
+
374 int err = iconv_convpath(ic, path, &newpath, 0);
+
375 if (!err) {
+
376 err = fuse_fs_open(ic->next, newpath, fi);
+
377 free(newpath);
+
378 }
+
379 return err;
+
380}
+
381
+
382static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp,
+
383 size_t size, off_t offset, struct fuse_file_info *fi)
+
384{
+
385 struct iconv *ic = iconv_get();
+
386 char *newpath;
+
387 int err = iconv_convpath(ic, path, &newpath, 0);
+
388 if (!err) {
+
389 err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi);
+
390 free(newpath);
+
391 }
+
392 return err;
+
393}
+
394
+
395static int iconv_write_buf(const char *path, struct fuse_bufvec *buf,
+
396 off_t offset, struct fuse_file_info *fi)
+
397{
+
398 struct iconv *ic = iconv_get();
+
399 char *newpath;
+
400 int err = iconv_convpath(ic, path, &newpath, 0);
+
401 if (!err) {
+
402 err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi);
+
403 free(newpath);
+
404 }
+
405 return err;
+
406}
+
407
+
408static int iconv_statfs(const char *path, struct statvfs *stbuf)
+
409{
+
410 struct iconv *ic = iconv_get();
+
411 char *newpath;
+
412 int err = iconv_convpath(ic, path, &newpath, 0);
+
413 if (!err) {
+
414 err = fuse_fs_statfs(ic->next, newpath, stbuf);
+
415 free(newpath);
+
416 }
+
417 return err;
+
418}
+
419
+
420static int iconv_flush(const char *path, struct fuse_file_info *fi)
+
421{
+
422 struct iconv *ic = iconv_get();
+
423 char *newpath;
+
424 int err = iconv_convpath(ic, path, &newpath, 0);
+
425 if (!err) {
+
426 err = fuse_fs_flush(ic->next, newpath, fi);
+
427 free(newpath);
+
428 }
+
429 return err;
+
430}
+
431
+
432static int iconv_release(const char *path, struct fuse_file_info *fi)
+
433{
+
434 struct iconv *ic = iconv_get();
+
435 char *newpath;
+
436 int err = iconv_convpath(ic, path, &newpath, 0);
+
437 if (!err) {
+
438 err = fuse_fs_release(ic->next, newpath, fi);
+
439 free(newpath);
+
440 }
+
441 return err;
+
442}
+
443
+
444static int iconv_fsync(const char *path, int isdatasync,
+
445 struct fuse_file_info *fi)
+
446{
+
447 struct iconv *ic = iconv_get();
+
448 char *newpath;
+
449 int err = iconv_convpath(ic, path, &newpath, 0);
+
450 if (!err) {
+
451 err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi);
+
452 free(newpath);
+
453 }
+
454 return err;
+
455}
+
456
+
457static int iconv_fsyncdir(const char *path, int isdatasync,
+
458 struct fuse_file_info *fi)
+
459{
+
460 struct iconv *ic = iconv_get();
+
461 char *newpath;
+
462 int err = iconv_convpath(ic, path, &newpath, 0);
+
463 if (!err) {
+
464 err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi);
+
465 free(newpath);
+
466 }
+
467 return err;
+
468}
+
469
+
470static int iconv_setxattr(const char *path, const char *name,
+
471 const char *value, size_t size, int flags)
+
472{
+
473 struct iconv *ic = iconv_get();
+
474 char *newpath;
+
475 int err = iconv_convpath(ic, path, &newpath, 0);
+
476 if (!err) {
+
477 err = fuse_fs_setxattr(ic->next, newpath, name, value, size,
+
478 flags);
+
479 free(newpath);
+
480 }
+
481 return err;
+
482}
+
483
+
484static int iconv_getxattr(const char *path, const char *name, char *value,
+
485 size_t size)
+
486{
+
487 struct iconv *ic = iconv_get();
+
488 char *newpath;
+
489 int err = iconv_convpath(ic, path, &newpath, 0);
+
490 if (!err) {
+
491 err = fuse_fs_getxattr(ic->next, newpath, name, value, size);
+
492 free(newpath);
+
493 }
+
494 return err;
+
495}
+
496
+
497static int iconv_listxattr(const char *path, char *list, size_t size)
+
498{
+
499 struct iconv *ic = iconv_get();
+
500 char *newpath;
+
501 int err = iconv_convpath(ic, path, &newpath, 0);
+
502 if (!err) {
+
503 err = fuse_fs_listxattr(ic->next, newpath, list, size);
+
504 free(newpath);
+
505 }
+
506 return err;
+
507}
+
508
+
509static int iconv_removexattr(const char *path, const char *name)
+
510{
+
511 struct iconv *ic = iconv_get();
+
512 char *newpath;
+
513 int err = iconv_convpath(ic, path, &newpath, 0);
+
514 if (!err) {
+
515 err = fuse_fs_removexattr(ic->next, newpath, name);
+
516 free(newpath);
+
517 }
+
518 return err;
+
519}
+
520
+
521static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
522 struct flock *lock)
+
523{
+
524 struct iconv *ic = iconv_get();
+
525 char *newpath;
+
526 int err = iconv_convpath(ic, path, &newpath, 0);
+
527 if (!err) {
+
528 err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock);
+
529 free(newpath);
+
530 }
+
531 return err;
+
532}
+
533
+
534static int iconv_flock(const char *path, struct fuse_file_info *fi, int op)
+
535{
+
536 struct iconv *ic = iconv_get();
+
537 char *newpath;
+
538 int err = iconv_convpath(ic, path, &newpath, 0);
+
539 if (!err) {
+
540 err = fuse_fs_flock(ic->next, newpath, fi, op);
+
541 free(newpath);
+
542 }
+
543 return err;
+
544}
+
545
+
546static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx)
+
547{
+
548 struct iconv *ic = iconv_get();
+
549 char *newpath;
+
550 int err = iconv_convpath(ic, path, &newpath, 0);
+
551 if (!err) {
+
552 err = fuse_fs_bmap(ic->next, newpath, blocksize, idx);
+
553 free(newpath);
+
554 }
+
555 return err;
+
556}
+
557
+
558static off_t iconv_lseek(const char *path, off_t off, int whence,
+
559 struct fuse_file_info *fi)
+
560{
+
561 struct iconv *ic = iconv_get();
+
562 char *newpath;
+
563 int res = iconv_convpath(ic, path, &newpath, 0);
+
564 if (!res) {
+
565 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
+
566 free(newpath);
+
567 }
+
568 return res;
+
569}
+
570
+
571#ifdef HAVE_STATX
+
572static int iconv_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
573 struct fuse_file_info *fi)
+
574{
+
575 struct iconv *ic = iconv_get();
+
576 char *newpath;
+
577 int res = iconv_convpath(ic, path, &newpath, 0);
+
578
+
579 if (!res) {
+
580 res = fuse_fs_statx(ic->next, newpath, flags, mask, stxbuf, fi);
+
581 free(newpath);
+
582 }
+
583 return res;
+
584}
+
585#endif
+
586
+
587static void *iconv_init(struct fuse_conn_info *conn,
+
588 struct fuse_config *cfg)
+
589{
+
590 struct iconv *ic = iconv_get();
+
591 fuse_fs_init(ic->next, conn, cfg);
+
592 /* Don't touch cfg->nullpath_ok, we can work with
+
593 either */
+
594 return ic;
+
595}
+
596
+
597static void iconv_destroy(void *data)
+
598{
+
599 struct iconv *ic = data;
+
600 fuse_fs_destroy(ic->next);
+
601 iconv_close(ic->tofs);
+
602 iconv_close(ic->fromfs);
+
603 pthread_mutex_destroy(&ic->lock);
+
604 free(ic->from_code);
+
605 free(ic->to_code);
+
606 free(ic);
+
607}
+
608
+
609static const struct fuse_operations iconv_oper = {
+
610 .destroy = iconv_destroy,
+
611 .init = iconv_init,
+
612 .getattr = iconv_getattr,
+
613 .access = iconv_access,
+
614 .readlink = iconv_readlink,
+
615 .opendir = iconv_opendir,
+
616 .readdir = iconv_readdir,
+
617 .releasedir = iconv_releasedir,
+
618 .mknod = iconv_mknod,
+
619 .mkdir = iconv_mkdir,
+
620 .symlink = iconv_symlink,
+
621 .unlink = iconv_unlink,
+
622 .rmdir = iconv_rmdir,
+
623 .rename = iconv_rename,
+
624 .link = iconv_link,
+
625 .chmod = iconv_chmod,
+
626 .chown = iconv_chown,
+
627 .truncate = iconv_truncate,
+
628 .utimens = iconv_utimens,
+
629 .create = iconv_create,
+
630 .open = iconv_open_file,
+
631 .read_buf = iconv_read_buf,
+
632 .write_buf = iconv_write_buf,
+
633 .statfs = iconv_statfs,
+
634 .flush = iconv_flush,
+
635 .release = iconv_release,
+
636 .fsync = iconv_fsync,
+
637 .fsyncdir = iconv_fsyncdir,
+
638 .setxattr = iconv_setxattr,
+
639 .getxattr = iconv_getxattr,
+
640 .listxattr = iconv_listxattr,
+
641 .removexattr = iconv_removexattr,
+
642 .lock = iconv_lock,
+
643 .flock = iconv_flock,
+
644 .bmap = iconv_bmap,
+
645 .lseek = iconv_lseek,
+
646#ifdef HAVE_STATX
+
647 .statx = iconv_statx,
+
648#endif
+
649};
+
650
+
651static const struct fuse_opt iconv_opts[] = {
+
652 FUSE_OPT_KEY("-h", 0),
+
653 FUSE_OPT_KEY("--help", 0),
+
654 { "from_code=%s", offsetof(struct iconv, from_code), 0 },
+
655 { "to_code=%s", offsetof(struct iconv, to_code), 1 },
+ +
657};
+
658
+
659static void iconv_help(void)
+
660{
+
661 char *charmap;
+
662 const char *old = setlocale(LC_CTYPE, "");
+
663
+
664 charmap = strdup(nl_langinfo(CODESET));
+
665 if (old)
+
666 setlocale(LC_CTYPE, old);
+
667 else
+
668 perror("setlocale");
+
669
+
670 printf(
+
671" -o from_code=CHARSET original encoding of file names (default: UTF-8)\n"
+
672" -o to_code=CHARSET new encoding of the file names (default: %s)\n",
+
673 charmap);
+
674 free(charmap);
+
675}
+
676
+
677static int iconv_opt_proc(void *data, const char *arg, int key,
+
678 struct fuse_args *outargs)
+
679{
+
680 (void) data; (void) arg; (void) outargs;
+
681
+
682 if (!key) {
+
683 iconv_help();
+
684 return -1;
+
685 }
+
686
+
687 return 1;
+
688}
+
689
+
690static struct fuse_fs *iconv_new(struct fuse_args *args,
+
691 struct fuse_fs *next[])
+
692{
+
693 struct fuse_fs *fs;
+
694 struct iconv *ic;
+
695 const char *old = NULL;
+
696 const char *from;
+
697 const char *to;
+
698
+
699 ic = calloc(1, sizeof(struct iconv));
+
700 if (ic == NULL) {
+
701 fuse_log(FUSE_LOG_ERR, "fuse-iconv: memory allocation failed\n");
+
702 return NULL;
+
703 }
+
704
+
705 if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1)
+
706 goto out_free;
+
707
+
708 if (!next[0] || next[1]) {
+
709 fuse_log(FUSE_LOG_ERR, "fuse-iconv: exactly one next filesystem required\n");
+
710 goto out_free;
+
711 }
+
712
+
713 from = ic->from_code ? ic->from_code : "UTF-8";
+
714 to = ic->to_code ? ic->to_code : "";
+
715 /* FIXME: detect charset equivalence? */
+
716 if (!to[0])
+
717 old = setlocale(LC_CTYPE, "");
+
718 ic->tofs = iconv_open(from, to);
+
719 if (ic->tofs == (iconv_t) -1) {
+
720 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
721 to, from);
+
722 goto out_free;
+
723 }
+
724 ic->fromfs = iconv_open(to, from);
+
725 if (ic->tofs == (iconv_t) -1) {
+
726 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
727 from, to);
+
728 goto out_iconv_close_to;
+
729 }
+
730 if (old) {
+
731 setlocale(LC_CTYPE, old);
+
732 old = NULL;
+
733 }
+
734
+
735 ic->next = next[0];
+
736 fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic);
+
737 if (!fs)
+
738 goto out_iconv_close_from;
+
739
+
740 return fs;
+
741
+
742out_iconv_close_from:
+
743 iconv_close(ic->fromfs);
+
744out_iconv_close_to:
+
745 iconv_close(ic->tofs);
+
746out_free:
+
747 free(ic->from_code);
+
748 free(ic->to_code);
+
749 free(ic);
+
750 if (old) {
+
751 setlocale(LC_CTYPE, old);
+
752 }
+
753 return NULL;
+
754}
+
755
+
756FUSE_REGISTER_MODULE(iconv, iconv_new);
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
fuse_fill_dir_flags
Definition fuse.h:58
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_REGISTER_MODULE(name_, factory_)
Definition fuse.h:1395
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + + +
void * private_data
Definition fuse.h:874
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+ +
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2modules_2subdir_8c_source.html b/doc/html/fuse-3_818_81_2lib_2modules_2subdir_8c_source.html new file mode 100644 index 0000000..deb5291 --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2modules_2subdir_8c_source.html @@ -0,0 +1,786 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/modules/subdir.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
subdir.c
+
+
+
1/*
+
2 fuse subdir module: offset paths with a base directory
+
3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt
+
7*/
+
8
+
9#include <fuse_config.h>
+
10
+
11#include <fuse.h>
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <stddef.h>
+
15#include <string.h>
+
16#include <errno.h>
+
17
+
18struct subdir {
+
19 char *base;
+
20 size_t baselen;
+
21 int rellinks;
+
22 struct fuse_fs *next;
+
23};
+
24
+
25static struct subdir *subdir_get(void)
+
26{
+ +
28}
+
29
+
30static int subdir_addpath(struct subdir *d, const char *path, char **newpathp)
+
31{
+
32 char *newpath = NULL;
+
33
+
34 if (path != NULL) {
+
35 unsigned newlen = d->baselen + strlen(path);
+
36
+
37 newpath = malloc(newlen + 2);
+
38 if (!newpath)
+
39 return -ENOMEM;
+
40
+
41 if (path[0] == '/')
+
42 path++;
+
43 strcpy(newpath, d->base);
+
44 strcpy(newpath + d->baselen, path);
+
45 if (!newpath[0])
+
46 strcpy(newpath, ".");
+
47 }
+
48 *newpathp = newpath;
+
49
+
50 return 0;
+
51}
+
52
+
53static int subdir_getattr(const char *path, struct stat *stbuf,
+
54 struct fuse_file_info *fi)
+
55{
+
56 struct subdir *d = subdir_get();
+
57 char *newpath;
+
58 int err = subdir_addpath(d, path, &newpath);
+
59 if (!err) {
+
60 err = fuse_fs_getattr(d->next, newpath, stbuf, fi);
+
61 free(newpath);
+
62 }
+
63 return err;
+
64}
+
65
+
66static int subdir_access(const char *path, int mask)
+
67{
+
68 struct subdir *d = subdir_get();
+
69 char *newpath;
+
70 int err = subdir_addpath(d, path, &newpath);
+
71 if (!err) {
+
72 err = fuse_fs_access(d->next, newpath, mask);
+
73 free(newpath);
+
74 }
+
75 return err;
+
76}
+
77
+
78
+
79static int count_components(const char *p)
+
80{
+
81 int ctr;
+
82
+
83 for (; *p == '/'; p++);
+
84 for (ctr = 0; *p; ctr++) {
+
85 for (; *p && *p != '/'; p++);
+
86 for (; *p == '/'; p++);
+
87 }
+
88 return ctr;
+
89}
+
90
+
91static void strip_common(const char **sp, const char **tp)
+
92{
+
93 const char *s = *sp;
+
94 const char *t = *tp;
+
95 do {
+
96 for (; *s == '/'; s++);
+
97 for (; *t == '/'; t++);
+
98 *tp = t;
+
99 *sp = s;
+
100 for (; *s == *t && *s && *s != '/'; s++, t++);
+
101 } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t));
+
102}
+
103
+
104static void transform_symlink(struct subdir *d, const char *path,
+
105 char *buf, size_t size)
+
106{
+
107 const char *l = buf;
+
108 size_t llen;
+
109 char *s;
+
110 int dotdots;
+
111 int i;
+
112
+
113 if (l[0] != '/' || d->base[0] != '/')
+
114 return;
+
115
+
116 strip_common(&l, &path);
+
117 if (l - buf < (long) d->baselen)
+
118 return;
+
119
+
120 dotdots = count_components(path);
+
121 if (!dotdots)
+
122 return;
+
123 dotdots--;
+
124
+
125 llen = strlen(l);
+
126 if (dotdots * 3 + llen + 2 > size)
+
127 return;
+
128
+
129 s = buf + dotdots * 3;
+
130 if (llen)
+
131 memmove(s, l, llen + 1);
+
132 else if (!dotdots)
+
133 strcpy(s, ".");
+
134 else
+
135 *s = '\0';
+
136
+
137 for (s = buf, i = 0; i < dotdots; i++, s += 3)
+
138 memcpy(s, "../", 3);
+
139}
+
140
+
141
+
142static int subdir_readlink(const char *path, char *buf, size_t size)
+
143{
+
144 struct subdir *d = subdir_get();
+
145 char *newpath;
+
146 int err = subdir_addpath(d, path, &newpath);
+
147 if (!err) {
+
148 err = fuse_fs_readlink(d->next, newpath, buf, size);
+
149 if (!err && d->rellinks)
+
150 transform_symlink(d, newpath, buf, size);
+
151 free(newpath);
+
152 }
+
153 return err;
+
154}
+
155
+
156static int subdir_opendir(const char *path, struct fuse_file_info *fi)
+
157{
+
158 struct subdir *d = subdir_get();
+
159 char *newpath;
+
160 int err = subdir_addpath(d, path, &newpath);
+
161 if (!err) {
+
162 err = fuse_fs_opendir(d->next, newpath, fi);
+
163 free(newpath);
+
164 }
+
165 return err;
+
166}
+
167
+
168static int subdir_readdir(const char *path, void *buf,
+
169 fuse_fill_dir_t filler, off_t offset,
+
170 struct fuse_file_info *fi,
+
171 enum fuse_readdir_flags flags)
+
172{
+
173 struct subdir *d = subdir_get();
+
174 char *newpath;
+
175 int err = subdir_addpath(d, path, &newpath);
+
176 if (!err) {
+
177 err = fuse_fs_readdir(d->next, newpath, buf, filler, offset,
+
178 fi, flags);
+
179 free(newpath);
+
180 }
+
181 return err;
+
182}
+
183
+
184static int subdir_releasedir(const char *path, struct fuse_file_info *fi)
+
185{
+
186 struct subdir *d = subdir_get();
+
187 char *newpath;
+
188 int err = subdir_addpath(d, path, &newpath);
+
189 if (!err) {
+
190 err = fuse_fs_releasedir(d->next, newpath, fi);
+
191 free(newpath);
+
192 }
+
193 return err;
+
194}
+
195
+
196static int subdir_mknod(const char *path, mode_t mode, dev_t rdev)
+
197{
+
198 struct subdir *d = subdir_get();
+
199 char *newpath;
+
200 int err = subdir_addpath(d, path, &newpath);
+
201 if (!err) {
+
202 err = fuse_fs_mknod(d->next, newpath, mode, rdev);
+
203 free(newpath);
+
204 }
+
205 return err;
+
206}
+
207
+
208static int subdir_mkdir(const char *path, mode_t mode)
+
209{
+
210 struct subdir *d = subdir_get();
+
211 char *newpath;
+
212 int err = subdir_addpath(d, path, &newpath);
+
213 if (!err) {
+
214 err = fuse_fs_mkdir(d->next, newpath, mode);
+
215 free(newpath);
+
216 }
+
217 return err;
+
218}
+
219
+
220static int subdir_unlink(const char *path)
+
221{
+
222 struct subdir *d = subdir_get();
+
223 char *newpath;
+
224 int err = subdir_addpath(d, path, &newpath);
+
225 if (!err) {
+
226 err = fuse_fs_unlink(d->next, newpath);
+
227 free(newpath);
+
228 }
+
229 return err;
+
230}
+
231
+
232static int subdir_rmdir(const char *path)
+
233{
+
234 struct subdir *d = subdir_get();
+
235 char *newpath;
+
236 int err = subdir_addpath(d, path, &newpath);
+
237 if (!err) {
+
238 err = fuse_fs_rmdir(d->next, newpath);
+
239 free(newpath);
+
240 }
+
241 return err;
+
242}
+
243
+
244static int subdir_symlink(const char *from, const char *path)
+
245{
+
246 struct subdir *d = subdir_get();
+
247 char *newpath;
+
248 int err = subdir_addpath(d, path, &newpath);
+
249 if (!err) {
+
250 err = fuse_fs_symlink(d->next, from, newpath);
+
251 free(newpath);
+
252 }
+
253 return err;
+
254}
+
255
+
256static int subdir_rename(const char *from, const char *to, unsigned int flags)
+
257{
+
258 struct subdir *d = subdir_get();
+
259 char *newfrom;
+
260 char *newto;
+
261 int err = subdir_addpath(d, from, &newfrom);
+
262 if (!err) {
+
263 err = subdir_addpath(d, to, &newto);
+
264 if (!err) {
+
265 err = fuse_fs_rename(d->next, newfrom, newto, flags);
+
266 free(newto);
+
267 }
+
268 free(newfrom);
+
269 }
+
270 return err;
+
271}
+
272
+
273static int subdir_link(const char *from, const char *to)
+
274{
+
275 struct subdir *d = subdir_get();
+
276 char *newfrom;
+
277 char *newto;
+
278 int err = subdir_addpath(d, from, &newfrom);
+
279 if (!err) {
+
280 err = subdir_addpath(d, to, &newto);
+
281 if (!err) {
+
282 err = fuse_fs_link(d->next, newfrom, newto);
+
283 free(newto);
+
284 }
+
285 free(newfrom);
+
286 }
+
287 return err;
+
288}
+
289
+
290static int subdir_chmod(const char *path, mode_t mode,
+
291 struct fuse_file_info *fi)
+
292{
+
293 struct subdir *d = subdir_get();
+
294 char *newpath;
+
295 int err = subdir_addpath(d, path, &newpath);
+
296 if (!err) {
+
297 err = fuse_fs_chmod(d->next, newpath, mode, fi);
+
298 free(newpath);
+
299 }
+
300 return err;
+
301}
+
302
+
303static int subdir_chown(const char *path, uid_t uid, gid_t gid,
+
304 struct fuse_file_info *fi)
+
305{
+
306 struct subdir *d = subdir_get();
+
307 char *newpath;
+
308 int err = subdir_addpath(d, path, &newpath);
+
309 if (!err) {
+
310 err = fuse_fs_chown(d->next, newpath, uid, gid, fi);
+
311 free(newpath);
+
312 }
+
313 return err;
+
314}
+
315
+
316static int subdir_truncate(const char *path, off_t size,
+
317 struct fuse_file_info *fi)
+
318{
+
319 struct subdir *d = subdir_get();
+
320 char *newpath;
+
321 int err = subdir_addpath(d, path, &newpath);
+
322 if (!err) {
+
323 err = fuse_fs_truncate(d->next, newpath, size, fi);
+
324 free(newpath);
+
325 }
+
326 return err;
+
327}
+
328
+
329static int subdir_utimens(const char *path, const struct timespec ts[2],
+
330 struct fuse_file_info *fi)
+
331{
+
332 struct subdir *d = subdir_get();
+
333 char *newpath;
+
334 int err = subdir_addpath(d, path, &newpath);
+
335 if (!err) {
+
336 err = fuse_fs_utimens(d->next, newpath, ts, fi);
+
337 free(newpath);
+
338 }
+
339 return err;
+
340}
+
341
+
342static int subdir_create(const char *path, mode_t mode,
+
343 struct fuse_file_info *fi)
+
344{
+
345 struct subdir *d = subdir_get();
+
346 char *newpath;
+
347 int err = subdir_addpath(d, path, &newpath);
+
348 if (!err) {
+
349 err = fuse_fs_create(d->next, newpath, mode, fi);
+
350 free(newpath);
+
351 }
+
352 return err;
+
353}
+
354
+
355static int subdir_open(const char *path, struct fuse_file_info *fi)
+
356{
+
357 struct subdir *d = subdir_get();
+
358 char *newpath;
+
359 int err = subdir_addpath(d, path, &newpath);
+
360 if (!err) {
+
361 err = fuse_fs_open(d->next, newpath, fi);
+
362 free(newpath);
+
363 }
+
364 return err;
+
365}
+
366
+
367static int subdir_read_buf(const char *path, struct fuse_bufvec **bufp,
+
368 size_t size, off_t offset, struct fuse_file_info *fi)
+
369{
+
370 struct subdir *d = subdir_get();
+
371 char *newpath;
+
372 int err = subdir_addpath(d, path, &newpath);
+
373 if (!err) {
+
374 err = fuse_fs_read_buf(d->next, newpath, bufp, size, offset, fi);
+
375 free(newpath);
+
376 }
+
377 return err;
+
378}
+
379
+
380static int subdir_write_buf(const char *path, struct fuse_bufvec *buf,
+
381 off_t offset, struct fuse_file_info *fi)
+
382{
+
383 struct subdir *d = subdir_get();
+
384 char *newpath;
+
385 int err = subdir_addpath(d, path, &newpath);
+
386 if (!err) {
+
387 err = fuse_fs_write_buf(d->next, newpath, buf, offset, fi);
+
388 free(newpath);
+
389 }
+
390 return err;
+
391}
+
392
+
393static int subdir_statfs(const char *path, struct statvfs *stbuf)
+
394{
+
395 struct subdir *d = subdir_get();
+
396 char *newpath;
+
397 int err = subdir_addpath(d, path, &newpath);
+
398 if (!err) {
+
399 err = fuse_fs_statfs(d->next, newpath, stbuf);
+
400 free(newpath);
+
401 }
+
402 return err;
+
403}
+
404
+
405static int subdir_flush(const char *path, struct fuse_file_info *fi)
+
406{
+
407 struct subdir *d = subdir_get();
+
408 char *newpath;
+
409 int err = subdir_addpath(d, path, &newpath);
+
410 if (!err) {
+
411 err = fuse_fs_flush(d->next, newpath, fi);
+
412 free(newpath);
+
413 }
+
414 return err;
+
415}
+
416
+
417static int subdir_release(const char *path, struct fuse_file_info *fi)
+
418{
+
419 struct subdir *d = subdir_get();
+
420 char *newpath;
+
421 int err = subdir_addpath(d, path, &newpath);
+
422 if (!err) {
+
423 err = fuse_fs_release(d->next, newpath, fi);
+
424 free(newpath);
+
425 }
+
426 return err;
+
427}
+
428
+
429static int subdir_fsync(const char *path, int isdatasync,
+
430 struct fuse_file_info *fi)
+
431{
+
432 struct subdir *d = subdir_get();
+
433 char *newpath;
+
434 int err = subdir_addpath(d, path, &newpath);
+
435 if (!err) {
+
436 err = fuse_fs_fsync(d->next, newpath, isdatasync, fi);
+
437 free(newpath);
+
438 }
+
439 return err;
+
440}
+
441
+
442static int subdir_fsyncdir(const char *path, int isdatasync,
+
443 struct fuse_file_info *fi)
+
444{
+
445 struct subdir *d = subdir_get();
+
446 char *newpath;
+
447 int err = subdir_addpath(d, path, &newpath);
+
448 if (!err) {
+
449 err = fuse_fs_fsyncdir(d->next, newpath, isdatasync, fi);
+
450 free(newpath);
+
451 }
+
452 return err;
+
453}
+
454
+
455static int subdir_setxattr(const char *path, const char *name,
+
456 const char *value, size_t size, int flags)
+
457{
+
458 struct subdir *d = subdir_get();
+
459 char *newpath;
+
460 int err = subdir_addpath(d, path, &newpath);
+
461 if (!err) {
+
462 err = fuse_fs_setxattr(d->next, newpath, name, value, size,
+
463 flags);
+
464 free(newpath);
+
465 }
+
466 return err;
+
467}
+
468
+
469static int subdir_getxattr(const char *path, const char *name, char *value,
+
470 size_t size)
+
471{
+
472 struct subdir *d = subdir_get();
+
473 char *newpath;
+
474 int err = subdir_addpath(d, path, &newpath);
+
475 if (!err) {
+
476 err = fuse_fs_getxattr(d->next, newpath, name, value, size);
+
477 free(newpath);
+
478 }
+
479 return err;
+
480}
+
481
+
482static int subdir_listxattr(const char *path, char *list, size_t size)
+
483{
+
484 struct subdir *d = subdir_get();
+
485 char *newpath;
+
486 int err = subdir_addpath(d, path, &newpath);
+
487 if (!err) {
+
488 err = fuse_fs_listxattr(d->next, newpath, list, size);
+
489 free(newpath);
+
490 }
+
491 return err;
+
492}
+
493
+
494static int subdir_removexattr(const char *path, const char *name)
+
495{
+
496 struct subdir *d = subdir_get();
+
497 char *newpath;
+
498 int err = subdir_addpath(d, path, &newpath);
+
499 if (!err) {
+
500 err = fuse_fs_removexattr(d->next, newpath, name);
+
501 free(newpath);
+
502 }
+
503 return err;
+
504}
+
505
+
506static int subdir_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
507 struct flock *lock)
+
508{
+
509 struct subdir *d = subdir_get();
+
510 char *newpath;
+
511 int err = subdir_addpath(d, path, &newpath);
+
512 if (!err) {
+
513 err = fuse_fs_lock(d->next, newpath, fi, cmd, lock);
+
514 free(newpath);
+
515 }
+
516 return err;
+
517}
+
518
+
519static int subdir_flock(const char *path, struct fuse_file_info *fi, int op)
+
520{
+
521 struct subdir *d = subdir_get();
+
522 char *newpath;
+
523 int err = subdir_addpath(d, path, &newpath);
+
524 if (!err) {
+
525 err = fuse_fs_flock(d->next, newpath, fi, op);
+
526 free(newpath);
+
527 }
+
528 return err;
+
529}
+
530
+
531static int subdir_bmap(const char *path, size_t blocksize, uint64_t *idx)
+
532{
+
533 struct subdir *d = subdir_get();
+
534 char *newpath;
+
535 int err = subdir_addpath(d, path, &newpath);
+
536 if (!err) {
+
537 err = fuse_fs_bmap(d->next, newpath, blocksize, idx);
+
538 free(newpath);
+
539 }
+
540 return err;
+
541}
+
542
+
543static off_t subdir_lseek(const char *path, off_t off, int whence,
+
544 struct fuse_file_info *fi)
+
545{
+
546 struct subdir *ic = subdir_get();
+
547 char *newpath;
+
548 int res = subdir_addpath(ic, path, &newpath);
+
549 if (!res) {
+
550 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
+
551 free(newpath);
+
552 }
+
553 return res;
+
554}
+
555
+
556#ifdef HAVE_STATX
+
557static int subdir_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
558 struct fuse_file_info *fi)
+
559{
+
560 struct subdir *ic = subdir_get();
+
561 char *newpath;
+
562 int res = subdir_addpath(ic, path, &newpath);
+
563
+
564 if (!res) {
+
565 res = fuse_fs_statx(ic->next, newpath, flags, mask, stxbuf, fi);
+
566 free(newpath);
+
567 }
+
568 return res;
+
569}
+
570#endif
+
571
+
572static void *subdir_init(struct fuse_conn_info *conn,
+
573 struct fuse_config *cfg)
+
574{
+
575 struct subdir *d = subdir_get();
+
576 fuse_fs_init(d->next, conn, cfg);
+
577 /* Don't touch cfg->nullpath_ok, we can work with
+
578 either */
+
579 return d;
+
580}
+
581
+
582static void subdir_destroy(void *data)
+
583{
+
584 struct subdir *d = data;
+
585 fuse_fs_destroy(d->next);
+
586 free(d->base);
+
587 free(d);
+
588}
+
589
+
590static const struct fuse_operations subdir_oper = {
+
591 .destroy = subdir_destroy,
+
592 .init = subdir_init,
+
593 .getattr = subdir_getattr,
+
594 .access = subdir_access,
+
595 .readlink = subdir_readlink,
+
596 .opendir = subdir_opendir,
+
597 .readdir = subdir_readdir,
+
598 .releasedir = subdir_releasedir,
+
599 .mknod = subdir_mknod,
+
600 .mkdir = subdir_mkdir,
+
601 .symlink = subdir_symlink,
+
602 .unlink = subdir_unlink,
+
603 .rmdir = subdir_rmdir,
+
604 .rename = subdir_rename,
+
605 .link = subdir_link,
+
606 .chmod = subdir_chmod,
+
607 .chown = subdir_chown,
+
608 .truncate = subdir_truncate,
+
609 .utimens = subdir_utimens,
+
610 .create = subdir_create,
+
611 .open = subdir_open,
+
612 .read_buf = subdir_read_buf,
+
613 .write_buf = subdir_write_buf,
+
614 .statfs = subdir_statfs,
+
615 .flush = subdir_flush,
+
616 .release = subdir_release,
+
617 .fsync = subdir_fsync,
+
618 .fsyncdir = subdir_fsyncdir,
+
619 .setxattr = subdir_setxattr,
+
620 .getxattr = subdir_getxattr,
+
621 .listxattr = subdir_listxattr,
+
622 .removexattr = subdir_removexattr,
+
623 .lock = subdir_lock,
+
624 .flock = subdir_flock,
+
625 .bmap = subdir_bmap,
+
626 .lseek = subdir_lseek,
+
627#ifdef HAVE_STATX
+
628 .statx = subdir_statx,
+
629#endif
+
630};
+
631
+
632static const struct fuse_opt subdir_opts[] = {
+
633 FUSE_OPT_KEY("-h", 0),
+
634 FUSE_OPT_KEY("--help", 0),
+
635 { "subdir=%s", offsetof(struct subdir, base), 0 },
+
636 { "rellinks", offsetof(struct subdir, rellinks), 1 },
+
637 { "norellinks", offsetof(struct subdir, rellinks), 0 },
+ +
639};
+
640
+
641static void subdir_help(void)
+
642{
+
643 printf(
+
644" -o subdir=DIR prepend this directory to all paths (mandatory)\n"
+
645" -o [no]rellinks transform absolute symlinks to relative\n");
+
646}
+
647
+
648static int subdir_opt_proc(void *data, const char *arg, int key,
+
649 struct fuse_args *outargs)
+
650{
+
651 (void) data; (void) arg; (void) outargs;
+
652
+
653 if (!key) {
+
654 subdir_help();
+
655 return -1;
+
656 }
+
657
+
658 return 1;
+
659}
+
660
+
661static struct fuse_fs *subdir_new(struct fuse_args *args,
+
662 struct fuse_fs *next[])
+
663{
+
664 struct fuse_fs *fs;
+
665 struct subdir *d;
+
666
+
667 d = calloc(1, sizeof(struct subdir));
+
668 if (d == NULL) {
+
669 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
+
670 return NULL;
+
671 }
+
672
+
673 if (fuse_opt_parse(args, d, subdir_opts, subdir_opt_proc) == -1)
+
674 goto out_free;
+
675
+
676 if (!next[0] || next[1]) {
+
677 fuse_log(FUSE_LOG_ERR, "fuse-subdir: exactly one next filesystem required\n");
+
678 goto out_free;
+
679 }
+
680
+
681 if (!d->base) {
+
682 fuse_log(FUSE_LOG_ERR, "fuse-subdir: missing 'subdir' option\n");
+
683 goto out_free;
+
684 }
+
685
+
686 if (d->base[0] && d->base[strlen(d->base)-1] != '/') {
+
687 char *tmp = realloc(d->base, strlen(d->base) + 2);
+
688 if (!tmp) {
+
689 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
+
690 goto out_free;
+
691 }
+
692 d->base = tmp;
+
693 strcat(d->base, "/");
+
694 }
+
695 d->baselen = strlen(d->base);
+
696 d->next = next[0];
+
697 fs = fuse_fs_new(&subdir_oper, sizeof(subdir_oper), d);
+
698 if (!fs)
+
699 goto out_free;
+
700 return fs;
+
701
+
702out_free:
+
703 free(d->base);
+
704 free(d);
+
705 return NULL;
+
706}
+
707
+
708FUSE_REGISTER_MODULE(subdir, subdir_new);
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_REGISTER_MODULE(name_, factory_)
Definition fuse.h:1395
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + + +
void * private_data
Definition fuse.h:874
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+ +
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2mount_8c_source.html b/doc/html/fuse-3_818_81_2lib_2mount_8c_source.html new file mode 100644 index 0000000..56a5ab8 --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2mount_8c_source.html @@ -0,0 +1,794 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/mount.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Architecture specific file system mounting (Linux).
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt.
+
9*/
+
10
+
11/* For environ */
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_misc.h"
+
17#include "fuse_opt.h"
+
18#include "mount_util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <unistd.h>
+
23#include <stddef.h>
+
24#include <string.h>
+
25#include <fcntl.h>
+
26#include <errno.h>
+
27#include <poll.h>
+
28#include <spawn.h>
+
29#include <sys/socket.h>
+
30#include <sys/un.h>
+
31#include <sys/wait.h>
+
32
+
33#include "fuse_mount_compat.h"
+
34
+
35#ifdef __NetBSD__
+
36#include <perfuse.h>
+
37
+
38#define MS_RDONLY MNT_RDONLY
+
39#define MS_NOSUID MNT_NOSUID
+
40#define MS_NODEV MNT_NODEV
+
41#define MS_NOEXEC MNT_NOEXEC
+
42#define MS_SYNCHRONOUS MNT_SYNCHRONOUS
+
43#define MS_NOATIME MNT_NOATIME
+
44#define MS_NOSYMFOLLOW MNT_NOSYMFOLLOW
+
45
+
46#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
+
47#endif
+
48
+
49#define FUSERMOUNT_PROG "fusermount3"
+
50#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
+
51#define FUSE_COMMFD2_ENV "_FUSE_COMMFD2"
+
52#define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE"
+
53
+
54#ifndef MS_DIRSYNC
+
55#define MS_DIRSYNC 128
+
56#endif
+
57
+
58enum {
+
59 KEY_KERN_FLAG,
+
60 KEY_KERN_OPT,
+
61 KEY_FUSERMOUNT_OPT,
+
62 KEY_SUBTYPE_OPT,
+
63 KEY_MTAB_OPT,
+
64 KEY_ALLOW_OTHER,
+
65 KEY_RO,
+
66};
+
67
+
68struct mount_opts {
+
69 int allow_other;
+
70 int flags;
+
71 int auto_unmount;
+
72 int blkdev;
+
73 char *fsname;
+
74 char *subtype;
+
75 char *subtype_opt;
+
76 char *mtab_opts;
+
77 char *fusermount_opts;
+
78 char *kernel_opts;
+
79 unsigned max_read;
+
80};
+
81
+
82#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 }
+
83
+
84static const struct fuse_opt fuse_mount_opts[] = {
+
85 FUSE_MOUNT_OPT("allow_other", allow_other),
+
86 FUSE_MOUNT_OPT("blkdev", blkdev),
+
87 FUSE_MOUNT_OPT("auto_unmount", auto_unmount),
+
88 FUSE_MOUNT_OPT("fsname=%s", fsname),
+
89 FUSE_MOUNT_OPT("max_read=%u", max_read),
+
90 FUSE_MOUNT_OPT("subtype=%s", subtype),
+
91 FUSE_OPT_KEY("allow_other", KEY_KERN_OPT),
+
92 FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT),
+
93 FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT),
+
94 FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT),
+
95 FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT),
+
96 FUSE_OPT_KEY("blksize=", KEY_KERN_OPT),
+
97 FUSE_OPT_KEY("default_permissions", KEY_KERN_OPT),
+
98 FUSE_OPT_KEY("context=", KEY_KERN_OPT),
+
99 FUSE_OPT_KEY("fscontext=", KEY_KERN_OPT),
+
100 FUSE_OPT_KEY("defcontext=", KEY_KERN_OPT),
+
101 FUSE_OPT_KEY("rootcontext=", KEY_KERN_OPT),
+
102 FUSE_OPT_KEY("max_read=", KEY_KERN_OPT),
+
103 FUSE_OPT_KEY("user=", KEY_MTAB_OPT),
+
104 FUSE_OPT_KEY("-n", KEY_MTAB_OPT),
+
105 FUSE_OPT_KEY("-r", KEY_RO),
+
106 FUSE_OPT_KEY("ro", KEY_KERN_FLAG),
+
107 FUSE_OPT_KEY("rw", KEY_KERN_FLAG),
+
108 FUSE_OPT_KEY("suid", KEY_KERN_FLAG),
+
109 FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG),
+
110 FUSE_OPT_KEY("dev", KEY_KERN_FLAG),
+
111 FUSE_OPT_KEY("nodev", KEY_KERN_FLAG),
+
112 FUSE_OPT_KEY("exec", KEY_KERN_FLAG),
+
113 FUSE_OPT_KEY("noexec", KEY_KERN_FLAG),
+
114 FUSE_OPT_KEY("async", KEY_KERN_FLAG),
+
115 FUSE_OPT_KEY("sync", KEY_KERN_FLAG),
+
116 FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG),
+
117 FUSE_OPT_KEY("noatime", KEY_KERN_FLAG),
+
118 FUSE_OPT_KEY("nodiratime", KEY_KERN_FLAG),
+
119 FUSE_OPT_KEY("nostrictatime", KEY_KERN_FLAG),
+
120 FUSE_OPT_KEY("symfollow", KEY_KERN_FLAG),
+
121 FUSE_OPT_KEY("nosymfollow", KEY_KERN_FLAG),
+ +
123};
+
124
+
125/*
+
126 * Running fusermount by calling 'posix_spawn'
+
127 *
+
128 * @param out_pid might be NULL
+
129 */
+
130static int fusermount_posix_spawn(posix_spawn_file_actions_t *action,
+
131 char const * const argv[], pid_t *out_pid)
+
132{
+
133 const char *full_path = FUSERMOUNT_DIR "/" FUSERMOUNT_PROG;
+
134 pid_t pid;
+
135
+
136 /* See man 7 environ for the global environ pointer */
+
137
+
138 /* first try the install path */
+
139 int status = posix_spawn(&pid, full_path, action, NULL,
+
140 (char * const *) argv, environ);
+
141 if (status != 0) {
+
142 /* if that fails, try a system install */
+
143 status = posix_spawnp(&pid, FUSERMOUNT_PROG, action, NULL,
+
144 (char * const *) argv, environ);
+
145 }
+
146
+
147 if (status != 0) {
+
148 fuse_log(FUSE_LOG_ERR, "Failed to call '%s': %s\n",
+
149 FUSERMOUNT_PROG, strerror(status));
+
150 return -status;
+
151 }
+
152
+
153 if (out_pid)
+
154 *out_pid = pid;
+
155 else
+
156 waitpid(pid, NULL, 0); /* FIXME: check exit code and return error if any */
+
157
+
158 return 0;
+
159}
+
160
+
161void fuse_mount_version(void)
+
162{
+
163 char const *const argv[] = {FUSERMOUNT_PROG, "--version", NULL};
+
164 int status = fusermount_posix_spawn(NULL, argv, NULL);
+
165
+
166 if(status != 0)
+
167 fuse_log(FUSE_LOG_ERR, "Running '%s --version' failed",
+
168 FUSERMOUNT_PROG);
+
169}
+
170
+
171struct mount_flags {
+
172 const char *opt;
+
173 unsigned long flag;
+
174 int on;
+
175};
+
176
+
177static const struct mount_flags mount_flags[] = {
+
178 {"rw", MS_RDONLY, 0},
+
179 {"ro", MS_RDONLY, 1},
+
180 {"suid", MS_NOSUID, 0},
+
181 {"nosuid", MS_NOSUID, 1},
+
182 {"dev", MS_NODEV, 0},
+
183 {"nodev", MS_NODEV, 1},
+
184 {"exec", MS_NOEXEC, 0},
+
185 {"noexec", MS_NOEXEC, 1},
+
186 {"async", MS_SYNCHRONOUS, 0},
+
187 {"sync", MS_SYNCHRONOUS, 1},
+
188 {"noatime", MS_NOATIME, 1},
+
189 {"nodiratime", MS_NODIRATIME, 1},
+
190 {"norelatime", MS_RELATIME, 0},
+
191 {"nostrictatime", MS_STRICTATIME, 0},
+
192 {"symfollow", MS_NOSYMFOLLOW, 0},
+
193 {"nosymfollow", MS_NOSYMFOLLOW, 1},
+
194#ifndef __NetBSD__
+
195 {"dirsync", MS_DIRSYNC, 1},
+
196#endif
+
197 {NULL, 0, 0}
+
198};
+
199
+
200unsigned get_max_read(struct mount_opts *o)
+
201{
+
202 return o->max_read;
+
203}
+
204
+
205static void set_mount_flag(const char *s, int *flags)
+
206{
+
207 int i;
+
208
+
209 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
210 const char *opt = mount_flags[i].opt;
+
211 if (strcmp(opt, s) == 0) {
+
212 if (mount_flags[i].on)
+
213 *flags |= mount_flags[i].flag;
+
214 else
+
215 *flags &= ~mount_flags[i].flag;
+
216 return;
+
217 }
+
218 }
+
219 fuse_log(FUSE_LOG_ERR, "fuse: internal error, can't find mount flag\n");
+
220 abort();
+
221}
+
222
+
223static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+
224 struct fuse_args *outargs)
+
225{
+
226 (void) outargs;
+
227 struct mount_opts *mo = data;
+
228
+
229 switch (key) {
+
230 case KEY_RO:
+
231 arg = "ro";
+
232 /* fall through */
+
233 case KEY_KERN_FLAG:
+
234 set_mount_flag(arg, &mo->flags);
+
235 return 0;
+
236
+
237 case KEY_KERN_OPT:
+
238 return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
239
+
240 case KEY_FUSERMOUNT_OPT:
+
241 return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg);
+
242
+
243 case KEY_SUBTYPE_OPT:
+
244 return fuse_opt_add_opt(&mo->subtype_opt, arg);
+
245
+
246 case KEY_MTAB_OPT:
+
247 return fuse_opt_add_opt(&mo->mtab_opts, arg);
+
248
+
249 /* Third party options like 'x-gvfs-notrash' */
+
250 case FUSE_OPT_KEY_OPT:
+
251 return (strncmp("x-", arg, 2) == 0) ?
+
252 fuse_opt_add_opt(&mo->mtab_opts, arg) :
+
253 1;
+
254 }
+
255
+
256 /* Pass through unknown options */
+
257 return 1;
+
258}
+
259
+
260/* return value:
+
261 * >= 0 => fd
+
262 * -1 => error
+
263 */
+
264static int receive_fd(int fd)
+
265{
+
266 struct msghdr msg;
+
267 struct iovec iov;
+
268 char buf[1];
+
269 int rv;
+
270 size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)];
+
271 struct cmsghdr *cmsg;
+
272
+
273 iov.iov_base = buf;
+
274 iov.iov_len = 1;
+
275
+
276 memset(&msg, 0, sizeof(msg));
+
277 msg.msg_name = 0;
+
278 msg.msg_namelen = 0;
+
279 msg.msg_iov = &iov;
+
280 msg.msg_iovlen = 1;
+
281 /* old BSD implementations should use msg_accrights instead of
+
282 * msg_control; the interface is different. */
+
283 msg.msg_control = ccmsg;
+
284 msg.msg_controllen = sizeof(ccmsg);
+
285
+
286 while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR);
+
287 if (rv == -1) {
+
288 fuse_log(FUSE_LOG_ERR, "recvmsg failed: %s", strerror(errno));
+
289 return -1;
+
290 }
+
291 if(!rv) {
+
292 /* EOF */
+
293 return -1;
+
294 }
+
295
+
296 cmsg = CMSG_FIRSTHDR(&msg);
+
297 if (cmsg->cmsg_type != SCM_RIGHTS) {
+
298 fuse_log(FUSE_LOG_ERR, "got control message of unknown type %d\n",
+
299 cmsg->cmsg_type);
+
300 return -1;
+
301 }
+
302 return *(int*)CMSG_DATA(cmsg);
+
303}
+
304
+
305void fuse_kern_unmount(const char *mountpoint, int fd)
+
306{
+
307 int res;
+
308
+
309 if (fd != -1) {
+
310 struct pollfd pfd;
+
311
+
312 pfd.fd = fd;
+
313 pfd.events = 0;
+
314 res = poll(&pfd, 1, 0);
+
315
+
316 /* Need to close file descriptor, otherwise synchronous umount
+
317 would recurse into filesystem, and deadlock.
+
318
+
319 Caller expects fuse_kern_unmount to close the fd, so close it
+
320 anyway. */
+
321 close(fd);
+
322
+
323 /* If file poll returns POLLERR on the device file descriptor,
+
324 then the filesystem is already unmounted or the connection
+
325 was severed via /sys/fs/fuse/connections/NNN/abort */
+
326 if (res == 1 && (pfd.revents & POLLERR))
+
327 return;
+
328 }
+
329
+
330 if (geteuid() == 0) {
+
331 fuse_mnt_umount("fuse", mountpoint, mountpoint, 1);
+
332 return;
+
333 }
+
334
+
335 res = umount2(mountpoint, 2);
+
336 if (res == 0)
+
337 return;
+
338
+
339 char const * const argv[] =
+
340 { FUSERMOUNT_PROG, "--unmount", "--quiet", "--lazy",
+
341 "--", mountpoint, NULL };
+
342 int status = fusermount_posix_spawn(NULL, argv, NULL);
+
343 if(status != 0) {
+
344 fuse_log(FUSE_LOG_ERR, "Spawning %s to unmount failed: %s",
+
345 FUSERMOUNT_PROG, strerror(-status));
+
346 return;
+
347 }
+
348}
+
349
+
350static int setup_auto_unmount(const char *mountpoint, int quiet)
+
351{
+
352 int fds[2];
+
353 pid_t pid;
+
354 int res;
+
355
+
356 if (!mountpoint) {
+
357 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
358 return -1;
+
359 }
+
360
+
361 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+
362 if(res == -1) {
+
363 fuse_log(FUSE_LOG_ERR, "Setting up auto-unmount socketpair() failed: %s\n",
+
364 strerror(errno));
+
365 return -1;
+
366 }
+
367
+
368 char arg_fd_entry[30];
+
369 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
+
370 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
+
371 /*
+
372 * This helps to identify the FD hold by parent process.
+
373 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
+
374 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
+
375 * One potential use case is to satisfy FD-Leak checks.
+
376 */
+
377 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
+
378 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
+
379
+
380 char const *const argv[] = {
+
381 FUSERMOUNT_PROG,
+
382 "--auto-unmount",
+
383 "--",
+
384 mountpoint,
+
385 NULL,
+
386 };
+
387
+
388 // TODO: add error handling for all manipulations of action.
+
389 posix_spawn_file_actions_t action;
+
390 posix_spawn_file_actions_init(&action);
+
391
+
392 if (quiet) {
+
393 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
+
394 posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
+
395 }
+
396 posix_spawn_file_actions_addclose(&action, fds[1]);
+
397
+
398 /*
+
399 * auto-umount runs in the background - it is not waiting for the
+
400 * process
+
401 */
+
402 int status = fusermount_posix_spawn(&action, argv, &pid);
+
403
+
404 posix_spawn_file_actions_destroy(&action);
+
405
+
406 if(status != 0) {
+
407 close(fds[0]);
+
408 close(fds[1]);
+
409 fuse_log(FUSE_LOG_ERR, "fuse: Setting up auto-unmount failed (spawn): %s",
+
410 strerror(-status));
+
411 return -1;
+
412 }
+
413 // passed to child now, so can close here.
+
414 close(fds[0]);
+
415
+
416 // Now fusermount3 will only exit when fds[1] closes automatically when our
+
417 // process exits.
+
418 return 0;
+
419 // Note: fds[1] is leakend and doesn't get FD_CLOEXEC
+
420}
+
421
+
422static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
+
423 const char *opts, int quiet)
+
424{
+
425 int fds[2];
+
426 pid_t pid;
+
427 int res;
+
428
+
429 if (!mountpoint) {
+
430 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
431 return -1;
+
432 }
+
433
+
434 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+
435 if(res == -1) {
+
436 fuse_log(FUSE_LOG_ERR, "Running %s: socketpair() failed: %s\n",
+
437 FUSERMOUNT_PROG, strerror(errno));
+
438 return -1;
+
439 }
+
440
+
441 char arg_fd_entry[30];
+
442 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
+
443 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
+
444 /*
+
445 * This helps to identify the FD hold by parent process.
+
446 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
+
447 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
+
448 * One potential use case is to satisfy FD-Leak checks.
+
449 */
+
450 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
+
451 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
+
452
+
453 char const *const argv[] = {
+
454 FUSERMOUNT_PROG,
+
455 "-o", opts ? opts : "",
+
456 "--",
+
457 mountpoint,
+
458 NULL,
+
459 };
+
460
+
461
+
462 posix_spawn_file_actions_t action;
+
463 posix_spawn_file_actions_init(&action);
+
464
+
465 if (quiet) {
+
466 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
+
467 posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
+
468 }
+
469 posix_spawn_file_actions_addclose(&action, fds[1]);
+
470
+
471 int status = fusermount_posix_spawn(&action, argv, &pid);
+
472
+
473 posix_spawn_file_actions_destroy(&action);
+
474
+
475 if(status != 0) {
+
476 close(fds[0]);
+
477 close(fds[1]);
+
478 fuse_log(FUSE_LOG_ERR, "posix_spawn(p)() for %s failed: %s",
+
479 FUSERMOUNT_PROG, strerror(-status));
+
480 return -1;
+
481 }
+
482
+
483 // passed to child now, so can close here.
+
484 close(fds[0]);
+
485
+
486 int fd = receive_fd(fds[1]);
+
487
+
488 if (!mo->auto_unmount) {
+
489 /* with auto_unmount option fusermount3 will not exit until
+
490 this socket is closed */
+
491 close(fds[1]);
+
492 waitpid(pid, NULL, 0); /* bury zombie */
+
493 }
+
494
+
495 if (fd >= 0)
+
496 fcntl(fd, F_SETFD, FD_CLOEXEC);
+
497
+
498 return fd;
+
499}
+
500
+
501#ifndef O_CLOEXEC
+
502#define O_CLOEXEC 0
+
503#endif
+
504
+
505static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
+
506 const char *mnt_opts)
+
507{
+
508 char tmp[128];
+
509 const char *devname = getenv(FUSE_KERN_DEVICE_ENV) ?: "/dev/fuse";
+
510 char *source = NULL;
+
511 char *type = NULL;
+
512 struct stat stbuf;
+
513 int fd;
+
514 int res;
+
515
+
516 if (!mnt) {
+
517 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
518 return -1;
+
519 }
+
520
+
521 res = stat(mnt, &stbuf);
+
522 if (res == -1) {
+
523 fuse_log(FUSE_LOG_ERR, "fuse: failed to access mountpoint %s: %s\n",
+
524 mnt, strerror(errno));
+
525 return -1;
+
526 }
+
527
+
528 fd = open(devname, O_RDWR | O_CLOEXEC);
+
529 if (fd == -1) {
+
530 if (errno == ENODEV || errno == ENOENT)
+
531 fuse_log(FUSE_LOG_ERR,
+
532 "fuse: device %s not found. Kernel module not loaded?\n",
+
533 devname);
+
534 else
+
535 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n",
+
536 devname, strerror(errno));
+
537 return -1;
+
538 }
+
539 if (!O_CLOEXEC)
+
540 fcntl(fd, F_SETFD, FD_CLOEXEC);
+
541
+
542 snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+
543 fd, stbuf.st_mode & S_IFMT, getuid(), getgid());
+
544
+
545 res = fuse_opt_add_opt(&mo->kernel_opts, tmp);
+
546 if (res == -1)
+
547 goto out_close;
+
548
+
549 source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
+
550 (mo->subtype ? strlen(mo->subtype) : 0) +
+
551 strlen(devname) + 32);
+
552
+
553 type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
+
554 if (!type || !source) {
+
555 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate memory\n");
+
556 goto out_close;
+
557 }
+
558
+
559 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+
560 if (mo->subtype) {
+
561 strcat(type, ".");
+
562 strcat(type, mo->subtype);
+
563 }
+
564 strcpy(source,
+
565 mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
+
566
+
567 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+
568 if (res == -1 && errno == ENODEV && mo->subtype) {
+
569 /* Probably missing subtype support */
+
570 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+
571 if (mo->fsname) {
+
572 if (!mo->blkdev)
+
573 sprintf(source, "%s#%s", mo->subtype,
+
574 mo->fsname);
+
575 } else {
+
576 strcpy(source, type);
+
577 }
+
578 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+
579 }
+
580 if (res == -1) {
+
581 /*
+
582 * Maybe kernel doesn't support unprivileged mounts, in this
+
583 * case try falling back to fusermount3
+
584 */
+
585 if (errno == EPERM) {
+
586 res = -2;
+
587 } else {
+
588 int errno_save = errno;
+
589 if (mo->blkdev && errno == ENODEV &&
+
590 !fuse_mnt_check_fuseblk())
+
591 fuse_log(FUSE_LOG_ERR,
+
592 "fuse: 'fuseblk' support missing\n");
+
593 else
+
594 fuse_log(FUSE_LOG_ERR, "fuse: mount failed: %s\n",
+
595 strerror(errno_save));
+
596 }
+
597
+
598 goto out_close;
+
599 }
+
600
+
601#ifndef IGNORE_MTAB
+
602 if (geteuid() == 0) {
+
603 char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
+
604 res = -1;
+
605 if (!newmnt)
+
606 goto out_umount;
+
607
+
608 res = fuse_mnt_add_mount("fuse", source, newmnt, type,
+
609 mnt_opts);
+
610 free(newmnt);
+
611 if (res == -1)
+
612 goto out_umount;
+
613 }
+
614#endif /* IGNORE_MTAB */
+
615 free(type);
+
616 free(source);
+
617
+
618 return fd;
+
619
+
620out_umount:
+
621 umount2(mnt, 2); /* lazy umount */
+
622out_close:
+
623 free(type);
+
624 free(source);
+
625 close(fd);
+
626 return res;
+
627}
+
628
+
629static int get_mnt_flag_opts(char **mnt_optsp, int flags)
+
630{
+
631 int i;
+
632
+
633 if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1)
+
634 return -1;
+
635
+
636 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
637 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+
638 fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1)
+
639 return -1;
+
640 }
+
641 return 0;
+
642}
+
643
+
644struct mount_opts *parse_mount_opts(struct fuse_args *args)
+
645{
+
646 struct mount_opts *mo;
+
647
+
648 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
+
649 if (mo == NULL)
+
650 return NULL;
+
651
+
652 memset(mo, 0, sizeof(struct mount_opts));
+
653 mo->flags = MS_NOSUID | MS_NODEV;
+
654
+
655 if (args &&
+
656 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+
657 goto err_out;
+
658
+
659 return mo;
+
660
+
661err_out:
+
662 destroy_mount_opts(mo);
+
663 return NULL;
+
664}
+
665
+
666void destroy_mount_opts(struct mount_opts *mo)
+
667{
+
668 free(mo->fsname);
+
669 free(mo->subtype);
+
670 free(mo->fusermount_opts);
+
671 free(mo->subtype_opt);
+
672 free(mo->kernel_opts);
+
673 free(mo->mtab_opts);
+
674 free(mo);
+
675}
+
676
+
677
+
678int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
+
679{
+
680 int res = -1;
+
681 char *mnt_opts = NULL;
+
682
+
683 res = -1;
+
684 if (get_mnt_flag_opts(&mnt_opts, mo->flags) == -1)
+
685 goto out;
+
686 if (mo->kernel_opts && fuse_opt_add_opt(&mnt_opts, mo->kernel_opts) == -1)
+
687 goto out;
+
688 if (mo->mtab_opts && fuse_opt_add_opt(&mnt_opts, mo->mtab_opts) == -1)
+
689 goto out;
+
690
+
691 res = fuse_mount_sys(mountpoint, mo, mnt_opts);
+
692 if (res >= 0 && mo->auto_unmount) {
+
693 if(0 > setup_auto_unmount(mountpoint, 0)) {
+
694 // Something went wrong, let's umount like in fuse_mount_sys.
+
695 umount2(mountpoint, MNT_DETACH); /* lazy umount */
+
696 res = -1;
+
697 }
+
698 } else if (res == -2) {
+
699 if (mo->fusermount_opts &&
+
700 fuse_opt_add_opt(&mnt_opts, mo->fusermount_opts) == -1)
+
701 goto out;
+
702
+
703 if (mo->subtype) {
+
704 char *tmp_opts = NULL;
+
705
+
706 res = -1;
+
707 if (fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1 ||
+
708 fuse_opt_add_opt(&tmp_opts, mo->subtype_opt) == -1) {
+
709 free(tmp_opts);
+
710 goto out;
+
711 }
+
712
+
713 res = fuse_mount_fusermount(mountpoint, mo, tmp_opts, 1);
+
714 free(tmp_opts);
+
715 if (res == -1)
+
716 res = fuse_mount_fusermount(mountpoint, mo,
+
717 mnt_opts, 0);
+
718 } else {
+
719 res = fuse_mount_fusermount(mountpoint, mo, mnt_opts, 0);
+
720 }
+
721 }
+
722out:
+
723 free(mnt_opts);
+
724 return res;
+
725}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
#define FUSE_OPT_KEY_OPT
Definition fuse_opt.h:129
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2mount__bsd_8c_source.html b/doc/html/fuse-3_818_81_2lib_2mount__bsd_8c_source.html new file mode 100644 index 0000000..fd3b573 --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2mount__bsd_8c_source.html @@ -0,0 +1,336 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/mount_bsd.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_bsd.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2005-2008 Csaba Henk <csaba.henk@creo.hu>
+
4
+
5 Architecture specific file system mounting (FreeBSD).
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt.
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_i.h"
+
13#include "fuse_misc.h"
+
14#include "fuse_opt.h"
+
15#include "util.h"
+
16
+
17#include <sys/param.h>
+
18#include "fuse_mount_compat.h"
+
19
+
20#include <sys/wait.h>
+
21#include <stdio.h>
+
22#include <stdlib.h>
+
23#include <unistd.h>
+
24#include <stddef.h>
+
25#include <fcntl.h>
+
26#include <errno.h>
+
27#include <string.h>
+
28
+
29#define FUSERMOUNT_PROG "mount_fusefs"
+
30#define FUSE_DEV_TRUNK "/dev/fuse"
+
31
+
32enum {
+
33 KEY_RO,
+
34 KEY_KERN
+
35};
+
36
+
37struct mount_opts {
+
38 int allow_other;
+
39 char *kernel_opts;
+
40 unsigned max_read;
+
41};
+
42
+
43#define FUSE_DUAL_OPT_KEY(templ, key) \
+
44 FUSE_OPT_KEY(templ, key), FUSE_OPT_KEY("no" templ, key)
+
45
+
46static const struct fuse_opt fuse_mount_opts[] = {
+
47 { "allow_other", offsetof(struct mount_opts, allow_other), 1 },
+
48 { "max_read=%u", offsetof(struct mount_opts, max_read), 1 },
+
49 FUSE_OPT_KEY("-r", KEY_RO),
+
50 /* standard FreeBSD mount options */
+
51 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
+
52 FUSE_DUAL_OPT_KEY("async", KEY_KERN),
+
53 FUSE_DUAL_OPT_KEY("atime", KEY_KERN),
+
54 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
+
55 FUSE_DUAL_OPT_KEY("exec", KEY_KERN),
+
56 FUSE_DUAL_OPT_KEY("suid", KEY_KERN),
+
57 FUSE_DUAL_OPT_KEY("symfollow", KEY_KERN),
+
58 FUSE_DUAL_OPT_KEY("rdonly", KEY_KERN),
+
59 FUSE_DUAL_OPT_KEY("sync", KEY_KERN),
+
60 FUSE_DUAL_OPT_KEY("union", KEY_KERN),
+
61 FUSE_DUAL_OPT_KEY("userquota", KEY_KERN),
+
62 FUSE_DUAL_OPT_KEY("groupquota", KEY_KERN),
+
63 FUSE_DUAL_OPT_KEY("clusterr", KEY_KERN),
+
64 FUSE_DUAL_OPT_KEY("clusterw", KEY_KERN),
+
65 FUSE_DUAL_OPT_KEY("suiddir", KEY_KERN),
+
66 FUSE_DUAL_OPT_KEY("snapshot", KEY_KERN),
+
67 FUSE_DUAL_OPT_KEY("multilabel", KEY_KERN),
+
68 FUSE_DUAL_OPT_KEY("acls", KEY_KERN),
+
69 FUSE_DUAL_OPT_KEY("force", KEY_KERN),
+
70 FUSE_DUAL_OPT_KEY("update", KEY_KERN),
+
71 FUSE_DUAL_OPT_KEY("ro", KEY_KERN),
+
72 FUSE_DUAL_OPT_KEY("rw", KEY_KERN),
+
73 FUSE_DUAL_OPT_KEY("auto", KEY_KERN),
+
74 FUSE_DUAL_OPT_KEY("automounted", KEY_KERN),
+
75 /* options supported under both Linux and FBSD */
+
76 FUSE_DUAL_OPT_KEY("allow_other", KEY_KERN),
+
77 FUSE_DUAL_OPT_KEY("default_permissions",KEY_KERN),
+
78 FUSE_OPT_KEY("max_read=", KEY_KERN),
+
79 FUSE_OPT_KEY("subtype=", KEY_KERN),
+
80 /* FBSD FUSE specific mount options */
+
81 FUSE_DUAL_OPT_KEY("private", KEY_KERN),
+
82 FUSE_DUAL_OPT_KEY("neglect_shares", KEY_KERN),
+
83 FUSE_DUAL_OPT_KEY("push_symlinks_in", KEY_KERN),
+
84#if __FreeBSD_version >= 1200519
+
85 FUSE_DUAL_OPT_KEY("intr", KEY_KERN),
+
86#endif
+
87 /* stock FBSD mountopt parsing routine lets anything be negated... */
+
88 /*
+
89 * Linux specific mount options, but let just the mount util
+
90 * handle them
+
91 */
+
92 FUSE_OPT_KEY("fsname=", KEY_KERN),
+ +
94};
+
95
+
96void fuse_mount_version(void)
+
97{
+
98 system(FUSERMOUNT_PROG " --version");
+
99}
+
100
+
101unsigned get_max_read(struct mount_opts *o)
+
102{
+
103 return o->max_read;
+
104}
+
105
+
106static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+
107 struct fuse_args *outargs)
+
108{
+
109 (void) outargs;
+
110 struct mount_opts *mo = data;
+
111
+
112 switch (key) {
+
113 case KEY_RO:
+
114 arg = "ro";
+
115 /* fall through */
+
116
+
117 case KEY_KERN:
+
118 return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
119 }
+
120
+
121 /* Pass through unknown options */
+
122 return 1;
+
123}
+
124
+
125void fuse_kern_unmount(const char *mountpoint, int fd)
+
126{
+
127 if (close(fd) < 0)
+
128 fuse_log(FUSE_LOG_ERR, "closing FD %d failed: %s", fd, strerror(errno));
+
129 if (unmount(mountpoint, MNT_FORCE) < 0)
+
130 fuse_log(FUSE_LOG_ERR, "unmounting %s failed: %s",
+
131 mountpoint, strerror(errno));
+
132}
+
133
+
134static int fuse_mount_core(const char *mountpoint, const char *opts)
+
135{
+
136 const char *mountprog = FUSERMOUNT_PROG;
+
137 long fd;
+
138 char *fdnam, *dev;
+
139 pid_t pid, cpid;
+
140 int status;
+
141 int err;
+
142
+
143 fdnam = getenv("FUSE_DEV_FD");
+
144
+
145 if (fdnam) {
+
146 err = libfuse_strtol(fdnam, &fd);
+
147 if (err || fd < 0) {
+
148 fuse_log(FUSE_LOG_ERR, "invalid value given in FUSE_DEV_FD\n");
+
149 return -1;
+
150 }
+
151
+
152 goto mount;
+
153 }
+
154
+
155 dev = getenv("FUSE_DEV_NAME");
+
156
+
157 if (! dev)
+
158 dev = (char *)FUSE_DEV_TRUNK;
+
159
+
160 if ((fd = open(dev, O_RDWR)) < 0) {
+
161 perror("fuse: failed to open fuse device");
+
162 return -1;
+
163 }
+
164
+
165mount:
+
166 if (getenv("FUSE_NO_MOUNT") || ! mountpoint)
+
167 goto out;
+
168
+
169 pid = fork();
+
170 cpid = pid;
+
171
+
172 if (pid == -1) {
+
173 perror("fuse: fork() failed");
+
174 close(fd);
+
175 return -1;
+
176 }
+
177
+
178 if (pid == 0) {
+
179 pid = fork();
+
180
+
181 if (pid == -1) {
+
182 perror("fuse: fork() failed");
+
183 close(fd);
+
184 _exit(EXIT_FAILURE);
+
185 }
+
186
+
187 if (pid == 0) {
+
188 const char *argv[32];
+
189 int a = 0;
+
190 int ret = -1;
+
191
+
192 if (! fdnam)
+
193 {
+
194 ret = asprintf(&fdnam, "%ld", fd);
+
195 if(ret == -1)
+
196 {
+
197 perror("fuse: failed to assemble mount arguments");
+
198 close(fd);
+
199 _exit(EXIT_FAILURE);
+
200 }
+
201 }
+
202
+
203 argv[a++] = mountprog;
+
204 if (opts) {
+
205 argv[a++] = "-o";
+
206 argv[a++] = opts;
+
207 }
+
208 argv[a++] = fdnam;
+
209 argv[a++] = mountpoint;
+
210 argv[a++] = NULL;
+
211 execvp(mountprog, (char **) argv);
+
212 perror("fuse: failed to exec mount program");
+
213 free(fdnam);
+
214 _exit(EXIT_FAILURE);
+
215 }
+
216
+
217 waitpid(pid, &status, 0);
+
218 if (!WIFEXITED(status))
+
219 _exit(EXIT_FAILURE);
+
220 _exit(WEXITSTATUS(status));
+
221 }
+
222
+
223 if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) {
+
224 perror("fuse: failed to mount file system");
+
225 if (close(fd) < 0)
+
226 perror("fuse: closing FD");
+
227 return -1;
+
228 }
+
229
+
230out:
+
231 return fd;
+
232}
+
233
+
234struct mount_opts *parse_mount_opts(struct fuse_args *args)
+
235{
+
236 struct mount_opts *mo;
+
237
+
238 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
+
239 if (mo == NULL)
+
240 return NULL;
+
241
+
242 memset(mo, 0, sizeof(struct mount_opts));
+
243
+
244 if (args &&
+
245 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+
246 goto err_out;
+
247
+
248 return mo;
+
249
+
250err_out:
+
251 destroy_mount_opts(mo);
+
252 return NULL;
+
253}
+
254
+
255void destroy_mount_opts(struct mount_opts *mo)
+
256{
+
257 free(mo->kernel_opts);
+
258 free(mo);
+
259}
+
260
+
261int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
+
262{
+
263 /* mount util should not try to spawn the daemon */
+
264 setenv("MOUNT_FUSEFS_SAFE", "1", 1);
+
265 /* to notify the mount util it's called from lib */
+
266 setenv("MOUNT_FUSEFS_CALL_BY_LIB", "1", 1);
+
267
+
268 return fuse_mount_core(mountpoint, mo->kernel_opts);
+
269}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2mount__util_8c_source.html b/doc/html/fuse-3_818_81_2lib_2mount__util_8c_source.html new file mode 100644 index 0000000..607cc65 --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2mount__util_8c_source.html @@ -0,0 +1,437 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/mount_util.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_util.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Architecture-independent mounting code.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt.
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "mount_util.h"
+
13
+
14#include <stdio.h>
+
15#include <unistd.h>
+
16#include <stdlib.h>
+
17#include <string.h>
+
18#include <signal.h>
+
19#include <dirent.h>
+
20#include <errno.h>
+
21#include <fcntl.h>
+
22#include <limits.h>
+
23#include <paths.h>
+
24#if !defined( __NetBSD__) && !defined(__FreeBSD__) && !defined(__DragonFly__) && !defined(__ANDROID__)
+
25#include <mntent.h>
+
26#else
+
27#define IGNORE_MTAB
+
28#endif
+
29#include <sys/stat.h>
+
30#include <sys/wait.h>
+
31
+
32#include "fuse_mount_compat.h"
+
33
+
34#include <sys/param.h>
+
35
+
36#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
+
37#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0)
+
38#endif
+
39
+
40#ifdef IGNORE_MTAB
+
41#define mtab_needs_update(mnt) 0
+
42#else
+
43static int mtab_needs_update(const char *mnt)
+
44{
+
45 int res;
+
46 struct stat stbuf;
+
47
+
48 /* If mtab is within new mount, don't touch it */
+
49 if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 &&
+
50 _PATH_MOUNTED[strlen(mnt)] == '/')
+
51 return 0;
+
52
+
53 /*
+
54 * Skip mtab update if /etc/mtab:
+
55 *
+
56 * - doesn't exist,
+
57 * - is on a read-only filesystem.
+
58 */
+
59 res = lstat(_PATH_MOUNTED, &stbuf);
+
60 if (res == -1) {
+
61 if (errno == ENOENT)
+
62 return 0;
+
63 } else {
+
64 uid_t ruid;
+
65 int err;
+
66
+
67 ruid = getuid();
+
68 if (ruid != 0)
+
69 setreuid(0, -1);
+
70
+
71 res = access(_PATH_MOUNTED, W_OK);
+
72 err = (res == -1) ? errno : 0;
+
73 if (ruid != 0)
+
74 setreuid(ruid, -1);
+
75
+
76 if (err == EROFS)
+
77 return 0;
+
78
+
79 res = access("/run/mount/utab", F_OK);
+
80 if (res == -1)
+
81 return 0;
+
82 }
+
83
+
84 return 1;
+
85}
+
86#endif /* IGNORE_MTAB */
+
87
+
88static int add_mount(const char *progname, const char *fsname,
+
89 const char *mnt, const char *type, const char *opts)
+
90{
+
91 int res;
+
92 int status;
+
93 sigset_t blockmask;
+
94 sigset_t oldmask;
+
95
+
96 sigemptyset(&blockmask);
+
97 sigaddset(&blockmask, SIGCHLD);
+
98 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
99 if (res == -1) {
+
100 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
101 return -1;
+
102 }
+
103
+
104 res = fork();
+
105 if (res == -1) {
+
106 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
107 goto out_restore;
+
108 }
+
109 if (res == 0) {
+
110 char *env = NULL;
+
111
+
112 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
113
+
114 if(setuid(geteuid()) == -1) {
+
115 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
116 res = -1;
+
117 goto out_restore;
+
118 }
+
119
+
120 execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
+
121 "-f", "-t", type, "-o", opts, fsname, mnt, NULL, &env);
+
122 fprintf(stderr, "%s: failed to execute /bin/mount: %s\n",
+
123 progname, strerror(errno));
+
124 exit(1);
+
125 }
+
126 res = waitpid(res, &status, 0);
+
127 if (res == -1)
+
128 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
129
+
130 if (status != 0)
+
131 res = -1;
+
132
+
133 out_restore:
+
134 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
135
+
136 return res;
+
137}
+
138
+
139int fuse_mnt_add_mount(const char *progname, const char *fsname,
+
140 const char *mnt, const char *type, const char *opts)
+
141{
+
142 if (!mtab_needs_update(mnt))
+
143 return 0;
+
144
+
145 return add_mount(progname, fsname, mnt, type, opts);
+
146}
+
147
+
148static int exec_umount(const char *progname, const char *rel_mnt, int lazy)
+
149{
+
150 int res;
+
151 int status;
+
152 sigset_t blockmask;
+
153 sigset_t oldmask;
+
154
+
155 sigemptyset(&blockmask);
+
156 sigaddset(&blockmask, SIGCHLD);
+
157 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
158 if (res == -1) {
+
159 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
160 return -1;
+
161 }
+
162
+
163 res = fork();
+
164 if (res == -1) {
+
165 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
166 goto out_restore;
+
167 }
+
168 if (res == 0) {
+
169 char *env = NULL;
+
170
+
171 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
172
+
173 if(setuid(geteuid()) == -1) {
+
174 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
175 res = -1;
+
176 goto out_restore;
+
177 }
+
178
+
179 if (lazy) {
+
180 execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+
181 "-l", NULL, &env);
+
182 } else {
+
183 execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+
184 NULL, &env);
+
185 }
+
186 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+
187 progname, strerror(errno));
+
188 exit(1);
+
189 }
+
190 res = waitpid(res, &status, 0);
+
191 if (res == -1)
+
192 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
193
+
194 if (status != 0) {
+
195 res = -1;
+
196 }
+
197
+
198 out_restore:
+
199 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
200 return res;
+
201
+
202}
+
203
+
204int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+
205 const char *rel_mnt, int lazy)
+
206{
+
207 int res;
+
208
+
209 if (!mtab_needs_update(abs_mnt)) {
+
210 res = umount2(rel_mnt, lazy ? 2 : 0);
+
211 if (res == -1)
+
212 fprintf(stderr, "%s: failed to unmount %s: %s\n",
+
213 progname, abs_mnt, strerror(errno));
+
214 return res;
+
215 }
+
216
+
217 return exec_umount(progname, rel_mnt, lazy);
+
218}
+
219
+
220static int remove_mount(const char *progname, const char *mnt)
+
221{
+
222 int res;
+
223 int status;
+
224 sigset_t blockmask;
+
225 sigset_t oldmask;
+
226
+
227 sigemptyset(&blockmask);
+
228 sigaddset(&blockmask, SIGCHLD);
+
229 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
230 if (res == -1) {
+
231 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
232 return -1;
+
233 }
+
234
+
235 res = fork();
+
236 if (res == -1) {
+
237 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
238 goto out_restore;
+
239 }
+
240 if (res == 0) {
+
241 char *env = NULL;
+
242
+
243 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
244
+
245 if(setuid(geteuid()) == -1) {
+
246 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
247 res = -1;
+
248 goto out_restore;
+
249 }
+
250
+
251 execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i",
+
252 "--fake", mnt, NULL, &env);
+
253 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+
254 progname, strerror(errno));
+
255 exit(1);
+
256 }
+
257 res = waitpid(res, &status, 0);
+
258 if (res == -1)
+
259 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
260
+
261 if (status != 0)
+
262 res = -1;
+
263
+
264 out_restore:
+
265 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
266 return res;
+
267}
+
268
+
269int fuse_mnt_remove_mount(const char *progname, const char *mnt)
+
270{
+
271 if (!mtab_needs_update(mnt))
+
272 return 0;
+
273
+
274 return remove_mount(progname, mnt);
+
275}
+
276
+
277char *fuse_mnt_resolve_path(const char *progname, const char *orig)
+
278{
+
279 char buf[PATH_MAX];
+
280 char *copy;
+
281 char *dst;
+
282 char *end;
+
283 char *lastcomp;
+
284 const char *toresolv;
+
285
+
286 if (!orig[0]) {
+
287 fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname,
+
288 orig);
+
289 return NULL;
+
290 }
+
291
+
292 copy = strdup(orig);
+
293 if (copy == NULL) {
+
294 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
295 return NULL;
+
296 }
+
297
+
298 toresolv = copy;
+
299 lastcomp = NULL;
+
300 for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --);
+
301 if (end[0] != '/') {
+
302 char *tmp;
+
303 end[1] = '\0';
+
304 tmp = strrchr(copy, '/');
+
305 if (tmp == NULL) {
+
306 lastcomp = copy;
+
307 toresolv = ".";
+
308 } else {
+
309 lastcomp = tmp + 1;
+
310 if (tmp == copy)
+
311 toresolv = "/";
+
312 }
+
313 if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) {
+
314 lastcomp = NULL;
+
315 toresolv = copy;
+
316 }
+
317 else if (tmp)
+
318 tmp[0] = '\0';
+
319 }
+
320 if (realpath(toresolv, buf) == NULL) {
+
321 fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig,
+
322 strerror(errno));
+
323 free(copy);
+
324 return NULL;
+
325 }
+
326 if (lastcomp == NULL)
+
327 dst = strdup(buf);
+
328 else {
+
329 dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1);
+
330 if (dst) {
+
331 unsigned buflen = strlen(buf);
+
332 if (buflen && buf[buflen-1] == '/')
+
333 sprintf(dst, "%s%s", buf, lastcomp);
+
334 else
+
335 sprintf(dst, "%s/%s", buf, lastcomp);
+
336 }
+
337 }
+
338 free(copy);
+
339 if (dst == NULL)
+
340 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
341 return dst;
+
342}
+
343
+
344int fuse_mnt_check_fuseblk(void)
+
345{
+
346 char buf[256];
+
347 FILE *f = fopen("/proc/filesystems", "r");
+
348 if (!f)
+
349 return 1;
+
350
+
351 while (fgets(buf, sizeof(buf), f))
+
352 if (strstr(buf, "fuseblk\n")) {
+
353 fclose(f);
+
354 return 1;
+
355 }
+
356
+
357 fclose(f);
+
358 return 0;
+
359}
+
360
+
361int fuse_mnt_parse_fuse_fd(const char *mountpoint)
+
362{
+
363 int fd = -1;
+
364 int len = 0;
+
365
+
366 if (mountpoint == NULL) {
+
367 fprintf(stderr, "Invalid null-ptr mount-point!\n");
+
368 return -1;
+
369 }
+
370
+
371 if (sscanf(mountpoint, "/dev/fd/%u%n", &fd, &len) == 1 &&
+
372 len == strlen(mountpoint)) {
+
373 return fd;
+
374 }
+
375
+
376 return -1;
+
377}
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2mount__util_8h_source.html b/doc/html/fuse-3_818_81_2lib_2mount__util_8h_source.html new file mode 100644 index 0000000..53b237b --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2mount__util_8h_source.html @@ -0,0 +1,78 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/mount_util.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_util.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#include <sys/types.h>
+
10
+
11int fuse_mnt_add_mount(const char *progname, const char *fsname,
+
12 const char *mnt, const char *type, const char *opts);
+
13int fuse_mnt_remove_mount(const char *progname, const char *mnt);
+
14int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+
15 const char *rel_mnt, int lazy);
+
16char *fuse_mnt_resolve_path(const char *progname, const char *orig);
+
17int fuse_mnt_check_fuseblk(void);
+
18int fuse_mnt_parse_fuse_fd(const char *mountpoint);
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2usdt_8h_source.html b/doc/html/fuse-3_818_81_2lib_2usdt_8h_source.html new file mode 100644 index 0000000..91515b4 --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2usdt_8h_source.html @@ -0,0 +1,595 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/usdt.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
usdt.h
+
+
+
1/*
+
2 * Copied from https://github.com/libbpf/usdt/
+
3 */
+
4
+
5// SPDX-License-Identifier: BSD-2-Clause
+
6/*
+
7 * This single-header library defines a collection of variadic macros for
+
8 * defining and triggering USDTs (User Statically-Defined Tracepoints):
+
9 *
+
10 * - For USDTs without associated semaphore:
+
11 * USDT(group, name, args...)
+
12 *
+
13 * - For USDTs with implicit (transparent to the user) semaphore:
+
14 * USDT_WITH_SEMA(group, name, args...)
+
15 * USDT_IS_ACTIVE(group, name)
+
16 *
+
17 * - For USDTs with explicit (user-defined and provided) semaphore:
+
18 * USDT_WITH_EXPLICIT_SEMA(sema, group, name, args...)
+
19 * USDT_SEMA_IS_ACTIVE(sema)
+
20 *
+
21 * all of which emit a NOP instruction into the instruction stream, and so
+
22 * have *zero* overhead for the surrounding code. USDTs are identified by
+
23 * a combination of `group` and `name` identifiers, which is used by external
+
24 * tracing tooling (tracers) for identifying exact USDTs of interest.
+
25 *
+
26 * USDTs can have an associated (2-byte) activity counter (USDT semaphore),
+
27 * automatically maintained by Linux kernel whenever any correctly written
+
28 * BPF-based tracer is attached to the USDT. This USDT semaphore can be used
+
29 * to check whether there is a need to do any extra data collection and
+
30 * processing for a given USDT (if necessary), and otherwise avoid extra work
+
31 * for a common case of USDT not being traced ("active").
+
32 *
+
33 * See documentation for USDT_WITH_SEMA()/USDT_IS_ACTIVE() or
+
34 * USDT_WITH_EXPLICIT_SEMA()/USDT_SEMA_IS_ACTIVE() APIs below for details on
+
35 * working with USDTs with implicitly or explicitly associated
+
36 * USDT semaphores, respectively.
+
37 *
+
38 * There is also some additional data recorded into an auxiliary note
+
39 * section. The data in the note section describes the operands, in terms of
+
40 * size and location, used by tracing tooling to know where to find USDT
+
41 * arguments. Each location is encoded as an assembler operand string.
+
42 * Tracing tools (bpftrace and BPF-based tracers, systemtap, etc) insert
+
43 * breakpoints on top of the nop, and decode the location operand-strings,
+
44 * like an assembler, to find the values being passed.
+
45 *
+
46 * The operand strings are selected by the compiler for each operand.
+
47 * They are constrained by inline-assembler codes.The default is:
+
48 *
+
49 * #define USDT_ARG_CONSTRAINT nor
+
50 *
+
51 * This is a good default if the operands tend to be integral and
+
52 * moderate in number (smaller than number of registers). In other
+
53 * cases, the compiler may report "'asm' requires impossible reload" or
+
54 * similar. In this case, consider simplifying the macro call (fewer
+
55 * and simpler operands), reduce optimization, or override the default
+
56 * constraints string via:
+
57 *
+
58 * #define USDT_ARG_CONSTRAINT g
+
59 * #include <usdt.h>
+
60 *
+
61 * For some historical description of USDT v3 format (the one used by this
+
62 * library and generally recognized and assumed by BPF-based tracing tools)
+
63 * see [0]. The more formal specification can be found at [1]. Additional
+
64 * argument constraints information can be found at [2].
+
65 *
+
66 * Original SystemTap's sys/sdt.h implementation ([3]) was used as a base for
+
67 * this USDT library implementation. Current implementation differs *a lot* in
+
68 * terms of exposed user API and general usability, which was the main goal
+
69 * and focus of the reimplementation work. Nevertheless, underlying recorded
+
70 * USDT definitions are fully binary compatible and any USDT-based tooling
+
71 * should work equally well with USDTs defined by either SystemTap's or this
+
72 * library's USDT implementation.
+
73 *
+
74 * [0] https://ecos.sourceware.org/ml/systemtap/2010-q3/msg00145.html
+
75 * [1] https://sourceware.org/systemtap/wiki/UserSpaceProbeImplementation
+
76 * [2] https://gcc.gnu.org/onlinedocs/gcc/Constraints.html
+
77 * [3] https://sourceware.org/git/?p=systemtap.git;a=blob;f=includes/sys/sdt.h
+
78 */
+
79#ifndef __USDT_H
+
80#define __USDT_H
+
81
+
82/*
+
83 * Changelog:
+
84 *
+
85 * 0.1.0
+
86 * -----
+
87 * - Initial release
+
88 */
+
89#define USDT_MAJOR_VERSION 0
+
90#define USDT_MINOR_VERSION 1
+
91#define USDT_PATCH_VERSION 0
+
92
+
93/* C++20 and C23 added __VA_OPT__ as a standard replacement for non-standard `##__VA_ARGS__` extension */
+
94#if (defined(__STDC_VERSION__) && __STDC_VERSION__ > 201710L) || (defined(__cplusplus) && __cplusplus > 201703L)
+
95#define __usdt_va_opt 1
+
96#define __usdt_va_args(...) __VA_OPT__(,) __VA_ARGS__
+
97#else
+
98#define __usdt_va_args(...) , ##__VA_ARGS__
+
99#endif
+
100
+
101/*
+
102 * Trigger USDT with `group`:`name` identifier and pass through `args` as its
+
103 * arguments. Zero arguments are acceptable as well. No USDT semaphore is
+
104 * associated with this USDT.
+
105 *
+
106 * Such "semaphoreless" USDTs are commonly used when there is no extra data
+
107 * collection or processing needed to collect and prepare USDT arguments and
+
108 * they are just available in the surrounding code. USDT() macro will just
+
109 * record their locations in CPU registers or in memory for tracing tooling to
+
110 * be able to access them, if necessary.
+
111 */
+
112#ifdef __usdt_va_opt
+
113#define USDT(group, name, ...) \
+
114 __usdt_probe(group, name, __usdt_sema_none, 0 __VA_OPT__(,) __VA_ARGS__)
+
115#else
+
116#define USDT(group, name, ...) \
+
117 __usdt_probe(group, name, __usdt_sema_none, 0, ##__VA_ARGS__)
+
118#endif
+
119
+
120/*
+
121 * Trigger USDT with `group`:`name` identifier and pass through `args` as its
+
122 * arguments. Zero arguments are acceptable as well. USDT also get an
+
123 * implicitly-defined associated USDT semaphore, which will be "activated" by
+
124 * tracing tooling and can be used to check whether USDT is being actively
+
125 * observed.
+
126 *
+
127 * USDTs with semaphore are commonly used when there is a need to perform
+
128 * additional data collection and processing to prepare USDT arguments, which
+
129 * otherwise might not be necessary for the rest of application logic. In such
+
130 * case, USDT semaphore can be used to avoid unnecessary extra work. If USDT
+
131 * is not traced (which is presumed to be a common situation), the associated
+
132 * USDT semaphore is "inactive", and so there is no need to waste resources to
+
133 * prepare USDT arguments. Use USDT_IS_ACTIVE(group, name) to check whether
+
134 * USDT is "active".
+
135 *
+
136 * N.B. There is an inherent (albeit short) gap between checking whether USDT
+
137 * is active and triggering corresponding USDT, in which external tracer can
+
138 * be attached to an USDT and activate USDT semaphore after the activity check.
+
139 * If such a race occurs, tracers might miss one USDT execution. Tracers are
+
140 * expected to accommodate such possibility and this is expected to not be
+
141 * a problem for applications and tracers.
+
142 *
+
143 * N.B. Implicit USDT semaphore defined by USDT_WITH_SEMA() is contained
+
144 * within a single executable or shared library and is not shared outside
+
145 * them. I.e., if you use USDT_WITH_SEMA() with the same USDT group and name
+
146 * identifier across executable and shared library, it will work and won't
+
147 * conflict, per se, but will define independent USDT semaphores, one for each
+
148 * shared library/executable in which USDT_WITH_SEMA(group, name) is used.
+
149 * That is, if you attach to this USDT in one shared library (or executable),
+
150 * then only USDT semaphore within that shared library (or executable) will be
+
151 * updated by the kernel, while other libraries (or executable) will not see
+
152 * activated USDT semaphore. In short, it's best to use unique USDT group:name
+
153 * identifiers across different shared libraries (and, equivalently, between
+
154 * executable and shared library). This is advanced consideration and is
+
155 * rarely (if ever) seen in practice, but just to avoid surprises this is
+
156 * called out here. (Static libraries become a part of final executable, once
+
157 * linked by linker, so the above considerations don't apply to them.)
+
158 */
+
159#ifdef __usdt_va_opt
+
160#define USDT_WITH_SEMA(group, name, ...) \
+
161 __usdt_probe(group, name, \
+
162 __usdt_sema_implicit, __usdt_sema_name(group, name) \
+
163 __VA_OPT__(,) __VA_ARGS__)
+
164#else
+
165#define USDT_WITH_SEMA(group, name, ...) \
+
166 __usdt_probe(group, name, \
+
167 __usdt_sema_implicit, __usdt_sema_name(group, name), \
+
168 ##__VA_ARGS__)
+
169#endif
+
170
+
171struct usdt_sema { volatile unsigned short active; };
+
172
+
173/*
+
174 * Check if USDT with `group`:`name` identifier is "active" (i.e., whether it
+
175 * is attached to by external tracing tooling and is actively observed).
+
176 *
+
177 * This macro can be used to decide whether any additional and potentially
+
178 * expensive data collection or processing should be done to pass extra
+
179 * information into the given USDT. It is assumed that USDT is triggered with
+
180 * USDT_WITH_SEMA() macro which will implicitly define associated USDT
+
181 * semaphore. (If one needs more control over USDT semaphore, see
+
182 * USDT_DEFINE_SEMA() and USDT_WITH_EXPLICIT_SEMA() macros below.)
+
183 *
+
184 * N.B. Such checks are necessarily racy and speculative. Between checking
+
185 * whether USDT is active and triggering the USDT itself, tracer can be
+
186 * detached with no notification. This race should be extremely rare and worst
+
187 * case should result in one-time wasted extra data collection and processing.
+
188 */
+
189#define USDT_IS_ACTIVE(group, name) ({ \
+
190 extern struct usdt_sema __usdt_sema_name(group, name) \
+
191 __usdt_asm_name(__usdt_sema_name(group, name)); \
+
192 __usdt_sema_implicit(__usdt_sema_name(group, name)); \
+
193 __usdt_sema_name(group, name).active > 0; \
+
194})
+
195
+
196/*
+
197 * APIs for working with user-defined explicit USDT semaphores.
+
198 *
+
199 * This is a less commonly used advanced API for use cases in which user needs
+
200 * an explicit control over (potentially shared across multiple USDTs) USDT
+
201 * semaphore instance. This can be used when there is a group of logically
+
202 * related USDTs that all need extra data collection and processing whenever
+
203 * any of a family of related USDTs are "activated" (i.e., traced). In such
+
204 * a case, all such related USDTs will be associated with the same shared USDT
+
205 * semaphore defined with USDT_DEFINE_SEMA() and the USDTs themselves will be
+
206 * triggered with USDT_WITH_EXPLICIT_SEMA() macros, taking an explicit extra
+
207 * USDT semaphore identifier as an extra parameter.
+
208 */
+
209
+
215#define USDT_SEMA(sema) __usdt_sema_##sema
+
216
+
217/*
+
218 * Define storage for user-defined USDT semaphore `sema`.
+
219 *
+
220 * Should be used only once in non-header source file to let compiler allocate
+
221 * space for the semaphore variable. Just like with any other global variable.
+
222 *
+
223 * This macro can be used anywhere where global variable declaration is
+
224 * allowed. Just like with global variable definitions, there should be only
+
225 * one definition of user-defined USDT semaphore with given `sema` identifier,
+
226 * otherwise compiler or linker will complain about duplicate variable
+
227 * definition.
+
228 *
+
229 * For C++, it is allowed to use USDT_DEFINE_SEMA() both in global namespace
+
230 * and inside namespaces (including nested namespaces). Just make sure that
+
231 * USDT_DECLARE_SEMA() is placed within the namespace where this semaphore is
+
232 * referenced, or any of its parent namespaces, so the C++ language-level
+
233 * identifier is visible to the code that needs to reference the semaphore.
+
234 * At the lowest layer, USDT semaphores have global naming and visibility
+
235 * (they have a corresponding `__usdt_sema_<name>` symbol, which can be linked
+
236 * against from C or C++ code, if necessary). To keep it simple, putting
+
237 * USDT_DECLARE_SEMA() declarations into global namespaces is the simplest
+
238 * no-brainer solution. All these aspects are irrelevant for plain C, because
+
239 * C doesn't have namespaces and everything is always in the global namespace.
+
240 *
+
241 * N.B. Due to USDT metadata being recorded in non-allocatable ELF note
+
242 * section, it has limitations when it comes to relocations, which, in
+
243 * practice, means that it's not possible to correctly share USDT semaphores
+
244 * between main executable and shared libraries, or even between multiple
+
245 * shared libraries. USDT semaphore has to be contained to individual shared
+
246 * library or executable to avoid unpleasant surprises with half-working USDT
+
247 * semaphores. We enforce this by marking semaphore ELF symbols as having
+
248 * a hidden visibility. This is quite an advanced use case and consideration
+
249 * and for most users this should have no consequences whatsoever.
+
250 */
+
251#define USDT_DEFINE_SEMA(sema) \
+
252 struct usdt_sema __usdt_sema_sec USDT_SEMA(sema) \
+
253 __usdt_asm_name(USDT_SEMA(sema)) \
+
254 __attribute__((visibility("hidden"))) = { 0 }
+
255
+
256/*
+
257 * Declare extern reference to user-defined USDT semaphore `sema`.
+
258 *
+
259 * Refers to a variable defined in another compilation unit by
+
260 * USDT_DEFINE_SEMA() and allows to use the same USDT semaphore across
+
261 * multiple compilation units (i.e., .c and .cpp files).
+
262 *
+
263 * See USDT_DEFINE_SEMA() notes above for C++ language usage peculiarities.
+
264 */
+
265#define USDT_DECLARE_SEMA(sema) \
+
266 extern struct usdt_sema USDT_SEMA(sema) __usdt_asm_name(USDT_SEMA(sema))
+
267
+
268/*
+
269 * Check if user-defined USDT semaphore `sema` is "active" (i.e., whether it
+
270 * is attached to by external tracing tooling and is actively observed).
+
271 *
+
272 * This macro can be used to decide whether any additional and potentially
+
273 * expensive data collection or processing should be done to pass extra
+
274 * information into USDT(s) associated with USDT semaphore `sema`.
+
275 *
+
276 * N.B. Such checks are necessarily racy. Between checking the state of USDT
+
277 * semaphore and triggering associated USDT(s), the active tracer might attach
+
278 * or detach. This race should be extremely rare and worst case should result
+
279 * in one-time missed USDT event or wasted extra data collection and
+
280 * processing. USDT-using tracers should be written with this in mind and is
+
281 * not a concern of the application defining USDTs with associated semaphore.
+
282 */
+
283#define USDT_SEMA_IS_ACTIVE(sema) (USDT_SEMA(sema).active > 0)
+
284
+
285/*
+
286 * Invoke USDT specified by `group` and `name` identifiers and associate
+
287 * explicitly user-defined semaphore `sema` with it. Pass through `args` as
+
288 * USDT arguments. `args` are optional and zero arguments are acceptable.
+
289 *
+
290 * Semaphore is defined with the help of USDT_DEFINE_SEMA() macro and can be
+
291 * checked whether active with USDT_SEMA_IS_ACTIVE().
+
292 */
+
293#ifdef __usdt_va_opt
+
294#define USDT_WITH_EXPLICIT_SEMA(sema, group, name, ...) \
+
295 __usdt_probe(group, name, __usdt_sema_explicit, USDT_SEMA(sema), ##__VA_ARGS__)
+
296#else
+
297#define USDT_WITH_EXPLICIT_SEMA(sema, group, name, ...) \
+
298 __usdt_probe(group, name, __usdt_sema_explicit, USDT_SEMA(sema) __VA_OPT__(,) __VA_ARGS__)
+
299#endif
+
300
+
301/*
+
302 * Adjustable implementation aspects
+
303 */
+
304#ifndef USDT_ARG_CONSTRAINT
+
305#if defined __powerpc__
+
306#define USDT_ARG_CONSTRAINT nZr
+
307#elif defined __arm__
+
308#define USDT_ARG_CONSTRAINT g
+
309#elif defined __loongarch__
+
310#define USDT_ARG_CONSTRAINT nmr
+
311#else
+
312#define USDT_ARG_CONSTRAINT nor
+
313#endif
+
314#endif /* USDT_ARG_CONSTRAINT */
+
315
+
316#ifndef USDT_NOP
+
317#if defined(__ia64__) || defined(__s390__) || defined(__s390x__)
+
318#define USDT_NOP nop 0
+
319#else
+
320#define USDT_NOP nop
+
321#endif
+
322#endif /* USDT_NOP */
+
323
+
324/*
+
325 * Implementation details
+
326 */
+
327/* USDT name for implicitly-defined USDT semaphore, derived from group:name */
+
328#define __usdt_sema_name(group, name) __usdt_sema_##group##__##name
+
329/* ELF section into which USDT semaphores are put */
+
330#define __usdt_sema_sec __attribute__((section(".probes")))
+
331
+
332#define __usdt_concat(a, b) a ## b
+
333#define __usdt_apply(fn, n) __usdt_concat(fn, n)
+
334
+
335#ifndef __usdt_nth
+
336#define __usdt_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, N, ...) N
+
337#endif
+
338
+
339#ifndef __usdt_narg
+
340#ifdef __usdt_va_opt
+
341#define __usdt_narg(...) __usdt_nth(_ __VA_OPT__(,) __VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
+
342#else
+
343#define __usdt_narg(...) __usdt_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
+
344#endif
+
345#endif /* __usdt_narg */
+
346
+
347#define __usdt_hash #
+
348#define __usdt_str_(x) #x
+
349#define __usdt_str(x) __usdt_str_(x)
+
350
+
351#ifndef __usdt_asm_name
+
352#define __usdt_asm_name(name) __asm__(__usdt_str(name))
+
353#endif
+
354
+
355#define __usdt_asm1(a) __usdt_str(a) "\n"
+
356#define __usdt_asm2(a,b) __usdt_str(a) "," __usdt_str(b) "\n"
+
357#define __usdt_asm3(a,b,c) __usdt_str(a) "," __usdt_str(b) "," __usdt_str(c) "\n"
+
358#define __usdt_asm5(a,b,c,d,e) __usdt_str(a) "," __usdt_str(b) "," __usdt_str(c) "," \
+
359 __usdt_str(d) "," __usdt_str(e) "\n"
+
360
+
361#ifdef __LP64__
+
362#define __usdt_asm_addr .8byte
+
363#else
+
364#define __usdt_asm_addr .4byte
+
365#endif
+
366
+
367#define __usdt_asm_strz_(x) __usdt_asm1(.asciz #x)
+
368#define __usdt_asm_strz(x) __usdt_asm_strz_(x)
+
369#define __usdt_asm_str_(x) __usdt_asm1(.ascii #x)
+
370#define __usdt_asm_str(x) __usdt_asm_str_(x)
+
371
+
372/* "semaphoreless" USDT case */
+
373#ifndef __usdt_sema_none
+
374#define __usdt_sema_none(sema)
+
375#endif
+
376
+
377/* implicitly defined __usdt_sema__group__name semaphore (using weak symbols) */
+
378#ifndef __usdt_sema_implicit
+
379#define __usdt_sema_implicit(sema) \
+
380 __asm__ __volatile__ ( \
+
381 __usdt_asm1(.ifndef sema) \
+
382 __usdt_asm3( .pushsection .probes, "aw", "progbits") \
+
383 __usdt_asm1( .weak sema) \
+
384 __usdt_asm1( .hidden sema) \
+
385 __usdt_asm1( .align 2) \
+
386 __usdt_asm1(sema:) \
+
387 __usdt_asm1( .zero 2) \
+
388 __usdt_asm2( .type sema, @object) \
+
389 __usdt_asm2( .size sema, 2) \
+
390 __usdt_asm1( .popsection) \
+
391 __usdt_asm1(.endif) \
+
392 );
+
393#endif
+
394
+
395/* externally defined semaphore using USDT_DEFINE_SEMA() and passed explicitly by user */
+
396#ifndef __usdt_sema_explicit
+
397#define __usdt_sema_explicit(sema) \
+
398 __asm__ __volatile__ ("" :: "m" (sema));
+
399#endif
+
400
+
401/* main USDT definition (nop and .note.stapsdt metadata) */
+
402#define __usdt_probe(group, name, sema_def, sema, ...) do { \
+
403 sema_def(sema) \
+
404 __asm__ __volatile__ ( \
+
405 __usdt_asm1(990: USDT_NOP) \
+
406 __usdt_asm3( .pushsection .note.stapsdt, "", "note") \
+
407 __usdt_asm1( .balign 4) \
+
408 __usdt_asm3( .4byte 992f-991f,994f-993f,3) \
+
409 __usdt_asm1(991: .asciz "stapsdt") \
+
410 __usdt_asm1(992: .balign 4) \
+
411 __usdt_asm1(993: __usdt_asm_addr 990b) \
+
412 __usdt_asm1( __usdt_asm_addr _.stapsdt.base) \
+
413 __usdt_asm1( __usdt_asm_addr sema) \
+
414 __usdt_asm_strz(group) \
+
415 __usdt_asm_strz(name) \
+
416 __usdt_asm_args(__VA_ARGS__) \
+
417 __usdt_asm1( .ascii "\0") \
+
418 __usdt_asm1(994: .balign 4) \
+
419 __usdt_asm1( .popsection) \
+
420 __usdt_asm1(.ifndef _.stapsdt.base) \
+
421 __usdt_asm5( .pushsection .stapsdt.base,"aG","progbits",.stapsdt.base,comdat)\
+
422 __usdt_asm1( .weak _.stapsdt.base) \
+
423 __usdt_asm1( .hidden _.stapsdt.base) \
+
424 __usdt_asm1(_.stapsdt.base:) \
+
425 __usdt_asm1( .space 1) \
+
426 __usdt_asm2( .size _.stapsdt.base, 1) \
+
427 __usdt_asm1( .popsection) \
+
428 __usdt_asm1(.endif) \
+
429 :: __usdt_asm_ops(__VA_ARGS__) \
+
430 ); \
+
431} while (0)
+
432
+
433/*
+
434 * NB: gdb PR24541 highlighted an unspecified corner of the sdt.h
+
435 * operand note format.
+
436 *
+
437 * The named register may be a longer or shorter (!) alias for the
+
438 * storage where the value in question is found. For example, on
+
439 * i386, 64-bit value may be put in register pairs, and a register
+
440 * name stored would identify just one of them. Previously, gcc was
+
441 * asked to emit the %w[id] (16-bit alias of some registers holding
+
442 * operands), even when a wider 32-bit value was used.
+
443 *
+
444 * Bottom line: the byte-width given before the @ sign governs. If
+
445 * there is a mismatch between that width and that of the named
+
446 * register, then a sys/sdt.h note consumer may need to employ
+
447 * architecture-specific heuristics to figure out where the compiler
+
448 * has actually put the complete value.
+
449 */
+
450#if defined(__powerpc__) || defined(__powerpc64__)
+
451#define __usdt_argref(id) %I[id]%[id]
+
452#elif defined(__i386__)
+
453#define __usdt_argref(id) %k[id] /* gcc.gnu.org/PR80115 sourceware.org/PR24541 */
+
454#else
+
455#define __usdt_argref(id) %[id]
+
456#endif
+
457
+
458#define __usdt_asm_arg(n) __usdt_asm_str(%c[__usdt_asz##n]) \
+
459 __usdt_asm1(.ascii "@") \
+
460 __usdt_asm_str(__usdt_argref(__usdt_aval##n))
+
461
+
462#define __usdt_asm_args0 /* no arguments */
+
463#define __usdt_asm_args1 __usdt_asm_arg(1)
+
464#define __usdt_asm_args2 __usdt_asm_args1 __usdt_asm1(.ascii " ") __usdt_asm_arg(2)
+
465#define __usdt_asm_args3 __usdt_asm_args2 __usdt_asm1(.ascii " ") __usdt_asm_arg(3)
+
466#define __usdt_asm_args4 __usdt_asm_args3 __usdt_asm1(.ascii " ") __usdt_asm_arg(4)
+
467#define __usdt_asm_args5 __usdt_asm_args4 __usdt_asm1(.ascii " ") __usdt_asm_arg(5)
+
468#define __usdt_asm_args6 __usdt_asm_args5 __usdt_asm1(.ascii " ") __usdt_asm_arg(6)
+
469#define __usdt_asm_args7 __usdt_asm_args6 __usdt_asm1(.ascii " ") __usdt_asm_arg(7)
+
470#define __usdt_asm_args8 __usdt_asm_args7 __usdt_asm1(.ascii " ") __usdt_asm_arg(8)
+
471#define __usdt_asm_args9 __usdt_asm_args8 __usdt_asm1(.ascii " ") __usdt_asm_arg(9)
+
472#define __usdt_asm_args10 __usdt_asm_args9 __usdt_asm1(.ascii " ") __usdt_asm_arg(10)
+
473#define __usdt_asm_args11 __usdt_asm_args10 __usdt_asm1(.ascii " ") __usdt_asm_arg(11)
+
474#define __usdt_asm_args12 __usdt_asm_args11 __usdt_asm1(.ascii " ") __usdt_asm_arg(12)
+
475#define __usdt_asm_args(...) __usdt_apply(__usdt_asm_args, __usdt_narg(__VA_ARGS__))
+
476
+
477#define __usdt_is_arr(x) (__builtin_classify_type(x) == 14 || __builtin_classify_type(x) == 5)
+
478#define __usdt_arg_size(x) (__usdt_is_arr(x) ? sizeof(void *) : sizeof(x))
+
479
+
480/*
+
481 * We can't use __builtin_choose_expr() in C++, so fall back to table-based
+
482 * signedness determination for known types, utilizing templates magic.
+
483 */
+
484#ifdef __cplusplus
+
485
+
486#define __usdt_is_signed(x) (!__usdt_is_arr(x) && __usdt_t<__typeof(x)>::is_signed)
+
487
+
488#include <cstddef>
+
489
+
490template<typename T> struct __usdt_t { static const bool is_signed = false; };
+
491template<typename A> struct __usdt_t<A[]> : public __usdt_t<A *> {};
+
492template<typename A, size_t N> struct __usdt_t<A[N]> : public __usdt_t<A *> {};
+
493
+
494#define __usdt_def_signed(T) \
+
495template<> struct __usdt_t<T> { static const bool is_signed = true; }; \
+
496template<> struct __usdt_t<const T> { static const bool is_signed = true; }; \
+
497template<> struct __usdt_t<volatile T> { static const bool is_signed = true; }; \
+
498template<> struct __usdt_t<const volatile T> { static const bool is_signed = true; }
+
499#define __usdt_maybe_signed(T) \
+
500template<> struct __usdt_t<T> { static const bool is_signed = (T)-1 < (T)1; }; \
+
501template<> struct __usdt_t<const T> { static const bool is_signed = (T)-1 < (T)1; }; \
+
502template<> struct __usdt_t<volatile T> { static const bool is_signed = (T)-1 < (T)1; }; \
+
503template<> struct __usdt_t<const volatile T> { static const bool is_signed = (T)-1 < (T)1; }
+
504
+
505__usdt_def_signed(signed char);
+
506__usdt_def_signed(short);
+
507__usdt_def_signed(int);
+
508__usdt_def_signed(long);
+
509__usdt_def_signed(long long);
+
510__usdt_maybe_signed(char);
+
511__usdt_maybe_signed(wchar_t);
+
512
+
513#else /* !__cplusplus */
+
514
+
515#define __usdt_is_inttype(x) (__builtin_classify_type(x) >= 1 && __builtin_classify_type(x) <= 4)
+
516#define __usdt_inttype(x) __typeof(__builtin_choose_expr(__usdt_is_inttype(x), (x), 0U))
+
517#define __usdt_is_signed(x) ((__usdt_inttype(x))-1 < (__usdt_inttype(x))1)
+
518
+
519#endif /* __cplusplus */
+
520
+
521#define __usdt_asm_op(n, x) \
+
522 [__usdt_asz##n] "n" ((__usdt_is_signed(x) ? (int)-1 : 1) * (int)__usdt_arg_size(x)), \
+
523 [__usdt_aval##n] __usdt_str(USDT_ARG_CONSTRAINT)(x)
+
524
+
525#define __usdt_asm_ops0() [__usdt_dummy] "g" (0)
+
526#define __usdt_asm_ops1(x) __usdt_asm_op(1, x)
+
527#define __usdt_asm_ops2(a,x) __usdt_asm_ops1(a), __usdt_asm_op(2, x)
+
528#define __usdt_asm_ops3(a,b,x) __usdt_asm_ops2(a,b), __usdt_asm_op(3, x)
+
529#define __usdt_asm_ops4(a,b,c,x) __usdt_asm_ops3(a,b,c), __usdt_asm_op(4, x)
+
530#define __usdt_asm_ops5(a,b,c,d,x) __usdt_asm_ops4(a,b,c,d), __usdt_asm_op(5, x)
+
531#define __usdt_asm_ops6(a,b,c,d,e,x) __usdt_asm_ops5(a,b,c,d,e), __usdt_asm_op(6, x)
+
532#define __usdt_asm_ops7(a,b,c,d,e,f,x) __usdt_asm_ops6(a,b,c,d,e,f), __usdt_asm_op(7, x)
+
533#define __usdt_asm_ops8(a,b,c,d,e,f,g,x) __usdt_asm_ops7(a,b,c,d,e,f,g), __usdt_asm_op(8, x)
+
534#define __usdt_asm_ops9(a,b,c,d,e,f,g,h,x) __usdt_asm_ops8(a,b,c,d,e,f,g,h), __usdt_asm_op(9, x)
+
535#define __usdt_asm_ops10(a,b,c,d,e,f,g,h,i,x) __usdt_asm_ops9(a,b,c,d,e,f,g,h,i), __usdt_asm_op(10, x)
+
536#define __usdt_asm_ops11(a,b,c,d,e,f,g,h,i,j,x) __usdt_asm_ops10(a,b,c,d,e,f,g,h,i,j), __usdt_asm_op(11, x)
+
537#define __usdt_asm_ops12(a,b,c,d,e,f,g,h,i,j,k,x) __usdt_asm_ops11(a,b,c,d,e,f,g,h,i,j,k), __usdt_asm_op(12, x)
+
538#define __usdt_asm_ops(...) __usdt_apply(__usdt_asm_ops, __usdt_narg(__VA_ARGS__))(__VA_ARGS__)
+
539
+
540#endif /* __USDT_H */
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2util_8c_source.html b/doc/html/fuse-3_818_81_2lib_2util_8c_source.html new file mode 100644 index 0000000..0023fad --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2util_8c_source.html @@ -0,0 +1,113 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/util.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
util.c
+
+
+
1
+
2#include "fuse_config.h"
+
3
+
4#ifdef HAVE_PTHREAD_SETNAME_NP
+
5#define _GNU_SOURCE
+
6#include <pthread.h>
+
7#endif
+
8
+
9#include <errno.h>
+
10#include <stdlib.h>
+
11#include <errno.h>
+
12
+
13#ifndef FUSE_USE_VERSION
+
14#define FUSE_USE_VERSION (FUSE_MAKE_VERSION(3, 18))
+
15#endif
+
16
+
17#include "util.h"
+
18#include "fuse_log.h"
+
19#include "fuse_lowlevel.h"
+
20#include <stdio.h>
+
21
+
22int libfuse_strtol(const char *str, long *res)
+
23{
+
24 char *endptr;
+
25 int base = 10;
+
26 long val;
+
27
+
28 errno = 0;
+
29
+
30 if (!str)
+
31 return -EINVAL;
+
32
+
33 val = strtol(str, &endptr, base);
+
34
+
35 if (errno)
+
36 return -errno;
+
37
+
38 if (endptr == str || *endptr != '\0')
+
39 return -EINVAL;
+
40
+
41 *res = val;
+
42 return 0;
+
43}
+
44
+
45void fuse_set_thread_name(const char *name)
+
46{
+
47#ifdef HAVE_PTHREAD_SETNAME_NP
+
48 pthread_setname_np(pthread_self(), name);
+
49#else
+
50 (void)name;
+
51#endif
+
52}
+
53
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2lib_2util_8h_source.html b/doc/html/fuse-3_818_81_2lib_2util_8h_source.html new file mode 100644 index 0000000..05df4d2 --- /dev/null +++ b/doc/html/fuse-3_818_81_2lib_2util_8h_source.html @@ -0,0 +1,104 @@ + + + + + + + +libfuse: fuse-3.18.1/lib/util.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
util.h
+
+
+
1#ifndef FUSE_UTIL_H_
+
2#define FUSE_UTIL_H_
+
3
+
4#include <stdint.h>
+
5#include <stdbool.h>
+
6
+
7#define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1))
+
8
+
9#define likely(x) __builtin_expect(!!(x), 1)
+
10#define unlikely(x) __builtin_expect(!!(x), 0)
+
11
+
12struct fuse_conn_info;
+
13
+
14int libfuse_strtol(const char *str, long *res);
+
15void fuse_set_thread_name(const char *name);
+
16
+
20static inline uint32_t fuse_lower_32_bits(uint64_t nr)
+
21{
+
22 return (uint32_t)(nr & 0xffffffff);
+
23}
+
24
+
28static inline uint64_t fuse_higher_32_bits(uint64_t nr)
+
29{
+
30 return nr & ~0xffffffffULL;
+
31}
+
32
+
33#ifndef FUSE_VAR_UNUSED
+
34#define FUSE_VAR_UNUSED __attribute__((__unused__))
+
35#endif
+
36
+
37#define container_of(ptr, type, member) \
+
38 ({ \
+
39 unsigned long __mptr = (unsigned long)(ptr); \
+
40 ((type *)(__mptr - offsetof(type, member))); \
+
41 })
+
42
+
43#if __has_attribute(__fallthrough__)
+
44#define fallthrough __attribute__((__fallthrough__))
+
45#else
+
46#define fallthrough do {} while (0)
+
47#endif
+
48
+
49#endif /* FUSE_UTIL_H_ */
+ +
+ + + + diff --git a/doc/html/fuse-3_818_81_2test_2hello_8c.html b/doc/html/fuse-3_818_81_2test_2hello_8c.html new file mode 100644 index 0000000..71c594b --- /dev/null +++ b/doc/html/fuse-3_818_81_2test_2hello_8c.html @@ -0,0 +1,265 @@ + + + + + + + +libfuse: fuse-3.18.1/test/hello.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using high-level API

+

Compile with:

gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <stddef.h>
+
#include <assert.h>
+
+
/*
+
* Command line options
+
*
+
* We can't set default values for the char* fields here because
+
* fuse_opt_parse would attempt to free() them when the user specifies
+
* different values on the command line.
+
*/
+
static struct options {
+
const char *filename;
+
const char *contents;
+
int show_help;
+
} options;
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--name=%s", filename),
+
OPTION("--contents=%s", contents),
+
OPTION("-h", show_help),
+
OPTION("--help", show_help),
+ +
};
+
+
static void *hello_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->kernel_cache = 1;
+
+
/* Test setting flags the old way */
+ + +
+
return NULL;
+
}
+
+
static int hello_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res = 0;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
} else if (strcmp(path+1, options.filename) == 0) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(options.contents);
+
} else
+
res = -ENOENT;
+
+
return res;
+
}
+
+
static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int hello_open(const char *path, struct fuse_file_info *fi)
+
{
+
if (strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
+
return 0;
+
}
+
+
static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
size_t len;
+
(void) fi;
+
if(strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
len = strlen(options.contents);
+
if (offset < len) {
+
if (offset + size > len)
+
size = len - offset;
+
memcpy(buf, options.contents + offset, size);
+
} else
+
size = 0;
+
+
return size;
+
}
+
+
static const struct fuse_operations hello_oper = {
+
.init = hello_init,
+
.getattr = hello_getattr,
+
.readdir = hello_readdir,
+
.open = hello_open,
+
.read = hello_read,
+
};
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --name=<s> Name of the \"hello\" file\n"
+
" (default: \"hello\")\n"
+
" --contents=<s> Contents \"hello\" file\n"
+
" (default \"Hello, World!\\n\")\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[])
+
{
+
int ret;
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
+
/* Set defaults -- we have to use strdup so that
+
fuse_opt_parse can free the defaults if other
+
values are specified */
+
options.filename = strdup("hello");
+
options.contents = strdup("Hello World!\n");
+
+
/* Parse options */
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
/* When --help is specified, first print our own file-system
+
specific help text, then signal fuse_main to show
+
additional help (by adding `--help` to the options again)
+
without usage: line (by setting argv[0] to the empty
+
string) */
+
if (options.show_help) {
+
show_help(argv[0]);
+
assert(fuse_opt_add_arg(&args, "--help") == 0);
+
args.argv[0][0] = '\0';
+
}
+
+
ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+ +
return ret;
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_ASYNC_READ
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file hello.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_81_2test_2hello_8c_source.html b/doc/html/fuse-3_818_81_2test_2hello_8c_source.html new file mode 100644 index 0000000..ad51069 --- /dev/null +++ b/doc/html/fuse-3_818_81_2test_2hello_8c_source.html @@ -0,0 +1,250 @@ + + + + + + + +libfuse: fuse-3.18.1/test/hello.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello.c
+
+
+Go to the documentation of this file.
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 * Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 *
+
5 * This program can be distributed under the terms of the GNU GPLv2.
+
6 * See the file GPL2.txt.
+
7 */
+
8
+
21#define FUSE_USE_VERSION 31
+
22
+
23#include <fuse.h>
+
24#include <stdio.h>
+
25#include <string.h>
+
26#include <errno.h>
+
27#include <fcntl.h>
+
28#include <stddef.h>
+
29#include <assert.h>
+
30
+
31/*
+
32 * Command line options
+
33 *
+
34 * We can't set default values for the char* fields here because
+
35 * fuse_opt_parse would attempt to free() them when the user specifies
+
36 * different values on the command line.
+
37 */
+
38static struct options {
+
39 const char *filename;
+
40 const char *contents;
+
41 int show_help;
+
42} options;
+
43
+
44#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
45static const struct fuse_opt option_spec[] = {
+
46 OPTION("--name=%s", filename), OPTION("--contents=%s", contents),
+
47 OPTION("-h", show_help), OPTION("--help", show_help), FUSE_OPT_END
+
48};
+
49
+
50static void *hello_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
51{
+
52 (void)conn;
+
53 cfg->kernel_cache = 1;
+
54
+
55 /* Test setting flags the old way */
+ +
57 conn->want &= ~FUSE_CAP_ASYNC_READ;
+
58
+
59 return NULL;
+
60}
+
61
+
62static int hello_getattr(const char *path, struct stat *stbuf,
+
63 struct fuse_file_info *fi)
+
64{
+
65 (void)fi;
+
66 int res = 0;
+
67
+
68 memset(stbuf, 0, sizeof(struct stat));
+
69 if (strcmp(path, "/") == 0) {
+
70 stbuf->st_mode = S_IFDIR | 0755;
+
71 stbuf->st_nlink = 2;
+
72 } else if (strcmp(path + 1, options.filename) == 0) {
+
73 stbuf->st_mode = S_IFREG | 0444;
+
74 stbuf->st_nlink = 1;
+
75 stbuf->st_size = strlen(options.contents);
+
76 } else
+
77 res = -ENOENT;
+
78
+
79 return res;
+
80}
+
81
+
82static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
83 off_t offset, struct fuse_file_info *fi,
+
84 enum fuse_readdir_flags flags)
+
85{
+
86 (void)offset;
+
87 (void)fi;
+
88 (void)flags;
+
89
+
90 if (strcmp(path, "/") != 0)
+
91 return -ENOENT;
+
92
+
93 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
94 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
95 filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
96
+
97 return 0;
+
98}
+
99
+
100static int hello_open(const char *path, struct fuse_file_info *fi)
+
101{
+
102 if (strcmp(path + 1, options.filename) != 0)
+
103 return -ENOENT;
+
104
+
105 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
106 return -EACCES;
+
107
+
108 return 0;
+
109}
+
110
+
111static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
112 struct fuse_file_info *fi)
+
113{
+
114 size_t len;
+
115 (void)fi;
+
116 if (strcmp(path + 1, options.filename) != 0)
+
117 return -ENOENT;
+
118
+
119 len = strlen(options.contents);
+
120 if (offset < len) {
+
121 if (offset + size > len)
+
122 size = len - offset;
+
123 memcpy(buf, options.contents + offset, size);
+
124 } else
+
125 size = 0;
+
126
+
127 return size;
+
128}
+
129
+
130static const struct fuse_operations hello_oper = {
+
131 .init = hello_init,
+
132 .getattr = hello_getattr,
+
133 .readdir = hello_readdir,
+
134 .open = hello_open,
+
135 .read = hello_read,
+
136};
+
137
+
138static void show_help(const char *progname)
+
139{
+
140 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
141 printf("File-system specific options:\n"
+
142 " --name=<s> Name of the \"hello\" file\n"
+
143 " (default: \"hello\")\n"
+
144 " --contents=<s> Contents \"hello\" file\n"
+
145 " (default \"Hello, World!\\n\")\n"
+
146 "\n");
+
147}
+
148
+
149int main(int argc, char *argv[])
+
150{
+
151 int ret;
+
152 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
153
+
154 /* Set defaults -- we have to use strdup so that
+
155 * fuse_opt_parse can free the defaults if other
+
156 * values are specified
+
157 */
+
158 options.filename = strdup("hello");
+
159 options.contents = strdup("Hello World!\n");
+
160
+
161 /* Parse options */
+
162 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
163 return 1;
+
164
+
165 /* When --help is specified, first print our own file-system
+
166 * specific help text, then signal fuse_main to show
+
167 * additional help (by adding `--help` to the options again)
+
168 * without usage: line (by setting argv[0] to the empty
+
169 * string)
+
170 */
+
171 if (options.show_help) {
+
172 show_help(argv[0]);
+
173 assert(fuse_opt_add_arg(&args, "--help") == 0);
+
174 args.argv[0][0] = '\0';
+
175 }
+
176
+
177 ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+
178 fuse_opt_free_args(&args);
+
179 return ret;
+
180}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_CAP_ASYNC_READ
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2test_2readdir__inode_8c_source.html b/doc/html/fuse-3_818_81_2test_2readdir__inode_8c_source.html new file mode 100644 index 0000000..4b5f9bc --- /dev/null +++ b/doc/html/fuse-3_818_81_2test_2readdir__inode_8c_source.html @@ -0,0 +1,117 @@ + + + + + + + +libfuse: fuse-3.18.1/test/readdir_inode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
readdir_inode.c
+
+
+
1/*
+
2 * Prints each directory entry, its inode and d_type as returned by 'readdir'.
+
3 * Skips '.' and '..' because readdir is not required to return them and
+
4 * some of our examples don't. However if they are returned, their d_type
+
5 * should be valid.
+
6 */
+
7
+
8#include <stdio.h>
+
9#include <string.h>
+
10#include <sys/types.h>
+
11#include <dirent.h>
+
12#include <errno.h>
+
13
+
14int main(int argc, char* argv[])
+
15{
+
16 DIR* dirp;
+
17 struct dirent* dent;
+
18
+
19 if (argc != 2) {
+
20 fprintf(stderr, "Usage: readdir_inode dir\n");
+
21 return 1;
+
22 }
+
23
+
24 dirp = opendir(argv[1]);
+
25 if (dirp == NULL) {
+
26 perror("failed to open directory");
+
27 return 2;
+
28 }
+
29
+
30 errno = 0;
+
31 dent = readdir(dirp);
+
32 while (dent != NULL) {
+
33 if (strcmp(dent->d_name, ".") != 0 && strcmp(dent->d_name, "..") != 0) {
+
34 printf("%llu %d %s\n", (unsigned long long)dent->d_ino,
+
35 (int)dent->d_type, dent->d_name);
+
36 if ((long long)dent->d_ino < 0)
+
37 fprintf(stderr,"%s : bad d_ino %llu\n",
+
38 dent->d_name, (unsigned long long)dent->d_ino);
+
39 if ((dent->d_type < 1) || (dent->d_type > 15))
+
40 fprintf(stderr,"%s : bad d_type %d\n",
+
41 dent->d_name, (int)dent->d_type);
+
42 } else {
+
43 if (dent->d_type != DT_DIR)
+
44 fprintf(stderr,"%s : bad d_type %d\n",
+
45 dent->d_name, (int)dent->d_type);
+
46 }
+
47 dent = readdir(dirp);
+
48 }
+
49 if (errno != 0) {
+
50 perror("failed to read directory entry");
+
51 return 3;
+
52 }
+
53
+
54 closedir(dirp);
+
55
+
56 return 0;
+
57}
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2test_2release__unlink__race_8c_source.html b/doc/html/fuse-3_818_81_2test_2release__unlink__race_8c_source.html new file mode 100644 index 0000000..82a7e7a --- /dev/null +++ b/doc/html/fuse-3_818_81_2test_2release__unlink__race_8c_source.html @@ -0,0 +1,183 @@ + + + + + + + +libfuse: fuse-3.18.1/test/release_unlink_race.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
release_unlink_race.c
+
+
+
1/*
+
2 This program can be distributed under the terms of the GNU GPLv2.
+
3 See the file GPL2.txt.
+
4*/
+
5
+
6#define FUSE_USE_VERSION 31
+
7
+
8#define _GNU_SOURCE
+
9
+
10#include <fuse.h>
+
11
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <unistd.h>
+
15#include <errno.h>
+
16
+
17static void *xmp_init(struct fuse_conn_info *conn,
+
18 struct fuse_config *cfg)
+
19{
+
20 (void) conn;
+
21
+
22 cfg->use_ino = 1;
+
23 cfg->nullpath_ok = 1;
+
24 cfg->entry_timeout = 0;
+
25 cfg->attr_timeout = 0;
+
26 cfg->negative_timeout = 0;
+
27
+
28 return NULL;
+
29}
+
30
+
31static int xmp_getattr(const char *path, struct stat *stbuf,
+
32 struct fuse_file_info *fi)
+
33{
+
34 int res;
+
35
+
36 (void) path;
+
37
+
38 if(fi)
+
39 res = fstat(fi->fh, stbuf);
+
40 else
+
41 res = lstat(path, stbuf);
+
42 if (res == -1)
+
43 return -errno;
+
44
+
45 return 0;
+
46}
+
47
+
48static int xmp_unlink(const char *path)
+
49{
+
50 int res;
+
51
+
52 res = unlink(path);
+
53 if (res == -1)
+
54 return -errno;
+
55
+
56 return 0;
+
57}
+
58
+
59static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
60{
+
61 int res;
+
62
+
63 if (flags)
+
64 return -EINVAL;
+
65
+
66 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
+
67
+
68 res = rename(from, to);
+
69 if (res == -1)
+
70 return -errno;
+
71
+
72 return 0;
+
73}
+
74
+
75static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
76{
+
77 int fd;
+
78
+
79 fd = open(path, fi->flags, mode);
+
80 if (fd == -1)
+
81 return -errno;
+
82
+
83 fi->fh = fd;
+
84 return 0;
+
85}
+
86
+
87static int xmp_release(const char *path, struct fuse_file_info *fi)
+
88{
+
89 (void) path;
+
90
+
91 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
+
92
+
93 close(fi->fh);
+
94
+
95 return 0;
+
96}
+
97
+
98static const struct fuse_operations xmp_oper = {
+
99 .init = xmp_init,
+
100 .getattr = xmp_getattr,
+
101 .unlink = xmp_unlink,
+
102 .rename = xmp_rename,
+
103 .create = xmp_create,
+
104 .release = xmp_release,
+
105};
+
106
+
107int main(int argc, char *argv[])
+
108{
+
109 umask(0);
+
110 return fuse_main(argc, argv, &xmp_oper, NULL);
+
111}
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2test_2stracedecode_8c_source.html b/doc/html/fuse-3_818_81_2test_2stracedecode_8c_source.html new file mode 100644 index 0000000..3119e11 --- /dev/null +++ b/doc/html/fuse-3_818_81_2test_2stracedecode_8c_source.html @@ -0,0 +1,259 @@ + + + + + + + +libfuse: fuse-3.18.1/test/stracedecode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
stracedecode.c
+
+
+
1#include <stdio.h>
+
2#include <string.h>
+
3#include "fuse_kernel.h"
+
4
+
5static struct {
+
6 const char *name;
+
7} fuse_ll_ops[] = {
+
8 [FUSE_LOOKUP] = { "LOOKUP" },
+
9 [FUSE_FORGET] = { "FORGET" },
+
10 [FUSE_GETATTR] = { "GETATTR" },
+
11 [FUSE_SETATTR] = { "SETATTR" },
+
12 [FUSE_READLINK] = { "READLINK" },
+
13 [FUSE_SYMLINK] = { "SYMLINK" },
+
14 [FUSE_MKNOD] = { "MKNOD" },
+
15 [FUSE_MKDIR] = { "MKDIR" },
+
16 [FUSE_UNLINK] = { "UNLINK" },
+
17 [FUSE_RMDIR] = { "RMDIR" },
+
18 [FUSE_RENAME] = { "RENAME" },
+
19 [FUSE_LINK] = { "LINK" },
+
20 [FUSE_OPEN] = { "OPEN" },
+
21 [FUSE_READ] = { "READ" },
+
22 [FUSE_WRITE] = { "WRITE" },
+
23 [FUSE_STATFS] = { "STATFS" },
+
24 [FUSE_RELEASE] = { "RELEASE" },
+
25 [FUSE_FSYNC] = { "FSYNC" },
+
26 [FUSE_SETXATTR] = { "SETXATTR" },
+
27 [FUSE_GETXATTR] = { "GETXATTR" },
+
28 [FUSE_LISTXATTR] = { "LISTXATTR" },
+
29 [FUSE_REMOVEXATTR] = { "REMOVEXATTR" },
+
30 [FUSE_FLUSH] = { "FLUSH" },
+
31 [FUSE_INIT] = { "INIT" },
+
32 [FUSE_OPENDIR] = { "OPENDIR" },
+
33 [FUSE_READDIR] = { "READDIR" },
+
34 [FUSE_RELEASEDIR] = { "RELEASEDIR" },
+
35 [FUSE_FSYNCDIR] = { "FSYNCDIR" },
+
36 [FUSE_GETLK] = { "GETLK" },
+
37 [FUSE_SETLK] = { "SETLK" },
+
38 [FUSE_SETLKW] = { "SETLKW" },
+
39 [FUSE_ACCESS] = { "ACCESS" },
+
40 [FUSE_CREATE] = { "CREATE" },
+
41 [FUSE_TMPFILE] = { "TMPFILE" },
+
42 [FUSE_INTERRUPT] = { "INTERRUPT" },
+
43 [FUSE_BMAP] = { "BMAP" },
+
44 [FUSE_DESTROY] = { "DESTROY" },
+
45 [FUSE_READDIRPLUS] = { "READDIRPLUS" },
+
46};
+
47
+
48#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
+
49
+
50static const char *opname(enum fuse_opcode opcode)
+
51{
+
52 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
+
53 return "???";
+
54 else
+
55 return fuse_ll_ops[opcode].name;
+
56}
+
57
+
58
+
59static void process_buf(int dir, char *buf, int len)
+
60{
+
61 static unsigned long long prevuniq = -1;
+
62 static int prevopcode;
+
63
+
64 if (!dir) {
+
65 struct fuse_in_header *in = (struct fuse_in_header *) buf;
+
66 buf += sizeof(struct fuse_in_header);
+
67
+
68 printf("unique: %llu, opcode: %s (%i), nodeid: %lu, len: %i, insize: %i\n",
+
69 (unsigned long long) in->unique,
+
70 opname((enum fuse_opcode) in->opcode), in->opcode,
+
71 (unsigned long) in->nodeid, in->len, len);
+
72
+
73 switch (in->opcode) {
+
74 case FUSE_READ: {
+
75 struct fuse_read_in *arg = (struct fuse_read_in *) buf;
+
76 printf("-READ fh:%llu off:%llu siz:%u rfl:%u own:%llu fl:%u\n",
+
77 arg->fh, arg->offset, arg->size, arg->read_flags,
+
78 arg->lock_owner, arg->flags);
+
79 break;
+
80 }
+
81 case FUSE_WRITE: {
+
82 struct fuse_write_in *arg = (struct fuse_write_in *) buf;
+
83 printf("-WRITE fh:%llu off:%llu siz:%u wfl:%u own:%llu fl:%u\n",
+
84 arg->fh, arg->offset, arg->size, arg->write_flags,
+
85 arg->lock_owner, arg->flags);
+
86 break;
+
87 }
+
88 }
+
89 prevuniq = in->unique;
+
90 prevopcode = in->opcode;
+
91 } else {
+
92 struct fuse_out_header *out = (struct fuse_out_header *) buf;
+
93 buf += sizeof(struct fuse_out_header);
+
94
+
95 printf(" unique: %llu, error: %i (%s), len: %i, outsize: %i\n",
+
96 (unsigned long long) out->unique, out->error,
+
97 strerror(-out->error), out->len, len);
+
98
+
99 if (out->unique == prevuniq) {
+
100 switch (prevopcode) {
+
101 case FUSE_GETATTR: {
+
102 struct fuse_attr_out *arg = (struct fuse_attr_out *) buf;
+
103 printf("+ATTR v:%llu.%09u i:%llu s:%llu b:%llu\n",
+
104 arg->attr_valid, arg->attr_valid_nsec,
+
105 arg->attr.ino, arg->attr.size, arg->attr.blocks);
+
106 break;
+
107 }
+
108 case FUSE_LOOKUP: {
+
109 struct fuse_entry_out *arg = (struct fuse_entry_out *) buf;
+
110 printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n",
+
111 arg->nodeid, arg->attr_valid, arg->attr_valid_nsec,
+
112 arg->attr.ino, arg->attr.size, arg->attr.blocks);
+
113 break;
+
114 }
+
115 }
+
116 }
+
117 }
+
118
+
119}
+
120
+
121int main(void)
+
122{
+
123 FILE *in = stdin;
+
124 while (1) {
+
125 int dir;
+
126 int res;
+
127 char buf[1048576];
+
128 unsigned len = 0;
+
129
+
130 memset(buf, 0, sizeof(buf));
+
131 while (1) {
+
132 char str[32];
+
133
+
134 res = fscanf(in, "%30s", str);
+
135 if (res != 1 && feof(in))
+
136 return 0;
+
137
+
138 if (res == 0)
+
139 continue;
+
140
+
141 if (strncmp(str, "read(", 5) == 0) {
+
142 dir = 0;
+
143 break;
+
144 } else if (strncmp(str, "writev(", 7) == 0) {
+
145 dir = 1;
+
146 break;
+
147 }
+
148 }
+
149
+
150 while (1) {
+
151 int c = getc(in);
+
152 if (c == '"') {
+
153 while (1) {
+
154 int val;
+
155
+
156 c = getc(in);
+
157 if (c == EOF) {
+
158 fprintf(stderr, "eof in string\n");
+
159 break;
+
160 }
+
161 if (c == '\n') {
+
162 fprintf(stderr, "eol in string\n");
+
163 break;
+
164 }
+
165 if (c == '"')
+
166 break;
+
167 if (c != '\\') {
+
168 val = c;
+
169 } else {
+
170 c = getc(in);
+
171 switch (c) {
+
172 case 'n': val = '\n'; break;
+
173 case 'r': val = '\r'; break;
+
174 case 't': val = '\t'; break;
+
175 case '"': val = '"'; break;
+
176 case '\\': val = '\\'; break;
+
177 case 'x':
+
178 res = scanf("%x", &val);
+
179 if (res != 1) {
+
180 fprintf(stderr, "parse error\n");
+
181 continue;
+
182 }
+
183 break;
+
184 default:
+
185 fprintf(stderr, "unknown sequence: '\\%c'\n", c);
+
186 continue;
+
187 }
+
188 }
+
189 buf[len++] = val;
+
190 }
+
191 }
+
192 if (c == '\n')
+
193 break;
+
194 }
+
195 process_buf(dir, buf, len);
+
196 memset(buf, 0, len);
+
197 len = 0;
+
198 }
+
199}
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2test_2test__abi_8c_source.html b/doc/html/fuse-3_818_81_2test_2test__abi_8c_source.html new file mode 100644 index 0000000..65e4fe3 --- /dev/null +++ b/doc/html/fuse-3_818_81_2test_2test__abi_8c_source.html @@ -0,0 +1,80 @@ + + + + + + + +libfuse: fuse-3.18.1/test/test_abi.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_abi.c
+
+
+
1#define FUSE_USE_VERSION 30
+
2
+
3#include "fuse.h"
+
4
+
5#include <stdio.h>
+
6#include <stdlib.h>
+
7
+
8int main(void)
+
9{
+
10 if (sizeof(struct fuse_file_info) != 64) {
+
11 fprintf(stderr, "struct fuse_file_info size mismatch\n");
+
12 exit(1);
+
13 }
+
14 if (sizeof(struct fuse_conn_info) != 128) {
+
15 fprintf(stderr, "struct fuse_conn_info size mismatch\n");
+
16 exit(1);
+
17 }
+
18}
+ + +
+ + + + diff --git a/doc/html/fuse-3_818_81_2test_2test__setattr_8c_source.html b/doc/html/fuse-3_818_81_2test_2test__setattr_8c_source.html new file mode 100644 index 0000000..d0e146e --- /dev/null +++ b/doc/html/fuse-3_818_81_2test_2test__setattr_8c_source.html @@ -0,0 +1,272 @@ + + + + + + + +libfuse: fuse-3.18.1/test/test_setattr.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_setattr.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
9
+
10#define FUSE_USE_VERSION 30
+
11
+
12/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
+
13#include <fuse.h>
+
14
+
15#include <fuse_config.h>
+
16#include <fuse_lowlevel.h>
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <errno.h>
+
21#include <fcntl.h>
+
22#include <assert.h>
+
23#include <stddef.h>
+
24#include <unistd.h>
+
25#include <pthread.h>
+
26
+
27#ifndef __linux__
+
28#include <limits.h>
+
29#else
+
30#include <linux/limits.h>
+
31#endif
+
32
+
33#define FILE_INO 2
+
34#define FILE_NAME "truncate_me"
+
35
+
36static int got_fh;
+
37static mode_t file_mode = S_IFREG | 0644;
+
38
+
39static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
40 stbuf->st_ino = ino;
+
41 if (ino == FUSE_ROOT_ID) {
+
42 stbuf->st_mode = S_IFDIR | 0755;
+
43 stbuf->st_nlink = 1;
+
44 }
+
45
+
46 else if (ino == FILE_INO) {
+
47 stbuf->st_mode = file_mode;
+
48 stbuf->st_nlink = 1;
+
49 stbuf->st_size = 0;
+
50 }
+
51
+
52 else
+
53 return -1;
+
54
+
55 return 0;
+
56}
+
57
+
58static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
59 const char *name) {
+
60 struct fuse_entry_param e;
+
61 memset(&e, 0, sizeof(e));
+
62
+
63 if (parent != FUSE_ROOT_ID)
+
64 goto err_out;
+
65 else if (strcmp(name, FILE_NAME) == 0)
+
66 e.ino = FILE_INO;
+
67 else
+
68 goto err_out;
+
69
+
70 if (tfs_stat(e.ino, &e.attr) != 0)
+
71 goto err_out;
+
72 fuse_reply_entry(req, &e);
+
73 return;
+
74
+
75err_out:
+
76 fuse_reply_err(req, ENOENT);
+
77}
+
78
+
79static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
80 struct fuse_file_info *fi) {
+
81 struct stat stbuf;
+
82
+
83 (void) fi;
+
84
+
85 memset(&stbuf, 0, sizeof(stbuf));
+
86 if (tfs_stat(ino, &stbuf) != 0)
+
87 fuse_reply_err(req, ENOENT);
+
88 else
+
89 fuse_reply_attr(req, &stbuf, 5);
+
90}
+
91
+
92static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
93 struct fuse_file_info *fi) {
+
94 if (ino == FUSE_ROOT_ID)
+
95 fuse_reply_err(req, EISDIR);
+
96 else {
+
97 assert(ino == FILE_INO);
+
98 fi->fh = FILE_INO;
+
99 fuse_reply_open(req, fi);
+
100 }
+
101}
+
102
+
103static void tfs_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
104 int to_set, struct fuse_file_info *fi) {
+
105 if(ino != FILE_INO ||
+
106 !(to_set & FUSE_SET_ATTR_MODE)) {
+
107 fuse_reply_err(req, EINVAL);
+
108 return;
+
109 }
+
110
+
111 if(fi == NULL)
+
112 fprintf(stderr, "setattr with fi == NULL\n");
+
113 else if (fi->fh != FILE_INO)
+
114 fprintf(stderr, "setattr with wrong fi->fh\n");
+
115 else {
+
116 fprintf(stderr, "setattr ok\n");
+
117 got_fh = 1;
+
118 file_mode = attr->st_mode;
+
119 }
+
120
+
121 tfs_getattr(req, ino, fi);
+
122}
+
123
+
124static struct fuse_lowlevel_ops tfs_oper = {
+
125 .lookup = tfs_lookup,
+
126 .getattr = tfs_getattr,
+
127 .open = tfs_open,
+
128 .setattr = tfs_setattr,
+
129};
+
130
+
131static void* run_fs(void *data) {
+
132 struct fuse_session *se = (struct fuse_session*) data;
+
133 assert(fuse_session_loop(se) == 0);
+
134 return NULL;
+
135}
+
136
+
137static void test_fs(char *mountpoint) {
+
138 char fname[PATH_MAX];
+
139 int fd;
+
140
+
141 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
+
142 mountpoint) > 0);
+
143 fd = open(fname, O_WRONLY);
+
144 if (fd == -1) {
+
145 perror(fname);
+
146 assert(0);
+
147 }
+
148
+
149 assert(fchmod(fd, 0600) == 0);
+
150 close(fd);
+
151}
+
152
+
153int main(int argc, char *argv[]) {
+
154 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
155 struct fuse_session *se;
+
156 struct fuse_cmdline_opts fuse_opts;
+
157 pthread_t fs_thread;
+
158
+
159 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
+
160#ifndef __FreeBSD__
+
161 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
+
162#endif
+
163 se = fuse_session_new(&args, &tfs_oper,
+
164 sizeof(tfs_oper), NULL);
+
165 assert (se != NULL);
+
166 assert(fuse_set_signal_handlers(se) == 0);
+
167 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
+
168
+
169 /* Start file-system thread */
+
170 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
+
171
+
172 /* Do test */
+
173 test_fs(fuse_opts.mountpoint);
+
174
+
175 /* Stop file system */
+
176 assert(pthread_cancel(fs_thread) == 0);
+
177
+ +
179 assert(got_fh == 1);
+ + +
182
+
183 printf("Test completed successfully.\n");
+
184 return 0;
+
185}
+
186
+
187
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
uint64_t fuse_ino_t
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
+
fuse_ino_t ino
+ + + +
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2test_2test__signals_8c_source.html b/doc/html/fuse-3_818_81_2test_2test__signals_8c_source.html new file mode 100644 index 0000000..ab8a89c --- /dev/null +++ b/doc/html/fuse-3_818_81_2test_2test__signals_8c_source.html @@ -0,0 +1,279 @@ + + + + + + + +libfuse: fuse-3.18.1/test/test_signals.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_signals.c
+
+
+
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 * Copyright (C) 2025 Bernd Schubert <bernd@bsbernd.com>
+
4 *
+
5 * Test for signal handling in libfuse.
+
6 *
+
7 * This program can be distributed under the terms of the GNU LGPLv2.
+
8 * See the file GPL2.txt
+
9 */
+
10
+
11#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17)
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_lowlevel.h"
+
15#include "fuse_i.h"
+
16
+
17#include <pthread.h>
+
18#include <stdatomic.h>
+
19#include <stdio.h>
+
20#include <stdlib.h>
+
21#include <string.h>
+
22#include <unistd.h>
+
23#include <signal.h>
+
24#include <errno.h>
+
25#include <sys/types.h>
+
26#include <sys/wait.h>
+
27
+
28static void test_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
29{
+
30 (void)parent;
+
31 (void)name;
+
32 /* Simulate slow lookup to test signal interruption */
+
33 sleep(2);
+
34 fuse_reply_err(req, ENOENT);
+
35}
+
36
+
37static void test_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
38 struct fuse_file_info *fi)
+
39{
+
40 (void)ino;
+
41 (void)fi;
+
42 /* Simulate slow getattr to test signal interruption */
+
43 sleep(2);
+
44 fuse_reply_err(req, ENOENT);
+
45}
+
46
+
47static const struct fuse_lowlevel_ops test_ll_ops = {
+
48 .lookup = test_ll_lookup,
+
49 .getattr = test_ll_getattr,
+
50};
+
51
+
52static void *signal_sender_thread(void *arg)
+
53{
+
54 (void)arg;
+
55
+
56 usleep(2 * 1000 * 1000);
+
57
+
58 /* Send SIGTERM to the process */
+
59 kill(getpid(), SIGTERM);
+
60 return NULL;
+
61}
+
62
+
63static void fork_child(void)
+
64{
+
65 struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+
66 struct fuse_session *se;
+
67 struct fuse_loop_config *loop_config;
+
68 pthread_t sig_thread;
+
69 char *mountpoint = NULL;
+
70 int ret = -1;
+
71
+
72 /* Add the program name to arg[0] */
+
73 if (fuse_opt_add_arg(&args, "test_signals")) {
+
74 fprintf(stderr, "Failed to add argument\n");
+
75 goto out_free_mountpoint;
+
76 }
+
77
+
78 /* Add debug flag to see more output */
+
79 fuse_opt_add_arg(&args, "-d");
+
80
+
81 /* Create temporary mount point */
+
82 mountpoint = strdup("/tmp/fuse_test_XXXXXX");
+
83 if (!mountpoint || !mkdtemp(mountpoint)) {
+
84 fprintf(stderr, "Failed to create temp dir\n");
+
85 goto out_free_args;
+
86 }
+
87
+
88 /* Create session */
+
89 se = fuse_session_new(&args, &test_ll_ops, sizeof(test_ll_ops), NULL);
+
90 if (!se) {
+
91 fprintf(stderr, "Failed to create FUSE session\n");
+
92 goto out_free_mountpoint;
+
93 }
+
94
+
95 /* Mount filesystem */
+
96 if (fuse_session_mount(se, mountpoint)) {
+
97 fprintf(stderr, "Failed to mount FUSE filesystem\n");
+
98 goto out_destroy_session;
+
99 }
+
100
+
101 /* Create loop config */
+
102 loop_config = fuse_loop_cfg_create();
+
103 if (!loop_config) {
+
104 fprintf(stderr, "Failed to create loop config\n");
+
105 goto out_unmount;
+
106 }
+
107 fuse_loop_cfg_set_clone_fd(loop_config, 0);
+
108 fuse_loop_cfg_set_max_threads(loop_config, 2);
+
109
+
110 /* Set up signal handlers */
+
111 if (fuse_set_signal_handlers(se)) {
+
112 fprintf(stderr, "Failed to set up signal handlers\n");
+
113 goto out_destroy_config;
+
114 }
+
115
+
116 /* Create thread that will send signals */
+
117 if (pthread_create(&sig_thread, NULL, signal_sender_thread, NULL)) {
+
118 fprintf(stderr, "Failed to create signal sender thread\n");
+
119 goto out_remove_handlers;
+
120 }
+
121
+
122 /* Enter FUSE loop */
+
123 ret = fuse_session_loop_mt_312(se, loop_config);
+
124
+
125 printf("Debug: fuse_session_loop_mt_312 returned %d\n", ret);
+
126 printf("Debug: session exited state: %d\n", fuse_session_exited(se));
+
127 printf("Debug: session status: %d\n", se->error);
+
128
+
129 /* Check exit status before cleanup */
+
130 int clean_exit = (fuse_session_exited(se) && se->error == SIGTERM);
+
131
+
132 /* Clean up */
+
133 pthread_join(sig_thread, NULL);
+ + + +
137 fuse_loop_cfg_destroy(loop_config);
+
138 rmdir(mountpoint);
+
139 free(mountpoint);
+
140 fuse_opt_free_args(&args);
+
141
+
142 /* Use saved exit status */
+
143 if (clean_exit) {
+
144 printf("Debug: Clean shutdown via SIGTERM\n");
+
145 exit(0);
+
146 }
+
147 printf("Debug: Exiting with status %d\n", ret != 0);
+
148 exit(ret != 0);
+
149
+
150out_remove_handlers:
+ +
152out_destroy_config:
+
153 fuse_loop_cfg_destroy(loop_config);
+
154out_unmount:
+ +
156out_destroy_session:
+ +
158out_free_mountpoint:
+
159 rmdir(mountpoint);
+
160 free(mountpoint);
+
161out_free_args:
+
162 fuse_opt_free_args(&args);
+
163 exit(1);
+
164}
+
165
+
166static void run_test_in_child(void)
+
167{
+
168 pid_t child;
+
169 int status;
+
170
+
171 child = fork();
+
172 if (child == -1) {
+
173 perror("fork");
+
174 exit(1);
+
175 }
+
176
+
177 if (child == 0)
+
178 fork_child();
+
179
+
180 /* In parent process */
+
181 if (waitpid(child, &status, 0) == -1) {
+
182 perror("waitpid");
+
183 exit(1);
+
184 }
+
185
+
186 /* Check if child exited due to SIGTERM - this is expected */
+
187 if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) {
+
188 printf("Child process terminated by SIGTERM as expected\n");
+
189 exit(0);
+
190 }
+
191
+
192 /* For any other type of exit, maintain existing behavior */
+
193 exit(WIFEXITED(status) ? WEXITSTATUS(status) : 1);
+
194}
+
195
+
196int main(void)
+
197{
+
198 printf("Testing SIGTERM handling in libfuse\n");
+
199 run_test_in_child();
+
200 printf("SIGTERM handling test passed\n");
+
201 return 0;
+
202}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_exited(struct fuse_session *se)
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
uint64_t fuse_ino_t
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + + + +
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2test_2test__syscalls_8c_source.html b/doc/html/fuse-3_818_81_2test_2test__syscalls_8c_source.html new file mode 100644 index 0000000..87962bb --- /dev/null +++ b/doc/html/fuse-3_818_81_2test_2test__syscalls_8c_source.html @@ -0,0 +1,2307 @@ + + + + + + + +libfuse: fuse-3.18.1/test/test_syscalls.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_syscalls.c
+
+
+
1#define _GNU_SOURCE
+
2#include "fuse_config.h"
+
3
+
4#include <stdio.h>
+
5#include <stdlib.h>
+
6#include <stdarg.h>
+
7#include <string.h>
+
8#include <unistd.h>
+
9#include <fcntl.h>
+
10#include <dirent.h>
+
11#include <utime.h>
+
12#include <errno.h>
+
13#include <assert.h>
+
14#include <time.h>
+
15#include <sys/socket.h>
+
16#include <sys/types.h>
+
17#include <sys/stat.h>
+
18#include <sys/un.h>
+
19
+
20#ifndef ALLPERMS
+
21# define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)/* 07777 */
+
22#endif
+
23
+
24
+
25static const char *basepath;
+
26static const char *basepath_r;
+
27static char testfile[1024];
+
28static char testfile2[1024];
+
29static char testdir[1024];
+
30static char testdir2[1024];
+
31static char testsock[1024];
+
32static char subfile[1280];
+
33
+
34static char testfile_r[1024];
+
35static char testfile2_r[1024];
+
36static char testdir_r[1024];
+
37static char testdir2_r[1024];
+
38static char subfile_r[1280];
+
39
+
40static char testname[256];
+
41static char testdata[] = "abcdefghijklmnopqrstuvwxyz";
+
42static char testdata2[] = "1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./";
+
43static const char *testdir_files[] = { "f1", "f2", NULL};
+
44static long seekdir_offsets[4];
+
45static char zerodata[4096];
+
46static int testdatalen = sizeof(testdata) - 1;
+
47static int testdata2len = sizeof(testdata2) - 1;
+
48static unsigned int testnum = 0;
+
49static unsigned int select_test = 0;
+
50static unsigned int skip_test = 0;
+
51static unsigned int unlinked_test = 0;
+
52
+
53#define MAX_ENTRIES 1024
+
54#define MAX_TESTS 100
+
55
+
56static struct test {
+
57 int fd;
+
58 struct stat stat;
+
59} tests[MAX_TESTS];
+
60
+
61static void test_perror(const char *func, const char *msg)
+
62{
+
63 fprintf(stderr, "%s %s() - %s: %s\n", testname, func, msg,
+
64 strerror(errno));
+
65}
+
66
+
67static void test_error(const char *func, const char *msg, ...)
+
68 __attribute__ ((format (printf, 2, 3)));
+
69
+
70static void __start_test(const char *fmt, ...)
+
71 __attribute__ ((format (printf, 1, 2)));
+
72
+
73static void test_error(const char *func, const char *msg, ...)
+
74{
+
75 va_list ap;
+
76 fprintf(stderr, "%s %s() - ", testname, func);
+
77 va_start(ap, msg);
+
78 vfprintf(stderr, msg, ap);
+
79 va_end(ap);
+
80 fprintf(stderr, "\n");
+
81}
+
82
+
83static int is_dot_or_dotdot(const char *name) {
+
84 return name[0] == '.' &&
+
85 (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
+
86}
+
87
+
88static void success(void)
+
89{
+
90 fprintf(stderr, "%s OK\n", testname);
+
91}
+
92
+
93#define this_test (&tests[testnum-1])
+
94#define next_test (&tests[testnum])
+
95
+
96static void __start_test(const char *fmt, ...)
+
97{
+
98 unsigned int n;
+
99 va_list ap;
+
100 n = sprintf(testname, "%3i [", testnum);
+
101 va_start(ap, fmt);
+
102 n += vsprintf(testname + n, fmt, ap);
+
103 va_end(ap);
+
104 sprintf(testname + n, "]");
+
105 // Use dedicated testfile per test
+
106 sprintf(testfile, "%s/testfile.%d", basepath, testnum);
+
107 sprintf(testfile_r, "%s/testfile.%d", basepath_r, testnum);
+
108 if (testnum > MAX_TESTS) {
+
109 fprintf(stderr, "%s - too many tests\n", testname);
+
110 exit(1);
+
111 }
+
112 this_test->fd = -1;
+
113}
+
114
+
115#define start_test(msg, args...) { \
+
116 testnum++; \
+
117 if ((select_test && testnum != select_test) || \
+
118 (testnum == skip_test)) { \
+
119 return 0; \
+
120 } \
+
121 __start_test(msg, ##args); \
+
122}
+
123
+
124#define PERROR(msg) test_perror(__FUNCTION__, msg)
+
125#define ERROR(msg, args...) test_error(__FUNCTION__, msg, ##args)
+
126
+
127#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
128
+
129static int st_check_size(struct stat *st, int len)
+
130{
+
131 if (st->st_size != len) {
+
132 ERROR("length %u instead of %u", (int) st->st_size,
+
133 (int) len);
+
134 return -1;
+
135 }
+
136 return 0;
+
137}
+
138
+
139static int check_size(const char *path, int len)
+
140{
+
141 struct stat stbuf;
+
142 int res = stat(path, &stbuf);
+
143 if (res == -1) {
+
144 PERROR("stat");
+
145 return -1;
+
146 }
+
147 return st_check_size(&stbuf, len);
+
148}
+
149
+
150static int check_testfile_size(const char *path, int len)
+
151{
+
152 this_test->stat.st_size = len;
+
153 return check_size(path, len);
+
154}
+
155
+
156static int st_check_type(struct stat *st, mode_t type)
+
157{
+
158 if ((st->st_mode & S_IFMT) != type) {
+
159 ERROR("type 0%o instead of 0%o", st->st_mode & S_IFMT, type);
+
160 return -1;
+
161 }
+
162 return 0;
+
163}
+
164
+
165static int check_type(const char *path, mode_t type)
+
166{
+
167 struct stat stbuf;
+
168 int res = lstat(path, &stbuf);
+
169 if (res == -1) {
+
170 PERROR("lstat");
+
171 return -1;
+
172 }
+
173 return st_check_type(&stbuf, type);
+
174}
+
175
+
176static int st_check_mode(struct stat *st, mode_t mode)
+
177{
+
178 if ((st->st_mode & ALLPERMS) != mode) {
+
179 ERROR("mode 0%o instead of 0%o", st->st_mode & ALLPERMS,
+
180 mode);
+
181 return -1;
+
182 }
+
183 return 0;
+
184}
+
185
+
186static int check_mode(const char *path, mode_t mode)
+
187{
+
188 struct stat stbuf;
+
189 int res = lstat(path, &stbuf);
+
190 if (res == -1) {
+
191 PERROR("lstat");
+
192 return -1;
+
193 }
+
194 return st_check_mode(&stbuf, mode);
+
195}
+
196
+
197static int check_testfile_mode(const char *path, mode_t mode)
+
198{
+
199 this_test->stat.st_mode &= ~ALLPERMS;
+
200 this_test->stat.st_mode |= mode;
+
201 return check_mode(path, mode);
+
202}
+
203
+
204static int check_times(const char *path, time_t atime, time_t mtime)
+
205{
+
206 int err = 0;
+
207 struct stat stbuf;
+
208 int res = lstat(path, &stbuf);
+
209 if (res == -1) {
+
210 PERROR("lstat");
+
211 return -1;
+
212 }
+
213 if (stbuf.st_atime != atime) {
+
214 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
+
215 err--;
+
216 }
+
217 if (stbuf.st_mtime != mtime) {
+
218 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
+
219 err--;
+
220 }
+
221 if (err)
+
222 return -1;
+
223
+
224 return 0;
+
225}
+
226
+
227#if 0
+
228static int fcheck_times(int fd, time_t atime, time_t mtime)
+
229{
+
230 int err = 0;
+
231 struct stat stbuf;
+
232 int res = fstat(fd, &stbuf);
+
233 if (res == -1) {
+
234 PERROR("fstat");
+
235 return -1;
+
236 }
+
237 if (stbuf.st_atime != atime) {
+
238 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
+
239 err--;
+
240 }
+
241 if (stbuf.st_mtime != mtime) {
+
242 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
+
243 err--;
+
244 }
+
245 if (err)
+
246 return -1;
+
247
+
248 return 0;
+
249}
+
250#endif
+
251
+
252static int st_check_nlink(struct stat *st, nlink_t nlink)
+
253{
+
254 if (st->st_nlink != nlink) {
+
255 ERROR("nlink %li instead of %li", (long) st->st_nlink,
+
256 (long) nlink);
+
257 return -1;
+
258 }
+
259 return 0;
+
260}
+
261
+
262static int check_nlink(const char *path, nlink_t nlink)
+
263{
+
264 struct stat stbuf;
+
265 int res = lstat(path, &stbuf);
+
266 if (res == -1) {
+
267 PERROR("lstat");
+
268 return -1;
+
269 }
+
270 return st_check_nlink(&stbuf, nlink);
+
271}
+
272
+
273static int fcheck_stat(int fd, int flags, struct stat *st)
+
274{
+
275 struct stat stbuf;
+
276 int res = fstat(fd, &stbuf);
+
277 if (res == -1) {
+
278 if (flags & O_PATH) {
+
279 // With O_PATH fd, the server does not have to keep
+
280 // the inode alive so FUSE inode may be stale or bad
+
281 if (errno == ESTALE || errno == EIO ||
+
282 errno == ENOENT || errno == EBADF)
+
283 return 0;
+
284 }
+
285 PERROR("fstat");
+
286 return -1;
+
287 }
+
288
+
289 int err = 0;
+
290 err += st_check_type(&stbuf, st->st_mode & S_IFMT);
+
291 err += st_check_mode(&stbuf, st->st_mode & ALLPERMS);
+
292 err += st_check_size(&stbuf, st->st_size);
+
293 err += st_check_nlink(&stbuf, st->st_nlink);
+
294
+
295 return err;
+
296}
+
297
+
298static int check_nonexist(const char *path)
+
299{
+
300 struct stat stbuf;
+
301 int res = lstat(path, &stbuf);
+
302 if (res == 0) {
+
303 ERROR("file should not exist");
+
304 return -1;
+
305 }
+
306 if (errno != ENOENT) {
+
307 ERROR("file should not exist: %s", strerror(errno));
+
308 return -1;
+
309 }
+
310 return 0;
+
311}
+
312
+
313static int check_buffer(const char *buf, const char *data, unsigned len)
+
314{
+
315 if (memcmp(buf, data, len) != 0) {
+
316 ERROR("data mismatch");
+
317 return -1;
+
318 }
+
319 return 0;
+
320}
+
321
+
322static int check_data(const char *path, const char *data, int offset,
+
323 unsigned len)
+
324{
+
325 char buf[4096];
+
326 int res;
+
327 int fd = open(path, O_RDONLY);
+
328 if (fd == -1) {
+
329 PERROR("open");
+
330 return -1;
+
331 }
+
332 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
+
333 PERROR("lseek");
+
334 close(fd);
+
335 return -1;
+
336 }
+
337 while (len) {
+
338 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
+
339 res = read(fd, buf, rdlen);
+
340 if (res == -1) {
+
341 PERROR("read");
+
342 close(fd);
+
343 return -1;
+
344 }
+
345 if (res != rdlen) {
+
346 ERROR("short read: %u instead of %u", res, rdlen);
+
347 close(fd);
+
348 return -1;
+
349 }
+
350 if (check_buffer(buf, data, rdlen) != 0) {
+
351 close(fd);
+
352 return -1;
+
353 }
+
354 data += rdlen;
+
355 len -= rdlen;
+
356 }
+
357 res = close(fd);
+
358 if (res == -1) {
+
359 PERROR("close");
+
360 return -1;
+
361 }
+
362 return 0;
+
363}
+
364
+
365static int fcheck_data(int fd, const char *data, int offset,
+
366 unsigned len)
+
367{
+
368 char buf[4096];
+
369 int res;
+
370 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
+
371 PERROR("lseek");
+
372 return -1;
+
373 }
+
374 while (len) {
+
375 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
+
376 res = read(fd, buf, rdlen);
+
377 if (res == -1) {
+
378 PERROR("read");
+
379 return -1;
+
380 }
+
381 if (res != rdlen) {
+
382 ERROR("short read: %u instead of %u", res, rdlen);
+
383 return -1;
+
384 }
+
385 if (check_buffer(buf, data, rdlen) != 0) {
+
386 return -1;
+
387 }
+
388 data += rdlen;
+
389 len -= rdlen;
+
390 }
+
391 return 0;
+
392}
+
393
+
394static int check_dir_contents(const char *path, const char **contents)
+
395{
+
396 int i;
+
397 int res;
+
398 int err = 0;
+
399 int found[MAX_ENTRIES];
+
400 const char *cont[MAX_ENTRIES];
+
401 DIR *dp;
+
402
+
403 for (i = 0; contents[i]; i++) {
+
404 assert(i < MAX_ENTRIES - 3);
+
405 found[i] = 0;
+
406 cont[i] = contents[i];
+
407 }
+
408 cont[i] = NULL;
+
409
+
410 dp = opendir(path);
+
411 if (dp == NULL) {
+
412 PERROR("opendir");
+
413 return -1;
+
414 }
+
415 memset(found, 0, sizeof(found));
+
416 while(1) {
+
417 struct dirent *de;
+
418 errno = 0;
+
419 de = readdir(dp);
+
420 if (de == NULL) {
+
421 if (errno) {
+
422 PERROR("readdir");
+
423 closedir(dp);
+
424 return -1;
+
425 }
+
426 break;
+
427 }
+
428 if (is_dot_or_dotdot(de->d_name))
+
429 continue;
+
430 for (i = 0; cont[i] != NULL; i++) {
+
431 assert(i < MAX_ENTRIES);
+
432 if (strcmp(cont[i], de->d_name) == 0) {
+
433 if (found[i]) {
+
434 ERROR("duplicate entry <%s>",
+
435 de->d_name);
+
436 err--;
+
437 } else
+
438 found[i] = 1;
+
439 break;
+
440 }
+
441 }
+
442 if (!cont[i]) {
+
443 ERROR("unexpected entry <%s>", de->d_name);
+
444 err --;
+
445 }
+
446 }
+
447 for (i = 0; cont[i] != NULL; i++) {
+
448 if (!found[i]) {
+
449 ERROR("missing entry <%s>", cont[i]);
+
450 err--;
+
451 }
+
452 }
+
453 res = closedir(dp);
+
454 if (res == -1) {
+
455 PERROR("closedir");
+
456 return -1;
+
457 }
+
458 if (err)
+
459 return -1;
+
460
+
461 return 0;
+
462}
+
463
+
464static int create_file(const char *path, const char *data, int len)
+
465{
+
466 int res;
+
467 int fd;
+
468
+
469 unlink(path);
+
470 fd = creat(path, 0644);
+
471 if (fd == -1) {
+
472 PERROR("creat");
+
473 return -1;
+
474 }
+
475 if (len) {
+
476 res = write(fd, data, len);
+
477 if (res == -1) {
+
478 PERROR("write");
+
479 close(fd);
+
480 return -1;
+
481 }
+
482 if (res != len) {
+
483 ERROR("write is short: %u instead of %u", res, len);
+
484 close(fd);
+
485 return -1;
+
486 }
+
487 }
+
488 res = close(fd);
+
489 if (res == -1) {
+
490 PERROR("close");
+
491 return -1;
+
492 }
+
493 res = check_type(path, S_IFREG);
+
494 if (res == -1)
+
495 return -1;
+
496 res = check_mode(path, 0644);
+
497 if (res == -1)
+
498 return -1;
+
499 res = check_nlink(path, 1);
+
500 if (res == -1)
+
501 return -1;
+
502 res = check_size(path, len);
+
503 if (res == -1)
+
504 return -1;
+
505
+
506 if (len) {
+
507 res = check_data(path, data, 0, len);
+
508 if (res == -1)
+
509 return -1;
+
510 }
+
511
+
512 return 0;
+
513}
+
514
+
515static int create_path_fd(const char *path, const char *data, int len)
+
516{
+
517 int path_fd;
+
518 int res;
+
519
+
520 res = create_file(path, data, len);
+
521 if (res == -1)
+
522 return -1;
+
523
+
524 path_fd = open(path, O_PATH);
+
525 if (path_fd == -1)
+
526 PERROR("open(O_PATH)");
+
527
+
528 return path_fd;
+
529}
+
530
+
531// Can be called once per test
+
532static int create_testfile(const char *path, const char *data, int len)
+
533{
+
534 struct test *t = this_test;
+
535 struct stat *st = &t->stat;
+
536 int res, fd;
+
537
+
538 if (t->fd > 0) {
+
539 ERROR("testfile already created");
+
540 return -1;
+
541 }
+
542
+
543 fd = create_path_fd(path, data, len);
+
544 if (fd == -1)
+
545 return -1;
+
546
+
547 t->fd = fd;
+
548
+
549 res = fstat(fd, st);
+
550 if (res == -1) {
+
551 PERROR("fstat");
+
552 return -1;
+
553 }
+
554
+
555 return 0;
+
556}
+
557
+
558static int check_unlinked_testfile(int fd)
+
559{
+
560 struct stat *st = &this_test->stat;
+
561
+
562 st->st_nlink = 0;
+
563 return fcheck_stat(fd, O_PATH, st);
+
564}
+
565
+
566// Check recorded testfiles after all tests completed
+
567static int check_unlinked_testfiles(void)
+
568{
+
569 int fd;
+
570 int res, err = 0;
+
571 int num = testnum;
+
572
+
573 if (!unlinked_test)
+
574 return 0;
+
575
+
576 testnum = 0;
+
577 while (testnum < num) {
+
578 fd = next_test->fd;
+
579 start_test("check_unlinked_testfile");
+
580 if (fd == -1)
+
581 continue;
+
582
+
583 err += check_unlinked_testfile(fd);
+
584 res = close(fd);
+
585 if (res == -1) {
+
586 PERROR("close(test_fd)");
+
587 err--;
+
588 }
+
589 }
+
590
+
591 if (err) {
+
592 fprintf(stderr, "%i unlinked testfile checks failed\n", -err);
+
593 return 1;
+
594 }
+
595
+
596 return err;
+
597}
+
598
+
599static int cleanup_dir(const char *path, const char **dir_files, int quiet)
+
600{
+
601 int i;
+
602 int err = 0;
+
603
+
604 for (i = 0; dir_files[i]; i++) {
+
605 int res;
+
606 char fpath[1280];
+
607 sprintf(fpath, "%s/%s", path, dir_files[i]);
+
608 res = unlink(fpath);
+
609 if (res == -1 && !quiet) {
+
610 PERROR("unlink");
+
611 err --;
+
612 }
+
613 }
+
614 if (err)
+
615 return -1;
+
616
+
617 return 0;
+
618}
+
619
+
620static int create_dir(const char *path, const char **dir_files)
+
621{
+
622 int res;
+
623 int i;
+
624
+
625 rmdir(path);
+
626 res = mkdir(path, 0755);
+
627 if (res == -1) {
+
628 PERROR("mkdir");
+
629 return -1;
+
630 }
+
631 res = check_type(path, S_IFDIR);
+
632 if (res == -1)
+
633 return -1;
+
634 res = check_mode(path, 0755);
+
635 if (res == -1)
+
636 return -1;
+
637
+
638 for (i = 0; dir_files[i]; i++) {
+
639 char fpath[1280];
+
640 sprintf(fpath, "%s/%s", path, dir_files[i]);
+
641 res = create_file(fpath, "", 0);
+
642 if (res == -1) {
+
643 cleanup_dir(path, dir_files, 1);
+
644 return -1;
+
645 }
+
646 }
+
647 res = check_dir_contents(path, dir_files);
+
648 if (res == -1) {
+
649 cleanup_dir(path, dir_files, 1);
+
650 return -1;
+
651 }
+
652
+
653 return 0;
+
654}
+
655
+
656static int test_truncate(int len)
+
657{
+
658 const char *data = testdata;
+
659 int datalen = testdatalen;
+
660 int res;
+
661
+
662 start_test("truncate(%u)", (int) len);
+
663 res = create_testfile(testfile, data, datalen);
+
664 if (res == -1)
+
665 return -1;
+
666
+
667 res = truncate(testfile, len);
+
668 if (res == -1) {
+
669 PERROR("truncate");
+
670 return -1;
+
671 }
+
672 res = check_testfile_size(testfile, len);
+
673 if (res == -1)
+
674 return -1;
+
675
+
676 if (len > 0) {
+
677 if (len <= datalen) {
+
678 res = check_data(testfile, data, 0, len);
+
679 if (res == -1)
+
680 return -1;
+
681 } else {
+
682 res = check_data(testfile, data, 0, datalen);
+
683 if (res == -1)
+
684 return -1;
+
685 res = check_data(testfile, zerodata, datalen,
+
686 len - datalen);
+
687 if (res == -1)
+
688 return -1;
+
689 }
+
690 }
+
691 res = unlink(testfile);
+
692 if (res == -1) {
+
693 PERROR("unlink");
+
694 return -1;
+
695 }
+
696 res = check_nonexist(testfile);
+
697 if (res == -1)
+
698 return -1;
+
699
+
700 success();
+
701 return 0;
+
702}
+
703
+
704static int test_ftruncate(int len, int mode)
+
705{
+
706 const char *data = testdata;
+
707 int datalen = testdatalen;
+
708 int res;
+
709 int fd;
+
710
+
711 start_test("ftruncate(%u) mode: 0%03o", len, mode);
+
712 res = create_testfile(testfile, data, datalen);
+
713 if (res == -1)
+
714 return -1;
+
715
+
716 fd = open(testfile, O_WRONLY);
+
717 if (fd == -1) {
+
718 PERROR("open");
+
719 return -1;
+
720 }
+
721
+
722 res = fchmod(fd, mode);
+
723 if (res == -1) {
+
724 PERROR("fchmod");
+
725 close(fd);
+
726 return -1;
+
727 }
+
728 res = check_testfile_mode(testfile, mode);
+
729 if (res == -1) {
+
730 close(fd);
+
731 return -1;
+
732 }
+
733 res = ftruncate(fd, len);
+
734 if (res == -1) {
+
735 PERROR("ftruncate");
+
736 close(fd);
+
737 return -1;
+
738 }
+
739 close(fd);
+
740 res = check_testfile_size(testfile, len);
+
741 if (res == -1)
+
742 return -1;
+
743
+
744 if (len > 0) {
+
745 if (len <= datalen) {
+
746 res = check_data(testfile, data, 0, len);
+
747 if (res == -1)
+
748 return -1;
+
749 } else {
+
750 res = check_data(testfile, data, 0, datalen);
+
751 if (res == -1)
+
752 return -1;
+
753 res = check_data(testfile, zerodata, datalen,
+
754 len - datalen);
+
755 if (res == -1)
+
756 return -1;
+
757 }
+
758 }
+
759 res = unlink(testfile);
+
760 if (res == -1) {
+
761 PERROR("unlink");
+
762 return -1;
+
763 }
+
764 res = check_nonexist(testfile);
+
765 if (res == -1)
+
766 return -1;
+
767
+
768 success();
+
769 return 0;
+
770}
+
771
+
772static int test_seekdir(void)
+
773{
+
774 int i;
+
775 int res;
+
776 DIR *dp;
+
777 struct dirent *de = NULL;
+
778
+
779 start_test("seekdir");
+
780 res = create_dir(testdir, testdir_files);
+
781 if (res == -1)
+
782 return res;
+
783
+
784 dp = opendir(testdir);
+
785 if (dp == NULL) {
+
786 PERROR("opendir");
+
787 return -1;
+
788 }
+
789
+
790 /* Remember dir offsets */
+
791 for (i = 0; i < ARRAY_SIZE(seekdir_offsets); i++) {
+
792 seekdir_offsets[i] = telldir(dp);
+
793 errno = 0;
+
794 de = readdir(dp);
+
795 if (de == NULL) {
+
796 if (errno) {
+
797 PERROR("readdir");
+
798 goto fail;
+
799 }
+
800 break;
+
801 }
+
802 }
+
803
+
804 /* Walk until the end of directory */
+
805 while (de)
+
806 de = readdir(dp);
+
807
+
808 /* Start from the last valid dir offset and seek backwards */
+
809 for (i--; i >= 0; i--) {
+
810 seekdir(dp, seekdir_offsets[i]);
+
811 de = readdir(dp);
+
812 if (de == NULL) {
+
813 ERROR("Unexpected end of directory after seekdir()");
+
814 goto fail;
+
815 }
+
816 }
+
817
+
818 closedir(dp);
+
819 res = cleanup_dir(testdir, testdir_files, 0);
+
820 if (!res)
+
821 success();
+
822 return res;
+
823fail:
+
824 closedir(dp);
+
825 cleanup_dir(testdir, testdir_files, 1);
+
826 return -1;
+
827}
+
828
+
829#ifdef HAVE_COPY_FILE_RANGE
+
830static int test_copy_file_range(void)
+
831{
+
832 const char *data = testdata;
+
833 int datalen = testdatalen;
+
834 int err = 0;
+
835 int res;
+
836 int fd_in, fd_out;
+
837 off_t pos_in = 0, pos_out = 0;
+
838
+
839 start_test("copy_file_range");
+
840 unlink(testfile);
+
841 fd_in = open(testfile, O_CREAT | O_RDWR, 0644);
+
842 if (fd_in == -1) {
+
843 PERROR("creat");
+
844 return -1;
+
845 }
+
846 res = write(fd_in, data, datalen);
+
847 if (res == -1) {
+
848 PERROR("write");
+
849 close(fd_in);
+
850 return -1;
+
851 }
+
852 if (res != datalen) {
+
853 ERROR("write is short: %u instead of %u", res, datalen);
+
854 close(fd_in);
+
855 return -1;
+
856 }
+
857
+
858 unlink(testfile2);
+
859 fd_out = creat(testfile2, 0644);
+
860 if (fd_out == -1) {
+
861 PERROR("creat");
+
862 close(fd_in);
+
863 return -1;
+
864 }
+
865 res = copy_file_range(fd_in, &pos_in, fd_out, &pos_out, datalen, 0);
+
866 if (res == -1) {
+
867 PERROR("copy_file_range");
+
868 close(fd_in);
+
869 close(fd_out);
+
870 return -1;
+
871 }
+
872 if (res != datalen) {
+
873 ERROR("copy is short: %u instead of %u", res, datalen);
+
874 close(fd_in);
+
875 close(fd_out);
+
876 return -1;
+
877 }
+
878
+
879 res = close(fd_in);
+
880 if (res == -1) {
+
881 PERROR("close");
+
882 close(fd_out);
+
883 return -1;
+
884 }
+
885 res = close(fd_out);
+
886 if (res == -1) {
+
887 PERROR("close");
+
888 return -1;
+
889 }
+
890
+
891 err = check_data(testfile2, data, 0, datalen);
+
892
+
893 res = unlink(testfile);
+
894 if (res == -1) {
+
895 PERROR("unlink");
+
896 return -1;
+
897 }
+
898 res = check_nonexist(testfile);
+
899 if (res == -1)
+
900 return -1;
+
901 if (err)
+
902 return -1;
+
903
+
904 res = unlink(testfile2);
+
905 if (res == -1) {
+
906 PERROR("unlink");
+
907 return -1;
+
908 }
+
909 res = check_nonexist(testfile2);
+
910 if (res == -1)
+
911 return -1;
+
912 if (err)
+
913 return -1;
+
914
+
915 success();
+
916 return 0;
+
917}
+
918#else
+
919static int test_copy_file_range(void)
+
920{
+
921 return 0;
+
922}
+
923#endif
+
924
+
925#ifdef HAVE_STATX
+
926static int test_statx(void)
+
927{
+
928 struct statx sb;
+
929 char msg[] = "hi";
+
930 size_t msg_size = sizeof(msg);
+
931 struct timespec tp;
+
932 int res;
+
933
+
934 memset(&sb, 0, sizeof(sb));
+
935 unlink(testfile);
+
936
+
937 start_test("statx");
+
938
+
939 res = create_testfile(testfile, msg, msg_size);
+
940 if (res == -1)
+
941 return -1;
+
942
+
943 res = statx(-1, testfile, AT_EMPTY_PATH,
+
944 STATX_BASIC_STATS | STATX_BTIME, &sb);
+
945 if (res == -1)
+
946 return -1;
+
947
+
948 if (sb.stx_size != msg_size)
+
949 return -1;
+
950
+
951 clock_gettime(CLOCK_REALTIME, &tp);
+
952
+
953 if (sb.stx_btime.tv_sec > tp.tv_sec)
+
954 return -1;
+
955
+
956 if (sb.stx_btime.tv_sec == tp.tv_sec &&
+
957 sb.stx_btime.tv_nsec >= tp.tv_nsec)
+
958 return -1;
+
959
+
960 unlink(testfile);
+
961
+
962 success();
+
963 return 0;
+
964}
+
965#else
+
966static int test_statx(void)
+
967{
+
968 return 0;
+
969}
+
970#endif
+
971
+
972static int test_utime(void)
+
973{
+
974 struct utimbuf utm;
+
975 time_t atime = 987631200;
+
976 time_t mtime = 123116400;
+
977 int res;
+
978
+
979 start_test("utime");
+
980 res = create_testfile(testfile, NULL, 0);
+
981 if (res == -1)
+
982 return -1;
+
983
+
984 utm.actime = atime;
+
985 utm.modtime = mtime;
+
986 res = utime(testfile, &utm);
+
987 if (res == -1) {
+
988 PERROR("utime");
+
989 return -1;
+
990 }
+
991 res = check_times(testfile, atime, mtime);
+
992 if (res == -1) {
+
993 return -1;
+
994 }
+
995 res = unlink(testfile);
+
996 if (res == -1) {
+
997 PERROR("unlink");
+
998 return -1;
+
999 }
+
1000 res = check_nonexist(testfile);
+
1001 if (res == -1)
+
1002 return -1;
+
1003
+
1004 success();
+
1005 return 0;
+
1006}
+
1007
+
1008static int test_create(void)
+
1009{
+
1010 const char *data = testdata;
+
1011 int datalen = testdatalen;
+
1012 int err = 0;
+
1013 int res;
+
1014 int fd;
+
1015
+
1016 start_test("create");
+
1017 unlink(testfile);
+
1018 fd = creat(testfile, 0644);
+
1019 if (fd == -1) {
+
1020 PERROR("creat");
+
1021 return -1;
+
1022 }
+
1023 res = write(fd, data, datalen);
+
1024 if (res == -1) {
+
1025 PERROR("write");
+
1026 close(fd);
+
1027 return -1;
+
1028 }
+
1029 if (res != datalen) {
+
1030 ERROR("write is short: %u instead of %u", res, datalen);
+
1031 close(fd);
+
1032 return -1;
+
1033 }
+
1034 res = close(fd);
+
1035 if (res == -1) {
+
1036 PERROR("close");
+
1037 return -1;
+
1038 }
+
1039 res = check_type(testfile, S_IFREG);
+
1040 if (res == -1)
+
1041 return -1;
+
1042 err += check_mode(testfile, 0644);
+
1043 err += check_nlink(testfile, 1);
+
1044 err += check_size(testfile, datalen);
+
1045 err += check_data(testfile, data, 0, datalen);
+
1046 res = unlink(testfile);
+
1047 if (res == -1) {
+
1048 PERROR("unlink");
+
1049 return -1;
+
1050 }
+
1051 res = check_nonexist(testfile);
+
1052 if (res == -1)
+
1053 return -1;
+
1054 if (err)
+
1055 return -1;
+
1056
+
1057 success();
+
1058 return 0;
+
1059}
+
1060
+
1061static int test_create_unlink(void)
+
1062{
+
1063 const char *data = testdata;
+
1064 int datalen = testdatalen;
+
1065 int err = 0;
+
1066 int res;
+
1067 int fd;
+
1068
+
1069 start_test("create+unlink");
+
1070 unlink(testfile);
+
1071 fd = open(testfile, O_CREAT | O_RDWR | O_TRUNC, 0644);
+
1072 if (fd == -1) {
+
1073 PERROR("creat");
+
1074 return -1;
+
1075 }
+
1076 res = unlink(testfile);
+
1077 if (res == -1) {
+
1078 PERROR("unlink");
+
1079 close(fd);
+
1080 return -1;
+
1081 }
+
1082 res = check_nonexist(testfile);
+
1083 if (res == -1) {
+
1084 close(fd);
+
1085 return -1;
+
1086 }
+
1087 res = write(fd, data, datalen);
+
1088 if (res == -1) {
+
1089 PERROR("write");
+
1090 close(fd);
+
1091 return -1;
+
1092 }
+
1093 if (res != datalen) {
+
1094 ERROR("write is short: %u instead of %u", res, datalen);
+
1095 close(fd);
+
1096 return -1;
+
1097 }
+
1098 struct stat st = {
+
1099 .st_mode = S_IFREG | 0644,
+
1100 .st_size = datalen,
+
1101 };
+
1102 err = fcheck_stat(fd, O_RDWR, &st);
+
1103 err += fcheck_data(fd, data, 0, datalen);
+
1104 res = close(fd);
+
1105 if (res == -1) {
+
1106 PERROR("close");
+
1107 err--;
+
1108 }
+
1109 if (err)
+
1110 return -1;
+
1111
+
1112 success();
+
1113 return 0;
+
1114}
+
1115
+
1116static int test_mknod(void)
+
1117{
+
1118 int err = 0;
+
1119 int res;
+
1120
+
1121 start_test("mknod");
+
1122 unlink(testfile);
+
1123 res = mknod(testfile, 0644, 0);
+
1124 if (res == -1) {
+
1125 PERROR("mknod");
+
1126 return -1;
+
1127 }
+
1128 res = check_type(testfile, S_IFREG);
+
1129 if (res == -1)
+
1130 return -1;
+
1131 err += check_mode(testfile, 0644);
+
1132 err += check_nlink(testfile, 1);
+
1133 err += check_size(testfile, 0);
+
1134 res = unlink(testfile);
+
1135 if (res == -1) {
+
1136 PERROR("unlink");
+
1137 return -1;
+
1138 }
+
1139 res = check_nonexist(testfile);
+
1140 if (res == -1)
+
1141 return -1;
+
1142 if (err)
+
1143 return -1;
+
1144
+
1145 success();
+
1146 return 0;
+
1147}
+
1148
+
1149#define test_open(exist, flags, mode) do_test_open(exist, flags, #flags, mode)
+
1150
+
1151static int do_test_open(int exist, int flags, const char *flags_str, int mode)
+
1152{
+
1153 char buf[4096];
+
1154 const char *data = testdata;
+
1155 int datalen = testdatalen;
+
1156 unsigned currlen = 0;
+
1157 int err = 0;
+
1158 int res;
+
1159 int fd;
+
1160 off_t off;
+
1161
+
1162 start_test("open(%s, %s, 0%03o)", exist ? "+" : "-", flags_str, mode);
+
1163 unlink(testfile);
+
1164 if (exist) {
+
1165 res = create_file(testfile_r, testdata2, testdata2len);
+
1166 if (res == -1)
+
1167 return -1;
+
1168
+
1169 currlen = testdata2len;
+
1170 }
+
1171
+
1172 fd = open(testfile, flags, mode);
+
1173 if ((flags & O_CREAT) && (flags & O_EXCL) && exist) {
+
1174 if (fd != -1) {
+
1175 ERROR("open should have failed");
+
1176 close(fd);
+
1177 return -1;
+
1178 } else if (errno == EEXIST)
+
1179 goto succ;
+
1180 }
+
1181 if (!(flags & O_CREAT) && !exist) {
+
1182 if (fd != -1) {
+
1183 ERROR("open should have failed");
+
1184 close(fd);
+
1185 return -1;
+
1186 } else if (errno == ENOENT)
+
1187 goto succ;
+
1188 }
+
1189 if (fd == -1) {
+
1190 PERROR("open");
+
1191 return -1;
+
1192 }
+
1193
+
1194 if (flags & O_TRUNC)
+
1195 currlen = 0;
+
1196
+
1197 err += check_type(testfile, S_IFREG);
+
1198 if (exist)
+
1199 err += check_mode(testfile, 0644);
+
1200 else
+
1201 err += check_mode(testfile, mode);
+
1202 err += check_nlink(testfile, 1);
+
1203 err += check_size(testfile, currlen);
+
1204 if (exist && !(flags & O_TRUNC) && (mode & S_IRUSR))
+
1205 err += check_data(testfile, testdata2, 0, testdata2len);
+
1206
+
1207 res = write(fd, data, datalen);
+
1208 if ((flags & O_ACCMODE) != O_RDONLY) {
+
1209 if (res == -1) {
+
1210 PERROR("write");
+
1211 err --;
+
1212 } else if (res != datalen) {
+
1213 ERROR("write is short: %u instead of %u", res, datalen);
+
1214 err --;
+
1215 } else {
+
1216 if (datalen > (int) currlen)
+
1217 currlen = datalen;
+
1218
+
1219 err += check_size(testfile, currlen);
+
1220
+
1221 if (mode & S_IRUSR) {
+
1222 err += check_data(testfile, data, 0, datalen);
+
1223 if (exist && !(flags & O_TRUNC) &&
+
1224 testdata2len > datalen)
+
1225 err += check_data(testfile,
+
1226 testdata2 + datalen,
+
1227 datalen,
+
1228 testdata2len - datalen);
+
1229 }
+
1230 }
+
1231 } else {
+
1232 if (res != -1) {
+
1233 ERROR("write should have failed");
+
1234 err --;
+
1235 } else if (errno != EBADF) {
+
1236 PERROR("write");
+
1237 err --;
+
1238 }
+
1239 }
+
1240 off = lseek(fd, SEEK_SET, 0);
+
1241 if (off == (off_t) -1) {
+
1242 PERROR("lseek");
+
1243 err--;
+
1244 } else if (off != 0) {
+
1245 ERROR("offset should have returned 0");
+
1246 err --;
+
1247 }
+
1248 res = read(fd, buf, sizeof(buf));
+
1249 if ((flags & O_ACCMODE) != O_WRONLY) {
+
1250 if (res == -1) {
+
1251 PERROR("read");
+
1252 err--;
+
1253 } else {
+
1254 int readsize =
+
1255 currlen < sizeof(buf) ? currlen : sizeof(buf);
+
1256 if (res != readsize) {
+
1257 ERROR("read is short: %i instead of %u",
+
1258 res, readsize);
+
1259 err--;
+
1260 } else {
+
1261 if ((flags & O_ACCMODE) != O_RDONLY) {
+
1262 err += check_buffer(buf, data, datalen);
+
1263 if (exist && !(flags & O_TRUNC) &&
+
1264 testdata2len > datalen)
+
1265 err += check_buffer(buf + datalen,
+
1266 testdata2 + datalen,
+
1267 testdata2len - datalen);
+
1268 } else if (exist)
+
1269 err += check_buffer(buf, testdata2,
+
1270 testdata2len);
+
1271 }
+
1272 }
+
1273 } else {
+
1274 if (res != -1) {
+
1275 ERROR("read should have failed");
+
1276 err --;
+
1277 } else if (errno != EBADF) {
+
1278 PERROR("read");
+
1279 err --;
+
1280 }
+
1281 }
+
1282
+
1283 res = close(fd);
+
1284 if (res == -1) {
+
1285 PERROR("close");
+
1286 return -1;
+
1287 }
+
1288 res = unlink(testfile);
+
1289 if (res == -1) {
+
1290 PERROR("unlink");
+
1291 return -1;
+
1292 }
+
1293 res = check_nonexist(testfile);
+
1294 if (res == -1)
+
1295 return -1;
+
1296 res = check_nonexist(testfile_r);
+
1297 if (res == -1)
+
1298 return -1;
+
1299 if (err)
+
1300 return -1;
+
1301
+
1302succ:
+
1303 success();
+
1304 return 0;
+
1305}
+
1306
+
1307#define test_open_acc(flags, mode, err) \
+
1308 do_test_open_acc(flags, #flags, mode, err)
+
1309
+
1310static int do_test_open_acc(int flags, const char *flags_str, int mode, int err)
+
1311{
+
1312 const char *data = testdata;
+
1313 int datalen = testdatalen;
+
1314 int res;
+
1315 int fd;
+
1316
+
1317 start_test("open_acc(%s) mode: 0%03o message: '%s'", flags_str, mode,
+
1318 strerror(err));
+
1319 unlink(testfile);
+
1320 res = create_testfile(testfile, data, datalen);
+
1321 if (res == -1)
+
1322 return -1;
+
1323
+
1324 res = chmod(testfile, mode);
+
1325 if (res == -1) {
+
1326 PERROR("chmod");
+
1327 return -1;
+
1328 }
+
1329
+
1330 res = check_testfile_mode(testfile, mode);
+
1331 if (res == -1)
+
1332 return -1;
+
1333
+
1334 fd = open(testfile, flags);
+
1335 if (fd == -1) {
+
1336 if (err != errno) {
+
1337 PERROR("open");
+
1338 return -1;
+
1339 }
+
1340 } else {
+
1341 if (err) {
+
1342 ERROR("open should have failed");
+
1343 close(fd);
+
1344 return -1;
+
1345 }
+
1346 close(fd);
+
1347 }
+
1348
+
1349 res = unlink(testfile);
+
1350 if (res == -1) {
+
1351 PERROR("unlink");
+
1352 return -1;
+
1353 }
+
1354 res = check_nonexist(testfile);
+
1355 if (res == -1)
+
1356 return -1;
+
1357 res = check_nonexist(testfile_r);
+
1358 if (res == -1)
+
1359 return -1;
+
1360
+
1361 success();
+
1362 return 0;
+
1363}
+
1364
+
1365static int test_symlink(void)
+
1366{
+
1367 char buf[1024];
+
1368 const char *data = testdata;
+
1369 int datalen = testdatalen;
+
1370 int linklen = strlen(testfile);
+
1371 int err = 0;
+
1372 int res;
+
1373
+
1374 start_test("symlink");
+
1375 res = create_testfile(testfile, data, datalen);
+
1376 if (res == -1)
+
1377 return -1;
+
1378
+
1379 unlink(testfile2);
+
1380 res = symlink(testfile, testfile2);
+
1381 if (res == -1) {
+
1382 PERROR("symlink");
+
1383 return -1;
+
1384 }
+
1385 res = check_type(testfile2, S_IFLNK);
+
1386 if (res == -1)
+
1387 return -1;
+
1388 err += check_mode(testfile2, 0777);
+
1389 err += check_nlink(testfile2, 1);
+
1390 res = readlink(testfile2, buf, sizeof(buf));
+
1391 if (res == -1) {
+
1392 PERROR("readlink");
+
1393 err--;
+
1394 }
+
1395 if (res != linklen) {
+
1396 ERROR("short readlink: %u instead of %u", res, linklen);
+
1397 err--;
+
1398 }
+
1399 if (memcmp(buf, testfile, linklen) != 0) {
+
1400 ERROR("link mismatch");
+
1401 err--;
+
1402 }
+
1403 err += check_size(testfile2, datalen);
+
1404 err += check_data(testfile2, data, 0, datalen);
+
1405 res = unlink(testfile2);
+
1406 if (res == -1) {
+
1407 PERROR("unlink");
+
1408 return -1;
+
1409 }
+
1410 res = check_nonexist(testfile2);
+
1411 if (res == -1)
+
1412 return -1;
+
1413 if (err)
+
1414 return -1;
+
1415
+
1416 res = unlink(testfile);
+
1417 if (res == -1) {
+
1418 PERROR("unlink");
+
1419 return -1;
+
1420 }
+
1421 res = check_nonexist(testfile);
+
1422 if (res == -1)
+
1423 return -1;
+
1424
+
1425 success();
+
1426 return 0;
+
1427}
+
1428
+
1429static int test_link(void)
+
1430{
+
1431 const char *data = testdata;
+
1432 int datalen = testdatalen;
+
1433 int err = 0;
+
1434 int res;
+
1435
+
1436 start_test("link");
+
1437 res = create_testfile(testfile, data, datalen);
+
1438 if (res == -1)
+
1439 return -1;
+
1440
+
1441 unlink(testfile2);
+
1442 res = link(testfile, testfile2);
+
1443 if (res == -1) {
+
1444 PERROR("link");
+
1445 return -1;
+
1446 }
+
1447 res = check_type(testfile2, S_IFREG);
+
1448 if (res == -1)
+
1449 return -1;
+
1450 err += check_mode(testfile2, 0644);
+
1451 err += check_nlink(testfile2, 2);
+
1452 err += check_size(testfile2, datalen);
+
1453 err += check_data(testfile2, data, 0, datalen);
+
1454 res = unlink(testfile);
+
1455 if (res == -1) {
+
1456 PERROR("unlink");
+
1457 return -1;
+
1458 }
+
1459 res = check_nonexist(testfile);
+
1460 if (res == -1)
+
1461 return -1;
+
1462
+
1463 err += check_nlink(testfile2, 1);
+
1464 res = unlink(testfile2);
+
1465 if (res == -1) {
+
1466 PERROR("unlink");
+
1467 return -1;
+
1468 }
+
1469 res = check_nonexist(testfile2);
+
1470 if (res == -1)
+
1471 return -1;
+
1472 if (err)
+
1473 return -1;
+
1474
+
1475 success();
+
1476 return 0;
+
1477}
+
1478
+
1479static int test_link2(void)
+
1480{
+
1481 const char *data = testdata;
+
1482 int datalen = testdatalen;
+
1483 int err = 0;
+
1484 int res;
+
1485
+
1486 start_test("link-unlink-link");
+
1487 res = create_testfile(testfile, data, datalen);
+
1488 if (res == -1)
+
1489 return -1;
+
1490
+
1491 unlink(testfile2);
+
1492 res = link(testfile, testfile2);
+
1493 if (res == -1) {
+
1494 PERROR("link");
+
1495 return -1;
+
1496 }
+
1497 res = unlink(testfile);
+
1498 if (res == -1) {
+
1499 PERROR("unlink");
+
1500 return -1;
+
1501 }
+
1502 res = check_nonexist(testfile);
+
1503 if (res == -1)
+
1504 return -1;
+
1505 res = link(testfile2, testfile);
+
1506 if (res == -1) {
+
1507 PERROR("link");
+
1508 }
+
1509 res = check_type(testfile, S_IFREG);
+
1510 if (res == -1)
+
1511 return -1;
+
1512 err += check_mode(testfile, 0644);
+
1513 err += check_nlink(testfile, 2);
+
1514 err += check_size(testfile, datalen);
+
1515 err += check_data(testfile, data, 0, datalen);
+
1516
+
1517 res = unlink(testfile2);
+
1518 if (res == -1) {
+
1519 PERROR("unlink");
+
1520 return -1;
+
1521 }
+
1522 err += check_nlink(testfile, 1);
+
1523 res = unlink(testfile);
+
1524 if (res == -1) {
+
1525 PERROR("unlink");
+
1526 return -1;
+
1527 }
+
1528 res = check_nonexist(testfile);
+
1529 if (res == -1)
+
1530 return -1;
+
1531 if (err)
+
1532 return -1;
+
1533
+
1534 success();
+
1535 return 0;
+
1536}
+
1537
+
1538static int test_rename_file(void)
+
1539{
+
1540 const char *data = testdata;
+
1541 int datalen = testdatalen;
+
1542 int err = 0;
+
1543 int res;
+
1544
+
1545 start_test("rename file");
+
1546 res = create_testfile(testfile, data, datalen);
+
1547 if (res == -1)
+
1548 return -1;
+
1549
+
1550 unlink(testfile2);
+
1551 res = rename(testfile, testfile2);
+
1552 if (res == -1) {
+
1553 PERROR("rename");
+
1554 return -1;
+
1555 }
+
1556 res = check_nonexist(testfile);
+
1557 if (res == -1)
+
1558 return -1;
+
1559 res = check_type(testfile2, S_IFREG);
+
1560 if (res == -1)
+
1561 return -1;
+
1562 err += check_mode(testfile2, 0644);
+
1563 err += check_nlink(testfile2, 1);
+
1564 err += check_size(testfile2, datalen);
+
1565 err += check_data(testfile2, data, 0, datalen);
+
1566 res = unlink(testfile2);
+
1567 if (res == -1) {
+
1568 PERROR("unlink");
+
1569 return -1;
+
1570 }
+
1571 res = check_nonexist(testfile2);
+
1572 if (res == -1)
+
1573 return -1;
+
1574 if (err)
+
1575 return -1;
+
1576
+
1577 success();
+
1578 return 0;
+
1579}
+
1580
+
1581static int test_rename_dir(void)
+
1582{
+
1583 int err = 0;
+
1584 int res;
+
1585
+
1586 start_test("rename dir");
+
1587 res = create_dir(testdir, testdir_files);
+
1588 if (res == -1)
+
1589 return -1;
+
1590
+
1591 rmdir(testdir2);
+
1592 res = rename(testdir, testdir2);
+
1593 if (res == -1) {
+
1594 PERROR("rename");
+
1595 cleanup_dir(testdir, testdir_files, 1);
+
1596 return -1;
+
1597 }
+
1598 res = check_nonexist(testdir);
+
1599 if (res == -1) {
+
1600 cleanup_dir(testdir, testdir_files, 1);
+
1601 return -1;
+
1602 }
+
1603 res = check_type(testdir2, S_IFDIR);
+
1604 if (res == -1) {
+
1605 cleanup_dir(testdir2, testdir_files, 1);
+
1606 return -1;
+
1607 }
+
1608 err += check_mode(testdir2, 0755);
+
1609 err += check_dir_contents(testdir2, testdir_files);
+
1610 err += cleanup_dir(testdir2, testdir_files, 0);
+
1611 res = rmdir(testdir2);
+
1612 if (res == -1) {
+
1613 PERROR("rmdir");
+
1614 return -1;
+
1615 }
+
1616 res = check_nonexist(testdir2);
+
1617 if (res == -1)
+
1618 return -1;
+
1619 if (err)
+
1620 return -1;
+
1621
+
1622 success();
+
1623 return 0;
+
1624}
+
1625
+
1626static int test_rename_dir_loop(void)
+
1627{
+
1628#define PATH(p) (snprintf(path, sizeof path, "%s/%s", testdir, p), path)
+
1629#define PATH2(p) (snprintf(path2, sizeof path2, "%s/%s", testdir, p), path2)
+
1630
+
1631 char path[1280], path2[1280];
+
1632 int err = 0;
+
1633 int res;
+
1634
+
1635 start_test("rename dir loop");
+
1636
+
1637 res = create_dir(testdir, testdir_files);
+
1638 if (res == -1)
+
1639 return -1;
+
1640
+
1641 res = mkdir(PATH("a"), 0755);
+
1642 if (res == -1) {
+
1643 PERROR("mkdir");
+
1644 goto fail;
+
1645 }
+
1646
+
1647 res = rename(PATH("a"), PATH2("a"));
+
1648 if (res == -1) {
+
1649 PERROR("rename");
+
1650 goto fail;
+
1651 }
+
1652
+
1653 errno = 0;
+
1654 res = rename(PATH("a"), PATH2("a/b"));
+
1655 if (res == 0 || errno != EINVAL) {
+
1656 PERROR("rename");
+
1657 goto fail;
+
1658 }
+
1659
+
1660 res = mkdir(PATH("a/b"), 0755);
+
1661 if (res == -1) {
+
1662 PERROR("mkdir");
+
1663 goto fail;
+
1664 }
+
1665
+
1666 res = mkdir(PATH("a/b/c"), 0755);
+
1667 if (res == -1) {
+
1668 PERROR("mkdir");
+
1669 goto fail;
+
1670 }
+
1671
+
1672 errno = 0;
+
1673 res = rename(PATH("a"), PATH2("a/b/c"));
+
1674 if (res == 0 || errno != EINVAL) {
+
1675 PERROR("rename");
+
1676 goto fail;
+
1677 }
+
1678
+
1679 errno = 0;
+
1680 res = rename(PATH("a"), PATH2("a/b/c/a"));
+
1681 if (res == 0 || errno != EINVAL) {
+
1682 PERROR("rename");
+
1683 goto fail;
+
1684 }
+
1685
+
1686 errno = 0;
+
1687 res = rename(PATH("a/b/c"), PATH2("a"));
+
1688 if (res == 0 || errno != ENOTEMPTY) {
+
1689 PERROR("rename");
+
1690 goto fail;
+
1691 }
+
1692
+
1693 res = open(PATH("a/foo"), O_CREAT, 0644);
+
1694 if (res == -1) {
+
1695 PERROR("open");
+
1696 goto fail;
+
1697 }
+
1698 close(res);
+
1699
+
1700 res = rename(PATH("a/foo"), PATH2("a/bar"));
+
1701 if (res == -1) {
+
1702 PERROR("rename");
+
1703 goto fail;
+
1704 }
+
1705
+
1706 res = rename(PATH("a/bar"), PATH2("a/foo"));
+
1707 if (res == -1) {
+
1708 PERROR("rename");
+
1709 goto fail;
+
1710 }
+
1711
+
1712 res = rename(PATH("a/foo"), PATH2("a/b/bar"));
+
1713 if (res == -1) {
+
1714 PERROR("rename");
+
1715 goto fail;
+
1716 }
+
1717
+
1718 res = rename(PATH("a/b/bar"), PATH2("a/foo"));
+
1719 if (res == -1) {
+
1720 PERROR("rename");
+
1721 goto fail;
+
1722 }
+
1723
+
1724 res = rename(PATH("a/foo"), PATH2("a/b/c/bar"));
+
1725 if (res == -1) {
+
1726 PERROR("rename");
+
1727 goto fail;
+
1728 }
+
1729
+
1730 res = rename(PATH("a/b/c/bar"), PATH2("a/foo"));
+
1731 if (res == -1) {
+
1732 PERROR("rename");
+
1733 goto fail;
+
1734 }
+
1735
+
1736 res = open(PATH("a/bar"), O_CREAT, 0644);
+
1737 if (res == -1) {
+
1738 PERROR("open");
+
1739 goto fail;
+
1740 }
+
1741 close(res);
+
1742
+
1743 res = rename(PATH("a/foo"), PATH2("a/bar"));
+
1744 if (res == -1) {
+
1745 PERROR("rename");
+
1746 goto fail;
+
1747 }
+
1748
+
1749 unlink(PATH("a/bar"));
+
1750
+
1751 res = rename(PATH("a/b"), PATH2("a/d"));
+
1752 if (res == -1) {
+
1753 PERROR("rename");
+
1754 goto fail;
+
1755 }
+
1756
+
1757 res = rename(PATH("a/d"), PATH2("a/b"));
+
1758 if (res == -1) {
+
1759 PERROR("rename");
+
1760 goto fail;
+
1761 }
+
1762
+
1763 res = mkdir(PATH("a/d"), 0755);
+
1764 if (res == -1) {
+
1765 PERROR("mkdir");
+
1766 goto fail;
+
1767 }
+
1768
+
1769 res = rename(PATH("a/b"), PATH2("a/d"));
+
1770 if (res == -1) {
+
1771 PERROR("rename");
+
1772 goto fail;
+
1773 }
+
1774
+
1775 res = rename(PATH("a/d"), PATH2("a/b"));
+
1776 if (res == -1) {
+
1777 PERROR("rename");
+
1778 goto fail;
+
1779 }
+
1780
+
1781 res = mkdir(PATH("a/d"), 0755);
+
1782 if (res == -1) {
+
1783 PERROR("mkdir");
+
1784 goto fail;
+
1785 }
+
1786
+
1787 res = mkdir(PATH("a/d/e"), 0755);
+
1788 if (res == -1) {
+
1789 PERROR("mkdir");
+
1790 goto fail;
+
1791 }
+
1792
+
1793 errno = 0;
+
1794 res = rename(PATH("a/b"), PATH2("a/d"));
+
1795 if (res == 0 || (errno != ENOTEMPTY && errno != EEXIST)) {
+
1796 PERROR("rename");
+
1797 goto fail;
+
1798 }
+
1799
+
1800 rmdir(PATH("a/d/e"));
+
1801 rmdir(PATH("a/d"));
+
1802
+
1803 rmdir(PATH("a/b/c"));
+
1804 rmdir(PATH("a/b"));
+
1805 rmdir(PATH("a"));
+
1806
+
1807 err += cleanup_dir(testdir, testdir_files, 0);
+
1808 res = rmdir(testdir);
+
1809 if (res == -1) {
+
1810 PERROR("rmdir");
+
1811 goto fail;
+
1812 }
+
1813 res = check_nonexist(testdir);
+
1814 if (res == -1)
+
1815 return -1;
+
1816 if (err)
+
1817 return -1;
+
1818
+
1819 success();
+
1820 return 0;
+
1821
+
1822fail:
+
1823 unlink(PATH("a/bar"));
+
1824
+
1825 rmdir(PATH("a/d/e"));
+
1826 rmdir(PATH("a/d"));
+
1827
+
1828 rmdir(PATH("a/b/c"));
+
1829 rmdir(PATH("a/b"));
+
1830 rmdir(PATH("a"));
+
1831
+
1832 cleanup_dir(testdir, testdir_files, 1);
+
1833 rmdir(testdir);
+
1834
+
1835 return -1;
+
1836
+
1837#undef PATH2
+
1838#undef PATH
+
1839}
+
1840
+
1841static int test_mkfifo(void)
+
1842{
+
1843 int res;
+
1844 int err = 0;
+
1845
+
1846 start_test("mkfifo");
+
1847 unlink(testfile);
+
1848 res = mkfifo(testfile, 0644);
+
1849 if (res == -1) {
+
1850 PERROR("mkfifo");
+
1851 return -1;
+
1852 }
+
1853 res = check_type(testfile, S_IFIFO);
+
1854 if (res == -1)
+
1855 return -1;
+
1856 err += check_mode(testfile, 0644);
+
1857 err += check_nlink(testfile, 1);
+
1858 res = unlink(testfile);
+
1859 if (res == -1) {
+
1860 PERROR("unlink");
+
1861 return -1;
+
1862 }
+
1863 res = check_nonexist(testfile);
+
1864 if (res == -1)
+
1865 return -1;
+
1866 if (err)
+
1867 return -1;
+
1868
+
1869 success();
+
1870 return 0;
+
1871}
+
1872
+
1873static int test_mkdir(void)
+
1874{
+
1875 int res;
+
1876 int err = 0;
+
1877 const char *dir_contents[] = {NULL};
+
1878
+
1879 start_test("mkdir");
+
1880 rmdir(testdir);
+
1881 res = mkdir(testdir, 0755);
+
1882 if (res == -1) {
+
1883 PERROR("mkdir");
+
1884 return -1;
+
1885 }
+
1886 res = check_type(testdir, S_IFDIR);
+
1887 if (res == -1)
+
1888 return -1;
+
1889 err += check_mode(testdir, 0755);
+
1890 /* Some file systems (like btrfs) don't track link
+
1891 count for directories */
+
1892 //err += check_nlink(testdir, 2);
+
1893 err += check_dir_contents(testdir, dir_contents);
+
1894 res = rmdir(testdir);
+
1895 if (res == -1) {
+
1896 PERROR("rmdir");
+
1897 return -1;
+
1898 }
+
1899 res = check_nonexist(testdir);
+
1900 if (res == -1)
+
1901 return -1;
+
1902 if (err)
+
1903 return -1;
+
1904
+
1905 success();
+
1906 return 0;
+
1907}
+
1908
+
1909static int test_socket(void)
+
1910{
+
1911 struct sockaddr_un su;
+
1912 int fd;
+
1913 int res;
+
1914 int err = 0;
+
1915 const size_t test_sock_len = strlen(testsock) + 1;
+
1916
+
1917 start_test("socket");
+
1918 if (test_sock_len > sizeof(su.sun_path)) {
+
1919 fprintf(stderr, "Need to shorten mount point by %zu chars\n",
+
1920 strlen(testsock) + 1 - sizeof(su.sun_path));
+
1921 return -1;
+
1922 }
+
1923 unlink(testsock);
+
1924 fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
1925 if (fd < 0) {
+
1926 PERROR("socket");
+
1927 return -1;
+
1928 }
+
1929 su.sun_family = AF_UNIX;
+
1930
+
1931 strncpy(su.sun_path, testsock, test_sock_len);
+
1932 su.sun_path[sizeof(su.sun_path) - 1] = '\0';
+
1933 res = bind(fd, (struct sockaddr*)&su, sizeof(su));
+
1934 if (res == -1) {
+
1935 PERROR("bind");
+
1936 return -1;
+
1937 }
+
1938
+
1939 res = check_type(testsock, S_IFSOCK);
+
1940 if (res == -1) {
+
1941 close(fd);
+
1942 return -1;
+
1943 }
+
1944 err += check_nlink(testsock, 1);
+
1945 close(fd);
+
1946 res = unlink(testsock);
+
1947 if (res == -1) {
+
1948 PERROR("unlink");
+
1949 return -1;
+
1950 }
+
1951 res = check_nonexist(testsock);
+
1952 if (res == -1)
+
1953 return -1;
+
1954 if (err)
+
1955 return -1;
+
1956
+
1957 success();
+
1958 return 0;
+
1959}
+
1960
+
1961#define test_create_ro_dir(flags) \
+
1962 do_test_create_ro_dir(flags, #flags)
+
1963
+
1964static int do_test_create_ro_dir(int flags, const char *flags_str)
+
1965{
+
1966 int res;
+
1967 int err = 0;
+
1968 int fd;
+
1969
+
1970 start_test("open(%s) in read-only directory", flags_str);
+
1971 rmdir(testdir);
+
1972 res = mkdir(testdir, 0555);
+
1973 if (res == -1) {
+
1974 PERROR("mkdir");
+
1975 return -1;
+
1976 }
+
1977 fd = open(subfile, flags, 0644);
+
1978 if (fd != -1) {
+
1979 close(fd);
+
1980 unlink(subfile);
+
1981 ERROR("open should have failed");
+
1982 err--;
+
1983 } else {
+
1984 res = check_nonexist(subfile);
+
1985 if (res == -1)
+
1986 err--;
+
1987 }
+
1988 unlink(subfile);
+
1989 res = rmdir(testdir);
+
1990 if (res == -1) {
+
1991 PERROR("rmdir");
+
1992 return -1;
+
1993 }
+
1994 res = check_nonexist(testdir);
+
1995 if (res == -1)
+
1996 return -1;
+
1997 if (err)
+
1998 return -1;
+
1999
+
2000 success();
+
2001 return 0;
+
2002}
+
2003
+
2004#ifndef __FreeBSD__
+
2005/* this tests open with O_TMPFILE
+
2006 note that this will only work with the fuse low level api
+
2007 you will get ENOTSUP with the high level api */
+
2008static int test_create_tmpfile(void)
+
2009{
+
2010 rmdir(testdir);
+
2011 int res = mkdir(testdir, 0777);
+
2012 if (res)
+
2013 return -1;
+
2014
+
2015 start_test("create tmpfile");
+
2016
+
2017 int fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
+
2018 if(fd == -1) {
+
2019 if (errno == ENOTSUP) {
+
2020 /* don't bother if we're working on an old kernel
+
2021 or on the high level API */
+
2022 return 0;
+
2023 }
+
2024
+
2025 PERROR("open O_TMPFILE | O_RDWR");
+
2026 return -1;
+
2027 }
+
2028 close(fd);
+
2029
+
2030 fd = open(testdir, O_TMPFILE | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
+
2031 if(fd == -1){
+
2032 PERROR("open with O_TMPFILE | O_WRONLY | O_EXCL");
+
2033 return -1;
+
2034 };
+
2035 close(fd);
+
2036
+
2037 fd = open(testdir, O_TMPFILE | O_RDONLY, S_IRUSR);
+
2038 if (fd != -1) {
+
2039 ERROR("open with O_TMPFILE | O_RDONLY succeeded");
+
2040 return -1;
+
2041 }
+
2042
+
2043 success();
+
2044 return 0;
+
2045}
+
2046
+
2047static int test_create_and_link_tmpfile(void)
+
2048{
+
2049 /* skip this test for now since the github runner will fail in the linkat call below */
+
2050 return 0;
+
2051
+
2052 rmdir(testdir);
+
2053 unlink(testfile);
+
2054
+
2055 int res = mkdir(testdir, 0777);
+
2056 if (res)
+
2057 return -1;
+
2058
+
2059 start_test("create and link tmpfile");
+
2060
+
2061 int fd = open(testdir, O_TMPFILE | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR);
+
2062 if(fd == -1) {
+
2063 if (errno == ENOTSUP) {
+
2064 /* don't bother if we're working on an old kernel
+
2065 or on the high level API */
+
2066 return 0;
+
2067 }
+
2068 PERROR("open with O_TMPFILE | O_RDWR | O_EXCL");
+
2069 return -1;
+
2070 }
+
2071
+
2072 if (!linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
+
2073 ERROR("linkat succeeded on a tmpfile opened with O_EXCL");
+
2074 return -1;
+
2075 }
+
2076 close(fd);
+
2077
+
2078 fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
+
2079 if(fd == -1) {
+
2080 PERROR("open O_TMPFILE");
+
2081 return -1;
+
2082 }
+
2083
+
2084 if (check_nonexist(testfile)) {
+
2085 return -1;
+
2086 }
+
2087
+
2088 if (linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
+
2089 PERROR("linkat tempfile");
+
2090 return -1;
+
2091 }
+
2092 close(fd);
+
2093
+
2094 if (check_nlink(testfile, 1)) {
+
2095 return -1;
+
2096 }
+
2097 unlink(testfile);
+
2098
+
2099 success();
+
2100 return 0;
+
2101}
+
2102#endif
+
2103
+
2104int main(int argc, char *argv[])
+
2105{
+
2106 int err = 0;
+
2107 int a;
+
2108 int is_root;
+
2109
+
2110 umask(0);
+
2111 if (argc < 2 || argc > 4) {
+
2112 fprintf(stderr, "usage: %s testdir [:realdir] [[-]test#] [-u]\n", argv[0]);
+
2113 return 1;
+
2114 }
+
2115 basepath = argv[1];
+
2116 basepath_r = basepath;
+
2117 for (a = 2; a < argc; a++) {
+
2118 char *endptr;
+
2119 char *arg = argv[a];
+
2120 if (arg[0] == ':') {
+
2121 basepath_r = arg + 1;
+
2122 } else {
+
2123 if (arg[0] == '-') {
+
2124 arg++;
+
2125 if (arg[0] == 'u') {
+
2126 unlinked_test = 1;
+
2127 endptr = arg + 1;
+
2128 } else {
+
2129 skip_test = strtoul(arg, &endptr, 10);
+
2130 }
+
2131 } else {
+
2132 select_test = strtoul(arg, &endptr, 10);
+
2133 }
+
2134 if (arg[0] == '\0' || *endptr != '\0') {
+
2135 fprintf(stderr, "invalid option: '%s'\n", argv[a]);
+
2136 return 1;
+
2137 }
+
2138 }
+
2139 }
+
2140 assert(strlen(basepath) < 512);
+
2141 assert(strlen(basepath_r) < 512);
+
2142 if (basepath[0] != '/') {
+
2143 fprintf(stderr, "testdir must be an absolute path\n");
+
2144 return 1;
+
2145 }
+
2146
+
2147 sprintf(testfile, "%s/testfile", basepath);
+
2148 sprintf(testfile2, "%s/testfile2", basepath);
+
2149 sprintf(testdir, "%s/testdir", basepath);
+
2150 sprintf(testdir2, "%s/testdir2", basepath);
+
2151 sprintf(subfile, "%s/subfile", testdir2);
+
2152 sprintf(testsock, "%s/testsock", basepath);
+
2153
+
2154 sprintf(testfile_r, "%s/testfile", basepath_r);
+
2155 sprintf(testfile2_r, "%s/testfile2", basepath_r);
+
2156 sprintf(testdir_r, "%s/testdir", basepath_r);
+
2157 sprintf(testdir2_r, "%s/testdir2", basepath_r);
+
2158 sprintf(subfile_r, "%s/subfile", testdir2_r);
+
2159
+
2160 is_root = (geteuid() == 0);
+
2161
+
2162 err += test_create();
+
2163 err += test_create_unlink();
+
2164 err += test_symlink();
+
2165 err += test_link();
+
2166 err += test_link2();
+
2167 err += test_mknod();
+
2168 err += test_mkfifo();
+
2169 err += test_mkdir();
+
2170 err += test_rename_file();
+
2171 err += test_rename_dir();
+
2172 err += test_rename_dir_loop();
+
2173 err += test_seekdir();
+
2174 err += test_socket();
+
2175 err += test_utime();
+
2176 err += test_truncate(0);
+
2177 err += test_truncate(testdatalen / 2);
+
2178 err += test_truncate(testdatalen);
+
2179 err += test_truncate(testdatalen + 100);
+
2180 err += test_ftruncate(0, 0600);
+
2181 err += test_ftruncate(testdatalen / 2, 0600);
+
2182 err += test_ftruncate(testdatalen, 0600);
+
2183 err += test_ftruncate(testdatalen + 100, 0600);
+
2184 err += test_ftruncate(0, 0400);
+
2185 err += test_ftruncate(0, 0200);
+
2186 err += test_ftruncate(0, 0000);
+
2187 err += test_open(0, O_RDONLY, 0);
+
2188 err += test_open(1, O_RDONLY, 0);
+
2189 err += test_open(1, O_RDWR, 0);
+
2190 err += test_open(1, O_WRONLY, 0);
+
2191 err += test_open(0, O_RDWR | O_CREAT, 0600);
+
2192 err += test_open(1, O_RDWR | O_CREAT, 0600);
+
2193 err += test_open(0, O_RDWR | O_CREAT | O_TRUNC, 0600);
+
2194 err += test_open(1, O_RDWR | O_CREAT | O_TRUNC, 0600);
+
2195 err += test_open(0, O_RDONLY | O_CREAT, 0600);
+
2196 err += test_open(0, O_RDONLY | O_CREAT, 0400);
+
2197 err += test_open(0, O_RDONLY | O_CREAT, 0200);
+
2198 err += test_open(0, O_RDONLY | O_CREAT, 0000);
+
2199 err += test_open(0, O_WRONLY | O_CREAT, 0600);
+
2200 err += test_open(0, O_WRONLY | O_CREAT, 0400);
+
2201 err += test_open(0, O_WRONLY | O_CREAT, 0200);
+
2202 err += test_open(0, O_WRONLY | O_CREAT, 0000);
+
2203 err += test_open(0, O_RDWR | O_CREAT, 0400);
+
2204 err += test_open(0, O_RDWR | O_CREAT, 0200);
+
2205 err += test_open(0, O_RDWR | O_CREAT, 0000);
+
2206 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0600);
+
2207 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0600);
+
2208 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0000);
+
2209 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0000);
+
2210 err += test_open_acc(O_RDONLY, 0600, 0);
+
2211 err += test_open_acc(O_WRONLY, 0600, 0);
+
2212 err += test_open_acc(O_RDWR, 0600, 0);
+
2213 err += test_open_acc(O_RDONLY, 0400, 0);
+
2214 err += test_open_acc(O_WRONLY, 0200, 0);
+
2215 if(!is_root) {
+
2216 err += test_open_acc(O_RDONLY | O_TRUNC, 0400, EACCES);
+
2217 err += test_open_acc(O_WRONLY, 0400, EACCES);
+
2218 err += test_open_acc(O_RDWR, 0400, EACCES);
+
2219 err += test_open_acc(O_RDONLY, 0200, EACCES);
+
2220 err += test_open_acc(O_RDWR, 0200, EACCES);
+
2221 err += test_open_acc(O_RDONLY, 0000, EACCES);
+
2222 err += test_open_acc(O_WRONLY, 0000, EACCES);
+
2223 err += test_open_acc(O_RDWR, 0000, EACCES);
+
2224 }
+
2225 err += test_create_ro_dir(O_CREAT);
+
2226 err += test_create_ro_dir(O_CREAT | O_EXCL);
+
2227 err += test_create_ro_dir(O_CREAT | O_WRONLY);
+
2228 err += test_create_ro_dir(O_CREAT | O_TRUNC);
+
2229 err += test_copy_file_range();
+
2230 err += test_statx();
+
2231#ifndef __FreeBSD__
+
2232 err += test_create_tmpfile();
+
2233 err += test_create_and_link_tmpfile();
+
2234#endif
+
2235
+
2236 unlink(testfile2);
+
2237 unlink(testsock);
+
2238 rmdir(testdir);
+
2239 rmdir(testdir2);
+
2240
+
2241 if (err) {
+
2242 fprintf(stderr, "%i tests failed\n", -err);
+
2243 return 1;
+
2244 }
+
2245
+
2246 return check_unlinked_testfiles();
+
2247}
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2test_2test__want__conversion_8c_source.html b/doc/html/fuse-3_818_81_2test_2test__want__conversion_8c_source.html new file mode 100644 index 0000000..e2b8012 --- /dev/null +++ b/doc/html/fuse-3_818_81_2test_2test__want__conversion_8c_source.html @@ -0,0 +1,264 @@ + + + + + + + +libfuse: fuse-3.18.1/test/test_want_conversion.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_want_conversion.c
+
+
+
1#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17)
+
2
+
3#include "util.h"
+
4#include "fuse_i.h"
+
5#include "fuse_lowlevel.h"
+
6#include <stdio.h>
+
7#include <assert.h>
+
8#include <inttypes.h>
+
9#include <stdbool.h>
+
10#include <err.h>
+
11
+
12static void print_conn_info(const char *prefix, struct fuse_conn_info *conn)
+
13{
+
14 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
15
+
16 printf("%s: want=0x%" PRIx32 " want_ext=0x%" PRIx64
+
17 " want_default=0x%" PRIx32 " want_ext_default=0x%" PRIx64 "\n",
+
18 prefix, conn->want, conn->want_ext, se->conn_want,
+
19 se->conn_want_ext);
+
20}
+
21
+
22static void application_init_old_style(struct fuse_conn_info *conn)
+
23{
+
24 /* Simulate application init the old style */
+ +
26 conn->want &= ~FUSE_CAP_SPLICE_READ;
+
27
+
28 /*
+
29 * Also use new style API, as that might happen through
+
30 * fuse_apply_conn_info_opts()
+
31 */
+ +
33}
+
34
+
35static void application_init_new_style(struct fuse_conn_info *conn)
+
36{
+
37 /* Simulate application init the new style */
+ + + +
41}
+
42
+
43static void test_fuse_fs_init(struct fuse_conn_info *conn, bool new_style)
+
44{
+
45 /* High-level init */
+ +
47
+
48 if (new_style)
+
49 application_init_new_style(conn);
+
50 else
+
51 application_init_old_style(conn);
+
52}
+
53
+
54static void test_do_init(struct fuse_conn_info *conn, bool new_style)
+
55{
+
56 /* Initial setup */
+ + + + +
61 conn->capable = fuse_lower_32_bits(conn->capable_ext);
+
62
+ + + +
66
+
67 print_conn_info("Initial state", conn);
+
68
+
69 int rc;
+
70
+
71 test_fuse_fs_init(conn, new_style);
+
72 print_conn_info("After init", conn);
+
73
+ +
75 assert(rc == 0);
+
76
+
77 /* Verify all expected flags are set */
+
78 assert(!(conn->want_ext & FUSE_CAP_SPLICE_READ));
+
79 assert(conn->want_ext & FUSE_CAP_SPLICE_WRITE);
+
80 assert(conn->want_ext & FUSE_CAP_SPLICE_MOVE);
+
81 assert(conn->want_ext & FUSE_CAP_EXPORT_SUPPORT);
+
82 assert(conn->want_ext & FUSE_CAP_ASYNC_READ);
+
83 assert(conn->want_ext & FUSE_CAP_IOCTL_DIR);
+
84
+
85 /* Verify no other flags are set */
+
86 assert(conn->want_ext ==
+ + + +
90
+
91 print_conn_info("After init", conn);
+
92}
+
93
+
94static void test_want_conversion_basic(void)
+
95{
+
96 const struct fuse_lowlevel_ops ops = { 0 };
+
97 struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+
98 struct fuse_session *se;
+
99 struct fuse_conn_info *conn;
+
100
+
101 /* Add the program name to arg[0] */
+
102 if (fuse_opt_add_arg(&args, "test_signals")) {
+
103 fprintf(stderr, "Failed to add argument\n");
+
104 errx(1, "Failed to add argument");
+
105 }
+
106
+
107
+
108 se = fuse_session_new(&args, &ops, sizeof(ops), NULL);
+
109 assert(se);
+
110 conn = &se->conn;
+
111 printf("\nTesting basic want conversion, old style:\n");
+
112 test_do_init(conn, false);
+ +
114
+
115 se = fuse_session_new(&args, &ops, sizeof(ops), NULL);
+
116 assert(se);
+
117 conn = &se->conn;
+
118 printf("\nTesting basic want conversion, new style:\n");
+
119 test_do_init(conn, true);
+
120 print_conn_info("After init", conn);
+ +
122
+
123 fuse_opt_free_args(&args);
+
124
+
125}
+
126
+
127static void test_want_conversion_conflict(void)
+
128{
+
129 struct fuse_conn_info conn = { 0 };
+
130 int rc;
+
131
+
132 printf("\nTesting want conversion conflict:\n");
+
133
+
134 /* Test conflicting values */
+
135 /* Initialize like fuse_lowlevel.c does */
+ + + +
139 conn.capable = fuse_lower_32_bits(conn.capable_ext);
+
140 conn.want_ext = conn.capable_ext;
+
141 conn.want = fuse_lower_32_bits(conn.want_ext);
+
142 print_conn_info("Test conflict initial", &conn);
+
143
+
144 /* Simulate application init modifying capabilities */
+
145 conn.want_ext |= FUSE_CAP_ATOMIC_O_TRUNC; /* Add new capability */
+
146 conn.want &= ~FUSE_CAP_SPLICE_READ; /* Remove a capability */
+
147
+ +
149 assert(rc == -EINVAL);
+
150 print_conn_info("Test conflict after", &conn);
+
151
+
152 printf("Want conversion conflict test passed\n");
+
153}
+
154
+
155static void test_want_conversion_high_bits(void)
+
156{
+
157 struct fuse_conn_info conn = { 0 };
+
158 int rc;
+
159
+
160 printf("\nTesting want conversion high bits preservation:\n");
+
161
+
162 /* Test high bits preservation */
+
163 conn.want_ext = (1ULL << 33) | FUSE_CAP_ASYNC_READ;
+
164 conn.want = fuse_lower_32_bits(conn.want_ext);
+
165 print_conn_info("Test high bits initial", &conn);
+
166
+ +
168 assert(rc == 0);
+
169 assert(conn.want_ext == ((1ULL << 33) | FUSE_CAP_ASYNC_READ));
+
170 print_conn_info("Test high bits after", &conn);
+
171
+
172 printf("Want conversion high bits test passed\n");
+
173}
+
174
+
175int main(void)
+
176{
+
177 test_want_conversion_basic();
+
178 test_want_conversion_conflict();
+
179 test_want_conversion_high_bits();
+
180 return 0;
+
181}
+
#define FUSE_CAP_IOCTL_DIR
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + + +
uint64_t capable_ext
+
uint32_t capable
+
uint64_t want_ext
+ +
+ + + + diff --git a/doc/html/fuse-3_818_81_2test_2test__write__cache_8c_source.html b/doc/html/fuse-3_818_81_2test_2test__write__cache_8c_source.html new file mode 100644 index 0000000..d315de1 --- /dev/null +++ b/doc/html/fuse-3_818_81_2test_2test__write__cache_8c_source.html @@ -0,0 +1,412 @@ + + + + + + + +libfuse: fuse-3.18.1/test/test_write_cache.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_write_cache.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
9#define FUSE_USE_VERSION 30
+
10
+
11/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
+
12#include <fuse.h>
+
13
+
14#include <fuse_config.h>
+
15#include <fuse_lowlevel.h>
+
16#include <stdio.h>
+
17#include <stdlib.h>
+
18#include <string.h>
+
19#include <errno.h>
+
20#include <fcntl.h>
+
21#include <assert.h>
+
22#include <stddef.h>
+
23#include <unistd.h>
+
24#include <sys/stat.h>
+
25#include <pthread.h>
+
26#include <stdatomic.h>
+
27
+
28#ifndef __linux__
+
29#include <limits.h>
+
30#else
+
31#include <linux/limits.h>
+
32#endif
+
33
+
34#define FILE_INO 2
+
35#define FILE_NAME "write_me"
+
36
+
37/* Command line parsing */
+
38struct options {
+
39 int writeback;
+
40 int data_size;
+
41 int delay_ms;
+
42} options = {
+
43 .writeback = 0,
+
44 .data_size = 2048,
+
45 .delay_ms = 0,
+
46};
+
47
+
48#define WRITE_SYSCALLS 64
+
49
+
50#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
51static const struct fuse_opt option_spec[] = {
+
52 OPTION("writeback_cache", writeback),
+
53 OPTION("--data-size=%d", data_size), OPTION("--delay_ms=%d", delay_ms),
+ +
55};
+
56static int got_write;
+
57static atomic_int write_cnt;
+
58
+
59pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+
60pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
61static int write_start, write_done;
+
62
+
63static void tfs_init(void *userdata, struct fuse_conn_info *conn)
+
64{
+
65 (void)userdata;
+
66
+
67 if (options.writeback) {
+ + +
70 }
+
71}
+
72
+
73static int tfs_stat(fuse_ino_t ino, struct stat *stbuf)
+
74{
+
75 stbuf->st_ino = ino;
+
76 if (ino == FUSE_ROOT_ID) {
+
77 stbuf->st_mode = S_IFDIR | 0755;
+
78 stbuf->st_nlink = 1;
+
79 }
+
80
+
81 else if (ino == FILE_INO) {
+
82 stbuf->st_mode = S_IFREG | 0222;
+
83 stbuf->st_nlink = 1;
+
84 stbuf->st_size = 0;
+
85 }
+
86
+
87 else
+
88 return -1;
+
89
+
90 return 0;
+
91}
+
92
+
93static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
94{
+
95 struct fuse_entry_param e;
+
96
+
97 memset(&e, 0, sizeof(e));
+
98
+
99 if (parent != FUSE_ROOT_ID)
+
100 goto err_out;
+
101 else if (strcmp(name, FILE_NAME) == 0)
+
102 e.ino = FILE_INO;
+
103 else
+
104 goto err_out;
+
105
+
106 if (tfs_stat(e.ino, &e.attr) != 0)
+
107 goto err_out;
+
108 fuse_reply_entry(req, &e);
+
109 return;
+
110
+
111err_out:
+
112 fuse_reply_err(req, ENOENT);
+
113}
+
114
+
115static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
116 struct fuse_file_info *fi)
+
117{
+
118 struct stat stbuf;
+
119
+
120 (void)fi;
+
121
+
122 memset(&stbuf, 0, sizeof(stbuf));
+
123 if (tfs_stat(ino, &stbuf) != 0)
+
124 fuse_reply_err(req, ENOENT);
+
125 else
+
126 fuse_reply_attr(req, &stbuf, 5);
+
127}
+
128
+
129static void tfs_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
130{
+
131 if (ino == FUSE_ROOT_ID)
+
132 fuse_reply_err(req, EISDIR);
+
133 else {
+
134 assert(ino == FILE_INO);
+
135 /* Test close(rofd) does not block waiting for pending writes */
+
136 fi->noflush = !options.writeback && options.delay_ms &&
+
137 (fi->flags & O_ACCMODE) == O_RDONLY;
+
138 fuse_reply_open(req, fi);
+
139 }
+
140}
+
141
+
142static void tfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+
143 size_t size, off_t off, struct fuse_file_info *fi)
+
144{
+
145 (void)fi;
+
146 (void)buf;
+
147 (void)off;
+
148 size_t expected;
+
149
+
150 assert(ino == FILE_INO);
+
151 expected = options.data_size;
+
152 if (options.writeback)
+
153 expected *= 2;
+
154
+
155 write_cnt++;
+
156
+
157 if (size != expected && !options.writeback)
+
158 fprintf(stderr, "ERROR: Expected %zd bytes, got %zd\n!",
+
159 expected, size);
+
160 else
+
161 got_write = 1;
+
162
+
163 /* Simulate waiting for pending writes */
+
164 if (options.delay_ms) {
+
165 pthread_mutex_lock(&lock);
+
166 write_start = 1;
+
167 pthread_cond_signal(&cond);
+
168 pthread_mutex_unlock(&lock);
+
169
+
170 usleep(options.delay_ms * 1000);
+
171
+
172 pthread_mutex_lock(&lock);
+
173 write_done = 1;
+
174 pthread_cond_signal(&cond);
+
175 pthread_mutex_unlock(&lock);
+
176 }
+
177
+
178 fuse_reply_write(req, size);
+
179}
+
180
+
181static struct fuse_lowlevel_ops tfs_oper = {
+
182 .init = tfs_init,
+
183 .lookup = tfs_lookup,
+
184 .getattr = tfs_getattr,
+
185 .open = tfs_open,
+
186 .write = tfs_write,
+
187};
+
188
+
189static void *close_rofd(void *data)
+
190{
+
191 int rofd = (int)(long)data;
+
192
+
193 /* Wait for first write to start */
+
194 pthread_mutex_lock(&lock);
+
195 while (!write_start && !write_done)
+
196 pthread_cond_wait(&cond, &lock);
+
197 pthread_mutex_unlock(&lock);
+
198
+
199 close(rofd);
+
200 printf("rofd closed. write_start: %d write_done: %d\n", write_start,
+
201 write_done);
+
202
+
203 /* First write should not have been completed */
+
204 if (write_done)
+
205 fprintf(stderr, "ERROR: close(rofd) blocked on write!\n");
+
206
+
207 return NULL;
+
208}
+
209
+
210static void *run_fs(void *data)
+
211{
+
212 struct fuse_session *se = (struct fuse_session *)data;
+
213
+
214 assert(fuse_session_loop(se) == 0);
+
215 return NULL;
+
216}
+
217
+
218static void test_fs(char *mountpoint)
+
219{
+
220 char fname[PATH_MAX];
+
221 char *buf;
+
222 const size_t iosize = options.data_size;
+
223 const size_t dsize = options.data_size * WRITE_SYSCALLS;
+
224 int fd, rofd;
+
225 pthread_t rofd_thread;
+
226 off_t off = 0;
+
227
+
228 buf = malloc(dsize);
+
229 assert(buf != NULL);
+
230 assert((fd = open("/dev/urandom", O_RDONLY)) != -1);
+
231 assert(read(fd, buf, dsize) == dsize);
+
232 close(fd);
+
233
+
234 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME, mountpoint) > 0);
+
235 fd = open(fname, O_WRONLY);
+
236 if (fd == -1) {
+
237 perror(fname);
+
238 assert(0);
+
239 }
+
240
+
241 if (options.delay_ms) {
+
242 /* Verify that close(rofd) does not block waiting for pending writes */
+
243 rofd = open(fname, O_RDONLY);
+
244 assert(pthread_create(&rofd_thread, NULL, close_rofd,
+
245 (void *)(long)rofd) == 0);
+
246 /* Give close_rofd time to start */
+
247 usleep(options.delay_ms * 1000);
+
248 }
+
249
+
250 for (int cnt = 0; cnt < WRITE_SYSCALLS; cnt++) {
+
251 assert(pwrite(fd, buf + off, iosize, off) == iosize);
+
252 off += iosize;
+
253 assert(off <= dsize);
+
254 }
+
255 free(buf);
+
256 close(fd);
+
257
+
258 if (options.delay_ms) {
+
259 printf("rwfd closed. write_start: %d write_done: %d\n",
+
260 write_start, write_done);
+
261 assert(pthread_join(rofd_thread, NULL) == 0);
+
262 }
+
263}
+
264
+
265int main(int argc, char *argv[])
+
266{
+
267 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
268 struct fuse_session *se;
+
269 struct fuse_cmdline_opts fuse_opts;
+
270 pthread_t fs_thread;
+
271
+
272 assert(fuse_opt_parse(&args, &options, option_spec, NULL) == 0);
+
273 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
+
274#ifndef __FreeBSD__
+
275 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
+
276#endif
+
277 se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), NULL);
+
278 fuse_opt_free_args(&args);
+
279 assert(se != NULL);
+
280 assert(fuse_set_signal_handlers(se) == 0);
+
281 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
+
282
+
283 /* Start file-system thread */
+
284 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
+
285
+
286 /* Write test data */
+
287 test_fs(fuse_opts.mountpoint);
+
288 free(fuse_opts.mountpoint);
+
289
+
290 /* Stop file system */
+ + +
293 assert(pthread_join(fs_thread, NULL) == 0);
+
294
+
295 assert(got_write == 1);
+
296
+
297 /*
+
298 * when writeback cache is enabled, kernel side can merge requests, but
+
299 * memory pressure, system 'sync' might trigger data flushes before - flush
+
300 * might happen in between write syscalls - merging subpage writes into
+
301 * a single page and pages into large fuse requests might or might not work.
+
302 * Though we can expect that that at least some (but maybe all) write
+
303 * system calls can be merged.
+
304 */
+
305 if (options.writeback)
+
306 assert(write_cnt < WRITE_SYSCALLS);
+
307 else
+
308 assert(write_cnt == WRITE_SYSCALLS);
+
309
+ + +
312
+
313 printf("Test completed successfully.\n");
+
314 return 0;
+
315}
+
316
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_WRITEBACK_CACHE
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
uint64_t fuse_ino_t
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
+
fuse_ino_t ino
+ +
uint32_t noflush
Definition fuse_common.h:93
+ + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_818_81_2test_2wrong__command_8c_source.html b/doc/html/fuse-3_818_81_2test_2wrong__command_8c_source.html new file mode 100644 index 0000000..b5aa685 --- /dev/null +++ b/doc/html/fuse-3_818_81_2test_2wrong__command_8c_source.html @@ -0,0 +1,76 @@ + + + + + + + +libfuse: fuse-3.18.1/test/wrong_command.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
wrong_command.c
+
+
+
1#include <stdio.h>
+
2
+
3int main(void) {
+
4#ifdef MESON_IS_SUBPROJECT
+
5 fprintf(stderr, "libfuse tests were skipped because it's a meson subproject.\n"
+
6 "If you wish to run them try:\n"
+
7 "'cd <srcdir>/subprojects/libfuse && meson . build && cd build && python3 -m pytest test/' instead");
+
8 return 77; /* report as a skipped test */
+
9#else
+
10 fprintf(stderr, "\x1B[31m\e[1m"
+
11 "This is not the command you are looking for.\n"
+
12 "You probably want to run 'python3 -m pytest test/' instead"
+
13 "\e[0m\n");
+
14 return 1;
+
15#endif
+
16}
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2util_2fusermount_8c_source.html b/doc/html/fuse-3_818_81_2util_2fusermount_8c_source.html new file mode 100644 index 0000000..43305e3 --- /dev/null +++ b/doc/html/fuse-3_818_81_2util_2fusermount_8c_source.html @@ -0,0 +1,1843 @@ + + + + + + + +libfuse: fuse-3.18.1/util/fusermount.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fusermount.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8/* This program does the mounting and unmounting of FUSE filesystems */
+
9
+
10#define _GNU_SOURCE /* for clone,strchrnul and close_range */
+
11#include "fuse_config.h"
+
12#include "mount_util.h"
+
13#include "util.h"
+
14
+
15#include <stdio.h>
+
16#include <stdlib.h>
+
17#include <string.h>
+
18#include <ctype.h>
+
19#include <unistd.h>
+
20#include <getopt.h>
+
21#include <errno.h>
+
22#include <fcntl.h>
+
23#include <pwd.h>
+
24#include <paths.h>
+
25#include <mntent.h>
+
26#include <sys/wait.h>
+
27#include <sys/stat.h>
+
28#include <sys/param.h>
+
29
+
30#include "fuse_mount_compat.h"
+
31
+
32#include <sys/fsuid.h>
+
33#include <sys/socket.h>
+
34#include <sys/utsname.h>
+
35#include <sched.h>
+
36#include <stdbool.h>
+
37#include <sys/vfs.h>
+
38
+
39#if defined HAVE_CLOSE_RANGE && defined linux
+
40#include <linux/close_range.h>
+
41#endif
+
42
+
43#if defined HAVE_LISTMOUNT
+
44#include <linux/mount.h>
+
45#include <syscall.h>
+
46#include <stdint.h>
+
47#endif
+
48
+
49#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
+
50#define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE"
+
51
+
52#define FUSE_DEV "/dev/fuse"
+
53
+
54static const char *progname;
+
55
+
56static int user_allow_other = 0;
+
57static int mount_max = 1000;
+
58
+
59static int auto_unmount = 0;
+
60
+
61#ifdef GETMNTENT_NEEDS_UNESCAPING
+
62// Older versions of musl libc don't unescape entries in /etc/mtab
+
63
+
64// unescapes octal sequences like \040 in-place
+
65// That's ok, because unescaping can not extend the length of the string.
+
66static void unescape(char *buf) {
+
67 char *src = buf;
+
68 char *dest = buf;
+
69 while (1) {
+
70 char *next_src = strchrnul(src, '\\');
+
71 int offset = next_src - src;
+
72 memmove(dest, src, offset);
+
73 src = next_src;
+
74 dest += offset;
+
75
+
76 if(*src == '\0') {
+
77 *dest = *src;
+
78 return;
+
79 }
+
80 src++;
+
81
+
82 if('0' <= src[0] && src[0] < '2' &&
+
83 '0' <= src[1] && src[1] < '8' &&
+
84 '0' <= src[2] && src[2] < '8') {
+
85 *dest++ = (src[0] - '0') << 6
+
86 | (src[1] - '0') << 3
+
87 | (src[2] - '0') << 0;
+
88 src += 3;
+
89 } else if (src[0] == '\\') {
+
90 *dest++ = '\\';
+
91 src += 1;
+
92 } else {
+
93 *dest++ = '\\';
+
94 }
+
95 }
+
96}
+
97
+
98static struct mntent *GETMNTENT(FILE *stream)
+
99{
+
100 struct mntent *entp = getmntent(stream);
+
101 if(entp != NULL) {
+
102 unescape(entp->mnt_fsname);
+
103 unescape(entp->mnt_dir);
+
104 unescape(entp->mnt_type);
+
105 unescape(entp->mnt_opts);
+
106 }
+
107 return entp;
+
108}
+
109#else
+
110#define GETMNTENT getmntent
+
111#endif // GETMNTENT_NEEDS_UNESCAPING
+
112
+
113/*
+
114 * Take a ',' separated option string and extract "x-" options
+
115 */
+
116static int extract_x_options(const char *original, char **non_x_opts,
+
117 char **x_opts)
+
118{
+
119 size_t orig_len;
+
120 const char *opt, *opt_end;
+
121
+
122 orig_len = strlen(original) + 1;
+
123
+
124 *non_x_opts = calloc(1, orig_len);
+
125 *x_opts = calloc(1, orig_len);
+
126
+
127 size_t non_x_opts_len = orig_len;
+
128 size_t x_opts_len = orig_len;
+
129
+
130 if (*non_x_opts == NULL || *x_opts == NULL) {
+
131 fprintf(stderr, "%s: Failed to allocate %zuB.\n",
+
132 __func__, orig_len);
+
133 return -ENOMEM;
+
134 }
+
135
+
136 for (opt = original; opt < original + orig_len; opt = opt_end + 1) {
+
137 char *opt_buf;
+
138
+
139 opt_end = strchr(opt, ',');
+
140 if (opt_end == NULL)
+
141 opt_end = original + orig_len;
+
142
+
143 size_t opt_len = opt_end - opt;
+
144 size_t opt_len_left = orig_len - (opt - original);
+
145 size_t buf_len;
+
146 bool is_x_opts;
+
147
+
148 if (strncmp(opt, "x-", MIN(2, opt_len_left)) == 0) {
+
149 buf_len = x_opts_len;
+
150 is_x_opts = true;
+
151 opt_buf = *x_opts;
+
152 } else {
+
153 buf_len = non_x_opts_len;
+
154 is_x_opts = false;
+
155 opt_buf = *non_x_opts;
+
156 }
+
157
+
158 if (buf_len < orig_len) {
+
159 strncat(opt_buf, ",", 2);
+
160 buf_len -= 1;
+
161 }
+
162
+
163 /* omits ',' */
+
164 if ((ssize_t)(buf_len - opt_len) < 0) {
+
165 /* This would be a bug */
+
166 fprintf(stderr, "%s: no buf space left in copy, orig='%s'\n",
+
167 __func__, original);
+
168 return -EIO;
+
169 }
+
170
+
171 strncat(opt_buf, opt, opt_end - opt);
+
172 buf_len -= opt_len;
+
173
+
174 if (is_x_opts)
+
175 x_opts_len = buf_len;
+
176 else
+
177 non_x_opts_len = buf_len;
+
178 }
+
179
+
180 return 0;
+
181}
+
182
+
183static const char *get_user_name(void)
+
184{
+
185 struct passwd *pw = getpwuid(getuid());
+
186 if (pw != NULL && pw->pw_name != NULL)
+
187 return pw->pw_name;
+
188 else {
+
189 fprintf(stderr, "%s: could not determine username\n", progname);
+
190 return NULL;
+
191 }
+
192}
+
193
+
194static uid_t oldfsuid;
+
195static gid_t oldfsgid;
+
196
+
197static void drop_privs(void)
+
198{
+
199 if (getuid() != 0) {
+
200 oldfsuid = setfsuid(getuid());
+
201 oldfsgid = setfsgid(getgid());
+
202 }
+
203}
+
204
+
205static void restore_privs(void)
+
206{
+
207 if (getuid() != 0) {
+
208 setfsuid(oldfsuid);
+
209 setfsgid(oldfsgid);
+
210 }
+
211}
+
212
+
213#ifndef IGNORE_MTAB
+
214/*
+
215 * Make sure that /etc/mtab is checked and updated atomically
+
216 */
+
217static int lock_umount(void)
+
218{
+
219 const char *mtab_lock = _PATH_MOUNTED ".fuselock";
+
220 int mtablock;
+
221 int res;
+
222 struct stat mtab_stat;
+
223
+
224 /* /etc/mtab could be a symlink to /proc/mounts */
+
225 if (lstat(_PATH_MOUNTED, &mtab_stat) == 0 && S_ISLNK(mtab_stat.st_mode))
+
226 return -1;
+
227
+
228 mtablock = open(mtab_lock, O_RDWR | O_CREAT, 0600);
+
229 if (mtablock == -1) {
+
230 fprintf(stderr, "%s: unable to open fuse lock file: %s\n",
+
231 progname, strerror(errno));
+
232 return -1;
+
233 }
+
234 res = lockf(mtablock, F_LOCK, 0);
+
235 if (res < 0) {
+
236 fprintf(stderr, "%s: error getting lock: %s\n", progname,
+
237 strerror(errno));
+
238 close(mtablock);
+
239 return -1;
+
240 }
+
241
+
242 return mtablock;
+
243}
+
244
+
245static void unlock_umount(int mtablock)
+
246{
+
247 if (mtablock >= 0) {
+
248 int res;
+
249
+
250 res = lockf(mtablock, F_ULOCK, 0);
+
251 if (res < 0) {
+
252 fprintf(stderr, "%s: error releasing lock: %s\n",
+
253 progname, strerror(errno));
+
254 }
+
255 close(mtablock);
+
256 }
+
257}
+
258
+
259static int add_mount(const char *source, const char *mnt, const char *type,
+
260 const char *opts)
+
261{
+
262 return fuse_mnt_add_mount(progname, source, mnt, type, opts);
+
263}
+
264
+
265static int may_unmount(const char *mnt, int quiet)
+
266{
+
267 struct mntent *entp;
+
268 FILE *fp;
+
269 const char *user = NULL;
+
270 char uidstr[32];
+
271 unsigned uidlen = 0;
+
272 int found;
+
273 const char *mtab = _PATH_MOUNTED;
+
274
+
275 user = get_user_name();
+
276 if (user == NULL)
+
277 return -1;
+
278
+
279 fp = setmntent(mtab, "r");
+
280 if (fp == NULL) {
+
281 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
+
282 strerror(errno));
+
283 return -1;
+
284 }
+
285
+
286 uidlen = sprintf(uidstr, "%u", getuid());
+
287
+
288 found = 0;
+
289 while ((entp = GETMNTENT(fp)) != NULL) {
+
290 if (!found && strcmp(entp->mnt_dir, mnt) == 0 &&
+
291 (strcmp(entp->mnt_type, "fuse") == 0 ||
+
292 strcmp(entp->mnt_type, "fuseblk") == 0 ||
+
293 strncmp(entp->mnt_type, "fuse.", 5) == 0 ||
+
294 strncmp(entp->mnt_type, "fuseblk.", 8) == 0)) {
+
295 char *p = strstr(entp->mnt_opts, "user=");
+
296 if (p &&
+
297 (p == entp->mnt_opts || *(p-1) == ',') &&
+
298 strcmp(p + 5, user) == 0) {
+
299 found = 1;
+
300 break;
+
301 }
+
302 /* /etc/mtab is a link pointing to
+
303 /proc/mounts: */
+
304 else if ((p =
+
305 strstr(entp->mnt_opts, "user_id=")) &&
+
306 (p == entp->mnt_opts ||
+
307 *(p-1) == ',') &&
+
308 strncmp(p + 8, uidstr, uidlen) == 0 &&
+
309 (*(p+8+uidlen) == ',' ||
+
310 *(p+8+uidlen) == '\0')) {
+
311 found = 1;
+
312 break;
+
313 }
+
314 }
+
315 }
+
316 endmntent(fp);
+
317
+
318 if (!found) {
+
319 if (!quiet)
+
320 fprintf(stderr,
+
321 "%s: entry for %s not found in %s\n",
+
322 progname, mnt, mtab);
+
323 return -1;
+
324 }
+
325
+
326 return 0;
+
327}
+
328#endif
+
329
+
330/*
+
331 * Check whether the file specified in "fusermount3 -u" is really a
+
332 * mountpoint and not a symlink. This is necessary otherwise the user
+
333 * could move the mountpoint away and replace it with a symlink
+
334 * pointing to an arbitrary mount, thereby tricking fusermount3 into
+
335 * unmounting that (umount(2) will follow symlinks).
+
336 *
+
337 * This is the child process running in a separate mount namespace, so
+
338 * we don't mess with the global namespace and if the process is
+
339 * killed for any reason, mounts are automatically cleaned up.
+
340 *
+
341 * First make sure nothing is propagated back into the parent
+
342 * namespace by marking all mounts "private".
+
343 *
+
344 * Then bind mount parent onto a stable base where the user can't move
+
345 * it around.
+
346 *
+
347 * Finally check /proc/mounts for an entry matching the requested
+
348 * mountpoint. If it's found then we are OK, and the user can't move
+
349 * it around within the parent directory as rename() will return
+
350 * EBUSY. Be careful to ignore any mounts that existed before the
+
351 * bind.
+
352 */
+
353static int check_is_mount_child(void *p)
+
354{
+
355 const char **a = p;
+
356 const char *last = a[0];
+
357 const char *mnt = a[1];
+
358 const char *type = a[2];
+
359 int res;
+
360 const char *procmounts = "/proc/mounts";
+
361 int found;
+
362 FILE *fp;
+
363 struct mntent *entp;
+
364 int count;
+
365
+
366 res = mount("", "/", "", MS_PRIVATE | MS_REC, NULL);
+
367 if (res == -1) {
+
368 fprintf(stderr, "%s: failed to mark mounts private: %s\n",
+
369 progname, strerror(errno));
+
370 return 1;
+
371 }
+
372
+
373 fp = setmntent(procmounts, "r");
+
374 if (fp == NULL) {
+
375 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
+
376 procmounts, strerror(errno));
+
377 return 1;
+
378 }
+
379
+
380 count = 0;
+
381 while (GETMNTENT(fp) != NULL)
+
382 count++;
+
383 endmntent(fp);
+
384
+
385 fp = setmntent(procmounts, "r");
+
386 if (fp == NULL) {
+
387 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
+
388 procmounts, strerror(errno));
+
389 return 1;
+
390 }
+
391
+
392 res = mount(".", "/", "", MS_BIND | MS_REC, NULL);
+
393 if (res == -1) {
+
394 fprintf(stderr, "%s: failed to bind parent to /: %s\n",
+
395 progname, strerror(errno));
+
396 return 1;
+
397 }
+
398
+
399 found = 0;
+
400 while ((entp = GETMNTENT(fp)) != NULL) {
+
401 if (count > 0) {
+
402 count--;
+
403 continue;
+
404 }
+
405 if (entp->mnt_dir[0] == '/' &&
+
406 strcmp(entp->mnt_dir + 1, last) == 0 &&
+
407 (!type || strcmp(entp->mnt_type, type) == 0)) {
+
408 found = 1;
+
409 break;
+
410 }
+
411 }
+
412 endmntent(fp);
+
413
+
414 if (!found) {
+
415 fprintf(stderr, "%s: %s not mounted\n", progname, mnt);
+
416 return 1;
+
417 }
+
418
+
419 return 0;
+
420}
+
421
+
422static pid_t clone_newns(void *a)
+
423{
+
424 char buf[131072];
+
425 char *stack = buf + (sizeof(buf) / 2 - ((size_t) buf & 15));
+
426
+
427#ifdef __ia64__
+
428 extern int __clone2(int (*fn)(void *),
+
429 void *child_stack_base, size_t stack_size,
+
430 int flags, void *arg, pid_t *ptid,
+
431 void *tls, pid_t *ctid);
+
432
+
433 return __clone2(check_is_mount_child, stack, sizeof(buf) / 2,
+
434 CLONE_NEWNS, a, NULL, NULL, NULL);
+
435#else
+
436 return clone(check_is_mount_child, stack, CLONE_NEWNS, a);
+
437#endif
+
438}
+
439
+
440static int check_is_mount(const char *last, const char *mnt, const char *type)
+
441{
+
442 pid_t pid, p;
+
443 int status;
+
444 const char *a[3] = { last, mnt, type };
+
445
+
446 pid = clone_newns((void *) a);
+
447 if (pid == (pid_t) -1) {
+
448 fprintf(stderr, "%s: failed to clone namespace: %s\n",
+
449 progname, strerror(errno));
+
450 return -1;
+
451 }
+
452 p = waitpid(pid, &status, __WCLONE);
+
453 if (p == (pid_t) -1) {
+
454 fprintf(stderr, "%s: waitpid failed: %s\n",
+
455 progname, strerror(errno));
+
456 return -1;
+
457 }
+
458 if (!WIFEXITED(status)) {
+
459 fprintf(stderr, "%s: child terminated abnormally (status %i)\n",
+
460 progname, status);
+
461 return -1;
+
462 }
+
463 if (WEXITSTATUS(status) != 0)
+
464 return -1;
+
465
+
466 return 0;
+
467}
+
468
+
469static int chdir_to_parent(char *copy, const char **lastp)
+
470{
+
471 char *tmp;
+
472 const char *parent;
+
473 char buf[65536];
+
474 int res;
+
475
+
476 tmp = strrchr(copy, '/');
+
477 if (tmp == NULL || tmp[1] == '\0') {
+
478 fprintf(stderr, "%s: internal error: invalid abs path: <%s>\n",
+
479 progname, copy);
+
480 return -1;
+
481 }
+
482 if (tmp != copy) {
+
483 *tmp = '\0';
+
484 parent = copy;
+
485 *lastp = tmp + 1;
+
486 } else if (tmp[1] != '\0') {
+
487 *lastp = tmp + 1;
+
488 parent = "/";
+
489 } else {
+
490 *lastp = ".";
+
491 parent = "/";
+
492 }
+
493
+
494 res = chdir(parent);
+
495 if (res == -1) {
+
496 fprintf(stderr, "%s: failed to chdir to %s: %s\n",
+
497 progname, parent, strerror(errno));
+
498 return -1;
+
499 }
+
500
+
501 if (getcwd(buf, sizeof(buf)) == NULL) {
+
502 fprintf(stderr, "%s: failed to obtain current directory: %s\n",
+
503 progname, strerror(errno));
+
504 return -1;
+
505 }
+
506 if (strcmp(buf, parent) != 0) {
+
507 fprintf(stderr, "%s: mountpoint moved (%s -> %s)\n", progname,
+
508 parent, buf);
+
509 return -1;
+
510
+
511 }
+
512
+
513 return 0;
+
514}
+
515
+
516#ifndef IGNORE_MTAB
+
517static int unmount_fuse_locked(const char *mnt, int quiet, int lazy)
+
518{
+
519 int res;
+
520 char *copy;
+
521 const char *last;
+
522 int umount_flags = (lazy ? UMOUNT_DETACH : 0) | UMOUNT_NOFOLLOW;
+
523
+
524 if (getuid() != 0) {
+
525 res = may_unmount(mnt, quiet);
+
526 if (res == -1)
+
527 return -1;
+
528 }
+
529
+
530 copy = strdup(mnt);
+
531 if (copy == NULL) {
+
532 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
533 return -1;
+
534 }
+
535
+
536 drop_privs();
+
537 res = chdir_to_parent(copy, &last);
+
538 if (res == -1) {
+
539 restore_privs();
+
540 goto out;
+
541 }
+
542
+
543 res = umount2(last, umount_flags);
+
544 restore_privs();
+
545 if (res == -1 && !quiet) {
+
546 fprintf(stderr, "%s: failed to unmount %s: %s\n",
+
547 progname, mnt, strerror(errno));
+
548 }
+
549
+
550out:
+
551 free(copy);
+
552 if (res == -1)
+
553 return -1;
+
554
+
555 res = chdir("/");
+
556 if (res == -1) {
+
557 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
558 return -1;
+
559 }
+
560
+
561 return fuse_mnt_remove_mount(progname, mnt);
+
562}
+
563
+
564static int unmount_fuse(const char *mnt, int quiet, int lazy)
+
565{
+
566 int res;
+
567 int mtablock = lock_umount();
+
568
+
569 res = unmount_fuse_locked(mnt, quiet, lazy);
+
570 unlock_umount(mtablock);
+
571
+
572 return res;
+
573}
+
574
+
575static int count_fuse_fs_mtab(void)
+
576{
+
577 struct mntent *entp;
+
578 int count = 0;
+
579 const char *mtab = _PATH_MOUNTED;
+
580 FILE *fp = setmntent(mtab, "r");
+
581 if (fp == NULL) {
+
582 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
+
583 strerror(errno));
+
584 return -1;
+
585 }
+
586 while ((entp = GETMNTENT(fp)) != NULL) {
+
587 if (strcmp(entp->mnt_type, "fuse") == 0 ||
+
588 strncmp(entp->mnt_type, "fuse.", 5) == 0)
+
589 count ++;
+
590 }
+
591 endmntent(fp);
+
592 return count;
+
593}
+
594
+
595#ifdef HAVE_LISTMOUNT
+
596static int count_fuse_fs_ls_mnt(void)
+
597{
+
598 #define SMBUF_SIZE 1024
+
599 #define MNT_ID_LEN 128
+
600
+
601 int fuse_count = 0;
+
602 int n_mounts = 0;
+
603 int ret = 0;
+
604 uint64_t mnt_ids[MNT_ID_LEN];
+
605 unsigned char smbuf[SMBUF_SIZE];
+
606 struct mnt_id_req req = {
+
607 .size = sizeof(struct mnt_id_req),
+
608 };
+
609 struct statmount *sm;
+
610
+
611 for (;;) {
+
612 req.mnt_id = LSMT_ROOT;
+
613
+
614 n_mounts = syscall(SYS_listmount, &req, &mnt_ids, MNT_ID_LEN, 0);
+
615 if (n_mounts == -1) {
+
616 if (errno != ENOSYS) {
+
617 fprintf(stderr, "%s: failed to list mounts: %s\n", progname,
+
618 strerror(errno));
+
619 }
+
620 return -1;
+
621 }
+
622
+
623 for (int i = 0; i < n_mounts; i++) {
+
624 req.mnt_id = mnt_ids[i];
+
625 req.param = STATMOUNT_FS_TYPE;
+
626 ret = syscall(SYS_statmount, &req, &smbuf, SMBUF_SIZE, 0);
+
627 if (ret) {
+
628 if (errno == ENOENT)
+
629 continue;
+
630
+
631 fprintf(stderr, "%s: failed to stat mount %lld: %s\n", progname,
+
632 req.mnt_id, strerror(errno));
+
633 return -1;
+
634 }
+
635
+
636 sm = (struct statmount *)smbuf;
+
637 if (sm->mask & STATMOUNT_FS_TYPE &&
+
638 strcmp(&sm->str[sm->fs_type], "fuse") == 0)
+
639 fuse_count++;
+
640 }
+
641
+
642 if (n_mounts < MNT_ID_LEN)
+
643 break;
+
644 req.param = mnt_ids[MNT_ID_LEN - 1];
+
645 }
+
646 return fuse_count;
+
647}
+
648
+
649static int count_fuse_fs(void)
+
650{
+
651 int count = count_fuse_fs_ls_mnt();
+
652
+
653 return count >= 0 ? count : count_fuse_fs_mtab();
+
654}
+
655#else
+
656static int count_fuse_fs(void)
+
657{
+
658 return count_fuse_fs_mtab();
+
659}
+
660#endif
+
661
+
662#else /* IGNORE_MTAB */
+
663static int count_fuse_fs(void)
+
664{
+
665 return 0;
+
666}
+
667
+
668static int add_mount(const char *source, const char *mnt, const char *type,
+
669 const char *opts)
+
670{
+
671 (void) source;
+
672 (void) mnt;
+
673 (void) type;
+
674 (void) opts;
+
675 return 0;
+
676}
+
677
+
678static int unmount_fuse(const char *mnt, int quiet, int lazy)
+
679{
+
680 (void) quiet;
+
681 return fuse_mnt_umount(progname, mnt, mnt, lazy);
+
682}
+
683#endif /* IGNORE_MTAB */
+
684
+
685static void strip_line(char *line)
+
686{
+
687 char *s = strchr(line, '#');
+
688 if (s != NULL)
+
689 s[0] = '\0';
+
690 for (s = line + strlen(line) - 1;
+
691 s >= line && isspace((unsigned char) *s); s--);
+
692 s[1] = '\0';
+
693 for (s = line; isspace((unsigned char) *s); s++);
+
694 if (s != line)
+
695 memmove(line, s, strlen(s)+1);
+
696}
+
697
+
698static void parse_line(char *line, int linenum)
+
699{
+
700 int tmp;
+
701 if (strcmp(line, "user_allow_other") == 0)
+
702 user_allow_other = 1;
+
703 else if (sscanf(line, "mount_max = %i", &tmp) == 1)
+
704 mount_max = tmp;
+
705 else if(line[0])
+
706 fprintf(stderr,
+
707 "%s: unknown parameter in %s at line %i: '%s'\n",
+
708 progname, FUSE_CONF, linenum, line);
+
709}
+
710
+
711static void read_conf(void)
+
712{
+
713 FILE *fp = fopen(FUSE_CONF, "r");
+
714 if (fp != NULL) {
+
715 int linenum = 1;
+
716 char line[256];
+
717 int isnewline = 1;
+
718 while (fgets(line, sizeof(line), fp) != NULL) {
+
719 if (isnewline) {
+
720 if (line[strlen(line)-1] == '\n') {
+
721 strip_line(line);
+
722 parse_line(line, linenum);
+
723 } else {
+
724 isnewline = 0;
+
725 }
+
726 } else if(line[strlen(line)-1] == '\n') {
+
727 fprintf(stderr, "%s: reading %s: line %i too long\n", progname, FUSE_CONF, linenum);
+
728
+
729 isnewline = 1;
+
730 }
+
731 if (isnewline)
+
732 linenum ++;
+
733 }
+
734 if (!isnewline) {
+
735 fprintf(stderr, "%s: reading %s: missing newline at end of file\n", progname, FUSE_CONF);
+
736
+
737 }
+
738 if (ferror(fp)) {
+
739 fprintf(stderr, "%s: reading %s: read failed\n", progname, FUSE_CONF);
+
740 exit(1);
+
741 }
+
742 fclose(fp);
+
743 } else if (errno != ENOENT) {
+
744 bool fatal = (errno != EACCES && errno != ELOOP &&
+
745 errno != ENAMETOOLONG && errno != ENOTDIR &&
+
746 errno != EOVERFLOW);
+
747 fprintf(stderr, "%s: failed to open %s: %s\n",
+
748 progname, FUSE_CONF, strerror(errno));
+
749 if (fatal)
+
750 exit(1);
+
751 }
+
752}
+
753
+
754static int begins_with(const char *s, const char *beg)
+
755{
+
756 if (strncmp(s, beg, strlen(beg)) == 0)
+
757 return 1;
+
758 else
+
759 return 0;
+
760}
+
761
+
762struct mount_flags {
+
763 const char *opt;
+
764 unsigned long flag;
+
765 int on;
+
766 int safe;
+
767};
+
768
+
769static struct mount_flags mount_flags[] = {
+
770 {"rw", MS_RDONLY, 0, 1},
+
771 {"ro", MS_RDONLY, 1, 1},
+
772 {"suid", MS_NOSUID, 0, 0},
+
773 {"nosuid", MS_NOSUID, 1, 1},
+
774 {"dev", MS_NODEV, 0, 0},
+
775 {"nodev", MS_NODEV, 1, 1},
+
776 {"exec", MS_NOEXEC, 0, 1},
+
777 {"noexec", MS_NOEXEC, 1, 1},
+
778 {"async", MS_SYNCHRONOUS, 0, 1},
+
779 {"sync", MS_SYNCHRONOUS, 1, 1},
+
780 {"atime", MS_NOATIME, 0, 1},
+
781 {"noatime", MS_NOATIME, 1, 1},
+
782 {"diratime", MS_NODIRATIME, 0, 1},
+
783 {"nodiratime", MS_NODIRATIME, 1, 1},
+
784 {"lazytime", MS_LAZYTIME, 1, 1},
+
785 {"nolazytime", MS_LAZYTIME, 0, 1},
+
786 {"relatime", MS_RELATIME, 1, 1},
+
787 {"norelatime", MS_RELATIME, 0, 1},
+
788 {"strictatime", MS_STRICTATIME, 1, 1},
+
789 {"nostrictatime", MS_STRICTATIME, 0, 1},
+
790 {"dirsync", MS_DIRSYNC, 1, 1},
+
791 {"symfollow", MS_NOSYMFOLLOW, 0, 1},
+
792 {"nosymfollow", MS_NOSYMFOLLOW, 1, 1},
+
793 {NULL, 0, 0, 0}
+
794};
+
795
+
796static int find_mount_flag(const char *s, unsigned len, int *on, int *flag)
+
797{
+
798 int i;
+
799
+
800 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
801 const char *opt = mount_flags[i].opt;
+
802 if (strlen(opt) == len && strncmp(opt, s, len) == 0) {
+
803 *on = mount_flags[i].on;
+
804 *flag = mount_flags[i].flag;
+
805 if (!mount_flags[i].safe && getuid() != 0) {
+
806 *flag = 0;
+
807 fprintf(stderr,
+
808 "%s: unsafe option %s ignored\n",
+
809 progname, opt);
+
810 }
+
811 return 1;
+
812 }
+
813 }
+
814 return 0;
+
815}
+
816
+
817static int add_option(char **optsp, const char *opt, unsigned expand)
+
818{
+
819 char *newopts;
+
820 if (*optsp == NULL)
+
821 newopts = strdup(opt);
+
822 else {
+
823 unsigned oldsize = strlen(*optsp);
+
824 unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1;
+
825 newopts = (char *) realloc(*optsp, newsize);
+
826 if (newopts)
+
827 sprintf(newopts + oldsize, ",%s", opt);
+
828 }
+
829 if (newopts == NULL) {
+
830 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
831 return -1;
+
832 }
+
833 *optsp = newopts;
+
834 return 0;
+
835}
+
836
+
837static int get_mnt_opts(int flags, char *opts, char **mnt_optsp)
+
838{
+
839 int i;
+
840 int l;
+
841
+
842 if (!(flags & MS_RDONLY) && add_option(mnt_optsp, "rw", 0) == -1)
+
843 return -1;
+
844
+
845 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
846 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+
847 add_option(mnt_optsp, mount_flags[i].opt, 0) == -1)
+
848 return -1;
+
849 }
+
850
+
851 if (add_option(mnt_optsp, opts, 0) == -1)
+
852 return -1;
+
853 /* remove comma from end of opts*/
+
854 l = strlen(*mnt_optsp);
+
855 if ((*mnt_optsp)[l-1] == ',')
+
856 (*mnt_optsp)[l-1] = '\0';
+
857 if (getuid() != 0) {
+
858 const char *user = get_user_name();
+
859 if (user == NULL)
+
860 return -1;
+
861
+
862 if (add_option(mnt_optsp, "user=", strlen(user)) == -1)
+
863 return -1;
+
864 strcat(*mnt_optsp, user);
+
865 }
+
866 return 0;
+
867}
+
868
+
869static int opt_eq(const char *s, unsigned len, const char *opt)
+
870{
+
871 if(strlen(opt) == len && strncmp(s, opt, len) == 0)
+
872 return 1;
+
873 else
+
874 return 0;
+
875}
+
876
+
877static int get_string_opt(const char *s, unsigned len, const char *opt,
+
878 char **val)
+
879{
+
880 int i;
+
881 unsigned opt_len = strlen(opt);
+
882 char *d;
+
883
+
884 if (*val)
+
885 free(*val);
+
886 *val = (char *) malloc(len - opt_len + 1);
+
887 if (!*val) {
+
888 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
889 return 0;
+
890 }
+
891
+
892 d = *val;
+
893 s += opt_len;
+
894 len -= opt_len;
+
895 for (i = 0; i < len; i++) {
+
896 if (s[i] == '\\' && i + 1 < len)
+
897 i++;
+
898 *d++ = s[i];
+
899 }
+
900 *d = '\0';
+
901 return 1;
+
902}
+
903
+
904/* The kernel silently truncates the "data" argument to PAGE_SIZE-1 characters.
+
905 * This can be dangerous if it e.g. truncates the option "group_id=1000" to
+
906 * "group_id=1".
+
907 * This wrapper detects this case and bails out with an error.
+
908 */
+
909static int mount_notrunc(const char *source, const char *target,
+
910 const char *filesystemtype, unsigned long mountflags,
+
911 const char *data) {
+
912 if (strlen(data) > sysconf(_SC_PAGESIZE) - 1) {
+
913 fprintf(stderr, "%s: mount options too long\n", progname);
+
914 errno = EINVAL;
+
915 return -1;
+
916 }
+
917 return mount(source, target, filesystemtype, mountflags, data);
+
918}
+
919
+
920
+
921static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
+
922 int fd, const char *opts, const char *dev, char **sourcep,
+
923 char **mnt_optsp)
+
924{
+
925 int res;
+
926 int flags = MS_NOSUID | MS_NODEV;
+
927 char *optbuf;
+
928 char *mnt_opts = NULL;
+
929 const char *s;
+
930 char *d;
+
931 char *fsname = NULL;
+
932 char *subtype = NULL;
+
933 char *source = NULL;
+
934 char *type = NULL;
+
935 int blkdev = 0;
+
936
+
937 optbuf = (char *) malloc(strlen(opts) + 128);
+
938 if (!optbuf) {
+
939 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
940 return -1;
+
941 }
+
942
+
943 for (s = opts, d = optbuf; *s;) {
+
944 unsigned len;
+
945 const char *fsname_str = "fsname=";
+
946 const char *subtype_str = "subtype=";
+
947 bool escape_ok = begins_with(s, fsname_str) ||
+
948 begins_with(s, subtype_str);
+
949 for (len = 0; s[len]; len++) {
+
950 if (escape_ok && s[len] == '\\' && s[len + 1])
+
951 len++;
+
952 else if (s[len] == ',')
+
953 break;
+
954 }
+
955 if (begins_with(s, fsname_str)) {
+
956 if (!get_string_opt(s, len, fsname_str, &fsname))
+
957 goto err;
+
958 } else if (begins_with(s, subtype_str)) {
+
959 if (!get_string_opt(s, len, subtype_str, &subtype))
+
960 goto err;
+
961 } else if (opt_eq(s, len, "blkdev")) {
+
962 if (getuid() != 0) {
+
963 fprintf(stderr,
+
964 "%s: option blkdev is privileged\n",
+
965 progname);
+
966 goto err;
+
967 }
+
968 blkdev = 1;
+
969 } else if (opt_eq(s, len, "auto_unmount")) {
+
970 auto_unmount = 1;
+
971 } else if (!opt_eq(s, len, "nonempty") &&
+
972 !begins_with(s, "fd=") &&
+
973 !begins_with(s, "rootmode=") &&
+
974 !begins_with(s, "user_id=") &&
+
975 !begins_with(s, "group_id=")) {
+
976 int on;
+
977 int flag;
+
978 int skip_option = 0;
+
979 if (opt_eq(s, len, "large_read")) {
+
980 struct utsname utsname;
+
981 unsigned kmaj, kmin;
+
982 res = uname(&utsname);
+
983 if (res == 0 &&
+
984 sscanf(utsname.release, "%u.%u",
+
985 &kmaj, &kmin) == 2 &&
+
986 (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
+
987 fprintf(stderr, "%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n", progname, kmaj, kmin);
+
988 skip_option = 1;
+
989 }
+
990 }
+
991 if (getuid() != 0 && !user_allow_other &&
+
992 (opt_eq(s, len, "allow_other") ||
+
993 opt_eq(s, len, "allow_root"))) {
+
994 fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n", progname, len, s, FUSE_CONF);
+
995 goto err;
+
996 }
+
997 if (!skip_option) {
+
998 if (find_mount_flag(s, len, &on, &flag)) {
+
999 if (on)
+
1000 flags |= flag;
+
1001 else
+
1002 flags &= ~flag;
+
1003 } else if (opt_eq(s, len, "default_permissions") ||
+
1004 opt_eq(s, len, "allow_other") ||
+
1005 begins_with(s, "max_read=") ||
+
1006 begins_with(s, "blksize=")) {
+
1007 memcpy(d, s, len);
+
1008 d += len;
+
1009 *d++ = ',';
+
1010 } else {
+
1011 fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, s);
+
1012 exit(1);
+
1013 }
+
1014 }
+
1015 }
+
1016 s += len;
+
1017 if (*s)
+
1018 s++;
+
1019 }
+
1020 *d = '\0';
+
1021 res = get_mnt_opts(flags, optbuf, &mnt_opts);
+
1022 if (res == -1)
+
1023 goto err;
+
1024
+
1025 sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+
1026 fd, rootmode, getuid(), getgid());
+
1027
+
1028 source = malloc((fsname ? strlen(fsname) : 0) +
+
1029 (subtype ? strlen(subtype) : 0) + strlen(dev) + 32);
+
1030
+
1031 type = malloc((subtype ? strlen(subtype) : 0) + 32);
+
1032 if (!type || !source) {
+
1033 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
1034 goto err;
+
1035 }
+
1036
+
1037 if (subtype)
+
1038 sprintf(type, "%s.%s", blkdev ? "fuseblk" : "fuse", subtype);
+
1039 else
+
1040 strcpy(type, blkdev ? "fuseblk" : "fuse");
+
1041
+
1042 if (fsname)
+
1043 strcpy(source, fsname);
+
1044 else
+
1045 strcpy(source, subtype ? subtype : dev);
+
1046
+
1047 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
1048 if (res == -1 && errno == ENODEV && subtype) {
+
1049 /* Probably missing subtype support */
+
1050 strcpy(type, blkdev ? "fuseblk" : "fuse");
+
1051 if (fsname) {
+
1052 if (!blkdev)
+
1053 sprintf(source, "%s#%s", subtype, fsname);
+
1054 } else {
+
1055 strcpy(source, type);
+
1056 }
+
1057
+
1058 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
1059 }
+
1060 if (res == -1 && errno == EINVAL) {
+
1061 /* It could be an old version not supporting group_id */
+
1062 sprintf(d, "fd=%i,rootmode=%o,user_id=%u",
+
1063 fd, rootmode, getuid());
+
1064 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
1065 }
+
1066 if (res == -1) {
+
1067 int errno_save = errno;
+
1068 if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
+
1069 fprintf(stderr, "%s: 'fuseblk' support missing\n",
+
1070 progname);
+
1071 else
+
1072 fprintf(stderr, "%s: mount failed: %s\n", progname,
+
1073 strerror(errno_save));
+
1074 goto err;
+
1075 }
+
1076 *sourcep = source;
+
1077 *typep = type;
+
1078 *mnt_optsp = mnt_opts;
+
1079 free(fsname);
+
1080 free(optbuf);
+
1081
+
1082 return 0;
+
1083
+
1084err:
+
1085 free(fsname);
+
1086 free(subtype);
+
1087 free(source);
+
1088 free(type);
+
1089 free(mnt_opts);
+
1090 free(optbuf);
+
1091 return -1;
+
1092}
+
1093
+
1094static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)
+
1095{
+
1096 int res;
+
1097 const char *mnt = *mntp;
+
1098 const char *origmnt = mnt;
+
1099 struct statfs fs_buf;
+
1100 size_t i;
+
1101
+
1102 res = lstat(mnt, stbuf);
+
1103 if (res == -1) {
+
1104 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
+
1105 progname, mnt, strerror(errno));
+
1106 return -1;
+
1107 }
+
1108
+
1109 /* No permission checking is done for root */
+
1110 if (getuid() == 0)
+
1111 return 0;
+
1112
+
1113 if (S_ISDIR(stbuf->st_mode)) {
+
1114 res = chdir(mnt);
+
1115 if (res == -1) {
+
1116 fprintf(stderr,
+
1117 "%s: failed to chdir to mountpoint: %s\n",
+
1118 progname, strerror(errno));
+
1119 return -1;
+
1120 }
+
1121 mnt = *mntp = ".";
+
1122 res = lstat(mnt, stbuf);
+
1123 if (res == -1) {
+
1124 fprintf(stderr,
+
1125 "%s: failed to access mountpoint %s: %s\n",
+
1126 progname, origmnt, strerror(errno));
+
1127 return -1;
+
1128 }
+
1129
+
1130 if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) {
+
1131 fprintf(stderr, "%s: mountpoint %s not owned by user\n",
+
1132 progname, origmnt);
+
1133 return -1;
+
1134 }
+
1135
+
1136 res = access(mnt, W_OK);
+
1137 if (res == -1) {
+
1138 fprintf(stderr, "%s: user has no write access to mountpoint %s\n",
+
1139 progname, origmnt);
+
1140 return -1;
+
1141 }
+
1142 } else if (S_ISREG(stbuf->st_mode)) {
+
1143 static char procfile[256];
+
1144 *mountpoint_fd = open(mnt, O_WRONLY);
+
1145 if (*mountpoint_fd == -1) {
+
1146 fprintf(stderr, "%s: failed to open %s: %s\n",
+
1147 progname, mnt, strerror(errno));
+
1148 return -1;
+
1149 }
+
1150 res = fstat(*mountpoint_fd, stbuf);
+
1151 if (res == -1) {
+
1152 fprintf(stderr,
+
1153 "%s: failed to access mountpoint %s: %s\n",
+
1154 progname, mnt, strerror(errno));
+
1155 return -1;
+
1156 }
+
1157 if (!S_ISREG(stbuf->st_mode)) {
+
1158 fprintf(stderr,
+
1159 "%s: mountpoint %s is no longer a regular file\n",
+
1160 progname, mnt);
+
1161 return -1;
+
1162 }
+
1163
+
1164 sprintf(procfile, "/proc/self/fd/%i", *mountpoint_fd);
+
1165 *mntp = procfile;
+
1166 } else {
+
1167 fprintf(stderr,
+
1168 "%s: mountpoint %s is not a directory or a regular file\n",
+
1169 progname, mnt);
+
1170 return -1;
+
1171 }
+
1172
+
1173 /* Do not permit mounting over anything in procfs - it has a couple
+
1174 * places to which we have "write access" without being supposed to be
+
1175 * able to just put anything we want there.
+
1176 * Luckily, without allow_other, we can't get other users to actually
+
1177 * use any fake information we try to put there anyway.
+
1178 * Use a whitelist to be safe. */
+
1179 if (statfs(*mntp, &fs_buf)) {
+
1180 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
+
1181 progname, mnt, strerror(errno));
+
1182 return -1;
+
1183 }
+
1184
+
1185 /* Define permitted filesystems for the mount target. This was
+
1186 * originally the same list as used by the ecryptfs mount helper
+
1187 * (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225)
+
1188 * but got expanded as we found more filesystems that needed to be
+
1189 * overlaid. */
+
1190 typeof(fs_buf.f_type) f_type_whitelist[] = {
+
1191 0x61756673 /* AUFS_SUPER_MAGIC */,
+
1192 0x00000187 /* AUTOFS_SUPER_MAGIC */,
+
1193 0xCA451A4E /* BCACHEFS_STATFS_MAGIC */,
+
1194 0x9123683E /* BTRFS_SUPER_MAGIC */,
+
1195 0x00C36400 /* CEPH_SUPER_MAGIC */,
+
1196 0xFF534D42 /* CIFS_MAGIC_NUMBER */,
+
1197 0x0000F15F /* ECRYPTFS_SUPER_MAGIC */,
+
1198 0X2011BAB0 /* EXFAT_SUPER_MAGIC */,
+
1199 0x0000EF53 /* EXT[234]_SUPER_MAGIC */,
+
1200 0xF2F52010 /* F2FS_SUPER_MAGIC */,
+
1201 0x65735546 /* FUSE_SUPER_MAGIC */,
+
1202 0x01161970 /* GFS2_MAGIC */,
+
1203 0x47504653 /* GPFS_SUPER_MAGIC */,
+
1204 0x0000482b /* HFSPLUS_SUPER_MAGIC */,
+
1205 0x000072B6 /* JFFS2_SUPER_MAGIC */,
+
1206 0x3153464A /* JFS_SUPER_MAGIC */,
+
1207 0x0BD00BD0 /* LL_SUPER_MAGIC */,
+
1208 0X00004D44 /* MSDOS_SUPER_MAGIC */,
+
1209 0x0000564C /* NCP_SUPER_MAGIC */,
+
1210 0x00006969 /* NFS_SUPER_MAGIC */,
+
1211 0x00003434 /* NILFS_SUPER_MAGIC */,
+
1212 0x5346544E /* NTFS_SB_MAGIC */,
+
1213 0x7366746E /* NTFS3_SUPER_MAGIC */,
+
1214 0x5346414f /* OPENAFS_SUPER_MAGIC */,
+
1215 0x794C7630 /* OVERLAYFS_SUPER_MAGIC */,
+
1216 0xAAD7AAEA /* PANFS_SUPER_MAGIC */,
+
1217 0x52654973 /* REISERFS_SUPER_MAGIC */,
+
1218 0xFE534D42 /* SMB2_SUPER_MAGIC */,
+
1219 0x73717368 /* SQUASHFS_MAGIC */,
+
1220 0x01021994 /* TMPFS_MAGIC */,
+
1221 0x24051905 /* UBIFS_SUPER_MAGIC */,
+
1222 0x18031977 /* WEKAFS_SUPER_MAGIC */,
+
1223#if __SIZEOF_LONG__ > 4
+
1224 0x736675005346544e /* UFSD */,
+
1225#endif
+
1226 0x58465342 /* XFS_SB_MAGIC */,
+
1227 0x2FC12FC1 /* ZFS_SUPER_MAGIC */,
+
1228 0x858458f6 /* RAMFS_MAGIC */,
+
1229 };
+
1230 for (i = 0; i < sizeof(f_type_whitelist)/sizeof(f_type_whitelist[0]); i++) {
+
1231 if (f_type_whitelist[i] == fs_buf.f_type)
+
1232 return 0;
+
1233 }
+
1234
+
1235 fprintf(stderr, "%s: mounting over filesystem type %#010lx is forbidden\n",
+
1236 progname, (unsigned long)fs_buf.f_type);
+
1237 return -1;
+
1238}
+
1239
+
1240static int open_fuse_device(const char *dev)
+
1241{
+
1242 int fd;
+
1243
+
1244 drop_privs();
+
1245 fd = open(dev, O_RDWR);
+
1246 if (fd == -1) {
+
1247 if (errno == ENODEV || errno == ENOENT)/* check for ENOENT too, for the udev case */
+
1248 fprintf(stderr,
+
1249 "%s: fuse device %s not found. Kernel module not loaded?\n",
+
1250 progname, dev);
+
1251 else
+
1252 fprintf(stderr,
+
1253 "%s: failed to open %s: %s\n", progname, dev, strerror(errno));
+
1254 }
+
1255 restore_privs();
+
1256 return fd;
+
1257}
+
1258
+
1259static int mount_fuse(const char *mnt, const char *opts, const char **type)
+
1260{
+
1261 int res;
+
1262 int fd;
+
1263 const char *dev = getenv(FUSE_KERN_DEVICE_ENV) ?: FUSE_DEV;
+
1264 struct stat stbuf;
+
1265 char *source = NULL;
+
1266 char *mnt_opts = NULL;
+
1267 const char *real_mnt = mnt;
+
1268 int mountpoint_fd = -1;
+
1269 char *do_mount_opts = NULL;
+
1270 char *x_opts = NULL;
+
1271
+
1272 fd = open_fuse_device(dev);
+
1273 if (fd == -1)
+
1274 return -1;
+
1275
+
1276 drop_privs();
+
1277 read_conf();
+
1278
+
1279 if (getuid() != 0 && mount_max != -1) {
+
1280 int mount_count = count_fuse_fs();
+
1281 if (mount_count >= mount_max) {
+
1282 fprintf(stderr, "%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n", progname, FUSE_CONF);
+
1283 goto fail_close_fd;
+
1284 }
+
1285 }
+
1286
+
1287 // Extract any options starting with "x-"
+
1288 res= extract_x_options(opts, &do_mount_opts, &x_opts);
+
1289 if (res)
+
1290 goto fail_close_fd;
+
1291
+
1292 res = check_perm(&real_mnt, &stbuf, &mountpoint_fd);
+
1293 restore_privs();
+
1294 if (res != -1)
+
1295 res = do_mount(real_mnt, type, stbuf.st_mode & S_IFMT,
+
1296 fd, do_mount_opts, dev, &source, &mnt_opts);
+
1297
+
1298 if (mountpoint_fd != -1)
+
1299 close(mountpoint_fd);
+
1300
+
1301 if (res == -1)
+
1302 goto fail_close_fd;
+
1303
+
1304 res = chdir("/");
+
1305 if (res == -1) {
+
1306 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1307 goto fail_close_fd;
+
1308 }
+
1309
+
1310 if (geteuid() == 0) {
+
1311 if (x_opts && strlen(x_opts) > 0) {
+
1312 /*
+
1313 * Add back the options starting with "x-" to opts from
+
1314 * do_mount. +2 for ',' and '\0'
+
1315 */
+
1316 size_t mnt_opts_len = strlen(mnt_opts);
+
1317 size_t x_mnt_opts_len = mnt_opts_len+
+
1318 strlen(x_opts) + 2;
+
1319 char *x_mnt_opts = calloc(1, x_mnt_opts_len);
+
1320
+
1321 if (mnt_opts_len) {
+
1322 strcpy(x_mnt_opts, mnt_opts);
+
1323 strncat(x_mnt_opts, ",", 2);
+
1324 }
+
1325
+
1326 strncat(x_mnt_opts, x_opts,
+
1327 x_mnt_opts_len - mnt_opts_len - 2);
+
1328
+
1329 free(mnt_opts);
+
1330 mnt_opts = x_mnt_opts;
+
1331 }
+
1332
+
1333 res = add_mount(source, mnt, *type, mnt_opts);
+
1334 if (res == -1) {
+
1335 /* Can't clean up mount in a non-racy way */
+
1336 goto fail_close_fd;
+
1337 }
+
1338 }
+
1339
+
1340out_free:
+
1341 free(source);
+
1342 free(mnt_opts);
+
1343 free(x_opts);
+
1344 free(do_mount_opts);
+
1345
+
1346 return fd;
+
1347
+
1348fail_close_fd:
+
1349 close(fd);
+
1350 fd = -1;
+
1351 goto out_free;
+
1352}
+
1353
+
1354static int send_fd(int sock_fd, int fd)
+
1355{
+
1356 int retval;
+
1357 struct msghdr msg;
+
1358 struct cmsghdr *p_cmsg;
+
1359 struct iovec vec;
+
1360 size_t cmsgbuf[CMSG_SPACE(sizeof(fd)) / sizeof(size_t)];
+
1361 int *p_fds;
+
1362 char sendchar = 0;
+
1363
+
1364 msg.msg_control = cmsgbuf;
+
1365 msg.msg_controllen = sizeof(cmsgbuf);
+
1366 p_cmsg = CMSG_FIRSTHDR(&msg);
+
1367 p_cmsg->cmsg_level = SOL_SOCKET;
+
1368 p_cmsg->cmsg_type = SCM_RIGHTS;
+
1369 p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+
1370 p_fds = (int *) CMSG_DATA(p_cmsg);
+
1371 *p_fds = fd;
+
1372 msg.msg_controllen = p_cmsg->cmsg_len;
+
1373 msg.msg_name = NULL;
+
1374 msg.msg_namelen = 0;
+
1375 msg.msg_iov = &vec;
+
1376 msg.msg_iovlen = 1;
+
1377 msg.msg_flags = 0;
+
1378 /* "To pass file descriptors or credentials you need to send/read at
+
1379 * least one byte" (man 7 unix) */
+
1380 vec.iov_base = &sendchar;
+
1381 vec.iov_len = sizeof(sendchar);
+
1382 while ((retval = sendmsg(sock_fd, &msg, 0)) == -1 && errno == EINTR);
+
1383 if (retval != 1) {
+
1384 perror("sending file descriptor");
+
1385 return -1;
+
1386 }
+
1387 return 0;
+
1388}
+
1389
+
1390/* Helper for should_auto_unmount
+
1391 *
+
1392 * fusermount typically has the s-bit set - initial open of `mnt` was as root
+
1393 * and got EACCESS as 'allow_other' was not specified.
+
1394 * Try opening `mnt` again with uid and guid of the calling process.
+
1395 */
+
1396static int recheck_ENOTCONN_as_owner(const char *mnt)
+
1397{
+
1398 int pid = fork();
+
1399 if(pid == -1) {
+
1400 perror("fuse: recheck_ENOTCONN_as_owner can't fork");
+
1401 _exit(EXIT_FAILURE);
+
1402 } else if(pid == 0) {
+
1403 uid_t uid = getuid();
+
1404 gid_t gid = getgid();
+
1405 if(setresgid(gid, gid, gid) == -1) {
+
1406 perror("fuse: can't set resgid");
+
1407 _exit(EXIT_FAILURE);
+
1408 }
+
1409 if(setresuid(uid, uid, uid) == -1) {
+
1410 perror("fuse: can't set resuid");
+
1411 _exit(EXIT_FAILURE);
+
1412 }
+
1413
+
1414 int fd = open(mnt, O_RDONLY);
+
1415 if(fd == -1 && errno == ENOTCONN)
+
1416 _exit(EXIT_SUCCESS);
+
1417 else
+
1418 _exit(EXIT_FAILURE);
+
1419 } else {
+
1420 int status;
+
1421 int res = waitpid(pid, &status, 0);
+
1422 if (res == -1) {
+
1423 perror("fuse: waiting for child failed");
+
1424 _exit(EXIT_FAILURE);
+
1425 }
+
1426 return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS;
+
1427 }
+
1428}
+
1429
+
1430/* The parent fuse process has died: decide whether to auto_unmount.
+
1431 *
+
1432 * In the normal case (umount or fusermount -u), the filesystem
+
1433 * has already been unmounted. If we simply unmount again we can
+
1434 * cause problems with stacked mounts (e.g. autofs).
+
1435 *
+
1436 * So we unmount here only in abnormal case where fuse process has
+
1437 * died without unmount happening. To detect this, we first look in
+
1438 * the mount table to make sure the mountpoint is still mounted and
+
1439 * has proper type. If so, we then see if opening the mount dir is
+
1440 * returning 'Transport endpoint is not connected'.
+
1441 *
+
1442 * The order of these is important, because if autofs is in use,
+
1443 * opening the dir to check for ENOTCONN will cause a new mount
+
1444 * in the normal case where filesystem has been unmounted cleanly.
+
1445 */
+
1446static int should_auto_unmount(const char *mnt, const char *type)
+
1447{
+
1448 char *copy;
+
1449 const char *last;
+
1450 int result = 0;
+
1451 int fd;
+
1452
+
1453 copy = strdup(mnt);
+
1454 if (copy == NULL) {
+
1455 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
1456 return 0;
+
1457 }
+
1458
+
1459 if (chdir_to_parent(copy, &last) == -1)
+
1460 goto out;
+
1461 if (check_is_mount(last, mnt, type) == -1)
+
1462 goto out;
+
1463
+
1464 fd = open(mnt, O_RDONLY);
+
1465
+
1466 if (fd != -1) {
+
1467 close(fd);
+
1468 } else {
+
1469 switch(errno) {
+
1470 case ENOTCONN:
+
1471 result = 1;
+
1472 break;
+
1473 case EACCES:
+
1474 result = recheck_ENOTCONN_as_owner(mnt);
+
1475 break;
+
1476 default:
+
1477 result = 0;
+
1478 break;
+
1479 }
+
1480 }
+
1481out:
+
1482 free(copy);
+
1483 return result;
+
1484}
+
1485
+
1486static void usage(void)
+
1487{
+
1488 printf("%s: [options] mountpoint\n"
+
1489 "Options:\n"
+
1490 " -h print help\n"
+
1491 " -V print version\n"
+
1492 " -o opt[,opt...] mount options\n"
+
1493 " -u unmount\n"
+
1494 " -q quiet\n"
+
1495 " -z lazy unmount\n",
+
1496 progname);
+
1497 exit(1);
+
1498}
+
1499
+
1500static void show_version(void)
+
1501{
+
1502 printf("fusermount3 version: %s\n", PACKAGE_VERSION);
+
1503 exit(0);
+
1504}
+
1505
+
1506static void close_range_loop(int min_fd, int max_fd, int cfd)
+
1507{
+
1508 for (int fd = min_fd; fd <= max_fd; fd++)
+
1509 if (fd != cfd)
+
1510 close(fd);
+
1511}
+
1512
+
1513/*
+
1514 * Close all inherited fds that are not needed
+
1515 * Ideally these wouldn't come up at all, applications should better
+
1516 * use FD_CLOEXEC / O_CLOEXEC
+
1517 */
+
1518static int close_inherited_fds(int cfd)
+
1519{
+
1520 int rc = -1;
+
1521 int nullfd;
+
1522
+
1523 /* We can't even report an error */
+
1524 if (cfd <= STDERR_FILENO)
+
1525 return -EINVAL;
+
1526
+
1527#ifdef HAVE_CLOSE_RANGE
+
1528 if (cfd < STDERR_FILENO + 2) {
+
1529 close_range_loop(STDERR_FILENO + 1, cfd - 1, cfd);
+
1530 } else {
+
1531 rc = close_range(STDERR_FILENO + 1, cfd - 1, 0);
+
1532 if (rc < 0)
+
1533 goto fallback;
+
1534 }
+
1535
+
1536 /* Close high range */
+
1537 rc = close_range(cfd + 1, ~0U, 0);
+
1538#else
+
1539 goto fallback; /* make use of fallback to avoid compiler warnings */
+
1540#endif
+
1541
+
1542fallback:
+
1543 if (rc < 0) {
+
1544 int max_fd = sysconf(_SC_OPEN_MAX) - 1;
+
1545
+
1546 close_range_loop(STDERR_FILENO + 1, max_fd, cfd);
+
1547 }
+
1548
+
1549 nullfd = open("/dev/null", O_RDWR);
+
1550 if (nullfd < 0) {
+
1551 perror("fusermount: cannot open /dev/null");
+
1552 return -errno;
+
1553 }
+
1554
+
1555 /* Redirect stdin, stdout, stderr to /dev/null */
+
1556 dup2(nullfd, STDIN_FILENO);
+
1557 dup2(nullfd, STDOUT_FILENO);
+
1558 dup2(nullfd, STDERR_FILENO);
+
1559 if (nullfd > STDERR_FILENO)
+
1560 close(nullfd);
+
1561
+
1562 return 0;
+
1563}
+
1564
+
1565int main(int argc, char *argv[])
+
1566{
+
1567 sigset_t sigset;
+
1568 int ch;
+
1569 int fd;
+
1570 int res;
+
1571 char *origmnt;
+
1572 char *mnt;
+
1573 static int unmount = 0;
+
1574 static int lazy = 0;
+
1575 static int quiet = 0;
+
1576 char *commfd = NULL;
+
1577 long cfd;
+
1578 const char *opts = "";
+
1579 const char *type = NULL;
+
1580 int setup_auto_unmount_only = 0;
+
1581
+
1582 static const struct option long_opts[] = {
+
1583 {"unmount", no_argument, NULL, 'u'},
+
1584 {"lazy", no_argument, NULL, 'z'},
+
1585 {"quiet", no_argument, NULL, 'q'},
+
1586 {"help", no_argument, NULL, 'h'},
+
1587 {"version", no_argument, NULL, 'V'},
+
1588 {"options", required_argument, NULL, 'o'},
+
1589 // Note: auto-unmount and comm-fd don't have short versions.
+
1590 // They'ne meant for internal use by mount.c
+
1591 {"auto-unmount", no_argument, NULL, 'U'},
+
1592 {"comm-fd", required_argument, NULL, 'c'},
+
1593 {0, 0, 0, 0}};
+
1594
+
1595 progname = strdup(argc > 0 ? argv[0] : "fusermount");
+
1596 if (progname == NULL) {
+
1597 fprintf(stderr, "%s: failed to allocate memory\n", argv[0]);
+
1598 exit(1);
+
1599 }
+
1600
+
1601 while ((ch = getopt_long(argc, argv, "hVo:uzq", long_opts,
+
1602 NULL)) != -1) {
+
1603 switch (ch) {
+
1604 case 'h':
+
1605 usage();
+
1606 break;
+
1607
+
1608 case 'V':
+
1609 show_version();
+
1610 break;
+
1611
+
1612 case 'o':
+
1613 opts = optarg;
+
1614 break;
+
1615
+
1616 case 'u':
+
1617 unmount = 1;
+
1618 break;
+
1619 case 'U':
+
1620 unmount = 1;
+
1621 auto_unmount = 1;
+
1622 setup_auto_unmount_only = 1;
+
1623 break;
+
1624 case 'c':
+
1625 commfd = optarg;
+
1626 break;
+
1627 case 'z':
+
1628 lazy = 1;
+
1629 break;
+
1630
+
1631 case 'q':
+
1632 quiet = 1;
+
1633 break;
+
1634
+
1635 default:
+
1636 exit(1);
+
1637 }
+
1638 }
+
1639
+
1640 if (lazy && !unmount) {
+
1641 fprintf(stderr, "%s: -z can only be used with -u\n", progname);
+
1642 exit(1);
+
1643 }
+
1644
+
1645 if (optind >= argc) {
+
1646 fprintf(stderr, "%s: missing mountpoint argument\n", progname);
+
1647 exit(1);
+
1648 } else if (argc > optind + 1) {
+
1649 fprintf(stderr, "%s: extra arguments after the mountpoint\n",
+
1650 progname);
+
1651 exit(1);
+
1652 }
+
1653
+
1654 origmnt = argv[optind];
+
1655
+
1656 drop_privs();
+
1657 mnt = fuse_mnt_resolve_path(progname, origmnt);
+
1658 if (mnt != NULL) {
+
1659 res = chdir("/");
+
1660 if (res == -1) {
+
1661 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1662 goto err_out;
+
1663 }
+
1664 }
+
1665 restore_privs();
+
1666 if (mnt == NULL)
+
1667 exit(1);
+
1668
+
1669 umask(033);
+
1670 if (!setup_auto_unmount_only && unmount)
+
1671 goto do_unmount;
+
1672
+
1673 if(commfd == NULL)
+
1674 commfd = getenv(FUSE_COMMFD_ENV);
+
1675 if (commfd == NULL) {
+
1676 fprintf(stderr, "%s: old style mounting not supported\n",
+
1677 progname);
+
1678 goto err_out;
+
1679 }
+
1680
+
1681 res = libfuse_strtol(commfd, &cfd);
+
1682 if (res) {
+
1683 fprintf(stderr,
+
1684 "%s: invalid _FUSE_COMMFD: %s\n",
+
1685 progname, commfd);
+
1686 goto err_out;
+
1687
+
1688 }
+
1689
+
1690 {
+
1691 struct stat statbuf;
+
1692 fstat(cfd, &statbuf);
+
1693 if(!S_ISSOCK(statbuf.st_mode)) {
+
1694 fprintf(stderr,
+
1695 "%s: file descriptor %li is not a socket, can't send fuse fd\n",
+
1696 progname, cfd);
+
1697 goto err_out;
+
1698 }
+
1699 }
+
1700
+
1701 if (setup_auto_unmount_only)
+
1702 goto wait_for_auto_unmount;
+
1703
+
1704 fd = mount_fuse(mnt, opts, &type);
+
1705 if (fd == -1)
+
1706 goto err_out;
+
1707
+
1708 res = send_fd(cfd, fd);
+
1709 if (res != 0) {
+
1710 umount2(mnt, MNT_DETACH); /* lazy umount */
+
1711 goto err_out;
+
1712 }
+
1713 close(fd);
+
1714
+
1715 if (!auto_unmount) {
+
1716 free(mnt);
+
1717 free((void*) type);
+
1718 return 0;
+
1719 }
+
1720
+
1721wait_for_auto_unmount:
+
1722 /* Become a daemon and wait for the parent to exit or die.
+
1723 ie For the control socket to get closed.
+
1724 Btw, we don't want to use daemon() function here because
+
1725 it forks and messes with the file descriptors. */
+
1726
+
1727 res = close_inherited_fds(cfd);
+
1728 if (res < 0)
+
1729 exit(EXIT_FAILURE);
+
1730
+
1731 setsid();
+
1732 res = chdir("/");
+
1733 if (res == -1) {
+
1734 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1735 goto err_out;
+
1736 }
+
1737
+
1738 sigfillset(&sigset);
+
1739 sigprocmask(SIG_BLOCK, &sigset, NULL);
+
1740
+
1741 lazy = 1;
+
1742 quiet = 1;
+
1743
+
1744 while (1) {
+
1745 unsigned char buf[16];
+
1746 int n = recv(cfd, buf, sizeof(buf), 0);
+
1747 if (!n)
+
1748 break;
+
1749
+
1750 if (n < 0) {
+
1751 if (errno == EINTR)
+
1752 continue;
+
1753 break;
+
1754 }
+
1755 }
+
1756
+
1757 if (!should_auto_unmount(mnt, type)) {
+
1758 goto success_out;
+
1759 }
+
1760
+
1761do_unmount:
+
1762 if (geteuid() == 0)
+
1763 res = unmount_fuse(mnt, quiet, lazy);
+
1764 else {
+
1765 res = umount2(mnt, lazy ? UMOUNT_DETACH : 0);
+
1766 if (res == -1 && !quiet)
+
1767 fprintf(stderr,
+
1768 "%s: failed to unmount %s: %s\n",
+
1769 progname, mnt, strerror(errno));
+
1770 }
+
1771 if (res == -1)
+
1772 goto err_out;
+
1773
+
1774success_out:
+
1775 free((void*) type);
+
1776 free(mnt);
+
1777 return 0;
+
1778
+
1779err_out:
+
1780 free((void*) type);
+
1781 free(mnt);
+
1782 exit(1);
+
1783}
+
+ + + + diff --git a/doc/html/fuse-3_818_81_2util_2mount_8fuse_8c_source.html b/doc/html/fuse-3_818_81_2util_2mount_8fuse_8c_source.html new file mode 100644 index 0000000..7866fbf --- /dev/null +++ b/doc/html/fuse-3_818_81_2util_2mount_8fuse_8c_source.html @@ -0,0 +1,515 @@ + + + + + + + +libfuse: fuse-3.18.1/util/mount.fuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount.fuse.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
9#include "fuse_config.h"
+
10
+
11#include <stdio.h>
+
12#include <stdlib.h>
+
13#include <string.h>
+
14#include <unistd.h>
+
15#include <errno.h>
+
16#include <stdint.h>
+
17#include <fcntl.h>
+
18#include <pwd.h>
+
19#include <sys/wait.h>
+
20
+
21#ifdef linux
+
22#include <sys/prctl.h>
+
23#include <sys/syscall.h>
+
24#include <linux/capability.h>
+
25#include <linux/securebits.h>
+
26/* for 2.6 kernels */
+
27#if !defined(SECBIT_KEEP_CAPS) && defined(SECURE_KEEP_CAPS)
+
28#define SECBIT_KEEP_CAPS (issecure_mask(SECURE_KEEP_CAPS))
+
29#endif
+
30#if !defined(SECBIT_KEEP_CAPS_LOCKED) && defined(SECURE_KEEP_CAPS_LOCKED)
+
31#define SECBIT_KEEP_CAPS_LOCKED (issecure_mask(SECURE_KEEP_CAPS_LOCKED))
+
32#endif
+
33#if !defined(SECBIT_NO_SETUID_FIXUP) && defined(SECURE_NO_SETUID_FIXUP)
+
34#define SECBIT_NO_SETUID_FIXUP (issecure_mask(SECURE_NO_SETUID_FIXUP))
+
35#endif
+
36#if !defined(SECBIT_NO_SETUID_FIXUP_LOCKED) && defined(SECURE_NO_SETUID_FIXUP_LOCKED)
+
37#define SECBIT_NO_SETUID_FIXUP_LOCKED (issecure_mask(SECURE_NO_SETUID_FIXUP_LOCKED))
+
38#endif
+
39#if !defined(SECBIT_NOROOT) && defined(SECURE_NOROOT)
+
40#define SECBIT_NOROOT (issecure_mask(SECURE_NOROOT))
+
41#endif
+
42#if !defined(SECBIT_NOROOT_LOCKED) && defined(SECURE_NOROOT_LOCKED)
+
43#define SECBIT_NOROOT_LOCKED (issecure_mask(SECURE_NOROOT_LOCKED))
+
44#endif
+
45#endif
+
46/* linux < 3.5 */
+
47#ifndef PR_SET_NO_NEW_PRIVS
+
48#define PR_SET_NO_NEW_PRIVS 38
+
49#endif
+
50
+
51#include "fuse.h"
+
52
+
53static char *progname;
+
54
+
55static char *xstrdup(const char *s)
+
56{
+
57 char *t = strdup(s);
+
58 if (!t) {
+
59 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
60 exit(1);
+
61 }
+
62 return t;
+
63}
+
64
+
65static void *xrealloc(void *oldptr, size_t size)
+
66{
+
67 void *ptr = realloc(oldptr, size);
+
68 if (!ptr) {
+
69 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
70 exit(1);
+
71 }
+
72 return ptr;
+
73}
+
74
+
75static void add_arg(char **cmdp, const char *opt)
+
76{
+
77 size_t optlen = strlen(opt);
+
78 size_t cmdlen = *cmdp ? strlen(*cmdp) : 0;
+
79 if (optlen >= (SIZE_MAX - cmdlen - 4)/4) {
+
80 fprintf(stderr, "%s: argument too long\n", progname);
+
81 exit(1);
+
82 }
+
83 char *cmd = xrealloc(*cmdp, cmdlen + optlen * 4 + 4);
+
84 char *s;
+
85 s = cmd + cmdlen;
+
86 if (*cmdp)
+
87 *s++ = ' ';
+
88
+
89 *s++ = '\'';
+
90 for (; *opt; opt++) {
+
91 if (*opt == '\'') {
+
92 *s++ = '\'';
+
93 *s++ = '\\';
+
94 *s++ = '\'';
+
95 *s++ = '\'';
+
96 } else
+
97 *s++ = *opt;
+
98 }
+
99 *s++ = '\'';
+
100 *s = '\0';
+
101 *cmdp = cmd;
+
102}
+
103
+
104static char *add_option(const char *opt, char *options)
+
105{
+
106 int oldlen = options ? strlen(options) : 0;
+
107
+
108 options = xrealloc(options, oldlen + 1 + strlen(opt) + 1);
+
109 if (!oldlen)
+
110 strcpy(options, opt);
+
111 else {
+
112 strcat(options, ",");
+
113 strcat(options, opt);
+
114 }
+
115 return options;
+
116}
+
117
+
118static int prepare_fuse_fd(const char *mountpoint, const char* subtype,
+
119 const char *options)
+
120{
+
121 int fuse_fd = -1;
+
122 int flags = -1;
+
123 int subtype_len = strlen(subtype) + 9;
+
124 char* options_copy = xrealloc(NULL, subtype_len);
+
125
+
126 snprintf(options_copy, subtype_len, "subtype=%s", subtype);
+
127 options_copy = add_option(options, options_copy);
+
128 fuse_fd = fuse_open_channel(mountpoint, options_copy);
+
129 if (fuse_fd == -1) {
+
130 exit(1);
+
131 }
+
132
+
133 flags = fcntl(fuse_fd, F_GETFD);
+
134 if (flags == -1 || fcntl(fuse_fd, F_SETFD, flags & ~FD_CLOEXEC) == 1) {
+
135 fprintf(stderr, "%s: Failed to clear CLOEXEC: %s\n",
+
136 progname, strerror(errno));
+
137 exit(1);
+
138 }
+
139
+
140 return fuse_fd;
+
141}
+
142
+
143#ifdef linux
+
144static uint64_t get_capabilities(void)
+
145{
+
146 /*
+
147 * This invokes the capset syscall directly to avoid the libcap
+
148 * dependency, which isn't really justified just for this.
+
149 */
+
150 struct __user_cap_header_struct header = {
+
151 .version = _LINUX_CAPABILITY_VERSION_3,
+
152 .pid = 0,
+
153 };
+
154 struct __user_cap_data_struct data[2];
+
155 memset(data, 0, sizeof(data));
+
156 if (syscall(SYS_capget, &header, data) == -1) {
+
157 fprintf(stderr, "%s: Failed to get capabilities: %s\n",
+
158 progname, strerror(errno));
+
159 exit(1);
+
160 }
+
161
+
162 return data[0].effective | ((uint64_t) data[1].effective << 32);
+
163}
+
164
+
165static void set_capabilities(uint64_t caps)
+
166{
+
167 /*
+
168 * This invokes the capset syscall directly to avoid the libcap
+
169 * dependency, which isn't really justified just for this.
+
170 */
+
171 struct __user_cap_header_struct header = {
+
172 .version = _LINUX_CAPABILITY_VERSION_3,
+
173 .pid = 0,
+
174 };
+
175 struct __user_cap_data_struct data[2];
+
176 memset(data, 0, sizeof(data));
+
177 data[0].effective = data[0].permitted = caps;
+
178 data[1].effective = data[1].permitted = caps >> 32;
+
179 if (syscall(SYS_capset, &header, data) == -1) {
+
180 fprintf(stderr, "%s: Failed to set capabilities: %s\n",
+
181 progname, strerror(errno));
+
182 exit(1);
+
183 }
+
184}
+
185
+
186static void drop_and_lock_capabilities(void)
+
187{
+
188 /* Set and lock securebits. */
+
189 if (prctl(PR_SET_SECUREBITS,
+
190 SECBIT_KEEP_CAPS_LOCKED |
+
191 SECBIT_NO_SETUID_FIXUP |
+
192 SECBIT_NO_SETUID_FIXUP_LOCKED |
+
193 SECBIT_NOROOT |
+
194 SECBIT_NOROOT_LOCKED) == -1) {
+
195 fprintf(stderr, "%s: Failed to set securebits %s\n",
+
196 progname, strerror(errno));
+
197 exit(1);
+
198 }
+
199
+
200 /* Clear the capability bounding set. */
+
201 int cap;
+
202 for (cap = 0; ; cap++) {
+
203 int cap_status = prctl(PR_CAPBSET_READ, cap);
+
204 if (cap_status == 0) {
+
205 continue;
+
206 }
+
207 if (cap_status == -1 && errno == EINVAL) {
+
208 break;
+
209 }
+
210
+
211 if (cap_status != 1) {
+
212 fprintf(stderr,
+
213 "%s: Failed to get capability %u: %s\n",
+
214 progname, cap, strerror(errno));
+
215 exit(1);
+
216 }
+
217 if (prctl(PR_CAPBSET_DROP, cap) == -1) {
+
218 fprintf(stderr,
+
219 "%s: Failed to drop capability %u: %s\n",
+
220 progname, cap, strerror(errno));
+
221 }
+
222 }
+
223
+
224 /* Drop capabilities. */
+
225 set_capabilities(0);
+
226
+
227 /* Prevent re-acquisition of privileges. */
+
228 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
+
229 fprintf(stderr, "%s: Failed to set no_new_privs: %s\n",
+
230 progname, strerror(errno));
+
231 exit(1);
+
232 }
+
233}
+
234#endif
+
235
+
236int main(int argc, char *argv[])
+
237{
+
238 char *type = NULL;
+
239 char *source;
+
240 char *dup_source = NULL;
+
241 const char *mountpoint;
+
242 char *basename;
+
243 char *options = NULL;
+
244 char *command = NULL;
+
245 char *setuid_name = NULL;
+
246 int i;
+
247 int dev = 1;
+
248 int suid = 1;
+
249 int pass_fuse_fd = 0;
+
250 int fuse_fd = 0;
+
251 int drop_privileges = 0;
+
252 char *dev_fd_mountpoint = NULL;
+
253
+
254 progname = argv[0];
+
255 basename = strrchr(argv[0], '/');
+
256 if (basename)
+
257 basename++;
+
258 else
+
259 basename = argv[0];
+
260
+
261 if (strncmp(basename, "mount.fuse.", 11) == 0)
+
262 type = basename + 11;
+
263 if (strncmp(basename, "mount.fuseblk.", 14) == 0)
+
264 type = basename + 14;
+
265
+
266 if (type && !type[0])
+
267 type = NULL;
+
268
+
269 if (argc < 3) {
+
270 fprintf(stderr,
+
271 "usage: %s %s destination [-t type] [-o opt[,opts...]]\n",
+
272 progname, type ? "source" : "type#[source]");
+
273 exit(1);
+
274 }
+
275
+
276 source = argv[1];
+
277 if (!source[0])
+
278 source = NULL;
+
279
+
280 mountpoint = argv[2];
+
281
+
282 for (i = 3; i < argc; i++) {
+
283 if (strcmp(argv[i], "-v") == 0) {
+
284 continue;
+
285 } else if (strcmp(argv[i], "-t") == 0) {
+
286 i++;
+
287
+
288 if (i == argc) {
+
289 fprintf(stderr,
+
290 "%s: missing argument to option '-t'\n",
+
291 progname);
+
292 exit(1);
+
293 }
+
294 type = argv[i];
+
295 if (strncmp(type, "fuse.", 5) == 0)
+
296 type += 5;
+
297 else if (strncmp(type, "fuseblk.", 8) == 0)
+
298 type += 8;
+
299
+
300 if (!type[0]) {
+
301 fprintf(stderr,
+
302 "%s: empty type given as argument to option '-t'\n",
+
303 progname);
+
304 exit(1);
+
305 }
+
306 } else if (strcmp(argv[i], "-o") == 0) {
+
307 char *opts;
+
308 char *opt;
+
309 i++;
+
310 if (i == argc)
+
311 break;
+
312
+
313 opts = xstrdup(argv[i]);
+
314 opt = strtok(opts, ",");
+
315 while (opt) {
+
316 int j;
+
317 int ignore = 0;
+
318 const char *ignore_opts[] = { "",
+
319 "user",
+
320 "nofail",
+
321 "nouser",
+
322 "users",
+
323 "auto",
+
324 "noauto",
+
325 "_netdev",
+
326 NULL};
+
327 if (strncmp(opt, "setuid=", 7) == 0) {
+
328 setuid_name = xstrdup(opt + 7);
+
329 ignore = 1;
+
330 } else if (strcmp(opt,
+
331 "drop_privileges") == 0) {
+
332 pass_fuse_fd = 1;
+
333 drop_privileges = 1;
+
334 ignore = 1;
+
335 }
+
336 for (j = 0; ignore_opts[j]; j++)
+
337 if (strcmp(opt, ignore_opts[j]) == 0)
+
338 ignore = 1;
+
339
+
340 if (!ignore) {
+
341 if (strcmp(opt, "nodev") == 0)
+
342 dev = 0;
+
343 else if (strcmp(opt, "nosuid") == 0)
+
344 suid = 0;
+
345
+
346 options = add_option(opt, options);
+
347 }
+
348 opt = strtok(NULL, ",");
+
349 }
+
350 free(opts);
+
351 }
+
352 }
+
353
+
354 if (drop_privileges) {
+
355 uint64_t required_caps = CAP_TO_MASK(CAP_SETPCAP) |
+
356 CAP_TO_MASK(CAP_SYS_ADMIN);
+
357 if ((get_capabilities() & required_caps) != required_caps) {
+
358 fprintf(stderr, "%s: drop_privileges was requested, which launches the FUSE file system fully unprivileged. In order to do so %s must be run with privileges, please invoke with CAP_SYS_ADMIN and CAP_SETPCAP (e.g. as root).\n",
+
359 progname, progname);
+
360 exit(1);
+
361 }
+
362 }
+
363
+
364 if (dev)
+
365 options = add_option("dev", options);
+
366 if (suid)
+
367 options = add_option("suid", options);
+
368
+
369 if (!type) {
+
370 if (source) {
+
371 dup_source = xstrdup(source);
+
372 type = dup_source;
+
373 source = strchr(type, '#');
+
374 if (source)
+
375 *source++ = '\0';
+
376 if (!type[0]) {
+
377 fprintf(stderr, "%s: empty filesystem type\n",
+
378 progname);
+
379 exit(1);
+
380 }
+
381 } else {
+
382 fprintf(stderr, "%s: empty source\n", progname);
+
383 exit(1);
+
384 }
+
385 }
+
386
+
387 if (setuid_name && setuid_name[0]) {
+
388#ifdef linux
+
389 if (drop_privileges) {
+
390 /*
+
391 * Make securebits more permissive before calling
+
392 * setuid(). Specifically, if SECBIT_KEEP_CAPS and
+
393 * SECBIT_NO_SETUID_FIXUP weren't set, setuid() would
+
394 * have the side effect of dropping all capabilities,
+
395 * and we need to retain CAP_SETPCAP in order to drop
+
396 * all privileges before exec().
+
397 */
+
398 if (prctl(PR_SET_SECUREBITS,
+
399 SECBIT_KEEP_CAPS |
+
400 SECBIT_NO_SETUID_FIXUP) == -1) {
+
401 fprintf(stderr,
+
402 "%s: Failed to set securebits %s\n",
+
403 progname, strerror(errno));
+
404 exit(1);
+
405 }
+
406 }
+
407#endif
+
408
+
409 struct passwd *pwd = getpwnam(setuid_name);
+
410 if (!pwd || setgid(pwd->pw_gid) == -1 || setuid(pwd->pw_uid) == -1) {
+
411 fprintf(stderr, "%s: Failed to setuid to %s: %s\n",
+
412 progname, setuid_name, strerror(errno));
+
413 exit(1);
+
414 }
+
415 } else if (!getenv("HOME")) {
+
416 /* Hack to make filesystems work in the boot environment */
+
417 setenv("HOME", "/root", 0);
+
418 }
+
419
+
420 if (pass_fuse_fd) {
+
421 fuse_fd = prepare_fuse_fd(mountpoint, type, options);
+
422 dev_fd_mountpoint = xrealloc(NULL, 20);
+
423 snprintf(dev_fd_mountpoint, 20, "/dev/fd/%u", fuse_fd);
+
424 mountpoint = dev_fd_mountpoint;
+
425 }
+
426
+
427#ifdef linux
+
428 if (drop_privileges) {
+
429 drop_and_lock_capabilities();
+
430 }
+
431#endif
+
432 add_arg(&command, type);
+
433 if (source)
+
434 add_arg(&command, source);
+
435 add_arg(&command, mountpoint);
+
436 if (options) {
+
437 add_arg(&command, "-o");
+
438 add_arg(&command, options);
+
439 }
+
440
+
441 free(options);
+
442 free(dev_fd_mountpoint);
+
443 free(dup_source);
+
444 free(setuid_name);
+
445
+
446 execl("/bin/sh", "/bin/sh", "-c", command, NULL);
+
447 fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
+
448 strerror(errno));
+
449
+
450 if (pass_fuse_fd)
+
451 close(fuse_fd);
+
452 free(command);
+
453 return 1;
+
454}
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:482
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2cuse_8c.html b/doc/html/fuse-3_818_82_2example_2cuse_8c.html new file mode 100644 index 0000000..b4e663a --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2cuse_8c.html @@ -0,0 +1,409 @@ + + + + + + + +libfuse: fuse-3.18.2/example/cuse.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
cuse.c File Reference
+
+
+
#include <cuse_lowlevel.h>
+#include <fuse_opt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This example demonstrates how to implement a character device in userspace ("CUSE"). This is only allowed for root. The character device should appear in /dev under the specified name. It can be tested with the cuse_client.c program.

+

Mount the file system with:

cuse -f --name=mydevice
+

You should now have a new /dev/mydevice character device. To "unmount" it, kill the "cuse" process.

+

To compile this example, run

gcc -Wall cuse.c `pkg-config fuse3 --cflags --libs` -o cuse
+

+Source code

+
/*
+
CUSE example: Character device in Userspace
+
Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <cuse_lowlevel.h>
+
#include <fuse_opt.h>
+
#include <stddef.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <errno.h>
+
+
#include "ioctl.h"
+
+
static void *cusexmp_buf;
+
static size_t cusexmp_size;
+
+
static const char *usage =
+
"usage: cusexmp [options]\n"
+
"\n"
+
"options:\n"
+
" --help|-h print this help message\n"
+
" --maj=MAJ|-M MAJ device major number\n"
+
" --min=MIN|-m MIN device minor number\n"
+
" --name=NAME|-n NAME device name (mandatory)\n"
+
" -d -o debug enable debug output (implies -f)\n"
+
" -f foreground operation\n"
+
" -s disable multi-threaded operation\n"
+
"\n";
+
+
static int cusexmp_resize(size_t new_size)
+
{
+
void *new_buf;
+
+
if (new_size == cusexmp_size)
+
return 0;
+
+
new_buf = realloc(cusexmp_buf, new_size);
+
if (!new_buf && new_size)
+
return -ENOMEM;
+
+
if (new_size > cusexmp_size)
+
memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
+
cusexmp_buf = new_buf;
+
cusexmp_size = new_size;
+
+
return 0;
+
}
+
+
static int cusexmp_expand(size_t new_size)
+
{
+
if (new_size > cusexmp_size)
+
return cusexmp_resize(new_size);
+
return 0;
+
}
+
+
static void cusexmp_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+
{
+
fuse_reply_open(req, fi);
+
}
+
+
static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+
struct fuse_file_info *fi)
+
{
+
(void)fi;
+
+
if (off >= cusexmp_size)
+
off = cusexmp_size;
+
if (size > cusexmp_size - off)
+
size = cusexmp_size - off;
+
+
fuse_reply_buf(req, cusexmp_buf + off, size);
+
}
+
+
static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void)fi;
+
+
if (cusexmp_expand(off + size)) {
+
fuse_reply_err(req, ENOMEM);
+
return;
+
}
+
+
memcpy(cusexmp_buf + off, buf, size);
+
fuse_reply_write(req, size);
+
}
+
+
static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+
size_t in_bufsz, size_t out_bufsz, int is_read)
+
{
+
const struct fioc_rw_arg *arg;
+
struct iovec in_iov[2], out_iov[3], iov[3];
+
size_t cur_size;
+
+
/* read in arg */
+
in_iov[0].iov_base = addr;
+
in_iov[0].iov_len = sizeof(*arg);
+
if (!in_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+
return;
+
}
+
arg = in_buf;
+
in_buf += sizeof(*arg);
+
in_bufsz -= sizeof(*arg);
+
+
/* prepare size outputs */
+
out_iov[0].iov_base =
+
addr + offsetof(struct fioc_rw_arg, prev_size);
+
out_iov[0].iov_len = sizeof(arg->prev_size);
+
+
out_iov[1].iov_base =
+
addr + offsetof(struct fioc_rw_arg, new_size);
+
out_iov[1].iov_len = sizeof(arg->new_size);
+
+
/* prepare client buf */
+
if (is_read) {
+
out_iov[2].iov_base = arg->buf;
+
out_iov[2].iov_len = arg->size;
+
if (!out_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+
return;
+
}
+
} else {
+
in_iov[1].iov_base = arg->buf;
+
in_iov[1].iov_len = arg->size;
+
if (arg->size && !in_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+
return;
+
}
+
}
+
+
/* we're all set */
+
cur_size = cusexmp_size;
+
iov[0].iov_base = &cur_size;
+
iov[0].iov_len = sizeof(cur_size);
+
+
iov[1].iov_base = &cusexmp_size;
+
iov[1].iov_len = sizeof(cusexmp_size);
+
+
if (is_read) {
+
size_t off = arg->offset;
+
size_t size = arg->size;
+
+
if (off >= cusexmp_size)
+
off = cusexmp_size;
+
if (size > cusexmp_size - off)
+
size = cusexmp_size - off;
+
+
iov[2].iov_base = cusexmp_buf + off;
+
iov[2].iov_len = size;
+
fuse_reply_ioctl_iov(req, size, iov, 3);
+
} else {
+
if (cusexmp_expand(arg->offset + in_bufsz)) {
+
fuse_reply_err(req, ENOMEM);
+
return;
+
}
+
+
memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+
fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+
}
+
}
+
+
static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+
struct fuse_file_info *fi, unsigned flags,
+
const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
{
+
int is_read = 0;
+
+
(void)fi;
+
+
if (flags & FUSE_IOCTL_COMPAT) {
+
fuse_reply_err(req, ENOSYS);
+
return;
+
}
+
+
switch (cmd) {
+
case FIOC_GET_SIZE:
+
if (!out_bufsz) {
+
struct iovec iov = { arg, sizeof(size_t) };
+
+
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+
} else
+
fuse_reply_ioctl(req, 0, &cusexmp_size,
+
sizeof(cusexmp_size));
+
break;
+
+
case FIOC_SET_SIZE:
+
if (!in_bufsz) {
+
struct iovec iov = { arg, sizeof(size_t) };
+
+
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+
} else {
+
cusexmp_resize(*(size_t *)in_buf);
+
fuse_reply_ioctl(req, 0, NULL, 0);
+
}
+
break;
+
+
case FIOC_READ:
+
is_read = 1;
+
/* fall through */
+
case FIOC_WRITE:
+
fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+
break;
+
+
default:
+
fuse_reply_err(req, EINVAL);
+
}
+
}
+
+
struct cusexmp_param {
+
unsigned major;
+
unsigned minor;
+
char *dev_name;
+
int is_help;
+
};
+
+
#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
+
static const struct fuse_opt cusexmp_opts[] = {
+
CUSEXMP_OPT("-M %u", major),
+
CUSEXMP_OPT("--maj=%u", major),
+
CUSEXMP_OPT("-m %u", minor),
+
CUSEXMP_OPT("--min=%u", minor),
+
CUSEXMP_OPT("-n %s", dev_name),
+
CUSEXMP_OPT("--name=%s", dev_name),
+
FUSE_OPT_KEY("-h", 0),
+
FUSE_OPT_KEY("--help", 0),
+ +
};
+
+
static int cusexmp_process_arg(void *data, const char *arg, int key,
+
struct fuse_args *outargs)
+
{
+
struct cusexmp_param *param = data;
+
+
(void)outargs;
+
(void)arg;
+
+
switch (key) {
+
case 0:
+
param->is_help = 1;
+
fprintf(stderr, "%s", usage);
+
return fuse_opt_add_arg(outargs, "-ho");
+
default:
+
return 1;
+
}
+
}
+
+
static const struct cuse_lowlevel_ops cusexmp_clop = {
+
.init = cusexmp_init,
+
.open = cusexmp_open,
+
.read = cusexmp_read,
+
.write = cusexmp_write,
+
.ioctl = cusexmp_ioctl,
+
};
+
+
int main(int argc, char **argv)
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct cusexmp_param param = { 0, 0, NULL, 0 };
+
char dev_name[128] = "DEVNAME=";
+
const char *dev_info_argv[] = { dev_name };
+
struct cuse_info ci;
+
int ret = 1;
+
+
if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+
printf("failed to parse option\n");
+
free(param.dev_name);
+
goto out;
+
}
+
+
if (!param.is_help) {
+
if (!param.dev_name) {
+
fprintf(stderr, "Error: device name missing\n");
+
goto out;
+
}
+
strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+
free(param.dev_name);
+
}
+
+
memset(&ci, 0, sizeof(ci));
+
ci.dev_major = param.major;
+
ci.dev_minor = param.minor;
+
ci.dev_info_argc = 1;
+
ci.dev_info_argv = dev_info_argv;
+
ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
+
ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL);
+
+
out:
+ +
return ret;
+
}
+
#define FUSE_IOCTL_COMPAT
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t no_interrupt
+ + +
+

Definition in file cuse.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2cuse_8c_source.html b/doc/html/fuse-3_818_82_2example_2cuse_8c_source.html new file mode 100644 index 0000000..766c398 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2cuse_8c_source.html @@ -0,0 +1,395 @@ + + + + + + + +libfuse: fuse-3.18.2/example/cuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse.c
+
+
+Go to the documentation of this file.
1/*
+
2 CUSE example: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8
+
9*/
+
10
+
34#define FUSE_USE_VERSION 31
+
35
+
36#include <cuse_lowlevel.h>
+
37#include <fuse_opt.h>
+
38#include <stddef.h>
+
39#include <stdio.h>
+
40#include <stdlib.h>
+
41#include <string.h>
+
42#include <unistd.h>
+
43#include <errno.h>
+
44
+
45#include "ioctl.h"
+
46
+
47static void *cusexmp_buf;
+
48static size_t cusexmp_size;
+
49
+
50static const char *usage =
+
51"usage: cusexmp [options]\n"
+
52"\n"
+
53"options:\n"
+
54" --help|-h print this help message\n"
+
55" --maj=MAJ|-M MAJ device major number\n"
+
56" --min=MIN|-m MIN device minor number\n"
+
57" --name=NAME|-n NAME device name (mandatory)\n"
+
58" -d -o debug enable debug output (implies -f)\n"
+
59" -f foreground operation\n"
+
60" -s disable multi-threaded operation\n"
+
61"\n";
+
62
+
63static int cusexmp_resize(size_t new_size)
+
64{
+
65 void *new_buf;
+
66
+
67 if (new_size == cusexmp_size)
+
68 return 0;
+
69
+
70 new_buf = realloc(cusexmp_buf, new_size);
+
71 if (!new_buf && new_size)
+
72 return -ENOMEM;
+
73
+
74 if (new_size > cusexmp_size)
+
75 memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
76
+
77 cusexmp_buf = new_buf;
+
78 cusexmp_size = new_size;
+
79
+
80 return 0;
+
81}
+
82
+
83static int cusexmp_expand(size_t new_size)
+
84{
+
85 if (new_size > cusexmp_size)
+
86 return cusexmp_resize(new_size);
+
87 return 0;
+
88}
+
89
+
90static void cusexmp_init(void *userdata, struct fuse_conn_info *conn)
+
91{
+
92 (void)userdata;
+
93
+
94 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
95 conn->no_interrupt = 1;
+
96}
+
97
+
98static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+
99{
+
100 fuse_reply_open(req, fi);
+
101}
+
102
+
103static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+
104 struct fuse_file_info *fi)
+
105{
+
106 (void)fi;
+
107
+
108 if (off >= cusexmp_size)
+
109 off = cusexmp_size;
+
110 if (size > cusexmp_size - off)
+
111 size = cusexmp_size - off;
+
112
+
113 fuse_reply_buf(req, cusexmp_buf + off, size);
+
114}
+
115
+
116static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+
117 off_t off, struct fuse_file_info *fi)
+
118{
+
119 (void)fi;
+
120
+
121 if (cusexmp_expand(off + size)) {
+
122 fuse_reply_err(req, ENOMEM);
+
123 return;
+
124 }
+
125
+
126 memcpy(cusexmp_buf + off, buf, size);
+
127 fuse_reply_write(req, size);
+
128}
+
129
+
130static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+
131 size_t in_bufsz, size_t out_bufsz, int is_read)
+
132{
+
133 const struct fioc_rw_arg *arg;
+
134 struct iovec in_iov[2], out_iov[3], iov[3];
+
135 size_t cur_size;
+
136
+
137 /* read in arg */
+
138 in_iov[0].iov_base = addr;
+
139 in_iov[0].iov_len = sizeof(*arg);
+
140 if (!in_bufsz) {
+
141 fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+
142 return;
+
143 }
+
144 arg = in_buf;
+
145 in_buf += sizeof(*arg);
+
146 in_bufsz -= sizeof(*arg);
+
147
+
148 /* prepare size outputs */
+
149 out_iov[0].iov_base =
+
150 addr + offsetof(struct fioc_rw_arg, prev_size);
+
151 out_iov[0].iov_len = sizeof(arg->prev_size);
+
152
+
153 out_iov[1].iov_base =
+
154 addr + offsetof(struct fioc_rw_arg, new_size);
+
155 out_iov[1].iov_len = sizeof(arg->new_size);
+
156
+
157 /* prepare client buf */
+
158 if (is_read) {
+
159 out_iov[2].iov_base = arg->buf;
+
160 out_iov[2].iov_len = arg->size;
+
161 if (!out_bufsz) {
+
162 fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+
163 return;
+
164 }
+
165 } else {
+
166 in_iov[1].iov_base = arg->buf;
+
167 in_iov[1].iov_len = arg->size;
+
168 if (arg->size && !in_bufsz) {
+
169 fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+
170 return;
+
171 }
+
172 }
+
173
+
174 /* we're all set */
+
175 cur_size = cusexmp_size;
+
176 iov[0].iov_base = &cur_size;
+
177 iov[0].iov_len = sizeof(cur_size);
+
178
+
179 iov[1].iov_base = &cusexmp_size;
+
180 iov[1].iov_len = sizeof(cusexmp_size);
+
181
+
182 if (is_read) {
+
183 size_t off = arg->offset;
+
184 size_t size = arg->size;
+
185
+
186 if (off >= cusexmp_size)
+
187 off = cusexmp_size;
+
188 if (size > cusexmp_size - off)
+
189 size = cusexmp_size - off;
+
190
+
191 iov[2].iov_base = cusexmp_buf + off;
+
192 iov[2].iov_len = size;
+
193 fuse_reply_ioctl_iov(req, size, iov, 3);
+
194 } else {
+
195 if (cusexmp_expand(arg->offset + in_bufsz)) {
+
196 fuse_reply_err(req, ENOMEM);
+
197 return;
+
198 }
+
199
+
200 memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+
201 fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+
202 }
+
203}
+
204
+
205static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+
206 struct fuse_file_info *fi, unsigned flags,
+
207 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
208{
+
209 int is_read = 0;
+
210
+
211 (void)fi;
+
212
+
213 if (flags & FUSE_IOCTL_COMPAT) {
+
214 fuse_reply_err(req, ENOSYS);
+
215 return;
+
216 }
+
217
+
218 switch (cmd) {
+
219 case FIOC_GET_SIZE:
+
220 if (!out_bufsz) {
+
221 struct iovec iov = { arg, sizeof(size_t) };
+
222
+
223 fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+
224 } else
+
225 fuse_reply_ioctl(req, 0, &cusexmp_size,
+
226 sizeof(cusexmp_size));
+
227 break;
+
228
+
229 case FIOC_SET_SIZE:
+
230 if (!in_bufsz) {
+
231 struct iovec iov = { arg, sizeof(size_t) };
+
232
+
233 fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+
234 } else {
+
235 cusexmp_resize(*(size_t *)in_buf);
+
236 fuse_reply_ioctl(req, 0, NULL, 0);
+
237 }
+
238 break;
+
239
+
240 case FIOC_READ:
+
241 is_read = 1;
+
242 /* fall through */
+
243 case FIOC_WRITE:
+
244 fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+
245 break;
+
246
+
247 default:
+
248 fuse_reply_err(req, EINVAL);
+
249 }
+
250}
+
251
+
252struct cusexmp_param {
+
253 unsigned major;
+
254 unsigned minor;
+
255 char *dev_name;
+
256 int is_help;
+
257};
+
258
+
259#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
260
+
261static const struct fuse_opt cusexmp_opts[] = {
+
262 CUSEXMP_OPT("-M %u", major),
+
263 CUSEXMP_OPT("--maj=%u", major),
+
264 CUSEXMP_OPT("-m %u", minor),
+
265 CUSEXMP_OPT("--min=%u", minor),
+
266 CUSEXMP_OPT("-n %s", dev_name),
+
267 CUSEXMP_OPT("--name=%s", dev_name),
+
268 FUSE_OPT_KEY("-h", 0),
+
269 FUSE_OPT_KEY("--help", 0),
+ +
271};
+
272
+
273static int cusexmp_process_arg(void *data, const char *arg, int key,
+
274 struct fuse_args *outargs)
+
275{
+
276 struct cusexmp_param *param = data;
+
277
+
278 (void)outargs;
+
279 (void)arg;
+
280
+
281 switch (key) {
+
282 case 0:
+
283 param->is_help = 1;
+
284 fprintf(stderr, "%s", usage);
+
285 return fuse_opt_add_arg(outargs, "-ho");
+
286 default:
+
287 return 1;
+
288 }
+
289}
+
290
+
291static const struct cuse_lowlevel_ops cusexmp_clop = {
+
292 .init = cusexmp_init,
+
293 .open = cusexmp_open,
+
294 .read = cusexmp_read,
+
295 .write = cusexmp_write,
+
296 .ioctl = cusexmp_ioctl,
+
297};
+
298
+
299int main(int argc, char **argv)
+
300{
+
301 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
302 struct cusexmp_param param = { 0, 0, NULL, 0 };
+
303 char dev_name[128] = "DEVNAME=";
+
304 const char *dev_info_argv[] = { dev_name };
+
305 struct cuse_info ci;
+
306 int ret = 1;
+
307
+
308 if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+
309 printf("failed to parse option\n");
+
310 free(param.dev_name);
+
311 goto out;
+
312 }
+
313
+
314 if (!param.is_help) {
+
315 if (!param.dev_name) {
+
316 fprintf(stderr, "Error: device name missing\n");
+
317 goto out;
+
318 }
+
319 strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+
320 free(param.dev_name);
+
321 }
+
322
+
323 memset(&ci, 0, sizeof(ci));
+
324 ci.dev_major = param.major;
+
325 ci.dev_minor = param.minor;
+
326 ci.dev_info_argc = 1;
+
327 ci.dev_info_argv = dev_info_argv;
+
328 ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
329
+
330 ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL);
+
331
+
332out:
+
333 fuse_opt_free_args(&args);
+
334 return ret;
+
335}
+
#define FUSE_IOCTL_COMPAT
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t no_interrupt
+ + +
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2cuse__client_8c.html b/doc/html/fuse-3_818_82_2example_2cuse__client_8c.html new file mode 100644 index 0000000..36091bf --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2cuse__client_8c.html @@ -0,0 +1,214 @@ + + + + + + + +libfuse: fuse-3.18.2/example/cuse_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
cuse_client.c File Reference
+
+
+
#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the cuse.c example file system.

+

Example usage (assuming that /dev/foobar is a CUSE device provided by the cuse.c example file system):

$ cuse_client /dev/foobar s
+0
+
+$ echo "hello" | cuse_client /dev/foobar w 6
+Writing 6 bytes
+transferred 6 bytes (0 -> 6)
+
+$ cuse_client /dev/foobar s
+6
+
+$ cuse_client /dev/foobar r 10
+hello
+transferred 6 bytes (6 -> 6)
+

Compiling this example

gcc -Wall cuse_client.c -o cuse_client
+

+Source Code

+
/*
+
FUSE fioclient: FUSE ioctl example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#include <sys/types.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <sys/ioctl.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <ctype.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include "ioctl.h"
+
+
const char *usage =
+
"Usage: cuse_client FIOC_FILE COMMAND\n"
+
"\n"
+
"COMMANDS\n"
+
" s [SIZE] : get size if SIZE is omitted, set size otherwise\n"
+
" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+
" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+
"\n";
+
+
static int do_rw(int fd, int is_read, size_t size, off_t offset,
+
size_t *prev_size, size_t *new_size)
+
{
+
struct fioc_rw_arg arg = { .offset = offset };
+
ssize_t ret;
+
+
arg.buf = calloc(1, size);
+
if (!arg.buf) {
+
fprintf(stderr, "failed to allocated %zu bytes\n", size);
+
return -1;
+
}
+
+
if (is_read) {
+
arg.size = size;
+
ret = ioctl(fd, FIOC_READ, &arg);
+
if (ret >= 0)
+
fwrite(arg.buf, 1, ret, stdout);
+
} else {
+
arg.size = fread(arg.buf, 1, size, stdin);
+
fprintf(stderr, "Writing %zu bytes\n", arg.size);
+
ret = ioctl(fd, FIOC_WRITE, &arg);
+
}
+
+
if (ret >= 0) {
+
*prev_size = arg.prev_size;
+
*new_size = arg.new_size;
+
} else
+
perror("ioctl");
+
+
free(arg.buf);
+
return ret;
+
}
+
+
int main(int argc, char **argv)
+
{
+
size_t param[2] = { };
+
size_t size, prev_size = 0, new_size = 0;
+
char cmd;
+
int fd, i, rc;
+
+
if (argc < 3)
+
goto usage;
+
+
fd = open(argv[1], O_RDWR);
+
if (fd < 0) {
+
perror("open");
+
return 1;
+
}
+
+
cmd = tolower(argv[2][0]);
+
argc -= 3;
+
argv += 3;
+
+
for (i = 0; i < argc; i++) {
+
char *endp;
+
param[i] = strtoul(argv[i], &endp, 0);
+
if (endp == argv[i] || *endp != '\0')
+
goto usage;
+
}
+
+
switch (cmd) {
+
case 's':
+
if (!argc) {
+
if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
perror("ioctl");
+
goto error;
+
}
+
printf("%zu\n", size);
+
} else {
+
size = param[0];
+
if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
perror("ioctl");
+
goto error;
+
}
+
}
+
close(fd);
+
return 0;
+
+
case 'r':
+
case 'w':
+
rc = do_rw(fd, cmd == 'r', param[0], param[1],
+
&prev_size, &new_size);
+
if (rc < 0)
+
goto error;
+
fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+
rc, prev_size, new_size);
+
close(fd);
+
return 0;
+
}
+
+
usage:
+
fprintf(stderr, "%s", usage);
+
return 1;
+
+
error:
+
close(fd);
+
return 1;
+
}
+
+

Definition in file cuse_client.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2cuse__client_8c_source.html b/doc/html/fuse-3_818_82_2example_2cuse__client_8c_source.html new file mode 100644 index 0000000..0bf38cc --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2cuse__client_8c_source.html @@ -0,0 +1,188 @@ + + + + + + + +libfuse: fuse-3.18.2/example/cuse_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioclient: FUSE ioctl example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
40#include <sys/types.h>
+
41#include <fcntl.h>
+
42#include <sys/stat.h>
+
43#include <sys/ioctl.h>
+
44#include <stdio.h>
+
45#include <stdlib.h>
+
46#include <ctype.h>
+
47#include <errno.h>
+
48#include <unistd.h>
+
49#include "ioctl.h"
+
50
+
51const char *usage =
+
52"Usage: cuse_client FIOC_FILE COMMAND\n"
+
53"\n"
+
54"COMMANDS\n"
+
55" s [SIZE] : get size if SIZE is omitted, set size otherwise\n"
+
56" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+
57" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+
58"\n";
+
59
+
60static int do_rw(int fd, int is_read, size_t size, off_t offset,
+
61 size_t *prev_size, size_t *new_size)
+
62{
+
63 struct fioc_rw_arg arg = { .offset = offset };
+
64 ssize_t ret;
+
65
+
66 arg.buf = calloc(1, size);
+
67 if (!arg.buf) {
+
68 fprintf(stderr, "failed to allocated %zu bytes\n", size);
+
69 return -1;
+
70 }
+
71
+
72 if (is_read) {
+
73 arg.size = size;
+
74 ret = ioctl(fd, FIOC_READ, &arg);
+
75 if (ret >= 0)
+
76 fwrite(arg.buf, 1, ret, stdout);
+
77 } else {
+
78 arg.size = fread(arg.buf, 1, size, stdin);
+
79 fprintf(stderr, "Writing %zu bytes\n", arg.size);
+
80 ret = ioctl(fd, FIOC_WRITE, &arg);
+
81 }
+
82
+
83 if (ret >= 0) {
+
84 *prev_size = arg.prev_size;
+
85 *new_size = arg.new_size;
+
86 } else
+
87 perror("ioctl");
+
88
+
89 free(arg.buf);
+
90 return ret;
+
91}
+
92
+
93int main(int argc, char **argv)
+
94{
+
95 size_t param[2] = { };
+
96 size_t size, prev_size = 0, new_size = 0;
+
97 char cmd;
+
98 int fd, i, rc;
+
99
+
100 if (argc < 3)
+
101 goto usage;
+
102
+
103 fd = open(argv[1], O_RDWR);
+
104 if (fd < 0) {
+
105 perror("open");
+
106 return 1;
+
107 }
+
108
+
109 cmd = tolower(argv[2][0]);
+
110 argc -= 3;
+
111 argv += 3;
+
112
+
113 for (i = 0; i < argc; i++) {
+
114 char *endp;
+
115 param[i] = strtoul(argv[i], &endp, 0);
+
116 if (endp == argv[i] || *endp != '\0')
+
117 goto usage;
+
118 }
+
119
+
120 switch (cmd) {
+
121 case 's':
+
122 if (!argc) {
+
123 if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
124 perror("ioctl");
+
125 goto error;
+
126 }
+
127 printf("%zu\n", size);
+
128 } else {
+
129 size = param[0];
+
130 if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
131 perror("ioctl");
+
132 goto error;
+
133 }
+
134 }
+
135 close(fd);
+
136 return 0;
+
137
+
138 case 'r':
+
139 case 'w':
+
140 rc = do_rw(fd, cmd == 'r', param[0], param[1],
+
141 &prev_size, &new_size);
+
142 if (rc < 0)
+
143 goto error;
+
144 fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+
145 rc, prev_size, new_size);
+
146 close(fd);
+
147 return 0;
+
148 }
+
149
+
150usage:
+
151 fprintf(stderr, "%s", usage);
+
152 return 1;
+
153
+
154error:
+
155 close(fd);
+
156 return 1;
+
157}
+ +
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2hello_8c.html b/doc/html/fuse-3_818_82_2example_2hello_8c.html new file mode 100644 index 0000000..4f6474b --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2hello_8c.html @@ -0,0 +1,265 @@ + + + + + + + +libfuse: fuse-3.18.2/example/hello.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using high-level API

+

Compile with:

gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <stddef.h>
+
#include <assert.h>
+
+
/*
+
* Command line options
+
*
+
* We can't set default values for the char* fields here because
+
* fuse_opt_parse would attempt to free() them when the user specifies
+
* different values on the command line.
+
*/
+
static struct options {
+
const char *filename;
+
const char *contents;
+
int show_help;
+
} options;
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--name=%s", filename),
+
OPTION("--contents=%s", contents),
+
OPTION("-h", show_help),
+
OPTION("--help", show_help),
+ +
};
+
+
static void *hello_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->kernel_cache = 1;
+
+
/* Test setting flags the old way */
+ + +
+
return NULL;
+
}
+
+
static int hello_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res = 0;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
} else if (strcmp(path+1, options.filename) == 0) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(options.contents);
+
} else
+
res = -ENOENT;
+
+
return res;
+
}
+
+
static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int hello_open(const char *path, struct fuse_file_info *fi)
+
{
+
if (strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
+
return 0;
+
}
+
+
static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
size_t len;
+
(void) fi;
+
if(strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
len = strlen(options.contents);
+
if (offset < len) {
+
if (offset + size > len)
+
size = len - offset;
+
memcpy(buf, options.contents + offset, size);
+
} else
+
size = 0;
+
+
return size;
+
}
+
+
static const struct fuse_operations hello_oper = {
+
.init = hello_init,
+
.getattr = hello_getattr,
+
.readdir = hello_readdir,
+
.open = hello_open,
+
.read = hello_read,
+
};
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --name=<s> Name of the \"hello\" file\n"
+
" (default: \"hello\")\n"
+
" --contents=<s> Contents \"hello\" file\n"
+
" (default \"Hello, World!\\n\")\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[])
+
{
+
int ret;
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
+
/* Set defaults -- we have to use strdup so that
+
fuse_opt_parse can free the defaults if other
+
values are specified */
+
options.filename = strdup("hello");
+
options.contents = strdup("Hello World!\n");
+
+
/* Parse options */
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
/* When --help is specified, first print our own file-system
+
specific help text, then signal fuse_main to show
+
additional help (by adding `--help` to the options again)
+
without usage: line (by setting argv[0] to the empty
+
string) */
+
if (options.show_help) {
+
show_help(argv[0]);
+
assert(fuse_opt_add_arg(&args, "--help") == 0);
+
args.argv[0][0] = '\0';
+
}
+
+
ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+ +
return ret;
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_ASYNC_READ
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file hello.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2hello_8c_source.html b/doc/html/fuse-3_818_82_2example_2hello_8c_source.html new file mode 100644 index 0000000..139f721 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2hello_8c_source.html @@ -0,0 +1,254 @@ + + + + + + + +libfuse: fuse-3.18.2/example/hello.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
22#define FUSE_USE_VERSION 31
+
23
+
24#include <fuse.h>
+
25#include <stdio.h>
+
26#include <string.h>
+
27#include <errno.h>
+
28#include <fcntl.h>
+
29#include <stddef.h>
+
30#include <assert.h>
+
31
+
32/*
+
33 * Command line options
+
34 *
+
35 * We can't set default values for the char* fields here because
+
36 * fuse_opt_parse would attempt to free() them when the user specifies
+
37 * different values on the command line.
+
38 */
+
39static struct options {
+
40 const char *filename;
+
41 const char *contents;
+
42 int show_help;
+
43} options;
+
44
+
45#define OPTION(t, p) \
+
46 { t, offsetof(struct options, p), 1 }
+
47static const struct fuse_opt option_spec[] = {
+
48 OPTION("--name=%s", filename),
+
49 OPTION("--contents=%s", contents),
+
50 OPTION("-h", show_help),
+
51 OPTION("--help", show_help),
+ +
53};
+
54
+
55static void *hello_init(struct fuse_conn_info *conn,
+
56 struct fuse_config *cfg)
+
57{
+
58 (void) conn;
+
59 cfg->kernel_cache = 1;
+
60
+
61 /* Test setting flags the old way */
+ + +
64
+
65 return NULL;
+
66}
+
67
+
68static int hello_getattr(const char *path, struct stat *stbuf,
+
69 struct fuse_file_info *fi)
+
70{
+
71 (void) fi;
+
72 int res = 0;
+
73
+
74 memset(stbuf, 0, sizeof(struct stat));
+
75 if (strcmp(path, "/") == 0) {
+
76 stbuf->st_mode = S_IFDIR | 0755;
+
77 stbuf->st_nlink = 2;
+
78 } else if (strcmp(path+1, options.filename) == 0) {
+
79 stbuf->st_mode = S_IFREG | 0444;
+
80 stbuf->st_nlink = 1;
+
81 stbuf->st_size = strlen(options.contents);
+
82 } else
+
83 res = -ENOENT;
+
84
+
85 return res;
+
86}
+
87
+
88static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
89 off_t offset, struct fuse_file_info *fi,
+
90 enum fuse_readdir_flags flags)
+
91{
+
92 (void) offset;
+
93 (void) fi;
+
94 (void) flags;
+
95
+
96 if (strcmp(path, "/") != 0)
+
97 return -ENOENT;
+
98
+
99 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
100 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
101 filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
102
+
103 return 0;
+
104}
+
105
+
106static int hello_open(const char *path, struct fuse_file_info *fi)
+
107{
+
108 if (strcmp(path+1, options.filename) != 0)
+
109 return -ENOENT;
+
110
+
111 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
112 return -EACCES;
+
113
+
114 return 0;
+
115}
+
116
+
117static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
118 struct fuse_file_info *fi)
+
119{
+
120 size_t len;
+
121 (void) fi;
+
122 if(strcmp(path+1, options.filename) != 0)
+
123 return -ENOENT;
+
124
+
125 len = strlen(options.contents);
+
126 if (offset < len) {
+
127 if (offset + size > len)
+
128 size = len - offset;
+
129 memcpy(buf, options.contents + offset, size);
+
130 } else
+
131 size = 0;
+
132
+
133 return size;
+
134}
+
135
+
136static const struct fuse_operations hello_oper = {
+
137 .init = hello_init,
+
138 .getattr = hello_getattr,
+
139 .readdir = hello_readdir,
+
140 .open = hello_open,
+
141 .read = hello_read,
+
142};
+
143
+
144static void show_help(const char *progname)
+
145{
+
146 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
147 printf("File-system specific options:\n"
+
148 " --name=<s> Name of the \"hello\" file\n"
+
149 " (default: \"hello\")\n"
+
150 " --contents=<s> Contents \"hello\" file\n"
+
151 " (default \"Hello, World!\\n\")\n"
+
152 "\n");
+
153}
+
154
+
155int main(int argc, char *argv[])
+
156{
+
157 int ret;
+
158 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
159
+
160 /* Set defaults -- we have to use strdup so that
+
161 fuse_opt_parse can free the defaults if other
+
162 values are specified */
+
163 options.filename = strdup("hello");
+
164 options.contents = strdup("Hello World!\n");
+
165
+
166 /* Parse options */
+
167 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
168 return 1;
+
169
+
170 /* When --help is specified, first print our own file-system
+
171 specific help text, then signal fuse_main to show
+
172 additional help (by adding `--help` to the options again)
+
173 without usage: line (by setting argv[0] to the empty
+
174 string) */
+
175 if (options.show_help) {
+
176 show_help(argv[0]);
+
177 assert(fuse_opt_add_arg(&args, "--help") == 0);
+
178 args.argv[0][0] = '\0';
+
179 }
+
180
+
181 ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+
182 fuse_opt_free_args(&args);
+
183 return ret;
+
184}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_ASYNC_READ
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2hello__ll_8c.html b/doc/html/fuse-3_818_82_2example_2hello__ll_8c.html new file mode 100644 index 0000000..2ec5401 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2hello__ll_8c.html @@ -0,0 +1,389 @@ + + + + + + + +libfuse: fuse-3.18.2/example/hello_ll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello_ll.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using low-level API

+

Compile with:

gcc -Wall hello_ll.c `pkg-config fuse3 --cflags --libs` -o hello_ll
+

Note: If the pkg-config command fails due to the absence of the fuse3.pc file, you should configure the path to the fuse3.pc file in the PKG_CONFIG_PATH variable.

+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <assert.h>
+
+
static const char *hello_str = "Hello World!\n";
+
static const char *hello_name = "hello";
+
+
static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
{
+
stbuf->st_ino = ino;
+
switch (ino) {
+
case 1:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
+
case 2:
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(hello_str);
+
break;
+
+
default:
+
return -1;
+
}
+
return 0;
+
}
+
+
static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
+
/* Test setting flags the old way */
+ +
conn->want &= ~FUSE_CAP_ASYNC_READ;
+
}
+
+
static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (hello_stat(ino, &stbuf) == -1)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, 1.0);
+
}
+
+
static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
+
if (parent != 1 || strcmp(name, hello_name) != 0)
+
fuse_reply_err(req, ENOENT);
+
else {
+
memset(&e, 0, sizeof(e));
+
e.ino = 2;
+
e.attr_timeout = 1.0;
+
e.entry_timeout = 1.0;
+
hello_stat(e.ino, &e.attr);
+
+
fuse_reply_entry(req, &e);
+
}
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+ +
{
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize)
+
{
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (ino != 1)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, ".", 1);
+
dirbuf_add(req, &b, "..", 1);
+
dirbuf_add(req, &b, hello_name, 2);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
if (ino != 2)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else
+
fuse_reply_open(req, fi);
+
}
+
+
static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
assert(ino == 2);
+
reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
}
+
+
static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
{
+
const char *buf = "hello_ll_getxattr_value";
+
fuse_reply_buf(req, buf, strlen(buf));
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
(void)flags;
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
const char* exp_val = "hello_ll_setxattr_value";
+
if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
strlen(exp_val) == size &&
+
strncmp(value, exp_val, size) == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops hello_ll_oper = {
+
.init = hello_ll_init,
+
.lookup = hello_ll_lookup,
+
.getattr = hello_ll_getattr,
+
.readdir = hello_ll_readdir,
+
.open = hello_ll_open,
+
.read = hello_ll_read,
+
.setxattr = hello_ll_setxattr,
+
.getxattr = hello_ll_getxattr,
+
.removexattr = hello_ll_removexattr,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
se = fuse_session_new(&args, &hello_ll_oper,
+
sizeof(hello_ll_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file hello_ll.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2hello__ll_8c_source.html b/doc/html/fuse-3_818_82_2example_2hello__ll_8c_source.html new file mode 100644 index 0000000..d6311d7 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2hello__ll_8c_source.html @@ -0,0 +1,376 @@ + + + + + + + +libfuse: fuse-3.18.2/example/hello_ll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello_ll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
25#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
26
+
27#include <fuse_lowlevel.h>
+
28#include <stdio.h>
+
29#include <stdlib.h>
+
30#include <string.h>
+
31#include <errno.h>
+
32#include <fcntl.h>
+
33#include <unistd.h>
+
34#include <assert.h>
+
35
+
36static const char *hello_str = "Hello World!\n";
+
37static const char *hello_name = "hello";
+
38
+
39static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
40{
+
41 stbuf->st_ino = ino;
+
42 switch (ino) {
+
43 case 1:
+
44 stbuf->st_mode = S_IFDIR | 0755;
+
45 stbuf->st_nlink = 2;
+
46 break;
+
47
+
48 case 2:
+
49 stbuf->st_mode = S_IFREG | 0444;
+
50 stbuf->st_nlink = 1;
+
51 stbuf->st_size = strlen(hello_str);
+
52 break;
+
53
+
54 default:
+
55 return -1;
+
56 }
+
57 return 0;
+
58}
+
59
+
60static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
61{
+
62 (void)userdata;
+
63
+
64 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
65 conn->no_interrupt = 1;
+
66
+
67 /* Test setting flags the old way */
+ +
69 conn->want &= ~FUSE_CAP_ASYNC_READ;
+
70}
+
71
+
72static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
73 struct fuse_file_info *fi)
+
74{
+
75 struct stat stbuf;
+
76
+
77 (void) fi;
+
78
+
79 memset(&stbuf, 0, sizeof(stbuf));
+
80 if (hello_stat(ino, &stbuf) == -1)
+
81 fuse_reply_err(req, ENOENT);
+
82 else
+
83 fuse_reply_attr(req, &stbuf, 1.0);
+
84}
+
85
+
86static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
87{
+
88 struct fuse_entry_param e;
+
89
+
90 if (parent != 1 || strcmp(name, hello_name) != 0)
+
91 fuse_reply_err(req, ENOENT);
+
92 else {
+
93 memset(&e, 0, sizeof(e));
+
94 e.ino = 2;
+
95 e.attr_timeout = 1.0;
+
96 e.entry_timeout = 1.0;
+
97 hello_stat(e.ino, &e.attr);
+
98
+
99 fuse_reply_entry(req, &e);
+
100 }
+
101}
+
102
+
103struct dirbuf {
+
104 char *p;
+
105 size_t size;
+
106};
+
107
+
108static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
109 fuse_ino_t ino)
+
110{
+
111 struct stat stbuf;
+
112 size_t oldsize = b->size;
+
113 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
114 b->p = (char *) realloc(b->p, b->size);
+
115 memset(&stbuf, 0, sizeof(stbuf));
+
116 stbuf.st_ino = ino;
+
117 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
118 b->size);
+
119}
+
120
+
121#define min(x, y) ((x) < (y) ? (x) : (y))
+
122
+
123static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
124 off_t off, size_t maxsize)
+
125{
+
126 if (off < bufsize)
+
127 return fuse_reply_buf(req, buf + off,
+
128 min(bufsize - off, maxsize));
+
129 else
+
130 return fuse_reply_buf(req, NULL, 0);
+
131}
+
132
+
133static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
134 off_t off, struct fuse_file_info *fi)
+
135{
+
136 (void) fi;
+
137
+
138 if (ino != 1)
+
139 fuse_reply_err(req, ENOTDIR);
+
140 else {
+
141 struct dirbuf b;
+
142
+
143 memset(&b, 0, sizeof(b));
+
144 dirbuf_add(req, &b, ".", 1);
+
145 dirbuf_add(req, &b, "..", 1);
+
146 dirbuf_add(req, &b, hello_name, 2);
+
147 reply_buf_limited(req, b.p, b.size, off, size);
+
148 free(b.p);
+
149 }
+
150}
+
151
+
152static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
153 struct fuse_file_info *fi)
+
154{
+
155 if (ino != 2)
+
156 fuse_reply_err(req, EISDIR);
+
157 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
158 fuse_reply_err(req, EACCES);
+
159 else
+
160 fuse_reply_open(req, fi);
+
161}
+
162
+
163static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
164 off_t off, struct fuse_file_info *fi)
+
165{
+
166 (void) fi;
+
167
+
168 assert(ino == 2);
+
169 reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
170}
+
171
+
172static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
173 size_t size)
+
174{
+
175 (void)size;
+
176 assert(ino == 1 || ino == 2);
+
177 if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
178 {
+
179 const char *buf = "hello_ll_getxattr_value";
+
180 fuse_reply_buf(req, buf, strlen(buf));
+
181 }
+
182 else
+
183 {
+
184 fuse_reply_err(req, ENOTSUP);
+
185 }
+
186}
+
187
+
188static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
189 const char *value, size_t size, int flags)
+
190{
+
191 (void)flags;
+
192 (void)size;
+
193 assert(ino == 1 || ino == 2);
+
194 const char* exp_val = "hello_ll_setxattr_value";
+
195 if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
196 strlen(exp_val) == size &&
+
197 strncmp(value, exp_val, size) == 0)
+
198 {
+
199 fuse_reply_err(req, 0);
+
200 }
+
201 else
+
202 {
+
203 fuse_reply_err(req, ENOTSUP);
+
204 }
+
205}
+
206
+
207static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
208{
+
209 assert(ino == 1 || ino == 2);
+
210 if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
211 {
+
212 fuse_reply_err(req, 0);
+
213 }
+
214 else
+
215 {
+
216 fuse_reply_err(req, ENOTSUP);
+
217 }
+
218}
+
219
+
220static const struct fuse_lowlevel_ops hello_ll_oper = {
+
221 .init = hello_ll_init,
+
222 .lookup = hello_ll_lookup,
+
223 .getattr = hello_ll_getattr,
+
224 .readdir = hello_ll_readdir,
+
225 .open = hello_ll_open,
+
226 .read = hello_ll_read,
+
227 .setxattr = hello_ll_setxattr,
+
228 .getxattr = hello_ll_getxattr,
+
229 .removexattr = hello_ll_removexattr,
+
230};
+
231
+
232int main(int argc, char *argv[])
+
233{
+
234 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
235 struct fuse_session *se;
+
236 struct fuse_cmdline_opts opts;
+
237 struct fuse_loop_config *config;
+
238 int ret = -1;
+
239
+
240 if (fuse_parse_cmdline(&args, &opts) != 0)
+
241 return 1;
+
242 if (opts.show_help) {
+
243 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
246 ret = 0;
+
247 goto err_out1;
+
248 } else if (opts.show_version) {
+
249 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
251 ret = 0;
+
252 goto err_out1;
+
253 }
+
254
+
255 if(opts.mountpoint == NULL) {
+
256 printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
257 printf(" %s --help\n", argv[0]);
+
258 ret = 1;
+
259 goto err_out1;
+
260 }
+
261
+
262 se = fuse_session_new(&args, &hello_ll_oper,
+
263 sizeof(hello_ll_oper), NULL);
+
264 if (se == NULL)
+
265 goto err_out1;
+
266
+
267 if (fuse_set_signal_handlers(se) != 0)
+
268 goto err_out2;
+
269
+
270 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
271 goto err_out3;
+
272
+
273 fuse_daemonize(opts.foreground);
+
274
+
275 /* Block until ctrl+c or fusermount -u */
+
276 if (opts.singlethread)
+
277 ret = fuse_session_loop(se);
+
278 else {
+
279 config = fuse_loop_cfg_create();
+
280 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
281 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
282 ret = fuse_session_loop_mt(se, config);
+
283 fuse_loop_cfg_destroy(config);
+
284 config = NULL;
+
285 }
+
286
+ +
288err_out3:
+ +
290err_out2:
+ +
292err_out1:
+
293 free(opts.mountpoint);
+
294 fuse_opt_free_args(&args);
+
295
+
296 return ret ? 1 : 0;
+
297}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2hello__ll__uds_8c.html b/doc/html/fuse-3_818_82_2example_2hello__ll__uds_8c.html new file mode 100644 index 0000000..794a0f6 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2hello__ll__uds_8c.html @@ -0,0 +1,391 @@ + + + + + + + +libfuse: fuse-3.18.2/example/hello_ll_uds.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello_ll_uds.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <fuse_kernel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using low-level API and a custom io. This custom io is implemented using UNIX domain sockets (of type SOCK_STREAM)

+

Compile with:

gcc -Wall hello_ll_uds.c `pkg-config fuse3 --cflags --libs` -o hello_ll_uds
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <assert.h>
+
+
static const char *hello_str = "Hello World!\n";
+
static const char *hello_name = "hello";
+
+
static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
{
+
stbuf->st_ino = ino;
+
switch (ino) {
+
case 1:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
+
case 2:
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(hello_str);
+
break;
+
+
default:
+
return -1;
+
}
+
return 0;
+
}
+
+
static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
+
/* Test setting flags the old way */
+ +
conn->want &= ~FUSE_CAP_ASYNC_READ;
+
}
+
+
static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (hello_stat(ino, &stbuf) == -1)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, 1.0);
+
}
+
+
static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
+
if (parent != 1 || strcmp(name, hello_name) != 0)
+
fuse_reply_err(req, ENOENT);
+
else {
+
memset(&e, 0, sizeof(e));
+
e.ino = 2;
+
e.attr_timeout = 1.0;
+
e.entry_timeout = 1.0;
+
hello_stat(e.ino, &e.attr);
+
+
fuse_reply_entry(req, &e);
+
}
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+ +
{
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize)
+
{
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (ino != 1)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, ".", 1);
+
dirbuf_add(req, &b, "..", 1);
+
dirbuf_add(req, &b, hello_name, 2);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
if (ino != 2)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else
+
fuse_reply_open(req, fi);
+
}
+
+
static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
assert(ino == 2);
+
reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
}
+
+
static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
{
+
const char *buf = "hello_ll_getxattr_value";
+
fuse_reply_buf(req, buf, strlen(buf));
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
(void)flags;
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
const char* exp_val = "hello_ll_setxattr_value";
+
if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
strlen(exp_val) == size &&
+
strncmp(value, exp_val, size) == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops hello_ll_oper = {
+
.init = hello_ll_init,
+
.lookup = hello_ll_lookup,
+
.getattr = hello_ll_getattr,
+
.readdir = hello_ll_readdir,
+
.open = hello_ll_open,
+
.read = hello_ll_read,
+
.setxattr = hello_ll_setxattr,
+
.getxattr = hello_ll_getxattr,
+
.removexattr = hello_ll_removexattr,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
se = fuse_session_new(&args, &hello_ll_oper,
+
sizeof(hello_ll_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file hello_ll_uds.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2hello__ll__uds_8c_source.html b/doc/html/fuse-3_818_82_2example_2hello__ll__uds_8c_source.html new file mode 100644 index 0000000..d688f1c --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2hello__ll__uds_8c_source.html @@ -0,0 +1,442 @@ + + + + + + + +libfuse: fuse-3.18.2/example/hello_ll_uds.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello_ll_uds.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2022 Tofik Sonono <tofik.sonono@intel.com>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
23#define FUSE_USE_VERSION 34
+
24
+
25
+
26#ifndef _GNU_SOURCE
+
27#define _GNU_SOURCE
+
28#endif
+
29
+
30#include <fuse_lowlevel.h>
+
31#include <fuse_kernel.h>
+
32#include <stdio.h>
+
33#include <stdlib.h>
+
34#include <string.h>
+
35#include <errno.h>
+
36#include <fcntl.h>
+
37#include <unistd.h>
+
38#include <assert.h>
+
39#include <sys/socket.h>
+
40#include <sys/un.h>
+
41
+
42static const char *hello_str = "Hello World!\n";
+
43static const char *hello_name = "hello";
+
44
+
45static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
46{
+
47 stbuf->st_ino = ino;
+
48 switch (ino) {
+
49 case 1:
+
50 stbuf->st_mode = S_IFDIR | 0755;
+
51 stbuf->st_nlink = 2;
+
52 break;
+
53
+
54 case 2:
+
55 stbuf->st_mode = S_IFREG | 0444;
+
56 stbuf->st_nlink = 1;
+
57 stbuf->st_size = strlen(hello_str);
+
58 break;
+
59
+
60 default:
+
61 return -1;
+
62 }
+
63 return 0;
+
64}
+
65
+
66static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
67 struct fuse_file_info *fi)
+
68{
+
69 struct stat stbuf;
+
70
+
71 (void) fi;
+
72
+
73 memset(&stbuf, 0, sizeof(stbuf));
+
74 if (hello_stat(ino, &stbuf) == -1)
+
75 fuse_reply_err(req, ENOENT);
+
76 else
+
77 fuse_reply_attr(req, &stbuf, 1.0);
+
78}
+
79
+
80static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
81{
+
82 (void)userdata;
+
83
+
84 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
85 conn->no_interrupt = 1;
+
86}
+
87
+
88static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
89{
+
90 struct fuse_entry_param e;
+
91
+
92 if (parent != 1 || strcmp(name, hello_name) != 0)
+
93 fuse_reply_err(req, ENOENT);
+
94 else {
+
95 memset(&e, 0, sizeof(e));
+
96 e.ino = 2;
+
97 e.attr_timeout = 1.0;
+
98 e.entry_timeout = 1.0;
+
99 hello_stat(e.ino, &e.attr);
+
100
+
101 fuse_reply_entry(req, &e);
+
102 }
+
103}
+
104
+
105struct dirbuf {
+
106 char *p;
+
107 size_t size;
+
108};
+
109
+
110static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
111 fuse_ino_t ino)
+
112{
+
113 struct stat stbuf;
+
114 size_t oldsize = b->size;
+
115 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
116 b->p = (char *) realloc(b->p, b->size);
+
117 memset(&stbuf, 0, sizeof(stbuf));
+
118 stbuf.st_ino = ino;
+
119 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
120 b->size);
+
121}
+
122
+
123#define min(x, y) ((x) < (y) ? (x) : (y))
+
124
+
125static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
126 off_t off, size_t maxsize)
+
127{
+
128 if (off < bufsize)
+
129 return fuse_reply_buf(req, buf + off,
+
130 min(bufsize - off, maxsize));
+
131 else
+
132 return fuse_reply_buf(req, NULL, 0);
+
133}
+
134
+
135static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
136 off_t off, struct fuse_file_info *fi)
+
137{
+
138 (void) fi;
+
139
+
140 if (ino != 1)
+
141 fuse_reply_err(req, ENOTDIR);
+
142 else {
+
143 struct dirbuf b;
+
144
+
145 memset(&b, 0, sizeof(b));
+
146 dirbuf_add(req, &b, ".", 1);
+
147 dirbuf_add(req, &b, "..", 1);
+
148 dirbuf_add(req, &b, hello_name, 2);
+
149 reply_buf_limited(req, b.p, b.size, off, size);
+
150 free(b.p);
+
151 }
+
152}
+
153
+
154static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
155 struct fuse_file_info *fi)
+
156{
+
157 if (ino != 2)
+
158 fuse_reply_err(req, EISDIR);
+
159 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
160 fuse_reply_err(req, EACCES);
+
161 else
+
162 fuse_reply_open(req, fi);
+
163}
+
164
+
165static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
166 off_t off, struct fuse_file_info *fi)
+
167{
+
168 (void) fi;
+
169
+
170 assert(ino == 2);
+
171 reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
172}
+
173
+
174static const struct fuse_lowlevel_ops hello_ll_oper = {
+
175 .init = hello_ll_init,
+
176 .lookup = hello_ll_lookup,
+
177 .getattr = hello_ll_getattr,
+
178 .readdir = hello_ll_readdir,
+
179 .open = hello_ll_open,
+
180 .read = hello_ll_read,
+
181};
+
182
+
183static int create_socket(const char *socket_path) {
+
184 struct sockaddr_un addr;
+
185
+
186 if (strnlen(socket_path, sizeof(addr.sun_path)) >=
+
187 sizeof(addr.sun_path)) {
+
188 printf("Socket path may not be longer than %zu characters\n",
+
189 sizeof(addr.sun_path) - 1);
+
190 return -1;
+
191 }
+
192
+
193 if (remove(socket_path) == -1 && errno != ENOENT) {
+
194 printf("Could not delete previous socket file entry at %s. Error: "
+
195 "%s\n", socket_path, strerror(errno));
+
196 return -1;
+
197 }
+
198
+
199 memset(&addr, 0, sizeof(struct sockaddr_un));
+
200 strcpy(addr.sun_path, socket_path);
+
201
+
202 int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+
203 if (sfd == -1) {
+
204 printf("Could not create socket. Error: %s\n", strerror(errno));
+
205 return -1;
+
206 }
+
207
+
208 addr.sun_family = AF_UNIX;
+
209 if (bind(sfd, (struct sockaddr *) &addr,
+
210 sizeof(struct sockaddr_un)) == -1) {
+
211 printf("Could not bind socket. Error: %s\n", strerror(errno));
+
212 return -1;
+
213 }
+
214
+
215 if (listen(sfd, 1) == -1)
+
216 return -1;
+
217
+
218 printf("Awaiting connection on socket at %s...\n", socket_path);
+
219 int cfd = accept(sfd, NULL, NULL);
+
220 if (cfd == -1) {
+
221 printf("Could not accept connection. Error: %s\n",
+
222 strerror(errno));
+
223 return -1;
+
224 } else {
+
225 printf("Accepted connection!\n");
+
226 }
+
227 return cfd;
+
228}
+
229
+
230static ssize_t stream_writev(int fd, struct iovec *iov, int count,
+
231 void *userdata) {
+
232 (void)userdata;
+
233
+
234 ssize_t written = 0;
+
235 int cur = 0;
+
236 for (;;) {
+
237 written = writev(fd, iov+cur, count-cur);
+
238 if (written < 0)
+
239 return written;
+
240
+
241 while (cur < count && written >= iov[cur].iov_len)
+
242 written -= iov[cur++].iov_len;
+
243 if (cur == count)
+
244 break;
+
245
+
246 iov[cur].iov_base = (char *)iov[cur].iov_base + written;
+
247 iov[cur].iov_len -= written;
+
248 }
+
249 return written;
+
250}
+
251
+
252
+
253static ssize_t readall(int fd, void *buf, size_t len) {
+
254 size_t count = 0;
+
255
+
256 while (count < len) {
+
257 int i = read(fd, (char *)buf + count, len - count);
+
258 if (!i)
+
259 break;
+
260
+
261 if (i < 0)
+
262 return i;
+
263
+
264 count += i;
+
265 }
+
266 return count;
+
267}
+
268
+
269static ssize_t stream_read(int fd, void *buf, size_t buf_len, void *userdata) {
+
270 (void)userdata;
+
271
+
272 int res = readall(fd, buf, sizeof(struct fuse_in_header));
+
273 if (res == -1)
+
274 return res;
+
275
+
276
+
277 uint32_t packet_len = ((struct fuse_in_header *)buf)->len;
+
278 if (packet_len > buf_len)
+
279 return -1;
+
280
+
281 int prev_res = res;
+
282
+
283 res = readall(fd, (char *)buf + sizeof(struct fuse_in_header),
+
284 packet_len - sizeof(struct fuse_in_header));
+
285
+
286 return (res == -1) ? res : (res + prev_res);
+
287}
+
288
+
289static ssize_t stream_splice_send(int fdin, off_t *offin, int fdout,
+
290 off_t *offout, size_t len,
+
291 unsigned int flags, void *userdata) {
+
292 (void)userdata;
+
293
+
294 size_t count = 0;
+
295 while (count < len) {
+
296 int i = splice(fdin, offin, fdout, offout, len - count, flags);
+
297 if (i < 1)
+
298 return i;
+
299
+
300 count += i;
+
301 }
+
302 return count;
+
303}
+
304
+
305static void fuse_cmdline_help_uds(void)
+
306{
+
307 printf(" -h --help print help\n"
+
308 " -V --version print version\n"
+
309 " -d -o debug enable debug output (implies -f)\n");
+
310}
+
311
+
312int main(int argc, char *argv[])
+
313{
+
314 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
315 struct fuse_session *se;
+
316 struct fuse_cmdline_opts opts;
+
317 const struct fuse_custom_io io = {
+
318 .writev = stream_writev,
+
319 .read = stream_read,
+
320 .splice_receive = NULL,
+
321 .splice_send = stream_splice_send,
+
322 };
+
323 int cfd = -1;
+
324 int ret = -1;
+
325
+
326 if (fuse_parse_cmdline(&args, &opts) != 0)
+
327 return 1;
+
328 if (opts.show_help) {
+
329 printf("usage: %s [options]\n\n", argv[0]);
+
330 fuse_cmdline_help_uds();
+ +
332 ret = 0;
+
333 goto err_out1;
+
334 } else if (opts.show_version) {
+
335 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
337 ret = 0;
+
338 goto err_out1;
+
339 }
+
340
+
341 se = fuse_session_new(&args, &hello_ll_oper,
+
342 sizeof(hello_ll_oper), NULL);
+
343 if (se == NULL)
+
344 goto err_out1;
+
345
+
346 if (fuse_set_signal_handlers(se) != 0)
+
347 goto err_out2;
+
348
+
349 cfd = create_socket("/tmp/libfuse-hello-ll.sock");
+
350 if (cfd == -1)
+
351 goto err_out3;
+
352
+
353 if (fuse_session_custom_io(se, &io, cfd) != 0)
+
354 goto err_out3;
+
355
+
356 /* Block until ctrl+c */
+
357 ret = fuse_session_loop(se);
+
358err_out3:
+ +
360err_out2:
+ +
362err_out1:
+
363 free(opts.mountpoint);
+
364 fuse_opt_free_args(&args);
+
365
+
366 return ret ? 1 : 0;
+
367}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_lowlevel_help(void)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2invalidate__path_8c.html b/doc/html/fuse-3_818_82_2example_2invalidate__path_8c.html new file mode 100644 index 0000000..9fc6da7 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2invalidate__path_8c.html @@ -0,0 +1,390 @@ + + + + + + + +libfuse: fuse-3.18.2/example/invalidate_path.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
invalidate_path.c File Reference
+
+
+
#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with two files:

    +
  • 'current-time', whose contents change dynamically: it always contains the current time (same as in notify_inval_inode.c).
  • +
  • 'growing', whose size changes dynamically, growing by 1 byte after each update. This aims to check if cached file metadata is also invalidated.
  • +
+

+Compilation

+
gcc -Wall invalidate_path.c `pkg-config fuse3 --cflags --libs` -o invalidate_path
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
(C) 2017 EditShare LLC <slawek.rudnicki@editshare.com>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 34
+
+
#include <fuse.h>
+
#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define TIME_FILE_NAME "current_time"
+
#define TIME_FILE_INO 2
+
#define GROW_FILE_NAME "growing"
+
#define GROW_FILE_INO 3
+
+
static char time_file_contents[MAX_STR_LEN];
+
static size_t grow_file_size;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->entry_timeout = NO_TIMEOUT;
+
cfg->attr_timeout = NO_TIMEOUT;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path,
+
struct stat *stbuf, struct fuse_file_info* fi) {
+
(void) fi;
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_ino = 1;
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
} else if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
stbuf->st_ino = TIME_FILE_INO;
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(time_file_contents);
+
} else if (strcmp(path, "/" GROW_FILE_NAME) == 0) {
+
stbuf->st_ino = GROW_FILE_INO;
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = grow_file_size;
+
} else {
+
return -ENOENT;
+
}
+
+
return 0;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags) {
+
(void) fi;
+
(void) offset;
+
(void) flags;
+
if (strcmp(path, "/") != 0) {
+
return -ENOTDIR;
+
} else {
+
(void) filler;
+
(void) buf;
+
struct stat file_stat;
+
xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL);
+
filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL);
+
filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
return 0;
+
}
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi) {
+
(void) path;
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi) {
+
(void) fi;
+
(void) offset;
+
if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
int file_length = strlen(time_file_contents);
+
int to_copy = offset + size <= file_length
+
? size
+
: file_length - offset;
+
memcpy(buf, time_file_contents, to_copy);
+
return to_copy;
+
} else {
+
assert(strcmp(path, "/" GROW_FILE_NAME) == 0);
+
int to_copy = offset + size <= grow_file_size
+
? size
+
: grow_file_size - offset;
+
memset(buf, 'x', to_copy);
+
return to_copy;
+
}
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.readdir = xmp_readdir,
+
.open = xmp_open,
+
.read = xmp_read,
+
};
+
+
static void update_fs(void) {
+
static int count = 0;
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
int time_file_size = strftime(time_file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(time_file_size != 0);
+
+
grow_file_size = count++;
+
}
+
+
static int invalidate(struct fuse *fuse, const char *path) {
+
int status = fuse_invalidate_path(fuse, path);
+
if (status == -ENOENT) {
+
return 0;
+
} else {
+
return status;
+
}
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse *fuse = (struct fuse*) data;
+
+
while (1) {
+
update_fs();
+
if (!options.no_notify) {
+
assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0);
+
assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0);
+
}
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse *fuse;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config config;
+
int res;
+
+
/* Initialize the files */
+
update_fs();
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
+
if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
res = 0;
+
goto out1;
+
} else if (opts.show_help) {
+
show_help(argv[0]);
+ +
fuse_lib_help(&args);
+
res = 0;
+
goto out1;
+
} else if (!opts.mountpoint) {
+
fprintf(stderr, "error: no mountpoint specified\n");
+
res = 1;
+
goto out1;
+
}
+
+
fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL);
+
if (fuse == NULL) {
+
res = 1;
+
goto out1;
+
}
+
+
if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
res = 1;
+
goto out2;
+
}
+
+
if (fuse_daemonize(opts.foreground) != 0) {
+
res = 1;
+
goto out3;
+
}
+
+
pthread_t updater; /* Start thread to update file contents */
+
int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n", strerror(ret));
+
return 1;
+
};
+
+
struct fuse_session *se = fuse_get_session(fuse);
+
if (fuse_set_signal_handlers(se) != 0) {
+
res = 1;
+
goto out3;
+
}
+
+
if (opts.singlethread)
+
res = fuse_loop(fuse);
+
else {
+
config.clone_fd = opts.clone_fd;
+
config.max_idle_threads = opts.max_idle_threads;
+
res = fuse_loop_mt(fuse, &config);
+
}
+
if (res)
+
res = 1;
+
+ +
out3:
+
fuse_unmount(fuse);
+
out2:
+
fuse_destroy(fuse);
+
out1:
+
free(opts.mountpoint);
+ +
return res;
+
}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5197
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5146
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5202
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint32_t keep_cache
Definition fuse_common.h:69
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file invalidate_path.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2invalidate__path_8c_source.html b/doc/html/fuse-3_818_82_2example_2invalidate__path_8c_source.html new file mode 100644 index 0000000..33f32c3 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2invalidate__path_8c_source.html @@ -0,0 +1,370 @@ + + + + + + + +libfuse: fuse-3.18.2/example/invalidate_path.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
invalidate_path.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4 (C) 2017 EditShare LLC <slawek.rudnicki@editshare.com>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8 */
+
9
+
28#define FUSE_USE_VERSION 34
+
29
+
30#include <fuse.h>
+
31#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
+
32
+
33#include <stdio.h>
+
34#include <stdlib.h>
+
35#include <string.h>
+
36#include <errno.h>
+
37#include <fcntl.h>
+
38#include <assert.h>
+
39#include <stddef.h>
+
40#include <unistd.h>
+
41#include <pthread.h>
+
42
+
43/* We can't actually tell the kernel that there is no
+
44 timeout, so we just send a big value */
+
45#define NO_TIMEOUT 500000
+
46
+
47#define MAX_STR_LEN 128
+
48#define TIME_FILE_NAME "current_time"
+
49#define TIME_FILE_INO 2
+
50#define GROW_FILE_NAME "growing"
+
51#define GROW_FILE_INO 3
+
52
+
53static char time_file_contents[MAX_STR_LEN];
+
54static size_t grow_file_size;
+
55
+
56/* Command line parsing */
+
57struct options {
+
58 int no_notify;
+
59 int update_interval;
+
60};
+
61static struct options options = {
+
62 .no_notify = 0,
+
63 .update_interval = 1,
+
64};
+
65
+
66#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
67static const struct fuse_opt option_spec[] = {
+
68 OPTION("--no-notify", no_notify),
+
69 OPTION("--update-interval=%d", update_interval),
+ +
71};
+
72
+
73static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
74{
+
75 (void) conn;
+
76 cfg->entry_timeout = NO_TIMEOUT;
+
77 cfg->attr_timeout = NO_TIMEOUT;
+
78 cfg->negative_timeout = 0;
+
79
+
80 return NULL;
+
81}
+
82
+
83static int xmp_getattr(const char *path,
+
84 struct stat *stbuf, struct fuse_file_info* fi) {
+
85 (void) fi;
+
86 if (strcmp(path, "/") == 0) {
+
87 stbuf->st_ino = 1;
+
88 stbuf->st_mode = S_IFDIR | 0755;
+
89 stbuf->st_nlink = 1;
+
90 } else if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
91 stbuf->st_ino = TIME_FILE_INO;
+
92 stbuf->st_mode = S_IFREG | 0444;
+
93 stbuf->st_nlink = 1;
+
94 stbuf->st_size = strlen(time_file_contents);
+
95 } else if (strcmp(path, "/" GROW_FILE_NAME) == 0) {
+
96 stbuf->st_ino = GROW_FILE_INO;
+
97 stbuf->st_mode = S_IFREG | 0444;
+
98 stbuf->st_nlink = 1;
+
99 stbuf->st_size = grow_file_size;
+
100 } else {
+
101 return -ENOENT;
+
102 }
+
103
+
104 return 0;
+
105}
+
106
+
107static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
108 off_t offset, struct fuse_file_info *fi,
+
109 enum fuse_readdir_flags flags) {
+
110 (void) fi;
+
111 (void) offset;
+
112 (void) flags;
+
113 if (strcmp(path, "/") != 0) {
+
114 return -ENOTDIR;
+
115 } else {
+
116 (void) filler;
+
117 (void) buf;
+
118 struct stat file_stat;
+
119 xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL);
+
120 filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
121 xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL);
+
122 filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
123 return 0;
+
124 }
+
125}
+
126
+
127static int xmp_open(const char *path, struct fuse_file_info *fi) {
+
128 (void) path;
+
129 /* Make cache persistent even if file is closed,
+
130 this makes it easier to see the effects */
+
131 fi->keep_cache = 1;
+
132 return 0;
+
133}
+
134
+
135static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
136 struct fuse_file_info *fi) {
+
137 (void) fi;
+
138 (void) offset;
+
139 if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
140 int file_length = strlen(time_file_contents);
+
141 int to_copy = offset + size <= file_length
+
142 ? size
+
143 : file_length - offset;
+
144 memcpy(buf, time_file_contents, to_copy);
+
145 return to_copy;
+
146 } else {
+
147 assert(strcmp(path, "/" GROW_FILE_NAME) == 0);
+
148 int to_copy = offset + size <= grow_file_size
+
149 ? size
+
150 : grow_file_size - offset;
+
151 memset(buf, 'x', to_copy);
+
152 return to_copy;
+
153 }
+
154}
+
155
+
156static const struct fuse_operations xmp_oper = {
+
157 .init = xmp_init,
+
158 .getattr = xmp_getattr,
+
159 .readdir = xmp_readdir,
+
160 .open = xmp_open,
+
161 .read = xmp_read,
+
162};
+
163
+
164static void update_fs(void) {
+
165 static int count = 0;
+
166 struct tm *now;
+
167 time_t t;
+
168 t = time(NULL);
+
169 now = localtime(&t);
+
170 assert(now != NULL);
+
171
+
172 int time_file_size = strftime(time_file_contents, MAX_STR_LEN,
+
173 "The current time is %H:%M:%S\n", now);
+
174 assert(time_file_size != 0);
+
175
+
176 grow_file_size = count++;
+
177}
+
178
+
179static int invalidate(struct fuse *fuse, const char *path) {
+
180 int status = fuse_invalidate_path(fuse, path);
+
181 if (status == -ENOENT) {
+
182 return 0;
+
183 } else {
+
184 return status;
+
185 }
+
186}
+
187
+
188static void* update_fs_loop(void *data) {
+
189 struct fuse *fuse = (struct fuse*) data;
+
190
+
191 while (1) {
+
192 update_fs();
+
193 if (!options.no_notify) {
+
194 assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0);
+
195 assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0);
+
196 }
+
197 sleep(options.update_interval);
+
198 }
+
199 return NULL;
+
200}
+
201
+
202static void show_help(const char *progname)
+
203{
+
204 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
205 printf("File-system specific options:\n"
+
206 " --update-interval=<secs> Update-rate of file system contents\n"
+
207 " --no-notify Disable kernel notifications\n"
+
208 "\n");
+
209}
+
210
+
211int main(int argc, char *argv[]) {
+
212 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
213 struct fuse *fuse;
+
214 struct fuse_cmdline_opts opts;
+
215 struct fuse_loop_config config;
+
216 int res;
+
217
+
218 /* Initialize the files */
+
219 update_fs();
+
220
+
221 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
222 return 1;
+
223
+
224 if (fuse_parse_cmdline(&args, &opts) != 0)
+
225 return 1;
+
226
+
227 if (opts.show_version) {
+
228 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
230 res = 0;
+
231 goto out1;
+
232 } else if (opts.show_help) {
+
233 show_help(argv[0]);
+ +
235 fuse_lib_help(&args);
+
236 res = 0;
+
237 goto out1;
+
238 } else if (!opts.mountpoint) {
+
239 fprintf(stderr, "error: no mountpoint specified\n");
+
240 res = 1;
+
241 goto out1;
+
242 }
+
243
+
244 fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL);
+
245 if (fuse == NULL) {
+
246 res = 1;
+
247 goto out1;
+
248 }
+
249
+
250 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
251 res = 1;
+
252 goto out2;
+
253 }
+
254
+
255 if (fuse_daemonize(opts.foreground) != 0) {
+
256 res = 1;
+
257 goto out3;
+
258 }
+
259
+
260 pthread_t updater; /* Start thread to update file contents */
+
261 int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse);
+
262 if (ret != 0) {
+
263 fprintf(stderr, "pthread_create failed with %s\n", strerror(ret));
+
264 return 1;
+
265 };
+
266
+
267 struct fuse_session *se = fuse_get_session(fuse);
+
268 if (fuse_set_signal_handlers(se) != 0) {
+
269 res = 1;
+
270 goto out3;
+
271 }
+
272
+
273 if (opts.singlethread)
+
274 res = fuse_loop(fuse);
+
275 else {
+
276 config.clone_fd = opts.clone_fd;
+
277 config.max_idle_threads = opts.max_idle_threads;
+
278 res = fuse_loop_mt(fuse, &config);
+
279 }
+
280 if (res)
+
281 res = 1;
+
282
+ +
284out3:
+
285 fuse_unmount(fuse);
+
286out2:
+
287 fuse_destroy(fuse);
+
288out1:
+
289 free(opts.mountpoint);
+
290 fuse_opt_free_args(&args);
+
291 return res;
+
292}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5197
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5146
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5202
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint32_t keep_cache
Definition fuse_common.h:69
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2ioctl_8c.html b/doc/html/fuse-3_818_82_2example_2ioctl_8c.html new file mode 100644 index 0000000..dbb499b --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2ioctl_8c.html @@ -0,0 +1,294 @@ + + + + + + + +libfuse: fuse-3.18.2/example/ioctl.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl.c File Reference
+
+
+
#include <fuse.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This example illustrates how to write a FUSE file system that can process (a restricted set of) ioctls. It can be tested with the ioctl_client.c program.

+

Compile with:

gcc -Wall ioctl.c `pkg-config fuse3 --cflags --libs` -o ioctl
+

+Source code

+
/*
+
FUSE fioc: FUSE ioctl example
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 35
+
+
#include <fuse.h>
+
#include <stdlib.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <time.h>
+
#include <errno.h>
+
+
#include "ioctl.h"
+
+
#define FIOC_NAME "fioc"
+
+
enum {
+
FIOC_NONE,
+
FIOC_ROOT,
+
FIOC_FILE,
+
};
+
+
static void *fioc_buf;
+
static size_t fioc_size;
+
+
static int fioc_resize(size_t new_size)
+
{
+
void *new_buf;
+
+
if (new_size == fioc_size)
+
return 0;
+
+
new_buf = realloc(fioc_buf, new_size);
+
if (!new_buf && new_size)
+
return -ENOMEM;
+
+
if (new_size > fioc_size)
+
memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
+
fioc_buf = new_buf;
+
fioc_size = new_size;
+
+
return 0;
+
}
+
+
static int fioc_expand(size_t new_size)
+
{
+
if (new_size > fioc_size)
+
return fioc_resize(new_size);
+
return 0;
+
}
+
+
static int fioc_file_type(const char *path)
+
{
+
if (strcmp(path, "/") == 0)
+
return FIOC_ROOT;
+
if (strcmp(path, "/" FIOC_NAME) == 0)
+
return FIOC_FILE;
+
return FIOC_NONE;
+
}
+
+
static int fioc_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
stbuf->st_uid = getuid();
+
stbuf->st_gid = getgid();
+
stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
+
switch (fioc_file_type(path)) {
+
case FIOC_ROOT:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
case FIOC_FILE:
+
stbuf->st_mode = S_IFREG | 0644;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = fioc_size;
+
break;
+
case FIOC_NONE:
+
return -ENOENT;
+
}
+
+
return 0;
+
}
+
+
static int fioc_open(const char *path, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_NONE)
+
return 0;
+
return -ENOENT;
+
}
+
+
static int fioc_do_read(char *buf, size_t size, off_t offset)
+
{
+
if (offset >= fioc_size)
+
return 0;
+
+
if (size > fioc_size - offset)
+
size = fioc_size - offset;
+
+
memcpy(buf, fioc_buf + offset, size);
+
+
return size;
+
}
+
+
static int fioc_read(const char *path, char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_do_read(buf, size, offset);
+
}
+
+
static int fioc_do_write(const char *buf, size_t size, off_t offset)
+
{
+
if (fioc_expand(offset + size))
+
return -ENOMEM;
+
+
memcpy(fioc_buf + offset, buf, size);
+
+
return size;
+
}
+
+
static int fioc_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_do_write(buf, size, offset);
+
}
+
+
static int fioc_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_resize(size);
+
}
+
+
static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) fi;
+
(void) offset;
+
(void) flags;
+
+
if (fioc_file_type(path) != FIOC_ROOT)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int fioc_ioctl(const char *path, unsigned int cmd, void *arg,
+
struct fuse_file_info *fi, unsigned int flags, void *data)
+
{
+
(void) arg;
+
(void) fi;
+
(void) flags;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
if (flags & FUSE_IOCTL_COMPAT)
+
return -ENOSYS;
+
+
switch (cmd) {
+
case FIOC_GET_SIZE:
+
*(size_t *)data = fioc_size;
+
return 0;
+
+
case FIOC_SET_SIZE:
+
fioc_resize(*(size_t *)data);
+
return 0;
+
}
+
+
return -EINVAL;
+
}
+
+
static const struct fuse_operations fioc_oper = {
+
.getattr = fioc_getattr,
+
.readdir = fioc_readdir,
+
.truncate = fioc_truncate,
+
.open = fioc_open,
+
.read = fioc_read,
+
.write = fioc_write,
+
.ioctl = fioc_ioctl,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
return fuse_main(argc, argv, &fioc_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_IOCTL_COMPAT
+ + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+

Definition in file ioctl.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2ioctl_8c_source.html b/doc/html/fuse-3_818_82_2example_2ioctl_8c_source.html new file mode 100644 index 0000000..ae4562b --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2ioctl_8c_source.html @@ -0,0 +1,283 @@ + + + + + + + +libfuse: fuse-3.18.2/example/ioctl.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioc: FUSE ioctl example
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
25#define FUSE_USE_VERSION 35
+
26
+
27#include <fuse.h>
+
28#include <stdlib.h>
+
29#include <stdio.h>
+
30#include <string.h>
+
31#include <unistd.h>
+
32#include <time.h>
+
33#include <errno.h>
+
34
+
35#include "ioctl.h"
+
36
+
37#define FIOC_NAME "fioc"
+
38
+
39enum {
+
40 FIOC_NONE,
+
41 FIOC_ROOT,
+
42 FIOC_FILE,
+
43};
+
44
+
45static void *fioc_buf;
+
46static size_t fioc_size;
+
47
+
48static int fioc_resize(size_t new_size)
+
49{
+
50 void *new_buf;
+
51
+
52 if (new_size == fioc_size)
+
53 return 0;
+
54
+
55 new_buf = realloc(fioc_buf, new_size);
+
56 if (!new_buf && new_size)
+
57 return -ENOMEM;
+
58
+
59 if (new_size > fioc_size)
+
60 memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
61
+
62 fioc_buf = new_buf;
+
63 fioc_size = new_size;
+
64
+
65 return 0;
+
66}
+
67
+
68static int fioc_expand(size_t new_size)
+
69{
+
70 if (new_size > fioc_size)
+
71 return fioc_resize(new_size);
+
72 return 0;
+
73}
+
74
+
75static int fioc_file_type(const char *path)
+
76{
+
77 if (strcmp(path, "/") == 0)
+
78 return FIOC_ROOT;
+
79 if (strcmp(path, "/" FIOC_NAME) == 0)
+
80 return FIOC_FILE;
+
81 return FIOC_NONE;
+
82}
+
83
+
84static int fioc_getattr(const char *path, struct stat *stbuf,
+
85 struct fuse_file_info *fi)
+
86{
+
87 (void) fi;
+
88 stbuf->st_uid = getuid();
+
89 stbuf->st_gid = getgid();
+
90 stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
91
+
92 switch (fioc_file_type(path)) {
+
93 case FIOC_ROOT:
+
94 stbuf->st_mode = S_IFDIR | 0755;
+
95 stbuf->st_nlink = 2;
+
96 break;
+
97 case FIOC_FILE:
+
98 stbuf->st_mode = S_IFREG | 0644;
+
99 stbuf->st_nlink = 1;
+
100 stbuf->st_size = fioc_size;
+
101 break;
+
102 case FIOC_NONE:
+
103 return -ENOENT;
+
104 }
+
105
+
106 return 0;
+
107}
+
108
+
109static int fioc_open(const char *path, struct fuse_file_info *fi)
+
110{
+
111 (void) fi;
+
112
+
113 if (fioc_file_type(path) != FIOC_NONE)
+
114 return 0;
+
115 return -ENOENT;
+
116}
+
117
+
118static int fioc_do_read(char *buf, size_t size, off_t offset)
+
119{
+
120 if (offset >= fioc_size)
+
121 return 0;
+
122
+
123 if (size > fioc_size - offset)
+
124 size = fioc_size - offset;
+
125
+
126 memcpy(buf, fioc_buf + offset, size);
+
127
+
128 return size;
+
129}
+
130
+
131static int fioc_read(const char *path, char *buf, size_t size,
+
132 off_t offset, struct fuse_file_info *fi)
+
133{
+
134 (void) fi;
+
135
+
136 if (fioc_file_type(path) != FIOC_FILE)
+
137 return -EINVAL;
+
138
+
139 return fioc_do_read(buf, size, offset);
+
140}
+
141
+
142static int fioc_do_write(const char *buf, size_t size, off_t offset)
+
143{
+
144 if (fioc_expand(offset + size))
+
145 return -ENOMEM;
+
146
+
147 memcpy(fioc_buf + offset, buf, size);
+
148
+
149 return size;
+
150}
+
151
+
152static int fioc_write(const char *path, const char *buf, size_t size,
+
153 off_t offset, struct fuse_file_info *fi)
+
154{
+
155 (void) fi;
+
156
+
157 if (fioc_file_type(path) != FIOC_FILE)
+
158 return -EINVAL;
+
159
+
160 return fioc_do_write(buf, size, offset);
+
161}
+
162
+
163static int fioc_truncate(const char *path, off_t size,
+
164 struct fuse_file_info *fi)
+
165{
+
166 (void) fi;
+
167 if (fioc_file_type(path) != FIOC_FILE)
+
168 return -EINVAL;
+
169
+
170 return fioc_resize(size);
+
171}
+
172
+
173static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
174 off_t offset, struct fuse_file_info *fi,
+
175 enum fuse_readdir_flags flags)
+
176{
+
177 (void) fi;
+
178 (void) offset;
+
179 (void) flags;
+
180
+
181 if (fioc_file_type(path) != FIOC_ROOT)
+
182 return -ENOENT;
+
183
+
184 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
185 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
186 filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
187
+
188 return 0;
+
189}
+
190
+
191static int fioc_ioctl(const char *path, unsigned int cmd, void *arg,
+
192 struct fuse_file_info *fi, unsigned int flags, void *data)
+
193{
+
194 (void) arg;
+
195 (void) fi;
+
196 (void) flags;
+
197
+
198 if (fioc_file_type(path) != FIOC_FILE)
+
199 return -EINVAL;
+
200
+
201 if (flags & FUSE_IOCTL_COMPAT)
+
202 return -ENOSYS;
+
203
+
204 switch (cmd) {
+
205 case FIOC_GET_SIZE:
+
206 *(size_t *)data = fioc_size;
+
207 return 0;
+
208
+
209 case FIOC_SET_SIZE:
+
210 fioc_resize(*(size_t *)data);
+
211 return 0;
+
212 }
+
213
+
214 return -EINVAL;
+
215}
+
216
+
217static const struct fuse_operations fioc_oper = {
+
218 .getattr = fioc_getattr,
+
219 .readdir = fioc_readdir,
+
220 .truncate = fioc_truncate,
+
221 .open = fioc_open,
+
222 .read = fioc_read,
+
223 .write = fioc_write,
+
224 .ioctl = fioc_ioctl,
+
225};
+
226
+
227int main(int argc, char *argv[])
+
228{
+
229 return fuse_main(argc, argv, &fioc_oper, NULL);
+
230}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_IOCTL_COMPAT
+ + + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2ioctl_8h.html b/doc/html/fuse-3_818_82_2example_2ioctl_8h.html new file mode 100644 index 0000000..44caa86 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2ioctl_8h.html @@ -0,0 +1,96 @@ + + + + + + + +libfuse: fuse-3.18.2/example/ioctl.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl.h File Reference
+
+
+
#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

Header file to share definitions between the ioctl.c example file system and the ioctl_client.c test program.

+
/*
+
FUSE-ioctl: ioctl support for FUSE
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#include <sys/types.h>
+
#include <sys/uio.h>
+
#include <sys/ioctl.h>
+
+
enum {
+
FIOC_GET_SIZE = _IOR('E', 0, size_t),
+
FIOC_SET_SIZE = _IOW('E', 1, size_t),
+
+
/*
+
* The following two ioctls don't follow usual encoding rules
+
* and transfer variable amount of data.
+
*/
+
FIOC_READ = _IO('E', 2),
+
FIOC_WRITE = _IO('E', 3),
+
};
+
+
struct fioc_rw_arg {
+
off_t offset;
+
void *buf;
+
size_t size;
+
size_t prev_size; /* out param for previous total size */
+
size_t new_size; /* out param for new total size */
+
};
+
+

Definition in file ioctl.h.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2ioctl_8h_source.html b/doc/html/fuse-3_818_82_2example_2ioctl_8h_source.html new file mode 100644 index 0000000..a5f22c2 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2ioctl_8h_source.html @@ -0,0 +1,92 @@ + + + + + + + +libfuse: fuse-3.18.2/example/ioctl.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE-ioctl: ioctl support for FUSE
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
20#include <sys/types.h>
+
21#include <sys/uio.h>
+
22#include <sys/ioctl.h>
+
23
+
24enum {
+
25 FIOC_GET_SIZE = _IOR('E', 0, size_t),
+
26 FIOC_SET_SIZE = _IOW('E', 1, size_t),
+
27
+
28 /*
+
29 * The following two ioctls don't follow usual encoding rules
+
30 * and transfer variable amount of data.
+
31 */
+
32 FIOC_READ = _IO('E', 2),
+
33 FIOC_WRITE = _IO('E', 3),
+
34};
+
35
+
36struct fioc_rw_arg {
+
37 off_t offset;
+
38 void *buf;
+
39 size_t size;
+
40 size_t prev_size; /* out param for previous total size */
+
41 size_t new_size; /* out param for new total size */
+
42};
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2ioctl__client_8c.html b/doc/html/fuse-3_818_82_2example_2ioctl__client_8c.html new file mode 100644 index 0000000..25ed125 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2ioctl__client_8c.html @@ -0,0 +1,136 @@ + + + + + + + +libfuse: fuse-3.18.2/example/ioctl_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl_client.c File Reference
+
+
+
#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the ioctl.c example file systsem.

+

Compile with:

gcc -Wall ioctl_client.c -o ioctl_client
+

+Source code

+
/*
+
FUSE fioclient: FUSE ioctl example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#include <sys/types.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <sys/ioctl.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <ctype.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include "ioctl.h"
+
+
const char *usage =
+
"Usage: fioclient FIOC_FILE [size]\n"
+
"\n"
+
"Get size if <size> is omitted, set size otherwise\n"
+
"\n";
+
+
int main(int argc, char **argv)
+
{
+
size_t size;
+
int fd;
+
int ret = 0;
+
+
if (argc < 2) {
+
fprintf(stderr, "%s", usage);
+
return 1;
+
}
+
+
fd = open(argv[1], O_RDWR);
+
if (fd < 0) {
+
perror("open");
+
return 1;
+
}
+
+
if (argc == 2) {
+
if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
perror("ioctl");
+
ret = 1;
+
goto out;
+
}
+
printf("%zu\n", size);
+
} else {
+
size = strtoul(argv[2], NULL, 0);
+
if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
perror("ioctl");
+
ret = 1;
+
goto out;
+
}
+
}
+
out:
+
close(fd);
+
return ret;
+
}
+
+

Definition in file ioctl_client.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2ioctl__client_8c_source.html b/doc/html/fuse-3_818_82_2example_2ioctl__client_8c_source.html new file mode 100644 index 0000000..4e735d3 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2ioctl__client_8c_source.html @@ -0,0 +1,123 @@ + + + + + + + +libfuse: fuse-3.18.2/example/ioctl_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioclient: FUSE ioctl example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
22#include <sys/types.h>
+
23#include <fcntl.h>
+
24#include <sys/stat.h>
+
25#include <sys/ioctl.h>
+
26#include <stdio.h>
+
27#include <stdlib.h>
+
28#include <ctype.h>
+
29#include <errno.h>
+
30#include <unistd.h>
+
31#include "ioctl.h"
+
32
+
33const char *usage =
+
34"Usage: fioclient FIOC_FILE [size]\n"
+
35"\n"
+
36"Get size if <size> is omitted, set size otherwise\n"
+
37"\n";
+
38
+
39int main(int argc, char **argv)
+
40{
+
41 size_t size;
+
42 int fd;
+
43 int ret = 0;
+
44
+
45 if (argc < 2) {
+
46 fprintf(stderr, "%s", usage);
+
47 return 1;
+
48 }
+
49
+
50 fd = open(argv[1], O_RDWR);
+
51 if (fd < 0) {
+
52 perror("open");
+
53 return 1;
+
54 }
+
55
+
56 if (argc == 2) {
+
57 if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
58 perror("ioctl");
+
59 ret = 1;
+
60 goto out;
+
61 }
+
62 printf("%zu\n", size);
+
63 } else {
+
64 size = strtoul(argv[2], NULL, 0);
+
65 if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
66 perror("ioctl");
+
67 ret = 1;
+
68 goto out;
+
69 }
+
70 }
+
71out:
+
72 close(fd);
+
73 return ret;
+
74}
+ +
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2notify__inval__entry_8c.html b/doc/html/fuse-3_818_82_2example_2notify__inval__entry_8c.html new file mode 100644 index 0000000..e7701fb --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2notify__inval__entry_8c.html @@ -0,0 +1,497 @@ + + + + + + + +libfuse: fuse-3.18.2/example/notify_inval_entry.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_inval_entry.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <signal.h>
+#include <stddef.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <pthread.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose file name changes dynamically to reflect the current time.

+

It illustrates the use of the fuse_lowlevel_notify_inval_entry() and fuse_lowlevel_notify_expire_entry() functions.

+

To see the effect, first start the file system with the --no-notify

$ notify_inval_entry --update-interval=1 --timeout=30 --no-notify mnt/
+

Observe that ls always prints the correct directory contents (since readdir output is not cached)::

$ ls mnt; sleep 1; ls mnt; sleep 1; ls mnt
+Time_is_15h_48m_33s  current_time
+Time_is_15h_48m_34s  current_time
+Time_is_15h_48m_35s  current_time
+

However, if you try to access a file by name the kernel will report that it still exists:

$ file=$(ls mnt/); echo $file
+Time_is_15h_50m_09s
+$ sleep 5; stat mnt/$file
+  File: ‘mnt/Time_is_15h_50m_09s’
+  Size: 32                Blocks: 0          IO Block: 4096   regular file
+Device: 2ah/42d     Inode: 3           Links: 1
+Access: (0444/-r--r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
+Access: 1969-12-31 16:00:00.000000000 -0800
+Modify: 1969-12-31 16:00:00.000000000 -0800
+Change: 1969-12-31 16:00:00.000000000 -0800
+ Birth: -
+

Only once the kernel cache timeout has been reached will the stat call fail:

$ sleep 30; stat mnt/$file
+stat: cannot stat ‘mnt/Time_is_15h_50m_09s’: No such file or directory
+

In contrast, if you enable notifications you will be unable to stat the file as soon as the file system updates its name:

$ notify_inval_entry --update-interval=1 --timeout=30 mnt/
+$ file=$(ls mnt/); stat mnt/$file
+  File: ‘mnt/Time_is_20h_42m_11s’
+  Size: 0                 Blocks: 0          IO Block: 4096   regular empty file
+Device: 2ah/42d     Inode: 2           Links: 1
+Access: (0000/----------)  Uid: (    0/    root)   Gid: (    0/    root)
+Access: 1969-12-31 16:00:00.000000000 -0800
+Modify: 1969-12-31 16:00:00.000000000 -0800
+Change: 1969-12-31 16:00:00.000000000 -0800
+ Birth: -
+$ sleep 1; stat mnt/$file
+stat: cannot stat ‘mnt/Time_is_20h_42m_11s’: No such file or directory
+

To use the function fuse_lowlevel_notify_expire_entry() instead of fuse_lowlevel_notify_inval_entry(), use the command line option –only-expire

+

Another possible command-line option is –inc-epoch, which will use the FUSE low-level function fuse_lowlevel_notify_increment_epoch() instead. This will function will force the invalidation of all dentries next time they are revalidated. Note that –inc-epoch and –only-expire options are mutually exclusive.

+

+Compilation

+
gcc -Wall notify_inval_entry.c `pkg-config fuse3 --cflags --libs` -o notify_inval_entry
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <signal.h>
+
#include <stddef.h>
+
#include <sys/stat.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
+
#define MAX_STR_LEN 128
+
static char file_name[MAX_STR_LEN];
+
static fuse_ino_t file_ino = 2;
+
static int lookup_cnt = 0;
+
static pthread_t main_thread;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
float timeout;
+
int update_interval;
+
int only_expire;
+
int inc_epoch;
+
};
+
static struct options options = {
+
.timeout = 5,
+
.no_notify = 0,
+
.update_interval = 1,
+
.only_expire = 0,
+
.inc_epoch = 0,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+
OPTION("--timeout=%f", timeout),
+
OPTION("--only-expire", only_expire),
+
OPTION("--inc-epoch", inc_epoch),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == file_ino) {
+
stbuf->st_mode = S_IFREG | 0000;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = 0;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, file_name) == 0) {
+
e.ino = file_ino;
+
lookup_cnt++;
+
} else
+
goto err_out;
+
+
e.attr_timeout = options.timeout;
+
e.entry_timeout = options.timeout;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == file_ino)
+
lookup_cnt -= nlookup;
+
else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, options.timeout);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, file_name, file_ino);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.forget = tfs_forget,
+
};
+
+
static void update_fs(void) {
+
time_t t;
+
struct tm *now;
+
ssize_t ret;
+
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
ret = strftime(file_name, MAX_STR_LEN,
+
"Time_is_%Hh_%Mm_%Ss", now);
+
assert(ret != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
char *old_name;
+
int ret = 0;
+
+
while(!fuse_session_exited(se)) {
+
old_name = strdup(file_name);
+
update_fs();
+
+
if (!options.no_notify && lookup_cnt) {
+
if(options.only_expire) { // expire entry
+ +
(se, FUSE_ROOT_ID, old_name, strlen(old_name));
+
+
// no kernel support
+
if (ret == -ENOSYS) {
+
printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n");
+
break;
+
}
+
+
// 1) ret == 0: successful expire of an existing entry
+
// 2) ret == -ENOENT: kernel has already expired the entry /
+
// entry does not exist anymore in the kernel
+
assert(ret == 0 || ret == -ENOENT);
+
} else if (options.inc_epoch) { // increment epoch
+ +
+
if (ret == -ENOSYS) {
+
printf("fuse_lowlevel_notify_increment_epoch not supported by kernel\n");
+
break;
+
}
+
assert(ret == 0);
+
} else { // invalidate entry
+ +
(se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0);
+
}
+
}
+
free(old_name);
+
sleep(options.update_interval);
+
}
+
+
if (ret == -ENOSYS) {
+
printf("Exiting...\n");
+
+ +
// Make sure to exit now, rather than on next request from userspace
+
pthread_kill(main_thread, SIGPIPE);
+
}
+
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --timeout=<secs> Timeout for kernel caches\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
" --only-expire Expire entries instead of invalidating them\n"
+
" --inc-epoch Increment epoch, invalidating all dentries\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
pthread_t updater;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
if (options.only_expire && options.inc_epoch) {
+
printf("'only-expire' and 'inc-epoch' options are exclusive\n");
+
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), &se);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
// Needed to ensure that the main thread continues/restarts processing as soon
+
// as the fuse session ends (immediately after calling fuse_session_exit() )
+
// and not only on the next request from userspace
+
main_thread = pthread_self();
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread) {
+
ret = fuse_session_loop(se);
+
} else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_inval_entry.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2notify__inval__entry_8c_source.html b/doc/html/fuse-3_818_82_2example_2notify__inval__entry_8c_source.html new file mode 100644 index 0000000..72efd21 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2notify__inval__entry_8c_source.html @@ -0,0 +1,446 @@ + + + + + + + +libfuse: fuse-3.18.2/example/notify_inval_entry.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_inval_entry.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
85#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
86
+
87#include <fuse_lowlevel.h>
+
88#include <stdio.h>
+
89#include <stdlib.h>
+
90#include <string.h>
+
91#include <errno.h>
+
92#include <fcntl.h>
+
93#include <assert.h>
+
94#include <signal.h>
+
95#include <stddef.h>
+
96#include <sys/stat.h>
+
97#include <unistd.h>
+
98#include <pthread.h>
+
99
+
100#define MAX_STR_LEN 128
+
101static char file_name[MAX_STR_LEN];
+
102static fuse_ino_t file_ino = 2;
+
103static int lookup_cnt = 0;
+
104static pthread_t main_thread;
+
105
+
106/* Command line parsing */
+
107struct options {
+
108 int no_notify;
+
109 float timeout;
+
110 int update_interval;
+
111 int only_expire;
+
112 int inc_epoch;
+
113};
+
114static struct options options = {
+
115 .timeout = 5,
+
116 .no_notify = 0,
+
117 .update_interval = 1,
+
118 .only_expire = 0,
+
119 .inc_epoch = 0,
+
120};
+
121
+
122#define OPTION(t, p) \
+
123 { t, offsetof(struct options, p), 1 }
+
124static const struct fuse_opt option_spec[] = {
+
125 OPTION("--no-notify", no_notify),
+
126 OPTION("--update-interval=%d", update_interval),
+
127 OPTION("--timeout=%f", timeout),
+
128 OPTION("--only-expire", only_expire),
+
129 OPTION("--inc-epoch", inc_epoch),
+ +
131};
+
132
+
133static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
134 stbuf->st_ino = ino;
+
135 if (ino == FUSE_ROOT_ID) {
+
136 stbuf->st_mode = S_IFDIR | 0755;
+
137 stbuf->st_nlink = 1;
+
138 }
+
139
+
140 else if (ino == file_ino) {
+
141 stbuf->st_mode = S_IFREG | 0000;
+
142 stbuf->st_nlink = 1;
+
143 stbuf->st_size = 0;
+
144 }
+
145
+
146 else
+
147 return -1;
+
148
+
149 return 0;
+
150}
+
151
+
152static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
153 (void)userdata;
+
154
+
155 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
156 conn->no_interrupt = 1;
+
157}
+
158
+
159static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
160 const char *name) {
+
161 struct fuse_entry_param e;
+
162 memset(&e, 0, sizeof(e));
+
163
+
164 if (parent != FUSE_ROOT_ID)
+
165 goto err_out;
+
166 else if (strcmp(name, file_name) == 0) {
+
167 e.ino = file_ino;
+
168 lookup_cnt++;
+
169 } else
+
170 goto err_out;
+
171
+
172 e.attr_timeout = options.timeout;
+
173 e.entry_timeout = options.timeout;
+
174 if (tfs_stat(e.ino, &e.attr) != 0)
+
175 goto err_out;
+
176 fuse_reply_entry(req, &e);
+
177 return;
+
178
+
179err_out:
+
180 fuse_reply_err(req, ENOENT);
+
181}
+
182
+
183static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
184 uint64_t nlookup) {
+
185 (void) req;
+
186 if(ino == file_ino)
+
187 lookup_cnt -= nlookup;
+
188 else
+
189 assert(ino == FUSE_ROOT_ID);
+
190 fuse_reply_none(req);
+
191}
+
192
+
193static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
194 struct fuse_file_info *fi) {
+
195 struct stat stbuf;
+
196
+
197 (void) fi;
+
198
+
199 memset(&stbuf, 0, sizeof(stbuf));
+
200 if (tfs_stat(ino, &stbuf) != 0)
+
201 fuse_reply_err(req, ENOENT);
+
202 else
+
203 fuse_reply_attr(req, &stbuf, options.timeout);
+
204}
+
205
+
206struct dirbuf {
+
207 char *p;
+
208 size_t size;
+
209};
+
210
+
211static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
212 fuse_ino_t ino) {
+
213 struct stat stbuf;
+
214 size_t oldsize = b->size;
+
215 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
216 b->p = (char *) realloc(b->p, b->size);
+
217 memset(&stbuf, 0, sizeof(stbuf));
+
218 stbuf.st_ino = ino;
+
219 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
220 b->size);
+
221}
+
222
+
223#define min(x, y) ((x) < (y) ? (x) : (y))
+
224
+
225static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
226 off_t off, size_t maxsize) {
+
227 if (off < bufsize)
+
228 return fuse_reply_buf(req, buf + off,
+
229 min(bufsize - off, maxsize));
+
230 else
+
231 return fuse_reply_buf(req, NULL, 0);
+
232}
+
233
+
234static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
235 off_t off, struct fuse_file_info *fi) {
+
236 (void) fi;
+
237
+
238 if (ino != FUSE_ROOT_ID)
+
239 fuse_reply_err(req, ENOTDIR);
+
240 else {
+
241 struct dirbuf b;
+
242
+
243 memset(&b, 0, sizeof(b));
+
244 dirbuf_add(req, &b, file_name, file_ino);
+
245 reply_buf_limited(req, b.p, b.size, off, size);
+
246 free(b.p);
+
247 }
+
248}
+
249
+
250static const struct fuse_lowlevel_ops tfs_oper = {
+
251 .init = tfs_init,
+
252 .lookup = tfs_lookup,
+
253 .getattr = tfs_getattr,
+
254 .readdir = tfs_readdir,
+
255 .forget = tfs_forget,
+
256};
+
257
+
258static void update_fs(void) {
+
259 time_t t;
+
260 struct tm *now;
+
261 ssize_t ret;
+
262
+
263 t = time(NULL);
+
264 now = localtime(&t);
+
265 assert(now != NULL);
+
266
+
267 ret = strftime(file_name, MAX_STR_LEN,
+
268 "Time_is_%Hh_%Mm_%Ss", now);
+
269 assert(ret != 0);
+
270}
+
271
+
272static void* update_fs_loop(void *data) {
+
273 struct fuse_session *se = (struct fuse_session*) data;
+
274 char *old_name;
+
275 int ret = 0;
+
276
+
277 while(!fuse_session_exited(se)) {
+
278 old_name = strdup(file_name);
+
279 update_fs();
+
280
+
281 if (!options.no_notify && lookup_cnt) {
+
282 if(options.only_expire) { // expire entry
+ +
284 (se, FUSE_ROOT_ID, old_name, strlen(old_name));
+
285
+
286 // no kernel support
+
287 if (ret == -ENOSYS) {
+
288 printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n");
+
289 break;
+
290 }
+
291
+
292 // 1) ret == 0: successful expire of an existing entry
+
293 // 2) ret == -ENOENT: kernel has already expired the entry /
+
294 // entry does not exist anymore in the kernel
+
295 assert(ret == 0 || ret == -ENOENT);
+
296 } else if (options.inc_epoch) { // increment epoch
+ +
298
+
299 if (ret == -ENOSYS) {
+
300 printf("fuse_lowlevel_notify_increment_epoch not supported by kernel\n");
+
301 break;
+
302 }
+
303 assert(ret == 0);
+
304 } else { // invalidate entry
+ +
306 (se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0);
+
307 }
+
308 }
+
309 free(old_name);
+
310 sleep(options.update_interval);
+
311 }
+
312
+
313 if (ret == -ENOSYS) {
+
314 printf("Exiting...\n");
+
315
+ +
317 // Make sure to exit now, rather than on next request from userspace
+
318 pthread_kill(main_thread, SIGPIPE);
+
319 }
+
320
+
321 return NULL;
+
322}
+
323
+
324static void show_help(const char *progname)
+
325{
+
326 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
327 printf("File-system specific options:\n"
+
328 " --timeout=<secs> Timeout for kernel caches\n"
+
329 " --update-interval=<secs> Update-rate of file system contents\n"
+
330 " --no-notify Disable kernel notifications\n"
+
331 " --only-expire Expire entries instead of invalidating them\n"
+
332 " --inc-epoch Increment epoch, invalidating all dentries\n"
+
333 "\n");
+
334}
+
335
+
336int main(int argc, char *argv[]) {
+
337 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
338 struct fuse_session *se;
+
339 struct fuse_cmdline_opts opts;
+
340 struct fuse_loop_config *config;
+
341 pthread_t updater;
+
342 int ret = -1;
+
343
+
344 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
345 return 1;
+
346
+
347 if (fuse_parse_cmdline(&args, &opts) != 0)
+
348 return 1;
+
349 if (opts.show_help) {
+
350 show_help(argv[0]);
+ + +
353 ret = 0;
+
354 goto err_out1;
+
355 } else if (opts.show_version) {
+
356 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
358 ret = 0;
+
359 goto err_out1;
+
360 }
+
361 if (options.only_expire && options.inc_epoch) {
+
362 printf("'only-expire' and 'inc-epoch' options are exclusive\n");
+
363 ret = 0;
+
364 goto err_out1;
+
365 }
+
366
+
367 /* Initial contents */
+
368 update_fs();
+
369
+
370 se = fuse_session_new(&args, &tfs_oper,
+
371 sizeof(tfs_oper), &se);
+
372 if (se == NULL)
+
373 goto err_out1;
+
374
+
375 if (fuse_set_signal_handlers(se) != 0)
+
376 goto err_out2;
+
377
+
378 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
379 goto err_out3;
+
380
+
381 fuse_daemonize(opts.foreground);
+
382
+
383 // Needed to ensure that the main thread continues/restarts processing as soon
+
384 // as the fuse session ends (immediately after calling fuse_session_exit() )
+
385 // and not only on the next request from userspace
+
386 main_thread = pthread_self();
+
387
+
388 /* Start thread to update file contents */
+
389 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
390 if (ret != 0) {
+
391 fprintf(stderr, "pthread_create failed with %s\n",
+
392 strerror(ret));
+
393 goto err_out3;
+
394 }
+
395
+
396 /* Block until ctrl+c or fusermount -u */
+
397 if (opts.singlethread) {
+
398 ret = fuse_session_loop(se);
+
399 } else {
+
400 config = fuse_loop_cfg_create();
+
401 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
402 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
403 ret = fuse_session_loop_mt(se, config);
+
404 fuse_loop_cfg_destroy(config);
+
405 config = NULL;
+
406 }
+
407
+ +
409err_out3:
+ +
411err_out2:
+ +
413err_out1:
+
414 free(opts.mountpoint);
+
415 fuse_opt_free_args(&args);
+
416
+
417 return ret ? 1 : 0;
+
418}
+
419
+
420
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2notify__inval__inode_8c.html b/doc/html/fuse-3_818_82_2example_2notify__inval__inode_8c.html new file mode 100644 index 0000000..8df018b --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2notify__inval__inode_8c.html @@ -0,0 +1,483 @@ + + + + + + + +libfuse: fuse-3.18.2/example/notify_inval_inode.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_inval_inode.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdatomic.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

+

While notify_store_retrieve.c uses fuse_lowlevel_notify_store() to actively push the updated data into the kernel cache, this example uses fuse_lowlevel_notify_inval_inode() to notify the kernel that the cache has to be invalidated - but the kernel still has to explicitly request the updated data on the next read.

+

To see the effect, first start the file system with the --no-notify option:

+

$ notify_inval_inode –update-interval=1 –no-notify mnt/

+

Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+

If you instead enable the notification functions, the changes become visible:

 $ notify_inval_inode --update-interval=1 mnt/
+ $ for i in 1 2 3 4 5; do
+ >     cat mnt/current_time
+ >     sleep 1
+ > done
+ The current time is 15:58:40
+ The current time is 15:58:41
+ The current time is 15:58:42
+ The current time is 15:58:43
+ The current time is 15:58:44
+

+Compilation

+
gcc -Wall notify_inval_inode.c `pkg-config fuse3 --cflags --libs` -o notify_inval_inode
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
#include <stdbool.h>
+
#include <stdatomic.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define FILE_INO 2
+
#define FILE_NAME "current_time"
+
static char file_contents[MAX_STR_LEN];
+
static int lookup_cnt = 0;
+
static size_t file_size;
+
static _Atomic bool is_stop = false;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == FILE_INO) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = file_size;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_destroy(void *userarg)
+
{
+
(void)userarg;
+
+
is_stop = true;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, FILE_NAME) == 0) {
+
e.ino = FILE_INO;
+
lookup_cnt++;
+
} else
+
goto err_out;
+
+
e.attr_timeout = NO_TIMEOUT;
+
e.entry_timeout = NO_TIMEOUT;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == FILE_INO)
+
lookup_cnt -= nlookup;
+
else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
+
if (ino == FUSE_ROOT_ID)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else if (ino == FILE_INO)
+
fuse_reply_open(req, fi);
+
else {
+
// This should not happen
+
fprintf(stderr, "Got open for non-existing inode!\n");
+
fuse_reply_err(req, ENOENT);
+
}
+
}
+
+
static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
assert(ino == FILE_INO);
+
reply_buf_limited(req, file_contents, file_size, off, size);
+
}
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.destroy = tfs_destroy,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.open = tfs_open,
+
.read = tfs_read,
+
.forget = tfs_forget,
+
};
+
+
static void update_fs(void) {
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
file_size = strftime(file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(file_size != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
+
while(!is_stop) {
+
update_fs();
+
if (!options.no_notify && lookup_cnt) {
+
/* Only send notification if the kernel is aware of the inode */
+
+
/* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
* might come up during umount, when kernel side already releases
+
* all inodes, but does not send FUSE_DESTROY yet.
+
*/
+
int ret =
+
fuse_lowlevel_notify_inval_inode(se, FILE_INO, 0, 0);
+
if ((ret != 0 && !is_stop) &&
+
ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
fprintf(stderr,
+
"ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
strerror(-ret), -ret);
+
abort();
+
}
+
}
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
pthread_t updater;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0) {
+
ret = 1;
+
goto err_out1;
+
}
+
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+ +
free(opts.mountpoint);
+
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_inval_inode.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2notify__inval__inode_8c_source.html b/doc/html/fuse-3_818_82_2example_2notify__inval__inode_8c_source.html new file mode 100644 index 0000000..ce310c4 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2notify__inval__inode_8c_source.html @@ -0,0 +1,443 @@ + + + + + + + +libfuse: fuse-3.18.2/example/notify_inval_inode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_inval_inode.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
62#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
63
+
64#include <fuse_lowlevel.h>
+
65#include <stdio.h>
+
66#include <stdlib.h>
+
67#include <string.h>
+
68#include <errno.h>
+
69#include <fcntl.h>
+
70#include <assert.h>
+
71#include <stddef.h>
+
72#include <unistd.h>
+
73#include <pthread.h>
+
74#include <stdbool.h>
+
75#include <stdatomic.h>
+
76
+
77/* We can't actually tell the kernel that there is no
+
78 timeout, so we just send a big value */
+
79#define NO_TIMEOUT 500000
+
80
+
81#define MAX_STR_LEN 128
+
82#define FILE_INO 2
+
83#define FILE_NAME "current_time"
+
84static char file_contents[MAX_STR_LEN];
+
85static int lookup_cnt = 0;
+
86static size_t file_size;
+
87static _Atomic bool is_stop = false;
+
88
+
89/* Command line parsing */
+
90struct options {
+
91 int no_notify;
+
92 int update_interval;
+
93};
+
94static struct options options = {
+
95 .no_notify = 0,
+
96 .update_interval = 1,
+
97};
+
98
+
99#define OPTION(t, p) \
+
100 { t, offsetof(struct options, p), 1 }
+
101static const struct fuse_opt option_spec[] = {
+
102 OPTION("--no-notify", no_notify),
+
103 OPTION("--update-interval=%d", update_interval),
+ +
105};
+
106
+
107static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
108 stbuf->st_ino = ino;
+
109 if (ino == FUSE_ROOT_ID) {
+
110 stbuf->st_mode = S_IFDIR | 0755;
+
111 stbuf->st_nlink = 1;
+
112 }
+
113
+
114 else if (ino == FILE_INO) {
+
115 stbuf->st_mode = S_IFREG | 0444;
+
116 stbuf->st_nlink = 1;
+
117 stbuf->st_size = file_size;
+
118 }
+
119
+
120 else
+
121 return -1;
+
122
+
123 return 0;
+
124}
+
125
+
126static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
127 (void)userdata;
+
128
+
129 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
130 conn->no_interrupt = 1;
+
131}
+
132
+
133static void tfs_destroy(void *userarg)
+
134{
+
135 (void)userarg;
+
136
+
137 is_stop = true;
+
138}
+
139
+
140static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
141 const char *name) {
+
142 struct fuse_entry_param e;
+
143 memset(&e, 0, sizeof(e));
+
144
+
145 if (parent != FUSE_ROOT_ID)
+
146 goto err_out;
+
147 else if (strcmp(name, FILE_NAME) == 0) {
+
148 e.ino = FILE_INO;
+
149 lookup_cnt++;
+
150 } else
+
151 goto err_out;
+
152
+
153 e.attr_timeout = NO_TIMEOUT;
+
154 e.entry_timeout = NO_TIMEOUT;
+
155 if (tfs_stat(e.ino, &e.attr) != 0)
+
156 goto err_out;
+
157 fuse_reply_entry(req, &e);
+
158 return;
+
159
+
160err_out:
+
161 fuse_reply_err(req, ENOENT);
+
162}
+
163
+
164static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
165 uint64_t nlookup) {
+
166 (void) req;
+
167 if(ino == FILE_INO)
+
168 lookup_cnt -= nlookup;
+
169 else
+
170 assert(ino == FUSE_ROOT_ID);
+
171 fuse_reply_none(req);
+
172}
+
173
+
174static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
175 struct fuse_file_info *fi) {
+
176 struct stat stbuf;
+
177
+
178 (void) fi;
+
179
+
180 memset(&stbuf, 0, sizeof(stbuf));
+
181 if (tfs_stat(ino, &stbuf) != 0)
+
182 fuse_reply_err(req, ENOENT);
+
183 else
+
184 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
185}
+
186
+
187struct dirbuf {
+
188 char *p;
+
189 size_t size;
+
190};
+
191
+
192static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
193 fuse_ino_t ino) {
+
194 struct stat stbuf;
+
195 size_t oldsize = b->size;
+
196 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
197 b->p = (char *) realloc(b->p, b->size);
+
198 memset(&stbuf, 0, sizeof(stbuf));
+
199 stbuf.st_ino = ino;
+
200 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
201 b->size);
+
202}
+
203
+
204#define min(x, y) ((x) < (y) ? (x) : (y))
+
205
+
206static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
207 off_t off, size_t maxsize) {
+
208 if (off < bufsize)
+
209 return fuse_reply_buf(req, buf + off,
+
210 min(bufsize - off, maxsize));
+
211 else
+
212 return fuse_reply_buf(req, NULL, 0);
+
213}
+
214
+
215static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
216 off_t off, struct fuse_file_info *fi) {
+
217 (void) fi;
+
218
+
219 if (ino != FUSE_ROOT_ID)
+
220 fuse_reply_err(req, ENOTDIR);
+
221 else {
+
222 struct dirbuf b;
+
223
+
224 memset(&b, 0, sizeof(b));
+
225 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
226 reply_buf_limited(req, b.p, b.size, off, size);
+
227 free(b.p);
+
228 }
+
229}
+
230
+
231static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
232 struct fuse_file_info *fi) {
+
233
+
234 /* Make cache persistent even if file is closed,
+
235 this makes it easier to see the effects */
+
236 fi->keep_cache = 1;
+
237
+
238 if (ino == FUSE_ROOT_ID)
+
239 fuse_reply_err(req, EISDIR);
+
240 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
241 fuse_reply_err(req, EACCES);
+
242 else if (ino == FILE_INO)
+
243 fuse_reply_open(req, fi);
+
244 else {
+
245 // This should not happen
+
246 fprintf(stderr, "Got open for non-existing inode!\n");
+
247 fuse_reply_err(req, ENOENT);
+
248 }
+
249}
+
250
+
251static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
252 off_t off, struct fuse_file_info *fi) {
+
253 (void) fi;
+
254
+
255 assert(ino == FILE_INO);
+
256 reply_buf_limited(req, file_contents, file_size, off, size);
+
257}
+
258
+
259static const struct fuse_lowlevel_ops tfs_oper = {
+
260 .init = tfs_init,
+
261 .destroy = tfs_destroy,
+
262 .lookup = tfs_lookup,
+
263 .getattr = tfs_getattr,
+
264 .readdir = tfs_readdir,
+
265 .open = tfs_open,
+
266 .read = tfs_read,
+
267 .forget = tfs_forget,
+
268};
+
269
+
270static void update_fs(void) {
+
271 struct tm *now;
+
272 time_t t;
+
273 t = time(NULL);
+
274 now = localtime(&t);
+
275 assert(now != NULL);
+
276
+
277 file_size = strftime(file_contents, MAX_STR_LEN,
+
278 "The current time is %H:%M:%S\n", now);
+
279 assert(file_size != 0);
+
280}
+
281
+
282static void* update_fs_loop(void *data) {
+
283 struct fuse_session *se = (struct fuse_session*) data;
+
284
+
285 while(!is_stop) {
+
286 update_fs();
+
287 if (!options.no_notify && lookup_cnt) {
+
288 /* Only send notification if the kernel is aware of the inode */
+
289
+
290 /* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
291 * might come up during umount, when kernel side already releases
+
292 * all inodes, but does not send FUSE_DESTROY yet.
+
293 */
+
294 int ret =
+
295 fuse_lowlevel_notify_inval_inode(se, FILE_INO, 0, 0);
+
296 if ((ret != 0 && !is_stop) &&
+
297 ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
298 fprintf(stderr,
+
299 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
300 strerror(-ret), -ret);
+
301 abort();
+
302 }
+
303 }
+
304 sleep(options.update_interval);
+
305 }
+
306 return NULL;
+
307}
+
308
+
309static void show_help(const char *progname)
+
310{
+
311 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
312 printf("File-system specific options:\n"
+
313 " --update-interval=<secs> Update-rate of file system contents\n"
+
314 " --no-notify Disable kernel notifications\n"
+
315 "\n");
+
316}
+
317
+
318int main(int argc, char *argv[]) {
+
319 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
320 struct fuse_session *se;
+
321 struct fuse_cmdline_opts opts;
+
322 struct fuse_loop_config *config;
+
323 pthread_t updater;
+
324 int ret = -1;
+
325
+
326 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
327 return 1;
+
328
+
329 if (fuse_parse_cmdline(&args, &opts) != 0) {
+
330 ret = 1;
+
331 goto err_out1;
+
332 }
+
333
+
334 if (opts.show_help) {
+
335 show_help(argv[0]);
+ + +
338 ret = 0;
+
339 goto err_out1;
+
340 } else if (opts.show_version) {
+
341 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
343 ret = 0;
+
344 goto err_out1;
+
345 }
+
346
+
347 /* Initial contents */
+
348 update_fs();
+
349
+
350 se = fuse_session_new(&args, &tfs_oper,
+
351 sizeof(tfs_oper), NULL);
+
352 if (se == NULL)
+
353 goto err_out1;
+
354
+
355 if (fuse_set_signal_handlers(se) != 0)
+
356 goto err_out2;
+
357
+
358 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
359 goto err_out3;
+
360
+
361 fuse_daemonize(opts.foreground);
+
362
+
363 /* Start thread to update file contents */
+
364 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
365 if (ret != 0) {
+
366 fprintf(stderr, "pthread_create failed with %s\n",
+
367 strerror(ret));
+
368 goto err_out3;
+
369 }
+
370
+
371 /* Block until ctrl+c or fusermount -u */
+
372 if (opts.singlethread)
+
373 ret = fuse_session_loop(se);
+
374 else {
+
375 config = fuse_loop_cfg_create();
+
376 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
377 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
378 ret = fuse_session_loop_mt(se, config);
+
379 fuse_loop_cfg_destroy(config);
+
380 config = NULL;
+
381 }
+
382
+ +
384err_out3:
+ +
386err_out2:
+ +
388err_out1:
+
389 fuse_opt_free_args(&args);
+
390 free(opts.mountpoint);
+
391
+
392 return ret ? 1 : 0;
+
393}
+
394
+
395
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2notify__store__retrieve_8c.html b/doc/html/fuse-3_818_82_2example_2notify__store__retrieve_8c.html new file mode 100644 index 0000000..abf2289 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2notify__store__retrieve_8c.html @@ -0,0 +1,560 @@ + + + + + + + +libfuse: fuse-3.18.2/example/notify_store_retrieve.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_store_retrieve.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

+

While notify_inval_inode.c uses fuse_lowlevel_notify_inval_inode() to let the kernel know that it has to invalidate the cache, this example actively pushes the updated data into the kernel cache using fuse_lowlevel_notify_store().

+

To see the effect, first start the file system with the --no-notify option:

$ notify_store_retrieve --update-interval=1 --no-notify mnt/
+

Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+

If you instead enable the notification functions, the changes become visible:

$ notify_store_retrieve --update-interval=1 mnt/
+$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:40
+The current time is 15:58:41
+The current time is 15:58:42
+The current time is 15:58:43
+The current time is 15:58:44
+

+Compilation

+
gcc -Wall notify_store_retrieve.c `pkg-config fuse3 --cflags --libs` -o notify_store_retrieve
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
#include <stdbool.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define FILE_INO 2
+
#define FILE_NAME "current_time"
+
static char file_contents[MAX_STR_LEN];
+
static int lookup_cnt = 0;
+
static int open_cnt = 0;
+
static size_t file_size;
+
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
+
/* Keep track if we ever stored data (==1), and
+
received it back correctly (==2) */
+
static int retrieve_status = 0;
+
+
static bool is_umount = false;
+
+
/* updater thread tid */
+
static pthread_t updater;
+
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == FILE_INO) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = file_size;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, FILE_NAME) == 0) {
+
e.ino = FILE_INO;
+
} else
+
goto err_out;
+
+
e.attr_timeout = NO_TIMEOUT;
+
e.entry_timeout = NO_TIMEOUT;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
+
/*
+
* must only be set when the kernel knows about the entry,
+
* otherwise update_fs_loop() might see a positive count, but kernel
+
* would not have the entry yet
+
*/
+
if (e.ino == FILE_INO) {
+
pthread_mutex_lock(&lock);
+
lookup_cnt++;
+
pthread_mutex_unlock(&lock);
+
}
+
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == FILE_INO) {
+
pthread_mutex_lock(&lock);
+
lookup_cnt -= nlookup;
+
pthread_mutex_unlock(&lock);
+
} else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
+
if (ino == FUSE_ROOT_ID)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else if (ino == FILE_INO) {
+
fuse_reply_open(req, fi);
+
pthread_mutex_lock(&lock);
+
open_cnt++;
+
pthread_mutex_unlock(&lock);
+
} else {
+
// This should not happen
+
fprintf(stderr, "Got open for non-existing inode!\n");
+
fuse_reply_err(req, ENOENT);
+
}
+
}
+
+
static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
assert(ino == FILE_INO);
+
reply_buf_limited(req, file_contents, file_size, off, size);
+
}
+
+
static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
+
off_t offset, struct fuse_bufvec *data) {
+
struct fuse_bufvec bufv;
+
char buf[MAX_STR_LEN];
+
char *expected;
+
ssize_t ret;
+
+
assert(ino == FILE_INO);
+
assert(offset == 0);
+
expected = (char*) cookie;
+
+
bufv.count = 1;
+
bufv.idx = 0;
+
bufv.off = 0;
+
bufv.buf[0].size = MAX_STR_LEN;
+
bufv.buf[0].mem = buf;
+
bufv.buf[0].flags = 0;
+
+
ret = fuse_buf_copy(&bufv, data, 0);
+
assert(ret > 0);
+
assert(strncmp(buf, expected, ret) == 0);
+
free(expected);
+
retrieve_status = 2;
+ +
}
+
+
static void tfs_destroy(void *userdata)
+
{
+
(void)userdata;
+
+
is_umount = true;
+
+
pthread_join(updater, NULL);
+
}
+
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.open = tfs_open,
+
.read = tfs_read,
+
.forget = tfs_forget,
+
.retrieve_reply = tfs_retrieve_reply,
+
.destroy = tfs_destroy,
+
};
+
+
static void update_fs(void) {
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
file_size = strftime(file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(file_size != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
struct fuse_bufvec bufv;
+
int ret;
+
+
while(!is_umount) {
+
update_fs();
+
pthread_mutex_lock(&lock);
+
if (!options.no_notify && open_cnt && lookup_cnt) {
+
/* Only send notification if the kernel
+
is aware of the inode */
+
bufv.count = 1;
+
bufv.idx = 0;
+
bufv.off = 0;
+
bufv.buf[0].size = file_size;
+
bufv.buf[0].mem = file_contents;
+
bufv.buf[0].flags = 0;
+
+
/*
+
* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
* might come up during umount, when kernel side already releases
+
* all inodes, but does not send FUSE_DESTROY yet.
+
*/
+
+
ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
+
if ((ret != 0 && !is_umount) &&
+
ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
fprintf(stderr,
+
"ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
strerror(-ret), -ret);
+
abort();
+
}
+
+
/* To make sure that everything worked correctly, ask the
+
kernel to send us back the stored data */
+
ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
+
0, (void*) strdup(file_contents));
+
assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF ||
+
ret != -ENODEV);
+
if(retrieve_status == 0)
+
retrieve_status = 1;
+
}
+
pthread_mutex_unlock(&lock);
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+
assert(retrieve_status != 1);
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_store_retrieve.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2notify__store__retrieve_8c_source.html b/doc/html/fuse-3_818_82_2example_2notify__store__retrieve_8c_source.html new file mode 100644 index 0000000..fe1cd13 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2notify__store__retrieve_8c_source.html @@ -0,0 +1,522 @@ + + + + + + + +libfuse: fuse-3.18.2/example/notify_store_retrieve.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_store_retrieve.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
61#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
62
+
63#include <fuse_lowlevel.h>
+
64#include <stdio.h>
+
65#include <stdlib.h>
+
66#include <string.h>
+
67#include <errno.h>
+
68#include <fcntl.h>
+
69#include <assert.h>
+
70#include <stddef.h>
+
71#include <unistd.h>
+
72#include <pthread.h>
+
73#include <stdbool.h>
+
74
+
75/* We can't actually tell the kernel that there is no
+
76 timeout, so we just send a big value */
+
77#define NO_TIMEOUT 500000
+
78
+
79#define MAX_STR_LEN 128
+
80#define FILE_INO 2
+
81#define FILE_NAME "current_time"
+
82static char file_contents[MAX_STR_LEN];
+
83static int lookup_cnt = 0;
+
84static int open_cnt = 0;
+
85static size_t file_size;
+
86static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
87
+
88/* Keep track if we ever stored data (==1), and
+
89 received it back correctly (==2) */
+
90static int retrieve_status = 0;
+
91
+
92static bool is_umount = false;
+
93
+
94/* updater thread tid */
+
95static pthread_t updater;
+
96
+
97
+
98/* Command line parsing */
+
99struct options {
+
100 int no_notify;
+
101 int update_interval;
+
102};
+
103static struct options options = {
+
104 .no_notify = 0,
+
105 .update_interval = 1,
+
106};
+
107
+
108#define OPTION(t, p) \
+
109 { t, offsetof(struct options, p), 1 }
+
110static const struct fuse_opt option_spec[] = {
+
111 OPTION("--no-notify", no_notify),
+
112 OPTION("--update-interval=%d", update_interval),
+ +
114};
+
115
+
116static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
117 stbuf->st_ino = ino;
+
118 if (ino == FUSE_ROOT_ID) {
+
119 stbuf->st_mode = S_IFDIR | 0755;
+
120 stbuf->st_nlink = 1;
+
121 }
+
122
+
123 else if (ino == FILE_INO) {
+
124 stbuf->st_mode = S_IFREG | 0444;
+
125 stbuf->st_nlink = 1;
+
126 stbuf->st_size = file_size;
+
127 }
+
128
+
129 else
+
130 return -1;
+
131
+
132 return 0;
+
133}
+
134
+
135static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
136 (void)userdata;
+
137
+
138 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
139 conn->no_interrupt = 1;
+
140}
+
141
+
142static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
143 const char *name) {
+
144 struct fuse_entry_param e;
+
145 memset(&e, 0, sizeof(e));
+
146
+
147 if (parent != FUSE_ROOT_ID)
+
148 goto err_out;
+
149 else if (strcmp(name, FILE_NAME) == 0) {
+
150 e.ino = FILE_INO;
+
151 } else
+
152 goto err_out;
+
153
+
154 e.attr_timeout = NO_TIMEOUT;
+
155 e.entry_timeout = NO_TIMEOUT;
+
156 if (tfs_stat(e.ino, &e.attr) != 0)
+
157 goto err_out;
+
158 fuse_reply_entry(req, &e);
+
159
+
160 /*
+
161 * must only be set when the kernel knows about the entry,
+
162 * otherwise update_fs_loop() might see a positive count, but kernel
+
163 * would not have the entry yet
+
164 */
+
165 if (e.ino == FILE_INO) {
+
166 pthread_mutex_lock(&lock);
+
167 lookup_cnt++;
+
168 pthread_mutex_unlock(&lock);
+
169 }
+
170
+
171 return;
+
172
+
173err_out:
+
174 fuse_reply_err(req, ENOENT);
+
175}
+
176
+
177static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
178 uint64_t nlookup) {
+
179 (void) req;
+
180 if(ino == FILE_INO) {
+
181 pthread_mutex_lock(&lock);
+
182 lookup_cnt -= nlookup;
+
183 pthread_mutex_unlock(&lock);
+
184 } else
+
185 assert(ino == FUSE_ROOT_ID);
+
186 fuse_reply_none(req);
+
187}
+
188
+
189static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
190 struct fuse_file_info *fi) {
+
191 struct stat stbuf;
+
192
+
193 (void) fi;
+
194
+
195 memset(&stbuf, 0, sizeof(stbuf));
+
196 if (tfs_stat(ino, &stbuf) != 0)
+
197 fuse_reply_err(req, ENOENT);
+
198 else
+
199 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
200}
+
201
+
202struct dirbuf {
+
203 char *p;
+
204 size_t size;
+
205};
+
206
+
207static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
208 fuse_ino_t ino) {
+
209 struct stat stbuf;
+
210 size_t oldsize = b->size;
+
211 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
212 b->p = (char *) realloc(b->p, b->size);
+
213 memset(&stbuf, 0, sizeof(stbuf));
+
214 stbuf.st_ino = ino;
+
215 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
216 b->size);
+
217}
+
218
+
219#define min(x, y) ((x) < (y) ? (x) : (y))
+
220
+
221static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
222 off_t off, size_t maxsize) {
+
223 if (off < bufsize)
+
224 return fuse_reply_buf(req, buf + off,
+
225 min(bufsize - off, maxsize));
+
226 else
+
227 return fuse_reply_buf(req, NULL, 0);
+
228}
+
229
+
230static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
231 off_t off, struct fuse_file_info *fi) {
+
232 (void) fi;
+
233
+
234 if (ino != FUSE_ROOT_ID)
+
235 fuse_reply_err(req, ENOTDIR);
+
236 else {
+
237 struct dirbuf b;
+
238
+
239 memset(&b, 0, sizeof(b));
+
240 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
241 reply_buf_limited(req, b.p, b.size, off, size);
+
242 free(b.p);
+
243 }
+
244}
+
245
+
246static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
247 struct fuse_file_info *fi) {
+
248
+
249 /* Make cache persistent even if file is closed,
+
250 this makes it easier to see the effects */
+
251 fi->keep_cache = 1;
+
252
+
253 if (ino == FUSE_ROOT_ID)
+
254 fuse_reply_err(req, EISDIR);
+
255 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
256 fuse_reply_err(req, EACCES);
+
257 else if (ino == FILE_INO) {
+
258 fuse_reply_open(req, fi);
+
259 pthread_mutex_lock(&lock);
+
260 open_cnt++;
+
261 pthread_mutex_unlock(&lock);
+
262 } else {
+
263 // This should not happen
+
264 fprintf(stderr, "Got open for non-existing inode!\n");
+
265 fuse_reply_err(req, ENOENT);
+
266 }
+
267}
+
268
+
269static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
270 off_t off, struct fuse_file_info *fi) {
+
271 (void) fi;
+
272
+
273 assert(ino == FILE_INO);
+
274 reply_buf_limited(req, file_contents, file_size, off, size);
+
275}
+
276
+
277static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
+
278 off_t offset, struct fuse_bufvec *data) {
+
279 struct fuse_bufvec bufv;
+
280 char buf[MAX_STR_LEN];
+
281 char *expected;
+
282 ssize_t ret;
+
283
+
284 assert(ino == FILE_INO);
+
285 assert(offset == 0);
+
286 expected = (char*) cookie;
+
287
+
288 bufv.count = 1;
+
289 bufv.idx = 0;
+
290 bufv.off = 0;
+
291 bufv.buf[0].size = MAX_STR_LEN;
+
292 bufv.buf[0].mem = buf;
+
293 bufv.buf[0].flags = 0;
+
294
+
295 ret = fuse_buf_copy(&bufv, data, 0);
+
296 assert(ret > 0);
+
297 assert(strncmp(buf, expected, ret) == 0);
+
298 free(expected);
+
299 retrieve_status = 2;
+
300 fuse_reply_none(req);
+
301}
+
302
+
303static void tfs_destroy(void *userdata)
+
304{
+
305 (void)userdata;
+
306
+
307 is_umount = true;
+
308
+
309 pthread_join(updater, NULL);
+
310}
+
311
+
312
+
313static const struct fuse_lowlevel_ops tfs_oper = {
+
314 .init = tfs_init,
+
315 .lookup = tfs_lookup,
+
316 .getattr = tfs_getattr,
+
317 .readdir = tfs_readdir,
+
318 .open = tfs_open,
+
319 .read = tfs_read,
+
320 .forget = tfs_forget,
+
321 .retrieve_reply = tfs_retrieve_reply,
+
322 .destroy = tfs_destroy,
+
323};
+
324
+
325static void update_fs(void) {
+
326 struct tm *now;
+
327 time_t t;
+
328 t = time(NULL);
+
329 now = localtime(&t);
+
330 assert(now != NULL);
+
331
+
332 file_size = strftime(file_contents, MAX_STR_LEN,
+
333 "The current time is %H:%M:%S\n", now);
+
334 assert(file_size != 0);
+
335}
+
336
+
337static void* update_fs_loop(void *data) {
+
338 struct fuse_session *se = (struct fuse_session*) data;
+
339 struct fuse_bufvec bufv;
+
340 int ret;
+
341
+
342 while(!is_umount) {
+
343 update_fs();
+
344 pthread_mutex_lock(&lock);
+
345 if (!options.no_notify && open_cnt && lookup_cnt) {
+
346 /* Only send notification if the kernel
+
347 is aware of the inode */
+
348 bufv.count = 1;
+
349 bufv.idx = 0;
+
350 bufv.off = 0;
+
351 bufv.buf[0].size = file_size;
+
352 bufv.buf[0].mem = file_contents;
+
353 bufv.buf[0].flags = 0;
+
354
+
355 /*
+
356 * Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
357 * might come up during umount, when kernel side already releases
+
358 * all inodes, but does not send FUSE_DESTROY yet.
+
359 */
+
360
+
361 ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
+
362 if ((ret != 0 && !is_umount) &&
+
363 ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
364 fprintf(stderr,
+
365 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
366 strerror(-ret), -ret);
+
367 abort();
+
368 }
+
369
+
370 /* To make sure that everything worked correctly, ask the
+
371 kernel to send us back the stored data */
+
372 ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
+
373 0, (void*) strdup(file_contents));
+
374 assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF ||
+
375 ret != -ENODEV);
+
376 if(retrieve_status == 0)
+
377 retrieve_status = 1;
+
378 }
+
379 pthread_mutex_unlock(&lock);
+
380 sleep(options.update_interval);
+
381 }
+
382 return NULL;
+
383}
+
384
+
385static void show_help(const char *progname)
+
386{
+
387 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
388 printf("File-system specific options:\n"
+
389 " --update-interval=<secs> Update-rate of file system contents\n"
+
390 " --no-notify Disable kernel notifications\n"
+
391 "\n");
+
392}
+
393
+
394int main(int argc, char *argv[]) {
+
395 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
396 struct fuse_session *se;
+
397 struct fuse_cmdline_opts opts;
+
398 struct fuse_loop_config *config;
+
399 int ret = -1;
+
400
+
401 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
402 return 1;
+
403
+
404 if (fuse_parse_cmdline(&args, &opts) != 0)
+
405 return 1;
+
406 if (opts.show_help) {
+
407 show_help(argv[0]);
+ + +
410 ret = 0;
+
411 goto err_out1;
+
412 } else if (opts.show_version) {
+
413 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
415 ret = 0;
+
416 goto err_out1;
+
417 }
+
418
+
419 /* Initial contents */
+
420 update_fs();
+
421
+
422 se = fuse_session_new(&args, &tfs_oper,
+
423 sizeof(tfs_oper), NULL);
+
424 if (se == NULL)
+
425 goto err_out1;
+
426
+
427 if (fuse_set_signal_handlers(se) != 0)
+
428 goto err_out2;
+
429
+
430 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
431 goto err_out3;
+
432
+
433 fuse_daemonize(opts.foreground);
+
434
+
435 /* Start thread to update file contents */
+
436 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
437 if (ret != 0) {
+
438 fprintf(stderr, "pthread_create failed with %s\n",
+
439 strerror(ret));
+
440 goto err_out3;
+
441 }
+
442
+
443 /* Block until ctrl+c or fusermount -u */
+
444 if (opts.singlethread)
+
445 ret = fuse_session_loop(se);
+
446 else {
+
447 config = fuse_loop_cfg_create();
+
448 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
449 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
450 ret = fuse_session_loop_mt(se, config);
+
451 fuse_loop_cfg_destroy(config);
+
452 config = NULL;
+
453 }
+
454
+
455 assert(retrieve_status != 1);
+ +
457err_out3:
+ +
459err_out2:
+ +
461err_out1:
+
462 free(opts.mountpoint);
+
463 fuse_opt_free_args(&args);
+
464
+
465 return ret ? 1 : 0;
+
466}
+
467
+
468
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2null_8c.html b/doc/html/fuse-3_818_82_2example_2null_8c.html new file mode 100644 index 0000000..80757b5 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2null_8c.html @@ -0,0 +1,779 @@ + + + + + + + +libfuse: fuse-3.18.2/example/null.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
null.c File Reference
+
+
+
#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This "filesystem" provides only a single file. The mountpoint needs to be a file rather than a directory. All writes to the file will be discarded, and reading the file always returns \0.

+

Compile with:

gcc -Wall null.c `pkg-config fuse3 --cflags --libs` -o null
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
+
#define _GNU_SOURCE
+
+
#include <fuse.h>
+
+
#ifdef HAVE_LIBULOCKMGR
+
#include <ulockmgr.h>
+
#endif
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
#include <sys/file.h> /* flock(2) */
+
+
#include "passthrough_helpers.h"
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
cfg->nullpath_ok = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
+
if(fi)
+
res = fstat(fi->fh, stbuf);
+
else
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
struct xmp_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
if (d == NULL)
+
return -ENOMEM;
+
+
d->dp = opendir(path);
+
if (d->dp == NULL) {
+
res = -errno;
+
free(d);
+
return res;
+
}
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (unsigned long) d;
+
return 0;
+
}
+
+
static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
{
+
return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
+
(void) path;
+
if (offset != d->offset) {
+
#ifndef __FreeBSD__
+
seekdir(d->dp, offset);
+
#else
+
/* Subtract the one that we add when calling
+
telldir() below */
+
seekdir(d->dp, offset-1);
+
#endif
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
struct stat st;
+
off_t nextoff;
+ +
+
if (!d->entry) {
+
d->entry = readdir(d->dp);
+
if (!d->entry)
+
break;
+
}
+
#ifdef HAVE_FSTATAT
+
if (flags & FUSE_READDIR_PLUS) {
+
int res;
+
+
res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
if (res != -1)
+
fill_flags |= FUSE_FILL_DIR_PLUS;
+
}
+
#endif
+
if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = d->entry->d_ino;
+
st.st_mode = d->entry->d_type << 12;
+
}
+
nextoff = telldir(d->dp);
+
#ifdef __FreeBSD__
+
/* Under FreeBSD, telldir() may return 0 the first time
+
it is called. But for libfuse, an offset of zero
+
means that offsets are not supported, so we shift
+
everything by one. */
+
nextoff++;
+
#endif
+
if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
break;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
return 0;
+
}
+
+
static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
(void) path;
+
closedir(d->dp);
+
free(d);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
if (S_ISFIFO(mode))
+
res = mkfifo(path, mode);
+
else
+
res = mknod(path, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
/* When we have renameat2() in libc, then we can implement flags */
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = fchmod(fi->fh, mode);
+
else
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi)
+
res = fchown(fi->fh, uid, gid);
+
else
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
if (fi)
+
res = futimens(fi->fh, ts);
+
else
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags, mode);
+
if (fd == -1)
+
return -errno;
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags);
+
if (fd == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pread(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
size_t size, off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec *src;
+
+
(void) path;
+
+
src = malloc(sizeof(struct fuse_bufvec));
+
if (src == NULL)
+
return -ENOMEM;
+
+
*src = FUSE_BUFVEC_INIT(size);
+
+ +
src->buf[0].fd = fi->fh;
+
src->buf[0].pos = offset;
+
+
*bufp = src;
+
+
return 0;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pwrite(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
+
(void) path;
+
+ +
dst.buf[0].fd = fi->fh;
+
dst.buf[0].pos = offset;
+
+ +
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
/* This is called from every close on an open file, so call the
+
close on the underlying filesystem. But since flush may be
+
called multiple times for an open file, this must not really
+
close the file. This is important if used on a network
+
filesystem like NFS which flush the data/metadata on close() */
+
res = close(dup(fi->fh));
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) path;
+
+
#ifndef HAVE_FDATASYNC
+
(void) isdatasync;
+
#else
+
if (isdatasync)
+
res = fdatasync(fi->fh);
+
else
+
#endif
+
res = fsync(fi->fh);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
(void) path;
+
+
return do_fallocate(fi->fh, mode, offset, length);
+
}
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_LIBULOCKMGR
+
static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
struct flock *lock)
+
{
+
(void) path;
+
+
return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
sizeof(fi->lock_owner));
+
}
+
#endif
+
+
static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
{
+
int res;
+
(void) path;
+
+
res = flock(fi->fh, op);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t off_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t off_out, size_t len, int flags)
+
{
+
ssize_t res;
+
(void) path_in;
+
(void) path_out;
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
off_t res;
+
(void) path;
+
+
res = lseek(fi->fh, off, whence);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
+
#ifdef HAVE_STATX
+
static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
struct fuse_file_info *fi)
+
{
+
int fd = -1;
+
int res;
+
+
if (fi)
+
fd = fi->fh;
+
+
res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.opendir = xmp_opendir,
+
.readdir = xmp_readdir,
+
.releasedir = xmp_releasedir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.create = xmp_create,
+
.open = xmp_open,
+
.read = xmp_read,
+
.read_buf = xmp_read_buf,
+
.write = xmp_write,
+
.write_buf = xmp_write_buf,
+
.statfs = xmp_statfs,
+
.flush = xmp_flush,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
.fallocate = xmp_fallocate,
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_LIBULOCKMGR
+
.lock = xmp_lock,
+
#endif
+
.flock = xmp_flock,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
#ifdef HAVE_STATX
+
.statx = xmp_statx,
+
#endif
+
};
+
+
int main(int argc, char *argv[])
+
{
+
umask(0);
+
return fuse_main(argc, argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file null.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2null_8c_source.html b/doc/html/fuse-3_818_82_2example_2null_8c_source.html new file mode 100644 index 0000000..3d5001c --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2null_8c_source.html @@ -0,0 +1,196 @@ + + + + + + + +libfuse: fuse-3.18.2/example/null.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
null.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
25#define FUSE_USE_VERSION 31
+
26
+
27#include <fuse.h>
+
28#include <fuse_lowlevel.h>
+
29#include <stdio.h>
+
30#include <stdlib.h>
+
31#include <string.h>
+
32#include <unistd.h>
+
33#include <time.h>
+
34#include <errno.h>
+
35
+
36static int null_getattr(const char *path, struct stat *stbuf,
+
37 struct fuse_file_info *fi)
+
38{
+
39 (void) fi;
+
40
+
41 if(strcmp(path, "/") != 0)
+
42 return -ENOENT;
+
43
+
44 stbuf->st_mode = S_IFREG | 0644;
+
45 stbuf->st_nlink = 1;
+
46 stbuf->st_uid = getuid();
+
47 stbuf->st_gid = getgid();
+
48 stbuf->st_size = (1ULL << 32); /* 4G */
+
49 stbuf->st_blocks = 0;
+
50 stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = time(NULL);
+
51
+
52 return 0;
+
53}
+
54
+
55static int null_truncate(const char *path, off_t size,
+
56 struct fuse_file_info *fi)
+
57{
+
58 (void) size;
+
59 (void) fi;
+
60
+
61 if(strcmp(path, "/") != 0)
+
62 return -ENOENT;
+
63
+
64 return 0;
+
65}
+
66
+
67static int null_open(const char *path, struct fuse_file_info *fi)
+
68{
+
69 (void) fi;
+
70
+
71 if(strcmp(path, "/") != 0)
+
72 return -ENOENT;
+
73
+
74 return 0;
+
75}
+
76
+
77static int null_read(const char *path, char *buf, size_t size,
+
78 off_t offset, struct fuse_file_info *fi)
+
79{
+
80 (void) buf;
+
81 (void) offset;
+
82 (void) fi;
+
83
+
84 if(strcmp(path, "/") != 0)
+
85 return -ENOENT;
+
86
+
87 if (offset >= (1ULL << 32))
+
88 return 0;
+
89
+
90 memset(buf, 0, size);
+
91 return size;
+
92}
+
93
+
94static int null_write(const char *path, const char *buf, size_t size,
+
95 off_t offset, struct fuse_file_info *fi)
+
96{
+
97 (void) buf;
+
98 (void) offset;
+
99 (void) fi;
+
100
+
101 if(strcmp(path, "/") != 0)
+
102 return -ENOENT;
+
103
+
104 return size;
+
105}
+
106
+
107static const struct fuse_operations null_oper = {
+
108 .getattr = null_getattr,
+
109 .truncate = null_truncate,
+
110 .open = null_open,
+
111 .read = null_read,
+
112 .write = null_write,
+
113};
+
114
+
115int main(int argc, char *argv[])
+
116{
+
117 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
118 struct fuse_cmdline_opts opts;
+
119 struct stat stbuf;
+
120
+
121 if (fuse_parse_cmdline(&args, &opts) != 0)
+
122 return 1;
+
123 fuse_opt_free_args(&args);
+
124
+
125 if (!opts.mountpoint) {
+
126 fprintf(stderr, "missing mountpoint parameter\n");
+
127 return 1;
+
128 }
+
129
+
130 if (stat(opts.mountpoint, &stbuf) == -1) {
+
131 fprintf(stderr ,"failed to access mountpoint %s: %s\n",
+
132 opts.mountpoint, strerror(errno));
+
133 free(opts.mountpoint);
+
134 return 1;
+
135 }
+
136 free(opts.mountpoint);
+
137 if (!S_ISREG(stbuf.st_mode)) {
+
138 fprintf(stderr, "mountpoint is not a regular file\n");
+
139 return 1;
+
140 }
+
141
+
142 return fuse_main(argc, argv, &null_oper, NULL);
+
143}
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2passthrough_8c.html b/doc/html/fuse-3_818_82_2example_2passthrough_8c.html new file mode 100644 index 0000000..3d571c8 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2passthrough_8c.html @@ -0,0 +1,679 @@ + + + + + + + +libfuse: fuse-3.18.2/example/passthrough.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. Its performance is terrible.

+

Compile with

gcc -Wall passthrough.c `pkg-config fuse3 --cflags --libs` -o passthrough
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
+
#define _GNU_SOURCE
+
+
#ifdef linux
+
/* For pread()/pwrite()/utimensat() */
+
#define _XOPEN_SOURCE 700
+
#endif
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
+
#include "passthrough_helpers.h"
+
+
static int fill_dir_plus = 0;
+
static int readdir_zero_ino;
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = !readdir_zero_ino;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
if (!cfg->auto_cache) {
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
}
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
DIR *dp;
+
struct dirent *de;
+
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
dp = opendir(path);
+
if (dp == NULL)
+
return -errno;
+
+
while ((de = readdir(dp)) != NULL) {
+
struct stat st;
+
if (fill_dir_plus) {
+
fstatat(dirfd(dp), de->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
} else {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = de->d_ino;
+
st.st_mode = de->d_type << 12;
+
}
+
if (readdir_zero_ino)
+
st.st_ino = 0;
+
if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
+
break;
+
}
+
+
closedir(dp);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi != NULL)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
res = open(path, fi->flags, mode);
+
if (res == -1)
+
return -errno;
+
+
fi->fh = res;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
res = open(path, fi->flags);
+
if (res == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = res;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
if(fi == NULL)
+
fd = open(path, O_RDONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = pread(fd, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
(void) fi;
+
if(fi == NULL)
+
fd = open(path, O_WRONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = pwrite(fd, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
/* Just a stub. This method is optional and can safely be left
+
unimplemented */
+
+
(void) path;
+
(void) isdatasync;
+
(void) fi;
+
return 0;
+
}
+
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
(void) fi;
+
+
if(fi == NULL)
+
fd = open(path, O_WRONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = do_fallocate(fd, mode, offset, length);
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t offset_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t offset_out, size_t len, int flags)
+
{
+
int fd_in, fd_out;
+
ssize_t res;
+
+
if(fi_in == NULL)
+
fd_in = open(path_in, O_RDONLY);
+
else
+
fd_in = fi_in->fh;
+
+
if (fd_in == -1)
+
return -errno;
+
+
if(fi_out == NULL)
+
fd_out = open(path_out, O_WRONLY);
+
else
+
fd_out = fi_out->fh;
+
+
if (fd_out == -1) {
+
close(fd_in);
+
return -errno;
+
}
+
+
res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
+
flags);
+
if (res == -1)
+
res = -errno;
+
+
if (fi_out == NULL)
+
close(fd_out);
+
if (fi_in == NULL)
+
close(fd_in);
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
int fd;
+
off_t res;
+
+
if (fi == NULL)
+
fd = open(path, O_RDONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = lseek(fd, off, whence);
+
if (res == -1)
+
res = -errno;
+
+
if (fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
#ifdef HAVE_STATX
+
static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
struct fuse_file_info *fi)
+
{
+
int fd = -1;
+
int res;
+
+
if (fi)
+
fd = fi->fh;
+
+
res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.readdir = xmp_readdir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.open = xmp_open,
+
.create = xmp_create,
+
.read = xmp_read,
+
.write = xmp_write,
+
.statfs = xmp_statfs,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
.fallocate = xmp_fallocate,
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
#ifdef HAVE_STATX
+
.statx = xmp_statx,
+
#endif
+
};
+
+
int main(int argc, char *argv[])
+
{
+
enum { MAX_ARGS = 10 };
+
int i,new_argc;
+
char *new_argv[MAX_ARGS];
+
+
umask(0);
+
/* Process the "--plus" option apart */
+
for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
+
if (!strcmp(argv[i], "--plus")) {
+
fill_dir_plus = FUSE_FILL_DIR_PLUS;
+
} else if (!strcmp(argv[i], "--readdir-zero-inodes")) {
+
// Return zero inodes from readdir
+
readdir_zero_ino = 1;
+
} else {
+
new_argv[new_argc++] = argv[i];
+
}
+
}
+
return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ +
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t auto_cache
Definition fuse.h:253
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file passthrough.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2passthrough_8c_source.html b/doc/html/fuse-3_818_82_2example_2passthrough_8c_source.html new file mode 100644 index 0000000..f52b4b8 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2passthrough_8c_source.html @@ -0,0 +1,665 @@ + + + + + + + +libfuse: fuse-3.18.2/example/passthrough.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
26#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
27
+
28#define _GNU_SOURCE
+
29
+
30#ifdef linux
+
31/* For pread()/pwrite()/utimensat() */
+
32#define _XOPEN_SOURCE 700
+
33#endif
+
34
+
35#include <fuse.h>
+
36#include <stdio.h>
+
37#include <string.h>
+
38#include <unistd.h>
+
39#include <fcntl.h>
+
40#include <sys/stat.h>
+
41#include <dirent.h>
+
42#include <errno.h>
+
43#include <sys/time.h>
+
44#ifdef HAVE_SETXATTR
+
45#include <sys/xattr.h>
+
46#endif
+
47
+
48#include "passthrough_helpers.h"
+
49
+
50static int fill_dir_plus = 0;
+
51static int readdir_zero_ino;
+
52
+
53static void *xmp_init(struct fuse_conn_info *conn,
+
54 struct fuse_config *cfg)
+
55{
+
56 (void) conn;
+
57 cfg->use_ino = !readdir_zero_ino;
+
58
+
59 /* parallel_direct_writes feature depends on direct_io features.
+
60 To make parallel_direct_writes valid, need either set cfg->direct_io
+
61 in current function (recommended in high level API) or set fi->direct_io
+
62 in xmp_create() or xmp_open(). */
+
63 // cfg->direct_io = 1;
+ +
65
+
66 /* Pick up changes from lower filesystem right away. This is
+
67 also necessary for better hardlink support. When the kernel
+
68 calls the unlink() handler, it does not know the inode of
+
69 the to-be-removed entry and can therefore not invalidate
+
70 the cache of the associated inode - resulting in an
+
71 incorrect st_nlink value being reported for any remaining
+
72 hardlinks to this inode. */
+
73 if (!cfg->auto_cache) {
+
74 cfg->entry_timeout = 0;
+
75 cfg->attr_timeout = 0;
+
76 cfg->negative_timeout = 0;
+
77 }
+
78
+
79 return NULL;
+
80}
+
81
+
82static int xmp_getattr(const char *path, struct stat *stbuf,
+
83 struct fuse_file_info *fi)
+
84{
+
85 (void) fi;
+
86 int res;
+
87
+
88 res = lstat(path, stbuf);
+
89 if (res == -1)
+
90 return -errno;
+
91
+
92 return 0;
+
93}
+
94
+
95static int xmp_access(const char *path, int mask)
+
96{
+
97 int res;
+
98
+
99 res = access(path, mask);
+
100 if (res == -1)
+
101 return -errno;
+
102
+
103 return 0;
+
104}
+
105
+
106static int xmp_readlink(const char *path, char *buf, size_t size)
+
107{
+
108 int res;
+
109
+
110 res = readlink(path, buf, size - 1);
+
111 if (res == -1)
+
112 return -errno;
+
113
+
114 buf[res] = '\0';
+
115 return 0;
+
116}
+
117
+
118
+
119static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
120 off_t offset, struct fuse_file_info *fi,
+
121 enum fuse_readdir_flags flags)
+
122{
+
123 DIR *dp;
+
124 struct dirent *de;
+
125
+
126 (void) offset;
+
127 (void) fi;
+
128 (void) flags;
+
129
+
130 dp = opendir(path);
+
131 if (dp == NULL)
+
132 return -errno;
+
133
+
134 while ((de = readdir(dp)) != NULL) {
+
135 struct stat st;
+
136 if (fill_dir_plus) {
+
137 fstatat(dirfd(dp), de->d_name, &st,
+
138 AT_SYMLINK_NOFOLLOW);
+
139 } else {
+
140 memset(&st, 0, sizeof(st));
+
141 st.st_ino = de->d_ino;
+
142 st.st_mode = de->d_type << 12;
+
143 }
+
144 if (readdir_zero_ino)
+
145 st.st_ino = 0;
+
146 if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
+
147 break;
+
148 }
+
149
+
150 closedir(dp);
+
151 return 0;
+
152}
+
153
+
154static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
155{
+
156 int res;
+
157
+
158 res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
+
159 if (res == -1)
+
160 return -errno;
+
161
+
162 return 0;
+
163}
+
164
+
165static int xmp_mkdir(const char *path, mode_t mode)
+
166{
+
167 int res;
+
168
+
169 res = mkdir(path, mode);
+
170 if (res == -1)
+
171 return -errno;
+
172
+
173 return 0;
+
174}
+
175
+
176static int xmp_unlink(const char *path)
+
177{
+
178 int res;
+
179
+
180 res = unlink(path);
+
181 if (res == -1)
+
182 return -errno;
+
183
+
184 return 0;
+
185}
+
186
+
187static int xmp_rmdir(const char *path)
+
188{
+
189 int res;
+
190
+
191 res = rmdir(path);
+
192 if (res == -1)
+
193 return -errno;
+
194
+
195 return 0;
+
196}
+
197
+
198static int xmp_symlink(const char *from, const char *to)
+
199{
+
200 int res;
+
201
+
202 res = symlink(from, to);
+
203 if (res == -1)
+
204 return -errno;
+
205
+
206 return 0;
+
207}
+
208
+
209static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
210{
+
211 int res;
+
212
+
213 if (flags)
+
214 return -EINVAL;
+
215
+
216 res = rename(from, to);
+
217 if (res == -1)
+
218 return -errno;
+
219
+
220 return 0;
+
221}
+
222
+
223static int xmp_link(const char *from, const char *to)
+
224{
+
225 int res;
+
226
+
227 res = link(from, to);
+
228 if (res == -1)
+
229 return -errno;
+
230
+
231 return 0;
+
232}
+
233
+
234static int xmp_chmod(const char *path, mode_t mode,
+
235 struct fuse_file_info *fi)
+
236{
+
237 (void) fi;
+
238 int res;
+
239
+
240 res = chmod(path, mode);
+
241 if (res == -1)
+
242 return -errno;
+
243
+
244 return 0;
+
245}
+
246
+
247static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
248 struct fuse_file_info *fi)
+
249{
+
250 (void) fi;
+
251 int res;
+
252
+
253 res = lchown(path, uid, gid);
+
254 if (res == -1)
+
255 return -errno;
+
256
+
257 return 0;
+
258}
+
259
+
260static int xmp_truncate(const char *path, off_t size,
+
261 struct fuse_file_info *fi)
+
262{
+
263 int res;
+
264
+
265 if (fi != NULL)
+
266 res = ftruncate(fi->fh, size);
+
267 else
+
268 res = truncate(path, size);
+
269 if (res == -1)
+
270 return -errno;
+
271
+
272 return 0;
+
273}
+
274
+
275#ifdef HAVE_UTIMENSAT
+
276static int xmp_utimens(const char *path, const struct timespec ts[2],
+
277 struct fuse_file_info *fi)
+
278{
+
279 (void) fi;
+
280 int res;
+
281
+
282 /* don't use utime/utimes since they follow symlinks */
+
283 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
284 if (res == -1)
+
285 return -errno;
+
286
+
287 return 0;
+
288}
+
289#endif
+
290
+
291static int xmp_create(const char *path, mode_t mode,
+
292 struct fuse_file_info *fi)
+
293{
+
294 int res;
+
295
+
296 res = open(path, fi->flags, mode);
+
297 if (res == -1)
+
298 return -errno;
+
299
+
300 fi->fh = res;
+
301 return 0;
+
302}
+
303
+
304static int xmp_open(const char *path, struct fuse_file_info *fi)
+
305{
+
306 int res;
+
307
+
308 res = open(path, fi->flags);
+
309 if (res == -1)
+
310 return -errno;
+
311
+
312 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
313 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
314 for writes to the same file). */
+
315 if (fi->flags & O_DIRECT) {
+
316 fi->direct_io = 1;
+ +
318 }
+
319
+
320 fi->fh = res;
+
321 return 0;
+
322}
+
323
+
324static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
325 struct fuse_file_info *fi)
+
326{
+
327 int fd;
+
328 int res;
+
329
+
330 if(fi == NULL)
+
331 fd = open(path, O_RDONLY);
+
332 else
+
333 fd = fi->fh;
+
334
+
335 if (fd == -1)
+
336 return -errno;
+
337
+
338 res = pread(fd, buf, size, offset);
+
339 if (res == -1)
+
340 res = -errno;
+
341
+
342 if(fi == NULL)
+
343 close(fd);
+
344 return res;
+
345}
+
346
+
347static int xmp_write(const char *path, const char *buf, size_t size,
+
348 off_t offset, struct fuse_file_info *fi)
+
349{
+
350 int fd;
+
351 int res;
+
352
+
353 (void) fi;
+
354 if(fi == NULL)
+
355 fd = open(path, O_WRONLY);
+
356 else
+
357 fd = fi->fh;
+
358
+
359 if (fd == -1)
+
360 return -errno;
+
361
+
362 res = pwrite(fd, buf, size, offset);
+
363 if (res == -1)
+
364 res = -errno;
+
365
+
366 if(fi == NULL)
+
367 close(fd);
+
368 return res;
+
369}
+
370
+
371static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
372{
+
373 int res;
+
374
+
375 res = statvfs(path, stbuf);
+
376 if (res == -1)
+
377 return -errno;
+
378
+
379 return 0;
+
380}
+
381
+
382static int xmp_release(const char *path, struct fuse_file_info *fi)
+
383{
+
384 (void) path;
+
385 close(fi->fh);
+
386 return 0;
+
387}
+
388
+
389static int xmp_fsync(const char *path, int isdatasync,
+
390 struct fuse_file_info *fi)
+
391{
+
392 /* Just a stub. This method is optional and can safely be left
+
393 unimplemented */
+
394
+
395 (void) path;
+
396 (void) isdatasync;
+
397 (void) fi;
+
398 return 0;
+
399}
+
400
+
401static int xmp_fallocate(const char *path, int mode,
+
402 off_t offset, off_t length, struct fuse_file_info *fi)
+
403{
+
404 int fd;
+
405 int res;
+
406
+
407 (void) fi;
+
408
+
409 if(fi == NULL)
+
410 fd = open(path, O_WRONLY);
+
411 else
+
412 fd = fi->fh;
+
413
+
414 if (fd == -1)
+
415 return -errno;
+
416
+
417 res = do_fallocate(fd, mode, offset, length);
+
418
+
419 if(fi == NULL)
+
420 close(fd);
+
421 return res;
+
422}
+
423
+
424#ifdef HAVE_SETXATTR
+
425/* xattr operations are optional and can safely be left unimplemented */
+
426static int xmp_setxattr(const char *path, const char *name, const char *value,
+
427 size_t size, int flags)
+
428{
+
429 int res = lsetxattr(path, name, value, size, flags);
+
430 if (res == -1)
+
431 return -errno;
+
432 return 0;
+
433}
+
434
+
435static int xmp_getxattr(const char *path, const char *name, char *value,
+
436 size_t size)
+
437{
+
438 int res = lgetxattr(path, name, value, size);
+
439 if (res == -1)
+
440 return -errno;
+
441 return res;
+
442}
+
443
+
444static int xmp_listxattr(const char *path, char *list, size_t size)
+
445{
+
446 int res = llistxattr(path, list, size);
+
447 if (res == -1)
+
448 return -errno;
+
449 return res;
+
450}
+
451
+
452static int xmp_removexattr(const char *path, const char *name)
+
453{
+
454 int res = lremovexattr(path, name);
+
455 if (res == -1)
+
456 return -errno;
+
457 return 0;
+
458}
+
459#endif /* HAVE_SETXATTR */
+
460
+
461#ifdef HAVE_COPY_FILE_RANGE
+
462static ssize_t xmp_copy_file_range(const char *path_in,
+
463 struct fuse_file_info *fi_in,
+
464 off_t offset_in, const char *path_out,
+
465 struct fuse_file_info *fi_out,
+
466 off_t offset_out, size_t len, int flags)
+
467{
+
468 int fd_in, fd_out;
+
469 ssize_t res;
+
470
+
471 if(fi_in == NULL)
+
472 fd_in = open(path_in, O_RDONLY);
+
473 else
+
474 fd_in = fi_in->fh;
+
475
+
476 if (fd_in == -1)
+
477 return -errno;
+
478
+
479 if(fi_out == NULL)
+
480 fd_out = open(path_out, O_WRONLY);
+
481 else
+
482 fd_out = fi_out->fh;
+
483
+
484 if (fd_out == -1) {
+
485 close(fd_in);
+
486 return -errno;
+
487 }
+
488
+
489 res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
+
490 flags);
+
491 if (res == -1)
+
492 res = -errno;
+
493
+
494 if (fi_out == NULL)
+
495 close(fd_out);
+
496 if (fi_in == NULL)
+
497 close(fd_in);
+
498
+
499 return res;
+
500}
+
501#endif
+
502
+
503static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
504{
+
505 int fd;
+
506 off_t res;
+
507
+
508 if (fi == NULL)
+
509 fd = open(path, O_RDONLY);
+
510 else
+
511 fd = fi->fh;
+
512
+
513 if (fd == -1)
+
514 return -errno;
+
515
+
516 res = lseek(fd, off, whence);
+
517 if (res == -1)
+
518 res = -errno;
+
519
+
520 if (fi == NULL)
+
521 close(fd);
+
522 return res;
+
523}
+
524
+
525#ifdef HAVE_STATX
+
526static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
527 struct fuse_file_info *fi)
+
528{
+
529 int fd = -1;
+
530 int res;
+
531
+
532 if (fi)
+
533 fd = fi->fh;
+
534
+
535 res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
+
536 if (res == -1)
+
537 return -errno;
+
538
+
539 return 0;
+
540}
+
541#endif
+
542
+
543static const struct fuse_operations xmp_oper = {
+
544 .init = xmp_init,
+
545 .getattr = xmp_getattr,
+
546 .access = xmp_access,
+
547 .readlink = xmp_readlink,
+
548 .readdir = xmp_readdir,
+
549 .mknod = xmp_mknod,
+
550 .mkdir = xmp_mkdir,
+
551 .symlink = xmp_symlink,
+
552 .unlink = xmp_unlink,
+
553 .rmdir = xmp_rmdir,
+
554 .rename = xmp_rename,
+
555 .link = xmp_link,
+
556 .chmod = xmp_chmod,
+
557 .chown = xmp_chown,
+
558 .truncate = xmp_truncate,
+
559#ifdef HAVE_UTIMENSAT
+
560 .utimens = xmp_utimens,
+
561#endif
+
562 .open = xmp_open,
+
563 .create = xmp_create,
+
564 .read = xmp_read,
+
565 .write = xmp_write,
+
566 .statfs = xmp_statfs,
+
567 .release = xmp_release,
+
568 .fsync = xmp_fsync,
+
569 .fallocate = xmp_fallocate,
+
570#ifdef HAVE_SETXATTR
+
571 .setxattr = xmp_setxattr,
+
572 .getxattr = xmp_getxattr,
+
573 .listxattr = xmp_listxattr,
+
574 .removexattr = xmp_removexattr,
+
575#endif
+
576#ifdef HAVE_COPY_FILE_RANGE
+
577 .copy_file_range = xmp_copy_file_range,
+
578#endif
+
579 .lseek = xmp_lseek,
+
580#ifdef HAVE_STATX
+
581 .statx = xmp_statx,
+
582#endif
+
583};
+
584
+
585int main(int argc, char *argv[])
+
586{
+
587 enum { MAX_ARGS = 10 };
+
588 int i,new_argc;
+
589 char *new_argv[MAX_ARGS];
+
590
+
591 umask(0);
+
592 /* Process the "--plus" option apart */
+
593 for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
+
594 if (!strcmp(argv[i], "--plus")) {
+
595 fill_dir_plus = FUSE_FILL_DIR_PLUS;
+
596 } else if (!strcmp(argv[i], "--readdir-zero-inodes")) {
+
597 // Return zero inodes from readdir
+
598 readdir_zero_ino = 1;
+
599 } else {
+
600 new_argv[new_argc++] = argv[i];
+
601 }
+
602 }
+
603 return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
+
604}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ +
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t auto_cache
Definition fuse.h:253
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2passthrough__fh_8c.html b/doc/html/fuse-3_818_82_2example_2passthrough__fh_8c.html new file mode 100644 index 0000000..1a0938d --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2passthrough__fh_8c.html @@ -0,0 +1,783 @@ + + + + + + + +libfuse: fuse-3.18.2/example/passthrough_fh.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough_fh.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/file.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. This implementation is a little more sophisticated than the one in passthrough.c, so performance is not quite as bad.

+

Compile with:

gcc -Wall passthrough_fh.c `pkg-config fuse3 --cflags --libs` -lulockmgr -o passthrough_fh
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
+
#define _GNU_SOURCE
+
+
#include <fuse.h>
+
+
#ifdef HAVE_LIBULOCKMGR
+
#include <ulockmgr.h>
+
#endif
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
#include <sys/file.h> /* flock(2) */
+
+
#include "passthrough_helpers.h"
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
cfg->nullpath_ok = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
+
if(fi)
+
res = fstat(fi->fh, stbuf);
+
else
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
struct xmp_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
if (d == NULL)
+
return -ENOMEM;
+
+
d->dp = opendir(path);
+
if (d->dp == NULL) {
+
res = -errno;
+
free(d);
+
return res;
+
}
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (unsigned long) d;
+
return 0;
+
}
+
+
static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
{
+
return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
+
(void) path;
+
if (offset != d->offset) {
+
#ifndef __FreeBSD__
+
seekdir(d->dp, offset);
+
#else
+
/* Subtract the one that we add when calling
+
telldir() below */
+
seekdir(d->dp, offset-1);
+
#endif
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
struct stat st;
+
off_t nextoff;
+ +
+
if (!d->entry) {
+
d->entry = readdir(d->dp);
+
if (!d->entry)
+
break;
+
}
+
#ifdef HAVE_FSTATAT
+
if (flags & FUSE_READDIR_PLUS) {
+
int res;
+
+
res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
if (res != -1)
+
fill_flags |= FUSE_FILL_DIR_PLUS;
+
}
+
#endif
+
if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = d->entry->d_ino;
+
st.st_mode = d->entry->d_type << 12;
+
}
+
nextoff = telldir(d->dp);
+
#ifdef __FreeBSD__
+
/* Under FreeBSD, telldir() may return 0 the first time
+
it is called. But for libfuse, an offset of zero
+
means that offsets are not supported, so we shift
+
everything by one. */
+
nextoff++;
+
#endif
+
if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
break;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
return 0;
+
}
+
+
static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
(void) path;
+
closedir(d->dp);
+
free(d);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
if (S_ISFIFO(mode))
+
res = mkfifo(path, mode);
+
else
+
res = mknod(path, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
/* When we have renameat2() in libc, then we can implement flags */
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = fchmod(fi->fh, mode);
+
else
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi)
+
res = fchown(fi->fh, uid, gid);
+
else
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
if (fi)
+
res = futimens(fi->fh, ts);
+
else
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags, mode);
+
if (fd == -1)
+
return -errno;
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags);
+
if (fd == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pread(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
size_t size, off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec *src;
+
+
(void) path;
+
+
src = malloc(sizeof(struct fuse_bufvec));
+
if (src == NULL)
+
return -ENOMEM;
+
+
*src = FUSE_BUFVEC_INIT(size);
+
+ +
src->buf[0].fd = fi->fh;
+
src->buf[0].pos = offset;
+
+
*bufp = src;
+
+
return 0;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pwrite(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
+
(void) path;
+
+ +
dst.buf[0].fd = fi->fh;
+
dst.buf[0].pos = offset;
+
+ +
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
/* This is called from every close on an open file, so call the
+
close on the underlying filesystem. But since flush may be
+
called multiple times for an open file, this must not really
+
close the file. This is important if used on a network
+
filesystem like NFS which flush the data/metadata on close() */
+
res = close(dup(fi->fh));
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) path;
+
+
#ifndef HAVE_FDATASYNC
+
(void) isdatasync;
+
#else
+
if (isdatasync)
+
res = fdatasync(fi->fh);
+
else
+
#endif
+
res = fsync(fi->fh);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
(void) path;
+
+
return do_fallocate(fi->fh, mode, offset, length);
+
}
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_LIBULOCKMGR
+
static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
struct flock *lock)
+
{
+
(void) path;
+
+
return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
sizeof(fi->lock_owner));
+
}
+
#endif
+
+
static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
{
+
int res;
+
(void) path;
+
+
res = flock(fi->fh, op);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t off_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t off_out, size_t len, int flags)
+
{
+
ssize_t res;
+
(void) path_in;
+
(void) path_out;
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
off_t res;
+
(void) path;
+
+
res = lseek(fi->fh, off, whence);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
+
#ifdef HAVE_STATX
+
static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
struct fuse_file_info *fi)
+
{
+
int fd = -1;
+
int res;
+
+
if (fi)
+
fd = fi->fh;
+
+
res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.opendir = xmp_opendir,
+
.readdir = xmp_readdir,
+
.releasedir = xmp_releasedir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.create = xmp_create,
+
.open = xmp_open,
+
.read = xmp_read,
+
.read_buf = xmp_read_buf,
+
.write = xmp_write,
+
.write_buf = xmp_write_buf,
+
.statfs = xmp_statfs,
+
.flush = xmp_flush,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
.fallocate = xmp_fallocate,
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_LIBULOCKMGR
+
.lock = xmp_lock,
+
#endif
+
.flock = xmp_flock,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
#ifdef HAVE_STATX
+
.statx = xmp_statx,
+
#endif
+
};
+
+
int main(int argc, char *argv[])
+
{
+
umask(0);
+
return fuse_main(argc, argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file passthrough_fh.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2passthrough__fh_8c_source.html b/doc/html/fuse-3_818_82_2example_2passthrough__fh_8c_source.html new file mode 100644 index 0000000..4b9fd2d --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2passthrough__fh_8c_source.html @@ -0,0 +1,767 @@ + + + + + + + +libfuse: fuse-3.18.2/example/passthrough_fh.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_fh.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
26#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
27
+
28#define _GNU_SOURCE
+
29
+
30#include <fuse.h>
+
31
+
32#ifdef HAVE_LIBULOCKMGR
+
33#include <ulockmgr.h>
+
34#endif
+
35
+
36#include <stdio.h>
+
37#include <stdlib.h>
+
38#include <string.h>
+
39#include <unistd.h>
+
40#include <fcntl.h>
+
41#include <sys/stat.h>
+
42#include <dirent.h>
+
43#include <errno.h>
+
44#include <sys/time.h>
+
45#ifdef HAVE_SETXATTR
+
46#include <sys/xattr.h>
+
47#endif
+
48#include <sys/file.h> /* flock(2) */
+
49
+
50#include "passthrough_helpers.h"
+
51
+
52static void *xmp_init(struct fuse_conn_info *conn,
+
53 struct fuse_config *cfg)
+
54{
+
55 (void) conn;
+
56 cfg->use_ino = 1;
+
57 cfg->nullpath_ok = 1;
+
58
+
59 /* parallel_direct_writes feature depends on direct_io features.
+
60 To make parallel_direct_writes valid, need either set cfg->direct_io
+
61 in current function (recommended in high level API) or set fi->direct_io
+
62 in xmp_create() or xmp_open(). */
+
63 // cfg->direct_io = 1;
+ +
65
+
66 /* Pick up changes from lower filesystem right away. This is
+
67 also necessary for better hardlink support. When the kernel
+
68 calls the unlink() handler, it does not know the inode of
+
69 the to-be-removed entry and can therefore not invalidate
+
70 the cache of the associated inode - resulting in an
+
71 incorrect st_nlink value being reported for any remaining
+
72 hardlinks to this inode. */
+
73 cfg->entry_timeout = 0;
+
74 cfg->attr_timeout = 0;
+
75 cfg->negative_timeout = 0;
+
76
+
77 return NULL;
+
78}
+
79
+
80static int xmp_getattr(const char *path, struct stat *stbuf,
+
81 struct fuse_file_info *fi)
+
82{
+
83 int res;
+
84
+
85 (void) path;
+
86
+
87 if(fi)
+
88 res = fstat(fi->fh, stbuf);
+
89 else
+
90 res = lstat(path, stbuf);
+
91 if (res == -1)
+
92 return -errno;
+
93
+
94 return 0;
+
95}
+
96
+
97static int xmp_access(const char *path, int mask)
+
98{
+
99 int res;
+
100
+
101 res = access(path, mask);
+
102 if (res == -1)
+
103 return -errno;
+
104
+
105 return 0;
+
106}
+
107
+
108static int xmp_readlink(const char *path, char *buf, size_t size)
+
109{
+
110 int res;
+
111
+
112 res = readlink(path, buf, size - 1);
+
113 if (res == -1)
+
114 return -errno;
+
115
+
116 buf[res] = '\0';
+
117 return 0;
+
118}
+
119
+
120struct xmp_dirp {
+
121 DIR *dp;
+
122 struct dirent *entry;
+
123 off_t offset;
+
124};
+
125
+
126static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
127{
+
128 int res;
+
129 struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
130 if (d == NULL)
+
131 return -ENOMEM;
+
132
+
133 d->dp = opendir(path);
+
134 if (d->dp == NULL) {
+
135 res = -errno;
+
136 free(d);
+
137 return res;
+
138 }
+
139 d->offset = 0;
+
140 d->entry = NULL;
+
141
+
142 fi->fh = (unsigned long) d;
+
143 return 0;
+
144}
+
145
+
146static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
147{
+
148 return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
149}
+
150
+
151static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
152 off_t offset, struct fuse_file_info *fi,
+
153 enum fuse_readdir_flags flags)
+
154{
+
155 struct xmp_dirp *d = get_dirp(fi);
+
156
+
157 (void) path;
+
158 if (offset != d->offset) {
+
159#ifndef __FreeBSD__
+
160 seekdir(d->dp, offset);
+
161#else
+
162 /* Subtract the one that we add when calling
+
163 telldir() below */
+
164 seekdir(d->dp, offset-1);
+
165#endif
+
166 d->entry = NULL;
+
167 d->offset = offset;
+
168 }
+
169 while (1) {
+
170 struct stat st;
+
171 off_t nextoff;
+ +
173
+
174 if (!d->entry) {
+
175 d->entry = readdir(d->dp);
+
176 if (!d->entry)
+
177 break;
+
178 }
+
179#ifdef HAVE_FSTATAT
+
180 if (flags & FUSE_READDIR_PLUS) {
+
181 int res;
+
182
+
183 res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
184 AT_SYMLINK_NOFOLLOW);
+
185 if (res != -1)
+
186 fill_flags |= FUSE_FILL_DIR_PLUS;
+
187 }
+
188#endif
+
189 if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
190 memset(&st, 0, sizeof(st));
+
191 st.st_ino = d->entry->d_ino;
+
192 st.st_mode = d->entry->d_type << 12;
+
193 }
+
194 nextoff = telldir(d->dp);
+
195#ifdef __FreeBSD__
+
196 /* Under FreeBSD, telldir() may return 0 the first time
+
197 it is called. But for libfuse, an offset of zero
+
198 means that offsets are not supported, so we shift
+
199 everything by one. */
+
200 nextoff++;
+
201#endif
+
202 if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
203 break;
+
204
+
205 d->entry = NULL;
+
206 d->offset = nextoff;
+
207 }
+
208
+
209 return 0;
+
210}
+
211
+
212static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
213{
+
214 struct xmp_dirp *d = get_dirp(fi);
+
215 (void) path;
+
216 closedir(d->dp);
+
217 free(d);
+
218 return 0;
+
219}
+
220
+
221static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
222{
+
223 int res;
+
224
+
225 if (S_ISFIFO(mode))
+
226 res = mkfifo(path, mode);
+
227 else
+
228 res = mknod(path, mode, rdev);
+
229 if (res == -1)
+
230 return -errno;
+
231
+
232 return 0;
+
233}
+
234
+
235static int xmp_mkdir(const char *path, mode_t mode)
+
236{
+
237 int res;
+
238
+
239 res = mkdir(path, mode);
+
240 if (res == -1)
+
241 return -errno;
+
242
+
243 return 0;
+
244}
+
245
+
246static int xmp_unlink(const char *path)
+
247{
+
248 int res;
+
249
+
250 res = unlink(path);
+
251 if (res == -1)
+
252 return -errno;
+
253
+
254 return 0;
+
255}
+
256
+
257static int xmp_rmdir(const char *path)
+
258{
+
259 int res;
+
260
+
261 res = rmdir(path);
+
262 if (res == -1)
+
263 return -errno;
+
264
+
265 return 0;
+
266}
+
267
+
268static int xmp_symlink(const char *from, const char *to)
+
269{
+
270 int res;
+
271
+
272 res = symlink(from, to);
+
273 if (res == -1)
+
274 return -errno;
+
275
+
276 return 0;
+
277}
+
278
+
279static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
280{
+
281 int res;
+
282
+
283 /* When we have renameat2() in libc, then we can implement flags */
+
284 if (flags)
+
285 return -EINVAL;
+
286
+
287 res = rename(from, to);
+
288 if (res == -1)
+
289 return -errno;
+
290
+
291 return 0;
+
292}
+
293
+
294static int xmp_link(const char *from, const char *to)
+
295{
+
296 int res;
+
297
+
298 res = link(from, to);
+
299 if (res == -1)
+
300 return -errno;
+
301
+
302 return 0;
+
303}
+
304
+
305static int xmp_chmod(const char *path, mode_t mode,
+
306 struct fuse_file_info *fi)
+
307{
+
308 int res;
+
309
+
310 if(fi)
+
311 res = fchmod(fi->fh, mode);
+
312 else
+
313 res = chmod(path, mode);
+
314 if (res == -1)
+
315 return -errno;
+
316
+
317 return 0;
+
318}
+
319
+
320static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
321 struct fuse_file_info *fi)
+
322{
+
323 int res;
+
324
+
325 if (fi)
+
326 res = fchown(fi->fh, uid, gid);
+
327 else
+
328 res = lchown(path, uid, gid);
+
329 if (res == -1)
+
330 return -errno;
+
331
+
332 return 0;
+
333}
+
334
+
335static int xmp_truncate(const char *path, off_t size,
+
336 struct fuse_file_info *fi)
+
337{
+
338 int res;
+
339
+
340 if(fi)
+
341 res = ftruncate(fi->fh, size);
+
342 else
+
343 res = truncate(path, size);
+
344
+
345 if (res == -1)
+
346 return -errno;
+
347
+
348 return 0;
+
349}
+
350
+
351#ifdef HAVE_UTIMENSAT
+
352static int xmp_utimens(const char *path, const struct timespec ts[2],
+
353 struct fuse_file_info *fi)
+
354{
+
355 int res;
+
356
+
357 /* don't use utime/utimes since they follow symlinks */
+
358 if (fi)
+
359 res = futimens(fi->fh, ts);
+
360 else
+
361 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
362 if (res == -1)
+
363 return -errno;
+
364
+
365 return 0;
+
366}
+
367#endif
+
368
+
369static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
370{
+
371 int fd;
+
372
+
373 fd = open(path, fi->flags, mode);
+
374 if (fd == -1)
+
375 return -errno;
+
376
+
377 fi->fh = fd;
+
378 return 0;
+
379}
+
380
+
381static int xmp_open(const char *path, struct fuse_file_info *fi)
+
382{
+
383 int fd;
+
384
+
385 fd = open(path, fi->flags);
+
386 if (fd == -1)
+
387 return -errno;
+
388
+
389 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
390 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
391 for writes to the same file). */
+
392 if (fi->flags & O_DIRECT) {
+
393 fi->direct_io = 1;
+ +
395 }
+
396
+
397 fi->fh = fd;
+
398 return 0;
+
399}
+
400
+
401static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
402 struct fuse_file_info *fi)
+
403{
+
404 int res;
+
405
+
406 (void) path;
+
407 res = pread(fi->fh, buf, size, offset);
+
408 if (res == -1)
+
409 res = -errno;
+
410
+
411 return res;
+
412}
+
413
+
414static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
415 size_t size, off_t offset, struct fuse_file_info *fi)
+
416{
+
417 struct fuse_bufvec *src;
+
418
+
419 (void) path;
+
420
+
421 src = malloc(sizeof(struct fuse_bufvec));
+
422 if (src == NULL)
+
423 return -ENOMEM;
+
424
+
425 *src = FUSE_BUFVEC_INIT(size);
+
426
+ +
428 src->buf[0].fd = fi->fh;
+
429 src->buf[0].pos = offset;
+
430
+
431 *bufp = src;
+
432
+
433 return 0;
+
434}
+
435
+
436static int xmp_write(const char *path, const char *buf, size_t size,
+
437 off_t offset, struct fuse_file_info *fi)
+
438{
+
439 int res;
+
440
+
441 (void) path;
+
442 res = pwrite(fi->fh, buf, size, offset);
+
443 if (res == -1)
+
444 res = -errno;
+
445
+
446 return res;
+
447}
+
448
+
449static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
450 off_t offset, struct fuse_file_info *fi)
+
451{
+
452 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
453
+
454 (void) path;
+
455
+ +
457 dst.buf[0].fd = fi->fh;
+
458 dst.buf[0].pos = offset;
+
459
+ +
461}
+
462
+
463static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
464{
+
465 int res;
+
466
+
467 res = statvfs(path, stbuf);
+
468 if (res == -1)
+
469 return -errno;
+
470
+
471 return 0;
+
472}
+
473
+
474static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
475{
+
476 int res;
+
477
+
478 (void) path;
+
479 /* This is called from every close on an open file, so call the
+
480 close on the underlying filesystem. But since flush may be
+
481 called multiple times for an open file, this must not really
+
482 close the file. This is important if used on a network
+
483 filesystem like NFS which flush the data/metadata on close() */
+
484 res = close(dup(fi->fh));
+
485 if (res == -1)
+
486 return -errno;
+
487
+
488 return 0;
+
489}
+
490
+
491static int xmp_release(const char *path, struct fuse_file_info *fi)
+
492{
+
493 (void) path;
+
494 close(fi->fh);
+
495
+
496 return 0;
+
497}
+
498
+
499static int xmp_fsync(const char *path, int isdatasync,
+
500 struct fuse_file_info *fi)
+
501{
+
502 int res;
+
503 (void) path;
+
504
+
505#ifndef HAVE_FDATASYNC
+
506 (void) isdatasync;
+
507#else
+
508 if (isdatasync)
+
509 res = fdatasync(fi->fh);
+
510 else
+
511#endif
+
512 res = fsync(fi->fh);
+
513 if (res == -1)
+
514 return -errno;
+
515
+
516 return 0;
+
517}
+
518
+
519static int xmp_fallocate(const char *path, int mode,
+
520 off_t offset, off_t length, struct fuse_file_info *fi)
+
521{
+
522 (void) path;
+
523
+
524 return do_fallocate(fi->fh, mode, offset, length);
+
525}
+
526
+
527#ifdef HAVE_SETXATTR
+
528/* xattr operations are optional and can safely be left unimplemented */
+
529static int xmp_setxattr(const char *path, const char *name, const char *value,
+
530 size_t size, int flags)
+
531{
+
532 int res = lsetxattr(path, name, value, size, flags);
+
533 if (res == -1)
+
534 return -errno;
+
535 return 0;
+
536}
+
537
+
538static int xmp_getxattr(const char *path, const char *name, char *value,
+
539 size_t size)
+
540{
+
541 int res = lgetxattr(path, name, value, size);
+
542 if (res == -1)
+
543 return -errno;
+
544 return res;
+
545}
+
546
+
547static int xmp_listxattr(const char *path, char *list, size_t size)
+
548{
+
549 int res = llistxattr(path, list, size);
+
550 if (res == -1)
+
551 return -errno;
+
552 return res;
+
553}
+
554
+
555static int xmp_removexattr(const char *path, const char *name)
+
556{
+
557 int res = lremovexattr(path, name);
+
558 if (res == -1)
+
559 return -errno;
+
560 return 0;
+
561}
+
562#endif /* HAVE_SETXATTR */
+
563
+
564#ifdef HAVE_LIBULOCKMGR
+
565static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
566 struct flock *lock)
+
567{
+
568 (void) path;
+
569
+
570 return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
571 sizeof(fi->lock_owner));
+
572}
+
573#endif
+
574
+
575static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
576{
+
577 int res;
+
578 (void) path;
+
579
+
580 res = flock(fi->fh, op);
+
581 if (res == -1)
+
582 return -errno;
+
583
+
584 return 0;
+
585}
+
586
+
587#ifdef HAVE_COPY_FILE_RANGE
+
588static ssize_t xmp_copy_file_range(const char *path_in,
+
589 struct fuse_file_info *fi_in,
+
590 off_t off_in, const char *path_out,
+
591 struct fuse_file_info *fi_out,
+
592 off_t off_out, size_t len, int flags)
+
593{
+
594 ssize_t res;
+
595 (void) path_in;
+
596 (void) path_out;
+
597
+
598 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
599 flags);
+
600 if (res == -1)
+
601 return -errno;
+
602
+
603 return res;
+
604}
+
605#endif
+
606
+
607static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
608{
+
609 off_t res;
+
610 (void) path;
+
611
+
612 res = lseek(fi->fh, off, whence);
+
613 if (res == -1)
+
614 return -errno;
+
615
+
616 return res;
+
617}
+
618
+
619#ifdef HAVE_STATX
+
620static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
621 struct fuse_file_info *fi)
+
622{
+
623 int fd = -1;
+
624 int res;
+
625
+
626 if (fi)
+
627 fd = fi->fh;
+
628
+
629 res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
+
630 if (res == -1)
+
631 return -errno;
+
632
+
633 return 0;
+
634}
+
635#endif
+
636
+
637static const struct fuse_operations xmp_oper = {
+
638 .init = xmp_init,
+
639 .getattr = xmp_getattr,
+
640 .access = xmp_access,
+
641 .readlink = xmp_readlink,
+
642 .opendir = xmp_opendir,
+
643 .readdir = xmp_readdir,
+
644 .releasedir = xmp_releasedir,
+
645 .mknod = xmp_mknod,
+
646 .mkdir = xmp_mkdir,
+
647 .symlink = xmp_symlink,
+
648 .unlink = xmp_unlink,
+
649 .rmdir = xmp_rmdir,
+
650 .rename = xmp_rename,
+
651 .link = xmp_link,
+
652 .chmod = xmp_chmod,
+
653 .chown = xmp_chown,
+
654 .truncate = xmp_truncate,
+
655#ifdef HAVE_UTIMENSAT
+
656 .utimens = xmp_utimens,
+
657#endif
+
658 .create = xmp_create,
+
659 .open = xmp_open,
+
660 .read = xmp_read,
+
661 .read_buf = xmp_read_buf,
+
662 .write = xmp_write,
+
663 .write_buf = xmp_write_buf,
+
664 .statfs = xmp_statfs,
+
665 .flush = xmp_flush,
+
666 .release = xmp_release,
+
667 .fsync = xmp_fsync,
+
668 .fallocate = xmp_fallocate,
+
669#ifdef HAVE_SETXATTR
+
670 .setxattr = xmp_setxattr,
+
671 .getxattr = xmp_getxattr,
+
672 .listxattr = xmp_listxattr,
+
673 .removexattr = xmp_removexattr,
+
674#endif
+
675#ifdef HAVE_LIBULOCKMGR
+
676 .lock = xmp_lock,
+
677#endif
+
678 .flock = xmp_flock,
+
679#ifdef HAVE_COPY_FILE_RANGE
+
680 .copy_file_range = xmp_copy_file_range,
+
681#endif
+
682 .lseek = xmp_lseek,
+
683#ifdef HAVE_STATX
+
684 .statx = xmp_statx,
+
685#endif
+
686};
+
687
+
688int main(int argc, char *argv[])
+
689{
+
690 umask(0);
+
691 return fuse_main(argc, argv, &xmp_oper, NULL);
+
692}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2passthrough__helpers_8h_source.html b/doc/html/fuse-3_818_82_2example_2passthrough__helpers_8h_source.html new file mode 100644 index 0000000..cbd3187 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2passthrough__helpers_8h_source.html @@ -0,0 +1,182 @@ + + + + + + + +libfuse: fuse-3.18.2/example/passthrough_helpers.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_helpers.h
+
+
+
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 *
+
4 * Redistribution and use in source and binary forms, with or without
+
5 * modification, are permitted provided that the following conditions
+
6 * are met:
+
7 * 1. Redistributions of source code must retain the above copyright
+
8 * notice, this list of conditions and the following disclaimer.
+
9 * 2. Redistributions in binary form must reproduce the above copyright
+
10 * notice, this list of conditions and the following disclaimer in the
+
11 * documentation and/or other materials provided with the distribution.
+
12 *
+
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
23 * SUCH DAMAGE
+
24 */
+
25
+
26#ifndef FUSE_EXAMPLE_PASSTHROUGH_HELPERS_H_
+
27#define FUSE_EXAMPLE_PASSTHROUGH_HELPERS_H_
+
28
+
29#include <errno.h>
+
30#include <fcntl.h>
+
31#include <string.h>
+
32#include <sys/stat.h>
+
33#include <unistd.h>
+
34
+
35#ifdef __FreeBSD__
+
36#include <sys/socket.h>
+
37#include <sys/un.h>
+
38#endif
+
39
+
40static inline int do_fallocate(int fd, int mode, off_t offset, off_t length)
+
41{
+
42#ifdef HAVE_FALLOCATE
+
43 if (fallocate(fd, mode, offset, length) == -1)
+
44 return -errno;
+
45 return 0;
+
46#else // HAVE_FALLOCATE
+
47
+
48#ifdef HAVE_POSIX_FALLOCATE
+
49 if (mode == 0)
+
50 return -posix_fallocate(fd, offset, length);
+
51#endif
+
52
+
53#ifdef HAVE_FSPACECTL
+
54 // 0x3 == FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE
+
55 if (mode == 0x3) {
+
56 struct spacectl_range sr;
+
57
+
58 sr.r_offset = offset;
+
59 sr.r_len = length;
+
60 if (fspacectl(fd, SPACECTL_DEALLOC, &sr, 0, NULL) == -1)
+
61 return -errno;
+
62 return 0;
+
63 }
+
64#endif
+
65
+
66 return -EOPNOTSUPP;
+
67#endif // HAVE_FALLOCATE
+
68}
+
69
+
70/*
+
71 * Creates files on the underlying file system in response to a FUSE_MKNOD
+
72 * operation
+
73 */
+
74static inline int mknod_wrapper(int dirfd, const char *path, const char *link,
+
75 int mode, dev_t rdev)
+
76{
+
77 int res;
+
78
+
79 if (S_ISREG(mode)) {
+
80 res = openat(dirfd, path, O_CREAT | O_EXCL | O_WRONLY, mode);
+
81 if (res >= 0)
+
82 res = close(res);
+
83 } else if (S_ISDIR(mode)) {
+
84 res = mkdirat(dirfd, path, mode);
+
85 } else if (S_ISLNK(mode) && link != NULL) {
+
86 res = symlinkat(link, dirfd, path);
+
87 } else if (S_ISFIFO(mode)) {
+
88 res = mkfifoat(dirfd, path, mode);
+
89#ifdef __FreeBSD__
+
90 } else if (S_ISSOCK(mode)) {
+
91 struct sockaddr_un su;
+
92 int fd;
+
93
+
94 if (strlen(path) >= sizeof(su.sun_path)) {
+
95 errno = ENAMETOOLONG;
+
96 return -1;
+
97 }
+
98 fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
99 if (fd >= 0) {
+
100 /*
+
101 * We must bind the socket to the underlying file
+
102 * system to create the socket file, even though
+
103 * we'll never listen on this socket.
+
104 */
+
105 su.sun_family = AF_UNIX;
+
106 strncpy(su.sun_path, path, sizeof(su.sun_path));
+
107 res = bindat(dirfd, fd, (struct sockaddr*)&su,
+
108 sizeof(su));
+
109 if (res == 0)
+
110 close(fd);
+
111 } else {
+
112 res = -1;
+
113 }
+
114#endif
+
115 } else {
+
116 res = mknodat(dirfd, path, mode, rdev);
+
117 }
+
118
+
119 return res;
+
120}
+
121
+
122#endif // FUSE_PASSTHROUGH_HELPERS_H_
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2passthrough__ll_8c.html b/doc/html/fuse-3_818_82_2example_2passthrough__ll_8c.html new file mode 100644 index 0000000..3248b2e --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2passthrough__ll_8c.html @@ -0,0 +1,1546 @@ + + + + + + + +libfuse: fuse-3.18.2/example/passthrough_ll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough_ll.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <sys/file.h>
+#include <sys/xattr.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. In contrast to passthrough.c and passthrough_fh.c, this implementation uses the low-level API. Its performance should be the least bad among the three.

+

When writeback caching is enabled (-o writeback mount option), it is only possible to write to files for which the mounting user has read permissions. This is because the writeback cache requires the kernel to be able to issue read requests for all files (which the passthrough filesystem cannot satisfy if it can't read the file in the underlying filesystem).

+

Compile with:

gcc -Wall passthrough_ll.c `pkg-config fuse3 --cflags --libs` -o passthrough_ll
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define _GNU_SOURCE
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
+
#include <fuse_lowlevel.h>
+
#include <unistd.h>
+
#include <stdlib.h>
+
#include <stdio.h>
+
#include <stddef.h>
+
#include <stdbool.h>
+
#include <string.h>
+
#include <limits.h>
+
#include <dirent.h>
+
#include <assert.h>
+
#include <errno.h>
+
#include <inttypes.h>
+
#include <pthread.h>
+
#include <sys/file.h>
+
#include <sys/xattr.h>
+
+
#include "passthrough_helpers.h"
+
+
/* We are re-using pointers to our `struct lo_inode` and `struct
+
lo_dirp` elements as inodes. This means that we must be able to
+
store uintptr_t values in a fuse_ino_t variable. The following
+
incantation checks this condition at compile time. */
+
#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
+
_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
+
"fuse_ino_t too small to hold uintptr_t values!");
+
#else
+
struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
+
{ unsigned _uintptr_to_must_hold_fuse_ino_t:
+
((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
+
#endif
+
+
struct lo_inode {
+
struct lo_inode *next; /* protected by lo->mutex */
+
struct lo_inode *prev; /* protected by lo->mutex */
+
int fd;
+
ino_t ino;
+
dev_t dev;
+
uint64_t refcount; /* protected by lo->mutex */
+
};
+
+
enum {
+
CACHE_NEVER,
+
CACHE_NORMAL,
+
CACHE_ALWAYS,
+
};
+
+
struct lo_data {
+
pthread_mutex_t mutex;
+
int debug;
+
int writeback;
+
int flock;
+
int xattr;
+
char *source;
+
double timeout;
+
int cache;
+
int timeout_set;
+
struct lo_inode root; /* protected by lo->mutex */
+
};
+
+
static const struct fuse_opt lo_opts[] = {
+
{ "writeback",
+
offsetof(struct lo_data, writeback), 1 },
+
{ "no_writeback",
+
offsetof(struct lo_data, writeback), 0 },
+
{ "source=%s",
+
offsetof(struct lo_data, source), 0 },
+
{ "flock",
+
offsetof(struct lo_data, flock), 1 },
+
{ "no_flock",
+
offsetof(struct lo_data, flock), 0 },
+
{ "xattr",
+
offsetof(struct lo_data, xattr), 1 },
+
{ "no_xattr",
+
offsetof(struct lo_data, xattr), 0 },
+
{ "timeout=%lf",
+
offsetof(struct lo_data, timeout), 0 },
+
{ "timeout=",
+
offsetof(struct lo_data, timeout_set), 1 },
+
{ "cache=never",
+
offsetof(struct lo_data, cache), CACHE_NEVER },
+
{ "cache=auto",
+
offsetof(struct lo_data, cache), CACHE_NORMAL },
+
{ "cache=always",
+
offsetof(struct lo_data, cache), CACHE_ALWAYS },
+
+ +
};
+
+
static void passthrough_ll_help(void)
+
{
+
printf(
+
" -o writeback Enable writeback\n"
+
" -o no_writeback Disable write back\n"
+
" -o source=/home/dir Source directory to be mounted\n"
+
" -o flock Enable flock\n"
+
" -o no_flock Disable flock\n"
+
" -o xattr Enable xattr\n"
+
" -o no_xattr Disable xattr\n"
+
" -o timeout=1.0 Caching timeout\n"
+
" -o timeout=0/1 Timeout is set\n"
+
" -o cache=never Disable cache\n"
+
" -o cache=auto Auto enable cache\n"
+
" -o cache=always Cache always\n");
+
}
+
+
static struct lo_data *lo_data(fuse_req_t req)
+
{
+
return (struct lo_data *) fuse_req_userdata(req);
+
}
+
+
static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
+
{
+
if (ino == FUSE_ROOT_ID)
+
return &lo_data(req)->root;
+
else
+
return (struct lo_inode *) (uintptr_t) ino;
+
}
+
+
static int lo_fd(fuse_req_t req, fuse_ino_t ino)
+
{
+
return lo_inode(req, ino)->fd;
+
}
+
+
static bool lo_debug(fuse_req_t req)
+
{
+
return lo_data(req)->debug != 0;
+
}
+
+
static void lo_init(void *userdata,
+
struct fuse_conn_info *conn)
+
{
+
struct lo_data *lo = (struct lo_data *)userdata;
+
bool has_flag;
+
+
if (lo->writeback) {
+ +
if (lo->debug && has_flag)
+
fuse_log(FUSE_LOG_DEBUG,
+
"lo_init: activating writeback\n");
+
}
+
if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
+ +
if (lo->debug && has_flag)
+
fuse_log(FUSE_LOG_DEBUG,
+
"lo_init: activating flock locks\n");
+
}
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void lo_destroy(void *userdata)
+
{
+
struct lo_data *lo = (struct lo_data*) userdata;
+
+
while (lo->root.next != &lo->root) {
+
struct lo_inode* next = lo->root.next;
+
lo->root.next = next->next;
+
close(next->fd);
+
free(next);
+
}
+
}
+
+
static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
struct stat buf;
+
struct lo_data *lo = lo_data(req);
+
int fd = fi ? fi->fh : lo_fd(req, ino);
+
+
(void) fi;
+
+
res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fuse_reply_attr(req, &buf, lo->timeout);
+
}
+
+
static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
int valid, struct fuse_file_info *fi)
+
{
+
int saverr;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
int ifd = inode->fd;
+
int res;
+
+
if (valid & FUSE_SET_ATTR_MODE) {
+
if (fi) {
+
res = fchmod(fi->fh, attr->st_mode);
+
} else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = chmod(procname, attr->st_mode);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+
uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
attr->st_uid : (uid_t) -1;
+
gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
attr->st_gid : (gid_t) -1;
+
+
res = fchownat(ifd, "", uid, gid,
+
AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & FUSE_SET_ATTR_SIZE) {
+
if (fi) {
+
res = ftruncate(fi->fh, attr->st_size);
+
} else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = truncate(procname, attr->st_size);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
struct timespec tv[2];
+
+
tv[0].tv_sec = 0;
+
tv[1].tv_sec = 0;
+
tv[0].tv_nsec = UTIME_OMIT;
+
tv[1].tv_nsec = UTIME_OMIT;
+
+
if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
tv[0].tv_nsec = UTIME_NOW;
+
else if (valid & FUSE_SET_ATTR_ATIME)
+
tv[0] = attr->st_atim;
+
+
if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
tv[1].tv_nsec = UTIME_NOW;
+
else if (valid & FUSE_SET_ATTR_MTIME)
+
tv[1] = attr->st_mtim;
+
+
if (fi)
+
res = futimens(fi->fh, tv);
+
else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = utimensat(AT_FDCWD, procname, tv, 0);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
+
return lo_getattr(req, ino, fi);
+
+
out_err:
+
saverr = errno;
+
fuse_reply_err(req, saverr);
+
}
+
+
static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+
{
+
struct lo_inode *p;
+
struct lo_inode *ret = NULL;
+
+
pthread_mutex_lock(&lo->mutex);
+
for (p = lo->root.next; p != &lo->root; p = p->next) {
+
if (p->ino == st->st_ino && p->dev == st->st_dev) {
+
assert(p->refcount > 0);
+
ret = p;
+
ret->refcount++;
+
break;
+
}
+
}
+
pthread_mutex_unlock(&lo->mutex);
+
return ret;
+
}
+
+
+
static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
+
{
+
struct lo_inode *inode = NULL;
+
struct lo_inode *prev, *next;
+
+
inode = calloc(1, sizeof(struct lo_inode));
+
if (!inode)
+
return NULL;
+
+
inode->refcount = 1;
+
inode->fd = fd;
+
inode->ino = e->attr.st_ino;
+
inode->dev = e->attr.st_dev;
+
+
pthread_mutex_lock(&lo->mutex);
+
prev = &lo->root;
+
next = prev->next;
+
next->prev = inode;
+
inode->next = next;
+
inode->prev = prev;
+
prev->next = inode;
+
pthread_mutex_unlock(&lo->mutex);
+
return inode;
+
}
+
+
static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
+
{
+
int res;
+
struct lo_data *lo = lo_data(req);
+
+
memset(e, 0, sizeof(*e));
+
e->attr_timeout = lo->timeout;
+
e->entry_timeout = lo->timeout;
+
+
res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return errno;
+
+
e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%d -> %lli\n",
+
(unsigned long long) parent, fd, (unsigned long long) e->ino);
+
+
return 0;
+
+
}
+
+
static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
+
struct fuse_entry_param *e)
+
{
+
int newfd;
+
int res;
+
int saverr;
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode;
+
+
memset(e, 0, sizeof(*e));
+
e->attr_timeout = lo->timeout;
+
e->entry_timeout = lo->timeout;
+
+
newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
+
if (newfd == -1)
+
goto out_err;
+
+
res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
inode = lo_find(lo_data(req), &e->attr);
+
if (inode) {
+
close(newfd);
+
newfd = -1;
+
} else {
+
inode = create_new_inode(newfd, e, lo);
+
if (!inode)
+
goto out_err;
+
}
+
e->ino = (uintptr_t) inode;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name, (unsigned long long) e->ino);
+
+
return 0;
+
+
out_err:
+
saverr = errno;
+
if (newfd != -1)
+
close(newfd);
+
return saverr;
+
}
+
+
static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
+
parent, name);
+
+
err = lo_do_lookup(req, parent, name, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_entry(req, &e);
+
}
+
+
static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
+
const char *name, mode_t mode, dev_t rdev,
+
const char *link)
+
{
+
int res;
+
int saverr;
+
struct lo_inode *dir = lo_inode(req, parent);
+
struct fuse_entry_param e;
+
+
res = mknod_wrapper(dir->fd, name, link, mode, rdev);
+
+
saverr = errno;
+
if (res == -1)
+
goto out;
+
+
saverr = lo_do_lookup(req, parent, name, &e);
+
if (saverr)
+
goto out;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name, (unsigned long long) e.ino);
+
+
fuse_reply_entry(req, &e);
+
return;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
+
const char *name, mode_t mode, dev_t rdev)
+
{
+
lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
+
}
+
+
static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
mode_t mode)
+
{
+
lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
+
}
+
+
static void lo_symlink(fuse_req_t req, const char *link,
+
fuse_ino_t parent, const char *name)
+
{
+
lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
+
}
+
+
static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
+
const char *name)
+
{
+
int res;
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode = lo_inode(req, ino);
+
struct fuse_entry_param e;
+
char procname[64];
+
int saverr;
+
+
memset(&e, 0, sizeof(struct fuse_entry_param));
+
e.attr_timeout = lo->timeout;
+
e.entry_timeout = lo->timeout;
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
+
AT_SYMLINK_FOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
pthread_mutex_lock(&lo->mutex);
+
inode->refcount++;
+
pthread_mutex_unlock(&lo->mutex);
+
e.ino = (uintptr_t) inode;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name,
+
(unsigned long long) e.ino);
+
+
fuse_reply_entry(req, &e);
+
return;
+
+
out_err:
+
saverr = errno;
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
int res;
+
+
res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
+
fuse_ino_t newparent, const char *newname,
+
unsigned int flags)
+
{
+
int res;
+
+
if (flags) {
+
fuse_reply_err(req, EINVAL);
+
return;
+
}
+
+
res = renameat(lo_fd(req, parent), name,
+
lo_fd(req, newparent), newname);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
int res;
+
+
res = unlinkat(lo_fd(req, parent), name, 0);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
+
{
+
if (!inode)
+
return;
+
+
pthread_mutex_lock(&lo->mutex);
+
assert(inode->refcount >= n);
+
inode->refcount -= n;
+
if (!inode->refcount) {
+
struct lo_inode *prev, *next;
+
+
prev = inode->prev;
+
next = inode->next;
+
next->prev = prev;
+
prev->next = next;
+
+
pthread_mutex_unlock(&lo->mutex);
+
close(inode->fd);
+
free(inode);
+
+
} else {
+
pthread_mutex_unlock(&lo->mutex);
+
}
+
}
+
+
static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
{
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode = lo_inode(req, ino);
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
+
(unsigned long long) ino,
+
(unsigned long long) inode->refcount,
+
(unsigned long long) nlookup);
+
}
+
+
unref_inode(lo, inode, nlookup);
+
}
+
+
static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
{
+
lo_forget_one(req, ino, nlookup);
+ +
}
+
+
static void lo_forget_multi(fuse_req_t req, size_t count,
+
struct fuse_forget_data *forgets)
+
{
+
int i;
+
+
for (i = 0; i < count; i++)
+
lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
+ +
}
+
+
static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
+
{
+
char buf[PATH_MAX + 1];
+
int res;
+
+
res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
+
if (res == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
if (res == sizeof(buf))
+
return (void) fuse_reply_err(req, ENAMETOOLONG);
+
+
buf[res] = '\0';
+
+ +
}
+
+
struct lo_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
+
{
+
return (struct lo_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int error = ENOMEM;
+
struct lo_data *lo = lo_data(req);
+
struct lo_dirp *d;
+
int fd = -1;
+
+
d = calloc(1, sizeof(struct lo_dirp));
+
if (d == NULL)
+
goto out_err;
+
+
fd = openat(lo_fd(req, ino), ".", O_RDONLY);
+
if (fd == -1)
+
goto out_errno;
+
+
d->dp = fdopendir(fd);
+
if (d->dp == NULL)
+
goto out_errno;
+
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (uintptr_t) d;
+
if (lo->cache != CACHE_NEVER)
+
fi->cache_readdir = 1;
+
if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
fuse_reply_open(req, fi);
+
return;
+
+
out_errno:
+
error = errno;
+
out_err:
+
if (d) {
+
if (fd != -1)
+
close(fd);
+
free(d);
+
}
+
fuse_reply_err(req, error);
+
}
+
+
static int is_dot_or_dotdot(const char *name)
+
{
+
return name[0] == '.' && (name[1] == '\0' ||
+
(name[1] == '.' && name[2] == '\0'));
+
}
+
+
static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi, int plus)
+
{
+
struct lo_dirp *d = lo_dirp(fi);
+
char *buf;
+
char *p;
+
size_t rem = size;
+
int err;
+
+
(void) ino;
+
+
buf = calloc(1, size);
+
if (!buf) {
+
err = ENOMEM;
+
goto error;
+
}
+
p = buf;
+
+
if (offset != d->offset) {
+
seekdir(d->dp, offset);
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
size_t entsize;
+
off_t nextoff;
+
const char *name;
+
+
if (!d->entry) {
+
errno = 0;
+
d->entry = readdir(d->dp);
+
if (!d->entry) {
+
if (errno) { // Error
+
err = errno;
+
goto error;
+
} else { // End of stream
+
break;
+
}
+
}
+
}
+
nextoff = d->entry->d_off;
+
name = d->entry->d_name;
+
fuse_ino_t entry_ino = 0;
+
if (plus) {
+
struct fuse_entry_param e;
+
if (is_dot_or_dotdot(name)) {
+
e = (struct fuse_entry_param) {
+
.attr.st_ino = d->entry->d_ino,
+
.attr.st_mode = d->entry->d_type << 12,
+
};
+
} else {
+
err = lo_do_lookup(req, ino, name, &e);
+
if (err)
+
goto error;
+
entry_ino = e.ino;
+
}
+
+
entsize = fuse_add_direntry_plus(req, p, rem, name,
+
&e, nextoff);
+
} else {
+
struct stat st = {
+
.st_ino = d->entry->d_ino,
+
.st_mode = d->entry->d_type << 12,
+
};
+
entsize = fuse_add_direntry(req, p, rem, name,
+
&st, nextoff);
+
}
+
if (entsize > rem) {
+
if (entry_ino != 0)
+
lo_forget_one(req, entry_ino, 1);
+
break;
+
}
+
+
p += entsize;
+
rem -= entsize;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
err = 0;
+
error:
+
// If there's an error, we can only signal it if we haven't stored
+
// any entries yet - otherwise we'd end up with wrong lookup
+
// counts for the entries that are already in the buffer. So we
+
// return what we've collected until that point.
+
if (err && rem == size)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_buf(req, buf, size - rem);
+
free(buf);
+
}
+
+
static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
lo_do_readdir(req, ino, size, offset, fi, 0);
+
}
+
+
static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
lo_do_readdir(req, ino, size, offset, fi, 1);
+
}
+
+
static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
struct lo_dirp *d = lo_dirp(fi);
+
(void) ino;
+
closedir(d->dp);
+
free(d);
+
fuse_reply_err(req, 0);
+
}
+
+
static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
+
mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
struct lo_data *lo = lo_data(req);
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
+
parent);
+
+
fd = openat(lo_fd(req, parent), ".",
+
(fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
err = fill_entry_param_new_inode(req, parent, fd, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_create(req, &e, fi);
+
}
+
+
static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+
mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
struct lo_data *lo = lo_data(req);
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
+
parent, name);
+
+
fd = openat(lo_fd(req, parent), name,
+
(fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
err = lo_do_lookup(req, parent, name, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_create(req, &e, fi);
+
}
+
+
static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
int fd = dirfd(lo_dirp(fi)->dp);
+
(void) ino;
+
if (datasync)
+
res = fdatasync(fd);
+
else
+
res = fsync(fd);
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int fd;
+
char buf[64];
+
struct lo_data *lo = lo_data(req);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
+
ino, fi->flags);
+
+
/* With writeback cache, kernel may send read requests even
+
when userspace opened write-only */
+
if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
+
fi->flags &= ~O_ACCMODE;
+
fi->flags |= O_RDWR;
+
}
+
+
/* With writeback cache, O_APPEND is handled by the kernel.
+
This breaks atomicity (since the file may change in the
+
underlying filesystem, so that the kernel's idea of the
+
end of the file isn't accurate anymore). In this example,
+
we just accept that. A more rigorous filesystem may want
+
to return an error here */
+
if (lo->writeback && (fi->flags & O_APPEND))
+
fi->flags &= ~O_APPEND;
+
+
sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
+
fd = open(buf, fi->flags & ~O_NOFOLLOW);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file in the kernel). */
+
if (fi->flags & O_DIRECT)
+
fi->direct_io = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
fuse_reply_open(req, fi);
+
}
+
+
static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
(void) ino;
+
+
close(fi->fh);
+
fuse_reply_err(req, 0);
+
}
+
+
static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int res;
+
(void) ino;
+
res = close(dup(fi->fh));
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) ino;
+
if (datasync)
+
res = fdatasync(fi->fh);
+
else
+
res = fsync(fi->fh);
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
+
"off=%lu)\n", ino, size, (unsigned long) offset);
+
+ +
buf.buf[0].fd = fi->fh;
+
buf.buf[0].pos = offset;
+
+ +
}
+
+
static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_bufvec *in_buf, off_t off,
+
struct fuse_file_info *fi)
+
{
+
(void) ino;
+
ssize_t res;
+
struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
+
+ +
out_buf.buf[0].fd = fi->fh;
+
out_buf.buf[0].pos = off;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%jd)\n",
+
ino, out_buf.buf[0].size, (intmax_t) off);
+
+
res = fuse_buf_copy(&out_buf, in_buf, 0);
+
if(res < 0)
+
fuse_reply_err(req, -res);
+
else
+
fuse_reply_write(req, (size_t) res);
+
}
+
+
static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
+
{
+
int res;
+
struct statvfs stbuf;
+
+
res = fstatvfs(lo_fd(req, ino), &stbuf);
+
if (res == -1)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_statfs(req, &stbuf);
+
}
+
+
static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
int err;
+
(void) ino;
+
+
err = -do_fallocate(fi->fh, mode, offset, length);
+
+
fuse_reply_err(req, err);
+
}
+
+
static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
int op)
+
{
+
int res;
+
(void) ino;
+
+
res = flock(fi->fh, op);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
char *value = NULL;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
+
ino, name, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
if (size) {
+
value = malloc(size);
+
if (!value)
+
goto out_err;
+
+
ret = getxattr(procname, name, value, size);
+
if (ret == -1)
+
goto out_err;
+
saverr = 0;
+
if (ret == 0)
+
goto out;
+
+
fuse_reply_buf(req, value, ret);
+
} else {
+
ret = getxattr(procname, name, NULL, 0);
+
if (ret == -1)
+
goto out_err;
+
+
fuse_reply_xattr(req, ret);
+
}
+
out_free:
+
free(value);
+
return;
+
+
out_err:
+
saverr = errno;
+
out:
+
fuse_reply_err(req, saverr);
+
goto out_free;
+
}
+
+
static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
{
+
char *value = NULL;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
+
ino, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
if (size) {
+
value = malloc(size);
+
if (!value)
+
goto out_err;
+
+
ret = listxattr(procname, value, size);
+
if (ret == -1)
+
goto out_err;
+
saverr = 0;
+
if (ret == 0)
+
goto out;
+
+
fuse_reply_buf(req, value, ret);
+
} else {
+
ret = listxattr(procname, NULL, 0);
+
if (ret == -1)
+
goto out_err;
+
+
fuse_reply_xattr(req, ret);
+
}
+
out_free:
+
free(value);
+
return;
+
+
out_err:
+
saverr = errno;
+
out:
+
fuse_reply_err(req, saverr);
+
goto out_free;
+
}
+
+
static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
+
ino, name, value, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
ret = setxattr(procname, name, value, size, flags);
+
saverr = ret == -1 ? errno : 0;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
+
ino, name);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
ret = removexattr(procname, name);
+
saverr = ret == -1 ? errno : 0;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
+
struct fuse_file_info *fi_in,
+
fuse_ino_t ino_out, off_t off_out,
+
struct fuse_file_info *fi_out, size_t len,
+
int flags)
+
{
+
ssize_t res;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG,
+
"%s(ino=%lld fd=%lld off=%jd ino=%lld fd=%lld off=%jd, size=%zd, flags=0x%x)\n",
+
__func__, (unsigned long long)ino_in,
+
(unsigned long long)fi_in->fh,
+
(intmax_t) off_in, (unsigned long long)ino_out,
+
(unsigned long long)fi_out->fh, (intmax_t) off_out,
+
len, flags);
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res < 0)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_write(req, res);
+
}
+
#endif
+
+
static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
struct fuse_file_info *fi)
+
{
+
off_t res;
+
+
(void)ino;
+
res = lseek(fi->fh, off, whence);
+
if (res != -1)
+
fuse_reply_lseek(req, res);
+
else
+
fuse_reply_err(req, errno);
+
}
+
+
#ifdef HAVE_STATX
+
static void lo_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
+
struct fuse_file_info *fi)
+
{
+
struct lo_data *lo = lo_data(req);
+
struct statx buf;
+
int res;
+
int fd;
+
+
if (fi)
+
fd = fi->fh;
+
else
+
fd = lo_fd(req, ino);
+
+
res = statx(fd, "", flags | AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, mask, &buf);
+
if (res == -1)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_statx(req, 0, &buf, lo->timeout);
+
}
+
#endif
+
+
static const struct fuse_lowlevel_ops lo_oper = {
+
.init = lo_init,
+
.destroy = lo_destroy,
+
.lookup = lo_lookup,
+
.mkdir = lo_mkdir,
+
.mknod = lo_mknod,
+
.symlink = lo_symlink,
+
.link = lo_link,
+
.unlink = lo_unlink,
+
.rmdir = lo_rmdir,
+
.rename = lo_rename,
+
.forget = lo_forget,
+
.forget_multi = lo_forget_multi,
+
.getattr = lo_getattr,
+
.setattr = lo_setattr,
+
.readlink = lo_readlink,
+
.opendir = lo_opendir,
+
.readdir = lo_readdir,
+
.readdirplus = lo_readdirplus,
+
.releasedir = lo_releasedir,
+
.fsyncdir = lo_fsyncdir,
+
.create = lo_create,
+
.tmpfile = lo_tmpfile,
+
.open = lo_open,
+
.release = lo_release,
+
.flush = lo_flush,
+
.fsync = lo_fsync,
+
.read = lo_read,
+
.write_buf = lo_write_buf,
+
.statfs = lo_statfs,
+
.fallocate = lo_fallocate,
+
.flock = lo_flock,
+
.getxattr = lo_getxattr,
+
.listxattr = lo_listxattr,
+
.setxattr = lo_setxattr,
+
.removexattr = lo_removexattr,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = lo_copy_file_range,
+
#endif
+
.lseek = lo_lseek,
+
#ifdef HAVE_STATX
+
.statx = lo_statx,
+
#endif
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
struct lo_data lo = { .debug = 0,
+
.writeback = 0 };
+
int ret = -1;
+
+
/* Don't mask creation mode, kernel already did that */
+
umask(0);
+
+
pthread_mutex_init(&lo.mutex, NULL);
+
lo.root.next = lo.root.prev = &lo.root;
+
lo.root.fd = -1;
+
lo.cache = CACHE_NORMAL;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
passthrough_ll_help();
+
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
+
return 1;
+
+
lo.debug = opts.debug;
+
lo.root.refcount = 2;
+
if (lo.source) {
+
struct stat stat;
+
int res;
+
+
res = lstat(lo.source, &stat);
+
if (res == -1) {
+
fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
+
lo.source);
+
exit(1);
+
}
+
if (!S_ISDIR(stat.st_mode)) {
+
fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
+
exit(1);
+
}
+
+
} else {
+
lo.source = strdup("/");
+
if(!lo.source) {
+
fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
exit(1);
+
}
+
}
+
if (!lo.timeout_set) {
+
switch (lo.cache) {
+
case CACHE_NEVER:
+
lo.timeout = 0.0;
+
break;
+
+
case CACHE_NORMAL:
+
lo.timeout = 1.0;
+
break;
+
+
case CACHE_ALWAYS:
+
lo.timeout = 86400.0;
+
break;
+
}
+
} else if (lo.timeout < 0) {
+
fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
+
lo.timeout);
+
exit(1);
+
}
+
+
lo.root.fd = open(lo.source, O_PATH);
+
if (lo.root.fd == -1) {
+
fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
+
lo.source);
+
exit(1);
+
}
+
+
se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
if (lo.root.fd >= 0)
+
close(lo.root.fd);
+
+
free(lo.source);
+
return ret ? 1 : 0;
+
}
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
off_t pos
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
uint32_t capable
+
+
double entry_timeout
+
fuse_ino_t ino
+
double attr_timeout
+
struct stat attr
+ + +
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file passthrough_ll.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2passthrough__ll_8c_source.html b/doc/html/fuse-3_818_82_2example_2passthrough__ll_8c_source.html new file mode 100644 index 0000000..d93d51c --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2passthrough__ll_8c_source.html @@ -0,0 +1,1525 @@ + + + + + + + +libfuse: fuse-3.18.2/example/passthrough_ll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_ll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
33#define _GNU_SOURCE
+
34#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
+
35
+
36#include <fuse_lowlevel.h>
+
37#include <unistd.h>
+
38#include <stdlib.h>
+
39#include <stdio.h>
+
40#include <stddef.h>
+
41#include <stdbool.h>
+
42#include <string.h>
+
43#include <limits.h>
+
44#include <dirent.h>
+
45#include <assert.h>
+
46#include <errno.h>
+
47#include <inttypes.h>
+
48#include <pthread.h>
+
49#include <sys/file.h>
+
50#include <sys/xattr.h>
+
51
+
52#include "passthrough_helpers.h"
+
53
+
54/* We are re-using pointers to our `struct lo_inode` and `struct
+
55 lo_dirp` elements as inodes. This means that we must be able to
+
56 store uintptr_t values in a fuse_ino_t variable. The following
+
57 incantation checks this condition at compile time. */
+
58#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
+
59_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
+
60 "fuse_ino_t too small to hold uintptr_t values!");
+
61#else
+
62struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
+
63 { unsigned _uintptr_to_must_hold_fuse_ino_t:
+
64 ((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
+
65#endif
+
66
+
67struct lo_inode {
+
68 struct lo_inode *next; /* protected by lo->mutex */
+
69 struct lo_inode *prev; /* protected by lo->mutex */
+
70 int fd;
+
71 ino_t ino;
+
72 dev_t dev;
+
73 uint64_t refcount; /* protected by lo->mutex */
+
74};
+
75
+
76enum {
+
77 CACHE_NEVER,
+
78 CACHE_NORMAL,
+
79 CACHE_ALWAYS,
+
80};
+
81
+
82struct lo_data {
+
83 pthread_mutex_t mutex;
+
84 int debug;
+
85 int writeback;
+
86 int flock;
+
87 int xattr;
+
88 char *source;
+
89 double timeout;
+
90 int cache;
+
91 int timeout_set;
+
92 struct lo_inode root; /* protected by lo->mutex */
+
93};
+
94
+
95static const struct fuse_opt lo_opts[] = {
+
96 { "writeback",
+
97 offsetof(struct lo_data, writeback), 1 },
+
98 { "no_writeback",
+
99 offsetof(struct lo_data, writeback), 0 },
+
100 { "source=%s",
+
101 offsetof(struct lo_data, source), 0 },
+
102 { "flock",
+
103 offsetof(struct lo_data, flock), 1 },
+
104 { "no_flock",
+
105 offsetof(struct lo_data, flock), 0 },
+
106 { "xattr",
+
107 offsetof(struct lo_data, xattr), 1 },
+
108 { "no_xattr",
+
109 offsetof(struct lo_data, xattr), 0 },
+
110 { "timeout=%lf",
+
111 offsetof(struct lo_data, timeout), 0 },
+
112 { "timeout=",
+
113 offsetof(struct lo_data, timeout_set), 1 },
+
114 { "cache=never",
+
115 offsetof(struct lo_data, cache), CACHE_NEVER },
+
116 { "cache=auto",
+
117 offsetof(struct lo_data, cache), CACHE_NORMAL },
+
118 { "cache=always",
+
119 offsetof(struct lo_data, cache), CACHE_ALWAYS },
+
120
+ +
122};
+
123
+
124static void passthrough_ll_help(void)
+
125{
+
126 printf(
+
127" -o writeback Enable writeback\n"
+
128" -o no_writeback Disable write back\n"
+
129" -o source=/home/dir Source directory to be mounted\n"
+
130" -o flock Enable flock\n"
+
131" -o no_flock Disable flock\n"
+
132" -o xattr Enable xattr\n"
+
133" -o no_xattr Disable xattr\n"
+
134" -o timeout=1.0 Caching timeout\n"
+
135" -o timeout=0/1 Timeout is set\n"
+
136" -o cache=never Disable cache\n"
+
137" -o cache=auto Auto enable cache\n"
+
138" -o cache=always Cache always\n");
+
139}
+
140
+
141static struct lo_data *lo_data(fuse_req_t req)
+
142{
+
143 return (struct lo_data *) fuse_req_userdata(req);
+
144}
+
145
+
146static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
+
147{
+
148 if (ino == FUSE_ROOT_ID)
+
149 return &lo_data(req)->root;
+
150 else
+
151 return (struct lo_inode *) (uintptr_t) ino;
+
152}
+
153
+
154static int lo_fd(fuse_req_t req, fuse_ino_t ino)
+
155{
+
156 return lo_inode(req, ino)->fd;
+
157}
+
158
+
159static bool lo_debug(fuse_req_t req)
+
160{
+
161 return lo_data(req)->debug != 0;
+
162}
+
163
+
164static void lo_init(void *userdata,
+
165 struct fuse_conn_info *conn)
+
166{
+
167 struct lo_data *lo = (struct lo_data *)userdata;
+
168 bool has_flag;
+
169
+
170 if (lo->writeback) {
+ +
172 if (lo->debug && has_flag)
+
173 fuse_log(FUSE_LOG_DEBUG,
+
174 "lo_init: activating writeback\n");
+
175 }
+
176 if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
+ +
178 if (lo->debug && has_flag)
+
179 fuse_log(FUSE_LOG_DEBUG,
+
180 "lo_init: activating flock locks\n");
+
181 }
+
182
+
183 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
184 conn->no_interrupt = 1;
+
185}
+
186
+
187static void lo_destroy(void *userdata)
+
188{
+
189 struct lo_data *lo = (struct lo_data*) userdata;
+
190
+
191 while (lo->root.next != &lo->root) {
+
192 struct lo_inode* next = lo->root.next;
+
193 lo->root.next = next->next;
+
194 close(next->fd);
+
195 free(next);
+
196 }
+
197}
+
198
+
199static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
+
200 struct fuse_file_info *fi)
+
201{
+
202 int res;
+
203 struct stat buf;
+
204 struct lo_data *lo = lo_data(req);
+
205 int fd = fi ? fi->fh : lo_fd(req, ino);
+
206
+
207 (void) fi;
+
208
+
209 res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
210 if (res == -1)
+
211 return (void) fuse_reply_err(req, errno);
+
212
+
213 fuse_reply_attr(req, &buf, lo->timeout);
+
214}
+
215
+
216static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
217 int valid, struct fuse_file_info *fi)
+
218{
+
219 int saverr;
+
220 char procname[64];
+
221 struct lo_inode *inode = lo_inode(req, ino);
+
222 int ifd = inode->fd;
+
223 int res;
+
224
+
225 if (valid & FUSE_SET_ATTR_MODE) {
+
226 if (fi) {
+
227 res = fchmod(fi->fh, attr->st_mode);
+
228 } else {
+
229 sprintf(procname, "/proc/self/fd/%i", ifd);
+
230 res = chmod(procname, attr->st_mode);
+
231 }
+
232 if (res == -1)
+
233 goto out_err;
+
234 }
+
235 if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+
236 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
237 attr->st_uid : (uid_t) -1;
+
238 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
239 attr->st_gid : (gid_t) -1;
+
240
+
241 res = fchownat(ifd, "", uid, gid,
+
242 AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
243 if (res == -1)
+
244 goto out_err;
+
245 }
+
246 if (valid & FUSE_SET_ATTR_SIZE) {
+
247 if (fi) {
+
248 res = ftruncate(fi->fh, attr->st_size);
+
249 } else {
+
250 sprintf(procname, "/proc/self/fd/%i", ifd);
+
251 res = truncate(procname, attr->st_size);
+
252 }
+
253 if (res == -1)
+
254 goto out_err;
+
255 }
+
256 if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
257 struct timespec tv[2];
+
258
+
259 tv[0].tv_sec = 0;
+
260 tv[1].tv_sec = 0;
+
261 tv[0].tv_nsec = UTIME_OMIT;
+
262 tv[1].tv_nsec = UTIME_OMIT;
+
263
+
264 if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
265 tv[0].tv_nsec = UTIME_NOW;
+
266 else if (valid & FUSE_SET_ATTR_ATIME)
+
267 tv[0] = attr->st_atim;
+
268
+
269 if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
270 tv[1].tv_nsec = UTIME_NOW;
+
271 else if (valid & FUSE_SET_ATTR_MTIME)
+
272 tv[1] = attr->st_mtim;
+
273
+
274 if (fi)
+
275 res = futimens(fi->fh, tv);
+
276 else {
+
277 sprintf(procname, "/proc/self/fd/%i", ifd);
+
278 res = utimensat(AT_FDCWD, procname, tv, 0);
+
279 }
+
280 if (res == -1)
+
281 goto out_err;
+
282 }
+
283
+
284 return lo_getattr(req, ino, fi);
+
285
+
286out_err:
+
287 saverr = errno;
+
288 fuse_reply_err(req, saverr);
+
289}
+
290
+
291static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+
292{
+
293 struct lo_inode *p;
+
294 struct lo_inode *ret = NULL;
+
295
+
296 pthread_mutex_lock(&lo->mutex);
+
297 for (p = lo->root.next; p != &lo->root; p = p->next) {
+
298 if (p->ino == st->st_ino && p->dev == st->st_dev) {
+
299 assert(p->refcount > 0);
+
300 ret = p;
+
301 ret->refcount++;
+
302 break;
+
303 }
+
304 }
+
305 pthread_mutex_unlock(&lo->mutex);
+
306 return ret;
+
307}
+
308
+
309
+
310static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
+
311{
+
312 struct lo_inode *inode = NULL;
+
313 struct lo_inode *prev, *next;
+
314
+
315 inode = calloc(1, sizeof(struct lo_inode));
+
316 if (!inode)
+
317 return NULL;
+
318
+
319 inode->refcount = 1;
+
320 inode->fd = fd;
+
321 inode->ino = e->attr.st_ino;
+
322 inode->dev = e->attr.st_dev;
+
323
+
324 pthread_mutex_lock(&lo->mutex);
+
325 prev = &lo->root;
+
326 next = prev->next;
+
327 next->prev = inode;
+
328 inode->next = next;
+
329 inode->prev = prev;
+
330 prev->next = inode;
+
331 pthread_mutex_unlock(&lo->mutex);
+
332 return inode;
+
333}
+
334
+
335static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
+
336{
+
337 int res;
+
338 struct lo_data *lo = lo_data(req);
+
339
+
340 memset(e, 0, sizeof(*e));
+
341 e->attr_timeout = lo->timeout;
+
342 e->entry_timeout = lo->timeout;
+
343
+
344 res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
345 if (res == -1)
+
346 return errno;
+
347
+
348 e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
+
349
+
350 if (lo_debug(req))
+
351 fuse_log(FUSE_LOG_DEBUG, " %lli/%d -> %lli\n",
+
352 (unsigned long long) parent, fd, (unsigned long long) e->ino);
+
353
+
354 return 0;
+
355
+
356}
+
357
+
358static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
+
359 struct fuse_entry_param *e)
+
360{
+
361 int newfd;
+
362 int res;
+
363 int saverr;
+
364 struct lo_data *lo = lo_data(req);
+
365 struct lo_inode *inode;
+
366
+
367 memset(e, 0, sizeof(*e));
+
368 e->attr_timeout = lo->timeout;
+
369 e->entry_timeout = lo->timeout;
+
370
+
371 newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
+
372 if (newfd == -1)
+
373 goto out_err;
+
374
+
375 res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
376 if (res == -1)
+
377 goto out_err;
+
378
+
379 inode = lo_find(lo_data(req), &e->attr);
+
380 if (inode) {
+
381 close(newfd);
+
382 newfd = -1;
+
383 } else {
+
384 inode = create_new_inode(newfd, e, lo);
+
385 if (!inode)
+
386 goto out_err;
+
387 }
+
388 e->ino = (uintptr_t) inode;
+
389
+
390 if (lo_debug(req))
+
391 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
392 (unsigned long long) parent, name, (unsigned long long) e->ino);
+
393
+
394 return 0;
+
395
+
396out_err:
+
397 saverr = errno;
+
398 if (newfd != -1)
+
399 close(newfd);
+
400 return saverr;
+
401}
+
402
+
403static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
404{
+
405 struct fuse_entry_param e;
+
406 int err;
+
407
+
408 if (lo_debug(req))
+
409 fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
+
410 parent, name);
+
411
+
412 err = lo_do_lookup(req, parent, name, &e);
+
413 if (err)
+
414 fuse_reply_err(req, err);
+
415 else
+
416 fuse_reply_entry(req, &e);
+
417}
+
418
+
419static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
+
420 const char *name, mode_t mode, dev_t rdev,
+
421 const char *link)
+
422{
+
423 int res;
+
424 int saverr;
+
425 struct lo_inode *dir = lo_inode(req, parent);
+
426 struct fuse_entry_param e;
+
427
+
428 res = mknod_wrapper(dir->fd, name, link, mode, rdev);
+
429
+
430 saverr = errno;
+
431 if (res == -1)
+
432 goto out;
+
433
+
434 saverr = lo_do_lookup(req, parent, name, &e);
+
435 if (saverr)
+
436 goto out;
+
437
+
438 if (lo_debug(req))
+
439 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
440 (unsigned long long) parent, name, (unsigned long long) e.ino);
+
441
+
442 fuse_reply_entry(req, &e);
+
443 return;
+
444
+
445out:
+
446 fuse_reply_err(req, saverr);
+
447}
+
448
+
449static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
+
450 const char *name, mode_t mode, dev_t rdev)
+
451{
+
452 lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
+
453}
+
454
+
455static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
456 mode_t mode)
+
457{
+
458 lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
+
459}
+
460
+
461static void lo_symlink(fuse_req_t req, const char *link,
+
462 fuse_ino_t parent, const char *name)
+
463{
+
464 lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
+
465}
+
466
+
467static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
+
468 const char *name)
+
469{
+
470 int res;
+
471 struct lo_data *lo = lo_data(req);
+
472 struct lo_inode *inode = lo_inode(req, ino);
+
473 struct fuse_entry_param e;
+
474 char procname[64];
+
475 int saverr;
+
476
+
477 memset(&e, 0, sizeof(struct fuse_entry_param));
+
478 e.attr_timeout = lo->timeout;
+
479 e.entry_timeout = lo->timeout;
+
480
+
481 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
482 res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
+
483 AT_SYMLINK_FOLLOW);
+
484 if (res == -1)
+
485 goto out_err;
+
486
+
487 res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
488 if (res == -1)
+
489 goto out_err;
+
490
+
491 pthread_mutex_lock(&lo->mutex);
+
492 inode->refcount++;
+
493 pthread_mutex_unlock(&lo->mutex);
+
494 e.ino = (uintptr_t) inode;
+
495
+
496 if (lo_debug(req))
+
497 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
498 (unsigned long long) parent, name,
+
499 (unsigned long long) e.ino);
+
500
+
501 fuse_reply_entry(req, &e);
+
502 return;
+
503
+
504out_err:
+
505 saverr = errno;
+
506 fuse_reply_err(req, saverr);
+
507}
+
508
+
509static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
510{
+
511 int res;
+
512
+
513 res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
+
514
+
515 fuse_reply_err(req, res == -1 ? errno : 0);
+
516}
+
517
+
518static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
+
519 fuse_ino_t newparent, const char *newname,
+
520 unsigned int flags)
+
521{
+
522 int res;
+
523
+
524 if (flags) {
+
525 fuse_reply_err(req, EINVAL);
+
526 return;
+
527 }
+
528
+
529 res = renameat(lo_fd(req, parent), name,
+
530 lo_fd(req, newparent), newname);
+
531
+
532 fuse_reply_err(req, res == -1 ? errno : 0);
+
533}
+
534
+
535static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
+
536{
+
537 int res;
+
538
+
539 res = unlinkat(lo_fd(req, parent), name, 0);
+
540
+
541 fuse_reply_err(req, res == -1 ? errno : 0);
+
542}
+
543
+
544static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
+
545{
+
546 if (!inode)
+
547 return;
+
548
+
549 pthread_mutex_lock(&lo->mutex);
+
550 assert(inode->refcount >= n);
+
551 inode->refcount -= n;
+
552 if (!inode->refcount) {
+
553 struct lo_inode *prev, *next;
+
554
+
555 prev = inode->prev;
+
556 next = inode->next;
+
557 next->prev = prev;
+
558 prev->next = next;
+
559
+
560 pthread_mutex_unlock(&lo->mutex);
+
561 close(inode->fd);
+
562 free(inode);
+
563
+
564 } else {
+
565 pthread_mutex_unlock(&lo->mutex);
+
566 }
+
567}
+
568
+
569static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
570{
+
571 struct lo_data *lo = lo_data(req);
+
572 struct lo_inode *inode = lo_inode(req, ino);
+
573
+
574 if (lo_debug(req)) {
+
575 fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
+
576 (unsigned long long) ino,
+
577 (unsigned long long) inode->refcount,
+
578 (unsigned long long) nlookup);
+
579 }
+
580
+
581 unref_inode(lo, inode, nlookup);
+
582}
+
583
+
584static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
585{
+
586 lo_forget_one(req, ino, nlookup);
+
587 fuse_reply_none(req);
+
588}
+
589
+
590static void lo_forget_multi(fuse_req_t req, size_t count,
+
591 struct fuse_forget_data *forgets)
+
592{
+
593 int i;
+
594
+
595 for (i = 0; i < count; i++)
+
596 lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
+
597 fuse_reply_none(req);
+
598}
+
599
+
600static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
+
601{
+
602 char buf[PATH_MAX + 1];
+
603 int res;
+
604
+
605 res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
+
606 if (res == -1)
+
607 return (void) fuse_reply_err(req, errno);
+
608
+
609 if (res == sizeof(buf))
+
610 return (void) fuse_reply_err(req, ENAMETOOLONG);
+
611
+
612 buf[res] = '\0';
+
613
+
614 fuse_reply_readlink(req, buf);
+
615}
+
616
+
617struct lo_dirp {
+
618 DIR *dp;
+
619 struct dirent *entry;
+
620 off_t offset;
+
621};
+
622
+
623static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
+
624{
+
625 return (struct lo_dirp *) (uintptr_t) fi->fh;
+
626}
+
627
+
628static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
629{
+
630 int error = ENOMEM;
+
631 struct lo_data *lo = lo_data(req);
+
632 struct lo_dirp *d;
+
633 int fd = -1;
+
634
+
635 d = calloc(1, sizeof(struct lo_dirp));
+
636 if (d == NULL)
+
637 goto out_err;
+
638
+
639 fd = openat(lo_fd(req, ino), ".", O_RDONLY);
+
640 if (fd == -1)
+
641 goto out_errno;
+
642
+
643 d->dp = fdopendir(fd);
+
644 if (d->dp == NULL)
+
645 goto out_errno;
+
646
+
647 d->offset = 0;
+
648 d->entry = NULL;
+
649
+
650 fi->fh = (uintptr_t) d;
+
651 if (lo->cache != CACHE_NEVER)
+
652 fi->cache_readdir = 1;
+
653 if (lo->cache == CACHE_ALWAYS)
+
654 fi->keep_cache = 1;
+
655 fuse_reply_open(req, fi);
+
656 return;
+
657
+
658out_errno:
+
659 error = errno;
+
660out_err:
+
661 if (d) {
+
662 if (fd != -1)
+
663 close(fd);
+
664 free(d);
+
665 }
+
666 fuse_reply_err(req, error);
+
667}
+
668
+
669static int is_dot_or_dotdot(const char *name)
+
670{
+
671 return name[0] == '.' && (name[1] == '\0' ||
+
672 (name[1] == '.' && name[2] == '\0'));
+
673}
+
674
+
675static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
676 off_t offset, struct fuse_file_info *fi, int plus)
+
677{
+
678 struct lo_dirp *d = lo_dirp(fi);
+
679 char *buf;
+
680 char *p;
+
681 size_t rem = size;
+
682 int err;
+
683
+
684 (void) ino;
+
685
+
686 buf = calloc(1, size);
+
687 if (!buf) {
+
688 err = ENOMEM;
+
689 goto error;
+
690 }
+
691 p = buf;
+
692
+
693 if (offset != d->offset) {
+
694 seekdir(d->dp, offset);
+
695 d->entry = NULL;
+
696 d->offset = offset;
+
697 }
+
698 while (1) {
+
699 size_t entsize;
+
700 off_t nextoff;
+
701 const char *name;
+
702
+
703 if (!d->entry) {
+
704 errno = 0;
+
705 d->entry = readdir(d->dp);
+
706 if (!d->entry) {
+
707 if (errno) { // Error
+
708 err = errno;
+
709 goto error;
+
710 } else { // End of stream
+
711 break;
+
712 }
+
713 }
+
714 }
+
715 nextoff = d->entry->d_off;
+
716 name = d->entry->d_name;
+
717 fuse_ino_t entry_ino = 0;
+
718 if (plus) {
+
719 struct fuse_entry_param e;
+
720 if (is_dot_or_dotdot(name)) {
+
721 e = (struct fuse_entry_param) {
+
722 .attr.st_ino = d->entry->d_ino,
+
723 .attr.st_mode = d->entry->d_type << 12,
+
724 };
+
725 } else {
+
726 err = lo_do_lookup(req, ino, name, &e);
+
727 if (err)
+
728 goto error;
+
729 entry_ino = e.ino;
+
730 }
+
731
+
732 entsize = fuse_add_direntry_plus(req, p, rem, name,
+
733 &e, nextoff);
+
734 } else {
+
735 struct stat st = {
+
736 .st_ino = d->entry->d_ino,
+
737 .st_mode = d->entry->d_type << 12,
+
738 };
+
739 entsize = fuse_add_direntry(req, p, rem, name,
+
740 &st, nextoff);
+
741 }
+
742 if (entsize > rem) {
+
743 if (entry_ino != 0)
+
744 lo_forget_one(req, entry_ino, 1);
+
745 break;
+
746 }
+
747
+
748 p += entsize;
+
749 rem -= entsize;
+
750
+
751 d->entry = NULL;
+
752 d->offset = nextoff;
+
753 }
+
754
+
755 err = 0;
+
756error:
+
757 // If there's an error, we can only signal it if we haven't stored
+
758 // any entries yet - otherwise we'd end up with wrong lookup
+
759 // counts for the entries that are already in the buffer. So we
+
760 // return what we've collected until that point.
+
761 if (err && rem == size)
+
762 fuse_reply_err(req, err);
+
763 else
+
764 fuse_reply_buf(req, buf, size - rem);
+
765 free(buf);
+
766}
+
767
+
768static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
769 off_t offset, struct fuse_file_info *fi)
+
770{
+
771 lo_do_readdir(req, ino, size, offset, fi, 0);
+
772}
+
773
+
774static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
775 off_t offset, struct fuse_file_info *fi)
+
776{
+
777 lo_do_readdir(req, ino, size, offset, fi, 1);
+
778}
+
779
+
780static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
781{
+
782 struct lo_dirp *d = lo_dirp(fi);
+
783 (void) ino;
+
784 closedir(d->dp);
+
785 free(d);
+
786 fuse_reply_err(req, 0);
+
787}
+
788
+
789static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
+
790 mode_t mode, struct fuse_file_info *fi)
+
791{
+
792 int fd;
+
793 struct lo_data *lo = lo_data(req);
+
794 struct fuse_entry_param e;
+
795 int err;
+
796
+
797 if (lo_debug(req))
+
798 fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
+
799 parent);
+
800
+
801 fd = openat(lo_fd(req, parent), ".",
+
802 (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
+
803 if (fd == -1)
+
804 return (void) fuse_reply_err(req, errno);
+
805
+
806 fi->fh = fd;
+
807 if (lo->cache == CACHE_NEVER)
+
808 fi->direct_io = 1;
+
809 else if (lo->cache == CACHE_ALWAYS)
+
810 fi->keep_cache = 1;
+
811
+
812 /* parallel_direct_writes feature depends on direct_io features.
+
813 To make parallel_direct_writes valid, need set fi->direct_io
+
814 in current function. */
+ +
816
+
817 err = fill_entry_param_new_inode(req, parent, fd, &e);
+
818 if (err)
+
819 fuse_reply_err(req, err);
+
820 else
+
821 fuse_reply_create(req, &e, fi);
+
822}
+
823
+
824static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+
825 mode_t mode, struct fuse_file_info *fi)
+
826{
+
827 int fd;
+
828 struct lo_data *lo = lo_data(req);
+
829 struct fuse_entry_param e;
+
830 int err;
+
831
+
832 if (lo_debug(req))
+
833 fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
+
834 parent, name);
+
835
+
836 fd = openat(lo_fd(req, parent), name,
+
837 (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
+
838 if (fd == -1)
+
839 return (void) fuse_reply_err(req, errno);
+
840
+
841 fi->fh = fd;
+
842 if (lo->cache == CACHE_NEVER)
+
843 fi->direct_io = 1;
+
844 else if (lo->cache == CACHE_ALWAYS)
+
845 fi->keep_cache = 1;
+
846
+
847 /* parallel_direct_writes feature depends on direct_io features.
+
848 To make parallel_direct_writes valid, need set fi->direct_io
+
849 in current function. */
+ +
851
+
852 err = lo_do_lookup(req, parent, name, &e);
+
853 if (err)
+
854 fuse_reply_err(req, err);
+
855 else
+
856 fuse_reply_create(req, &e, fi);
+
857}
+
858
+
859static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
860 struct fuse_file_info *fi)
+
861{
+
862 int res;
+
863 int fd = dirfd(lo_dirp(fi)->dp);
+
864 (void) ino;
+
865 if (datasync)
+
866 res = fdatasync(fd);
+
867 else
+
868 res = fsync(fd);
+
869 fuse_reply_err(req, res == -1 ? errno : 0);
+
870}
+
871
+
872static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
873{
+
874 int fd;
+
875 char buf[64];
+
876 struct lo_data *lo = lo_data(req);
+
877
+
878 if (lo_debug(req))
+
879 fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
+
880 ino, fi->flags);
+
881
+
882 /* With writeback cache, kernel may send read requests even
+
883 when userspace opened write-only */
+
884 if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
+
885 fi->flags &= ~O_ACCMODE;
+
886 fi->flags |= O_RDWR;
+
887 }
+
888
+
889 /* With writeback cache, O_APPEND is handled by the kernel.
+
890 This breaks atomicity (since the file may change in the
+
891 underlying filesystem, so that the kernel's idea of the
+
892 end of the file isn't accurate anymore). In this example,
+
893 we just accept that. A more rigorous filesystem may want
+
894 to return an error here */
+
895 if (lo->writeback && (fi->flags & O_APPEND))
+
896 fi->flags &= ~O_APPEND;
+
897
+
898 sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
+
899 fd = open(buf, fi->flags & ~O_NOFOLLOW);
+
900 if (fd == -1)
+
901 return (void) fuse_reply_err(req, errno);
+
902
+
903 fi->fh = fd;
+
904 if (lo->cache == CACHE_NEVER)
+
905 fi->direct_io = 1;
+
906 else if (lo->cache == CACHE_ALWAYS)
+
907 fi->keep_cache = 1;
+
908
+
909 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
910 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
911 for writes to the same file in the kernel). */
+
912 if (fi->flags & O_DIRECT)
+
913 fi->direct_io = 1;
+
914
+
915 /* parallel_direct_writes feature depends on direct_io features.
+
916 To make parallel_direct_writes valid, need set fi->direct_io
+
917 in current function. */
+ +
919
+
920 fuse_reply_open(req, fi);
+
921}
+
922
+
923static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
924{
+
925 (void) ino;
+
926
+
927 close(fi->fh);
+
928 fuse_reply_err(req, 0);
+
929}
+
930
+
931static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
932{
+
933 int res;
+
934 (void) ino;
+
935 res = close(dup(fi->fh));
+
936 fuse_reply_err(req, res == -1 ? errno : 0);
+
937}
+
938
+
939static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
940 struct fuse_file_info *fi)
+
941{
+
942 int res;
+
943 (void) ino;
+
944 if (datasync)
+
945 res = fdatasync(fi->fh);
+
946 else
+
947 res = fsync(fi->fh);
+
948 fuse_reply_err(req, res == -1 ? errno : 0);
+
949}
+
950
+
951static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
952 off_t offset, struct fuse_file_info *fi)
+
953{
+
954 struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
+
955
+
956 if (lo_debug(req))
+
957 fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
+
958 "off=%lu)\n", ino, size, (unsigned long) offset);
+
959
+ +
961 buf.buf[0].fd = fi->fh;
+
962 buf.buf[0].pos = offset;
+
963
+ +
965}
+
966
+
967static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
+
968 struct fuse_bufvec *in_buf, off_t off,
+
969 struct fuse_file_info *fi)
+
970{
+
971 (void) ino;
+
972 ssize_t res;
+
973 struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
+
974
+ +
976 out_buf.buf[0].fd = fi->fh;
+
977 out_buf.buf[0].pos = off;
+
978
+
979 if (lo_debug(req))
+
980 fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%jd)\n",
+
981 ino, out_buf.buf[0].size, (intmax_t) off);
+
982
+
983 res = fuse_buf_copy(&out_buf, in_buf, 0);
+
984 if(res < 0)
+
985 fuse_reply_err(req, -res);
+
986 else
+
987 fuse_reply_write(req, (size_t) res);
+
988}
+
989
+
990static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
+
991{
+
992 int res;
+
993 struct statvfs stbuf;
+
994
+
995 res = fstatvfs(lo_fd(req, ino), &stbuf);
+
996 if (res == -1)
+
997 fuse_reply_err(req, errno);
+
998 else
+
999 fuse_reply_statfs(req, &stbuf);
+
1000}
+
1001
+
1002static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
1003 off_t offset, off_t length, struct fuse_file_info *fi)
+
1004{
+
1005 int err;
+
1006 (void) ino;
+
1007
+
1008 err = -do_fallocate(fi->fh, mode, offset, length);
+
1009
+
1010 fuse_reply_err(req, err);
+
1011}
+
1012
+
1013static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
1014 int op)
+
1015{
+
1016 int res;
+
1017 (void) ino;
+
1018
+
1019 res = flock(fi->fh, op);
+
1020
+
1021 fuse_reply_err(req, res == -1 ? errno : 0);
+
1022}
+
1023
+
1024static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
1025 size_t size)
+
1026{
+
1027 char *value = NULL;
+
1028 char procname[64];
+
1029 struct lo_inode *inode = lo_inode(req, ino);
+
1030 ssize_t ret;
+
1031 int saverr;
+
1032
+
1033 saverr = ENOSYS;
+
1034 if (!lo_data(req)->xattr)
+
1035 goto out;
+
1036
+
1037 if (lo_debug(req)) {
+
1038 fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
+
1039 ino, name, size);
+
1040 }
+
1041
+
1042 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1043
+
1044 if (size) {
+
1045 value = malloc(size);
+
1046 if (!value)
+
1047 goto out_err;
+
1048
+
1049 ret = getxattr(procname, name, value, size);
+
1050 if (ret == -1)
+
1051 goto out_err;
+
1052 saverr = 0;
+
1053 if (ret == 0)
+
1054 goto out;
+
1055
+
1056 fuse_reply_buf(req, value, ret);
+
1057 } else {
+
1058 ret = getxattr(procname, name, NULL, 0);
+
1059 if (ret == -1)
+
1060 goto out_err;
+
1061
+
1062 fuse_reply_xattr(req, ret);
+
1063 }
+
1064out_free:
+
1065 free(value);
+
1066 return;
+
1067
+
1068out_err:
+
1069 saverr = errno;
+
1070out:
+
1071 fuse_reply_err(req, saverr);
+
1072 goto out_free;
+
1073}
+
1074
+
1075static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
1076{
+
1077 char *value = NULL;
+
1078 char procname[64];
+
1079 struct lo_inode *inode = lo_inode(req, ino);
+
1080 ssize_t ret;
+
1081 int saverr;
+
1082
+
1083 saverr = ENOSYS;
+
1084 if (!lo_data(req)->xattr)
+
1085 goto out;
+
1086
+
1087 if (lo_debug(req)) {
+
1088 fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
+
1089 ino, size);
+
1090 }
+
1091
+
1092 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1093
+
1094 if (size) {
+
1095 value = malloc(size);
+
1096 if (!value)
+
1097 goto out_err;
+
1098
+
1099 ret = listxattr(procname, value, size);
+
1100 if (ret == -1)
+
1101 goto out_err;
+
1102 saverr = 0;
+
1103 if (ret == 0)
+
1104 goto out;
+
1105
+
1106 fuse_reply_buf(req, value, ret);
+
1107 } else {
+
1108 ret = listxattr(procname, NULL, 0);
+
1109 if (ret == -1)
+
1110 goto out_err;
+
1111
+
1112 fuse_reply_xattr(req, ret);
+
1113 }
+
1114out_free:
+
1115 free(value);
+
1116 return;
+
1117
+
1118out_err:
+
1119 saverr = errno;
+
1120out:
+
1121 fuse_reply_err(req, saverr);
+
1122 goto out_free;
+
1123}
+
1124
+
1125static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
1126 const char *value, size_t size, int flags)
+
1127{
+
1128 char procname[64];
+
1129 struct lo_inode *inode = lo_inode(req, ino);
+
1130 ssize_t ret;
+
1131 int saverr;
+
1132
+
1133 saverr = ENOSYS;
+
1134 if (!lo_data(req)->xattr)
+
1135 goto out;
+
1136
+
1137 if (lo_debug(req)) {
+
1138 fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
+
1139 ino, name, value, size);
+
1140 }
+
1141
+
1142 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1143
+
1144 ret = setxattr(procname, name, value, size, flags);
+
1145 saverr = ret == -1 ? errno : 0;
+
1146
+
1147out:
+
1148 fuse_reply_err(req, saverr);
+
1149}
+
1150
+
1151static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
1152{
+
1153 char procname[64];
+
1154 struct lo_inode *inode = lo_inode(req, ino);
+
1155 ssize_t ret;
+
1156 int saverr;
+
1157
+
1158 saverr = ENOSYS;
+
1159 if (!lo_data(req)->xattr)
+
1160 goto out;
+
1161
+
1162 if (lo_debug(req)) {
+
1163 fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
+
1164 ino, name);
+
1165 }
+
1166
+
1167 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1168
+
1169 ret = removexattr(procname, name);
+
1170 saverr = ret == -1 ? errno : 0;
+
1171
+
1172out:
+
1173 fuse_reply_err(req, saverr);
+
1174}
+
1175
+
1176#ifdef HAVE_COPY_FILE_RANGE
+
1177static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
+
1178 struct fuse_file_info *fi_in,
+
1179 fuse_ino_t ino_out, off_t off_out,
+
1180 struct fuse_file_info *fi_out, size_t len,
+
1181 int flags)
+
1182{
+
1183 ssize_t res;
+
1184
+
1185 if (lo_debug(req))
+
1186 fuse_log(FUSE_LOG_DEBUG,
+
1187 "%s(ino=%lld fd=%lld off=%jd ino=%lld fd=%lld off=%jd, size=%zd, flags=0x%x)\n",
+
1188 __func__, (unsigned long long)ino_in,
+
1189 (unsigned long long)fi_in->fh,
+
1190 (intmax_t) off_in, (unsigned long long)ino_out,
+
1191 (unsigned long long)fi_out->fh, (intmax_t) off_out,
+
1192 len, flags);
+
1193
+
1194 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
1195 flags);
+
1196 if (res < 0)
+
1197 fuse_reply_err(req, errno);
+
1198 else
+
1199 fuse_reply_write(req, res);
+
1200}
+
1201#endif
+
1202
+
1203static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
1204 struct fuse_file_info *fi)
+
1205{
+
1206 off_t res;
+
1207
+
1208 (void)ino;
+
1209 res = lseek(fi->fh, off, whence);
+
1210 if (res != -1)
+
1211 fuse_reply_lseek(req, res);
+
1212 else
+
1213 fuse_reply_err(req, errno);
+
1214}
+
1215
+
1216#ifdef HAVE_STATX
+
1217static void lo_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
+
1218 struct fuse_file_info *fi)
+
1219{
+
1220 struct lo_data *lo = lo_data(req);
+
1221 struct statx buf;
+
1222 int res;
+
1223 int fd;
+
1224
+
1225 if (fi)
+
1226 fd = fi->fh;
+
1227 else
+
1228 fd = lo_fd(req, ino);
+
1229
+
1230 res = statx(fd, "", flags | AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, mask, &buf);
+
1231 if (res == -1)
+
1232 fuse_reply_err(req, errno);
+
1233 else
+
1234 fuse_reply_statx(req, 0, &buf, lo->timeout);
+
1235}
+
1236#endif
+
1237
+
1238static const struct fuse_lowlevel_ops lo_oper = {
+
1239 .init = lo_init,
+
1240 .destroy = lo_destroy,
+
1241 .lookup = lo_lookup,
+
1242 .mkdir = lo_mkdir,
+
1243 .mknod = lo_mknod,
+
1244 .symlink = lo_symlink,
+
1245 .link = lo_link,
+
1246 .unlink = lo_unlink,
+
1247 .rmdir = lo_rmdir,
+
1248 .rename = lo_rename,
+
1249 .forget = lo_forget,
+
1250 .forget_multi = lo_forget_multi,
+
1251 .getattr = lo_getattr,
+
1252 .setattr = lo_setattr,
+
1253 .readlink = lo_readlink,
+
1254 .opendir = lo_opendir,
+
1255 .readdir = lo_readdir,
+
1256 .readdirplus = lo_readdirplus,
+
1257 .releasedir = lo_releasedir,
+
1258 .fsyncdir = lo_fsyncdir,
+
1259 .create = lo_create,
+
1260 .tmpfile = lo_tmpfile,
+
1261 .open = lo_open,
+
1262 .release = lo_release,
+
1263 .flush = lo_flush,
+
1264 .fsync = lo_fsync,
+
1265 .read = lo_read,
+
1266 .write_buf = lo_write_buf,
+
1267 .statfs = lo_statfs,
+
1268 .fallocate = lo_fallocate,
+
1269 .flock = lo_flock,
+
1270 .getxattr = lo_getxattr,
+
1271 .listxattr = lo_listxattr,
+
1272 .setxattr = lo_setxattr,
+
1273 .removexattr = lo_removexattr,
+
1274#ifdef HAVE_COPY_FILE_RANGE
+
1275 .copy_file_range = lo_copy_file_range,
+
1276#endif
+
1277 .lseek = lo_lseek,
+
1278#ifdef HAVE_STATX
+
1279 .statx = lo_statx,
+
1280#endif
+
1281};
+
1282
+
1283int main(int argc, char *argv[])
+
1284{
+
1285 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
1286 struct fuse_session *se;
+
1287 struct fuse_cmdline_opts opts;
+
1288 struct fuse_loop_config *config;
+
1289 struct lo_data lo = { .debug = 0,
+
1290 .writeback = 0 };
+
1291 int ret = -1;
+
1292
+
1293 /* Don't mask creation mode, kernel already did that */
+
1294 umask(0);
+
1295
+
1296 pthread_mutex_init(&lo.mutex, NULL);
+
1297 lo.root.next = lo.root.prev = &lo.root;
+
1298 lo.root.fd = -1;
+
1299 lo.cache = CACHE_NORMAL;
+
1300
+
1301 if (fuse_parse_cmdline(&args, &opts) != 0)
+
1302 return 1;
+
1303 if (opts.show_help) {
+
1304 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
1307 passthrough_ll_help();
+
1308 ret = 0;
+
1309 goto err_out1;
+
1310 } else if (opts.show_version) {
+
1311 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
1313 ret = 0;
+
1314 goto err_out1;
+
1315 }
+
1316
+
1317 if(opts.mountpoint == NULL) {
+
1318 printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
1319 printf(" %s --help\n", argv[0]);
+
1320 ret = 1;
+
1321 goto err_out1;
+
1322 }
+
1323
+
1324 if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
+
1325 return 1;
+
1326
+
1327 lo.debug = opts.debug;
+
1328 lo.root.refcount = 2;
+
1329 if (lo.source) {
+
1330 struct stat stat;
+
1331 int res;
+
1332
+
1333 res = lstat(lo.source, &stat);
+
1334 if (res == -1) {
+
1335 fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
+
1336 lo.source);
+
1337 exit(1);
+
1338 }
+
1339 if (!S_ISDIR(stat.st_mode)) {
+
1340 fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
+
1341 exit(1);
+
1342 }
+
1343
+
1344 } else {
+
1345 lo.source = strdup("/");
+
1346 if(!lo.source) {
+
1347 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
1348 exit(1);
+
1349 }
+
1350 }
+
1351 if (!lo.timeout_set) {
+
1352 switch (lo.cache) {
+
1353 case CACHE_NEVER:
+
1354 lo.timeout = 0.0;
+
1355 break;
+
1356
+
1357 case CACHE_NORMAL:
+
1358 lo.timeout = 1.0;
+
1359 break;
+
1360
+
1361 case CACHE_ALWAYS:
+
1362 lo.timeout = 86400.0;
+
1363 break;
+
1364 }
+
1365 } else if (lo.timeout < 0) {
+
1366 fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
+
1367 lo.timeout);
+
1368 exit(1);
+
1369 }
+
1370
+
1371 lo.root.fd = open(lo.source, O_PATH);
+
1372 if (lo.root.fd == -1) {
+
1373 fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
+
1374 lo.source);
+
1375 exit(1);
+
1376 }
+
1377
+
1378 se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
+
1379 if (se == NULL)
+
1380 goto err_out1;
+
1381
+
1382 if (fuse_set_signal_handlers(se) != 0)
+
1383 goto err_out2;
+
1384
+
1385 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
1386 goto err_out3;
+
1387
+
1388 fuse_daemonize(opts.foreground);
+
1389
+
1390 /* Block until ctrl+c or fusermount -u */
+
1391 if (opts.singlethread)
+
1392 ret = fuse_session_loop(se);
+
1393 else {
+
1394 config = fuse_loop_cfg_create();
+
1395 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
1396 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
1397 ret = fuse_session_loop_mt(se, config);
+
1398 fuse_loop_cfg_destroy(config);
+
1399 config = NULL;
+
1400 }
+
1401
+ +
1403err_out3:
+ +
1405err_out2:
+ +
1407err_out1:
+
1408 free(opts.mountpoint);
+
1409 fuse_opt_free_args(&args);
+
1410
+
1411 if (lo.root.fd >= 0)
+
1412 close(lo.root.fd);
+
1413
+
1414 free(lo.source);
+
1415 return ret ? 1 : 0;
+
1416}
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
off_t pos
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
uint32_t capable
+
+
double entry_timeout
+
fuse_ino_t ino
+
double attr_timeout
+
struct stat attr
+ + +
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2poll_8c.html b/doc/html/fuse-3_818_82_2example_2poll_8c.html new file mode 100644 index 0000000..e4b1ddc --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2poll_8c.html @@ -0,0 +1,379 @@ + + + + + + + +libfuse: fuse-3.18.2/example/poll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
poll.c File Reference
+
+
+
#include <fuse.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include <pthread.h>
+#include <poll.h>
+#include <stdbool.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example illustrates how to write a FUSE file system that supports polling for changes that don't come through the kernel. It can be tested with the poll_client.c program.

+

Compile with:

gcc -Wall poll.c `pkg-config fuse3 --cflags --libs` -o poll
+

+Source code

+
/*
+
FUSE fsel: FUSE select example
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <unistd.h>
+
#include <ctype.h>
+
#include <string.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <errno.h>
+
#include <time.h>
+
#include <pthread.h>
+
#include <poll.h>
+
#include <stdbool.h>
+
+
/*
+
* fsel_open_mask is used to limit the number of opens to 1 per file.
+
* This is to use file index (0-F) as fh as poll support requires
+
* unique fh per open file. Lifting this would require proper open
+
* file management.
+
*/
+
static unsigned fsel_open_mask;
+
static const char fsel_hex_map[] = "0123456789ABCDEF";
+
static struct fuse *fsel_fuse; /* needed for poll notification */
+
+
#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
+
#define FSEL_FILES 16
+
+
static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
+
static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
+
static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
+
static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
+
static _Atomic bool fsel_stop = false;
+
static pthread_t fsel_producer_thread;
+
+
+
static int fsel_path_index(const char *path)
+
{
+
char ch = path[1];
+
+
if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
+
return -1;
+
return ch <= '9' ? ch - '0' : ch - 'A' + 10;
+
}
+
+
static void fsel_destroy(void *private_data)
+
{
+
(void)private_data;
+
+
fsel_stop = true;
+
+
pthread_join(fsel_producer_thread, NULL);
+
}
+
+
static int fsel_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int idx;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0555;
+
stbuf->st_nlink = 2;
+
return 0;
+
}
+
+
idx = fsel_path_index(path);
+
if (idx < 0)
+
return -ENOENT;
+
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = fsel_cnt[idx];
+
return 0;
+
}
+
+
static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
char name[2] = { };
+
int i;
+
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
name[0] = fsel_hex_map[i];
+
filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
}
+
+
return 0;
+
}
+
+
static int fsel_open(const char *path, struct fuse_file_info *fi)
+
{
+
int idx = fsel_path_index(path);
+
+
if (idx < 0)
+
return -ENOENT;
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
if (fsel_open_mask & (1 << idx))
+
return -EBUSY;
+
fsel_open_mask |= (1 << idx);
+
+
/*
+
* fsel files are nonseekable somewhat pipe-like files which
+
* gets filled up periodically by producer thread and consumed
+
* on read. Tell FUSE as such.
+
*/
+
fi->fh = idx;
+
fi->direct_io = 1;
+
fi->nonseekable = 1;
+
+
return 0;
+
}
+
+
static int fsel_release(const char *path, struct fuse_file_info *fi)
+
{
+
int idx = fi->fh;
+
+
(void) path;
+
+
fsel_open_mask &= ~(1 << idx);
+
return 0;
+
}
+
+
static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int idx = fi->fh;
+
+
(void) path;
+
(void) offset;
+
+
pthread_mutex_lock(&fsel_mutex);
+
if (fsel_cnt[idx] < size)
+
size = fsel_cnt[idx];
+
printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
+
fsel_cnt[idx] -= size;
+
pthread_mutex_unlock(&fsel_mutex);
+
+
memset(buf, fsel_hex_map[idx], size);
+
return size;
+
}
+
+
static int fsel_poll(const char *path, struct fuse_file_info *fi,
+
struct fuse_pollhandle *ph, unsigned *reventsp)
+
{
+
static unsigned polled_zero;
+
int idx = fi->fh;
+
+
(void) path;
+
+
/*
+
* Poll notification requires pointer to struct fuse which
+
* can't be obtained when using fuse_main(). As notification
+
* happens only after poll is called, fill it here from
+
* fuse_context.
+
*/
+
if (!fsel_fuse) {
+
struct fuse_context *cxt = fuse_get_context();
+
if (cxt)
+
fsel_fuse = cxt->fuse;
+
}
+
+
pthread_mutex_lock(&fsel_mutex);
+
+
if (ph != NULL) {
+
struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
+
+
if (oldph)
+ +
+
fsel_poll_notify_mask |= (1 << idx);
+
fsel_poll_handle[idx] = ph;
+
}
+
+
if (fsel_cnt[idx]) {
+
*reventsp |= POLLIN;
+
printf("POLL %X cnt=%u polled_zero=%u\n",
+
idx, fsel_cnt[idx], polled_zero);
+
polled_zero = 0;
+
} else
+
polled_zero++;
+
+
pthread_mutex_unlock(&fsel_mutex);
+
return 0;
+
}
+
+
static const struct fuse_operations fsel_oper = {
+
.destroy = fsel_destroy,
+
.getattr = fsel_getattr,
+
.readdir = fsel_readdir,
+
.open = fsel_open,
+
.release = fsel_release,
+
.read = fsel_read,
+
.poll = fsel_poll,
+
};
+
+
static void *fsel_producer(void *data)
+
{
+
const struct timespec interval = { 0, 250000000 };
+
unsigned idx = 0, nr = 1;
+
+
(void) data;
+
+
while (!fsel_stop) {
+
int i, t;
+
+
pthread_mutex_lock(&fsel_mutex);
+
+
/*
+
* This is the main producer loop which is executed
+
* ever 500ms. On each iteration, it fills one byte
+
* to 1, 2 or 4 files and sends poll notification if
+
* requested.
+
*/
+
for (i = 0, t = idx; i < nr;
+
i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
+
if (fsel_cnt[t] == FSEL_CNT_MAX)
+
continue;
+
+
fsel_cnt[t]++;
+
if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
+
struct fuse_pollhandle *ph;
+
+
printf("NOTIFY %X\n", t);
+
ph = fsel_poll_handle[t];
+
fuse_notify_poll(ph);
+ +
fsel_poll_notify_mask &= ~(1 << t);
+
fsel_poll_handle[t] = NULL;
+
}
+
}
+
+
idx = (idx + 1) % FSEL_FILES;
+
if (idx == 0)
+
nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
+
+
pthread_mutex_unlock(&fsel_mutex);
+
+
nanosleep(&interval, NULL);
+
}
+
+
return NULL;
+
}
+
+
int main(int argc, char *argv[])
+
{
+
pthread_attr_t attr;
+
int ret;
+
+
errno = pthread_mutex_init(&fsel_mutex, NULL);
+
if (errno) {
+
perror("pthread_mutex_init");
+
return 1;
+
}
+
+
errno = pthread_attr_init(&attr);
+
if (errno) {
+
perror("pthread_attr_init");
+
return 1;
+
}
+
+
errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
+
if (errno) {
+
perror("pthread_create");
+
return 1;
+
}
+
+
ret = fuse_main(argc, argv, &fsel_oper, NULL);
+
+
return ret;
+
}
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+ +
struct fuse * fuse
Definition fuse.h:862
+ + +
uint32_t nonseekable
Definition fuse_common.h:78
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+
+

Definition in file poll.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2poll_8c_source.html b/doc/html/fuse-3_818_82_2example_2poll_8c_source.html new file mode 100644 index 0000000..219a7e8 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2poll_8c_source.html @@ -0,0 +1,364 @@ + + + + + + + +libfuse: fuse-3.18.2/example/poll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
poll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fsel: FUSE select example
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
24#define FUSE_USE_VERSION 31
+
25
+
26#include <fuse.h>
+
27#include <unistd.h>
+
28#include <ctype.h>
+
29#include <string.h>
+
30#include <stdio.h>
+
31#include <stdlib.h>
+
32#include <errno.h>
+
33#include <time.h>
+
34#include <pthread.h>
+
35#include <poll.h>
+
36#include <stdbool.h>
+
37
+
38/*
+
39 * fsel_open_mask is used to limit the number of opens to 1 per file.
+
40 * This is to use file index (0-F) as fh as poll support requires
+
41 * unique fh per open file. Lifting this would require proper open
+
42 * file management.
+
43 */
+
44static unsigned fsel_open_mask;
+
45static const char fsel_hex_map[] = "0123456789ABCDEF";
+
46static struct fuse *fsel_fuse; /* needed for poll notification */
+
47
+
48#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
+
49#define FSEL_FILES 16
+
50
+
51static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
+
52static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
+
53static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
+
54static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
+
55static _Atomic bool fsel_stop = false;
+
56static pthread_t fsel_producer_thread;
+
57
+
58
+
59static int fsel_path_index(const char *path)
+
60{
+
61 char ch = path[1];
+
62
+
63 if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
+
64 return -1;
+
65 return ch <= '9' ? ch - '0' : ch - 'A' + 10;
+
66}
+
67
+
68static void fsel_destroy(void *private_data)
+
69{
+
70 (void)private_data;
+
71
+
72 fsel_stop = true;
+
73
+
74 pthread_join(fsel_producer_thread, NULL);
+
75}
+
76
+
77static int fsel_getattr(const char *path, struct stat *stbuf,
+
78 struct fuse_file_info *fi)
+
79{
+
80 (void) fi;
+
81 int idx;
+
82
+
83 memset(stbuf, 0, sizeof(struct stat));
+
84
+
85 if (strcmp(path, "/") == 0) {
+
86 stbuf->st_mode = S_IFDIR | 0555;
+
87 stbuf->st_nlink = 2;
+
88 return 0;
+
89 }
+
90
+
91 idx = fsel_path_index(path);
+
92 if (idx < 0)
+
93 return -ENOENT;
+
94
+
95 stbuf->st_mode = S_IFREG | 0444;
+
96 stbuf->st_nlink = 1;
+
97 stbuf->st_size = fsel_cnt[idx];
+
98 return 0;
+
99}
+
100
+
101static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
102 off_t offset, struct fuse_file_info *fi,
+
103 enum fuse_readdir_flags flags)
+
104{
+
105 char name[2] = { };
+
106 int i;
+
107
+
108 (void) offset;
+
109 (void) fi;
+
110 (void) flags;
+
111
+
112 if (strcmp(path, "/") != 0)
+
113 return -ENOENT;
+
114
+
115 for (i = 0; i < FSEL_FILES; i++) {
+
116 name[0] = fsel_hex_map[i];
+
117 filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
118 }
+
119
+
120 return 0;
+
121}
+
122
+
123static int fsel_open(const char *path, struct fuse_file_info *fi)
+
124{
+
125 int idx = fsel_path_index(path);
+
126
+
127 if (idx < 0)
+
128 return -ENOENT;
+
129 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
130 return -EACCES;
+
131 if (fsel_open_mask & (1 << idx))
+
132 return -EBUSY;
+
133 fsel_open_mask |= (1 << idx);
+
134
+
135 /*
+
136 * fsel files are nonseekable somewhat pipe-like files which
+
137 * gets filled up periodically by producer thread and consumed
+
138 * on read. Tell FUSE as such.
+
139 */
+
140 fi->fh = idx;
+
141 fi->direct_io = 1;
+
142 fi->nonseekable = 1;
+
143
+
144 return 0;
+
145}
+
146
+
147static int fsel_release(const char *path, struct fuse_file_info *fi)
+
148{
+
149 int idx = fi->fh;
+
150
+
151 (void) path;
+
152
+
153 fsel_open_mask &= ~(1 << idx);
+
154 return 0;
+
155}
+
156
+
157static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
+
158 struct fuse_file_info *fi)
+
159{
+
160 int idx = fi->fh;
+
161
+
162 (void) path;
+
163 (void) offset;
+
164
+
165 pthread_mutex_lock(&fsel_mutex);
+
166 if (fsel_cnt[idx] < size)
+
167 size = fsel_cnt[idx];
+
168 printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
+
169 fsel_cnt[idx] -= size;
+
170 pthread_mutex_unlock(&fsel_mutex);
+
171
+
172 memset(buf, fsel_hex_map[idx], size);
+
173 return size;
+
174}
+
175
+
176static int fsel_poll(const char *path, struct fuse_file_info *fi,
+
177 struct fuse_pollhandle *ph, unsigned *reventsp)
+
178{
+
179 static unsigned polled_zero;
+
180 int idx = fi->fh;
+
181
+
182 (void) path;
+
183
+
184 /*
+
185 * Poll notification requires pointer to struct fuse which
+
186 * can't be obtained when using fuse_main(). As notification
+
187 * happens only after poll is called, fill it here from
+
188 * fuse_context.
+
189 */
+
190 if (!fsel_fuse) {
+
191 struct fuse_context *cxt = fuse_get_context();
+
192 if (cxt)
+
193 fsel_fuse = cxt->fuse;
+
194 }
+
195
+
196 pthread_mutex_lock(&fsel_mutex);
+
197
+
198 if (ph != NULL) {
+
199 struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
+
200
+
201 if (oldph)
+ +
203
+
204 fsel_poll_notify_mask |= (1 << idx);
+
205 fsel_poll_handle[idx] = ph;
+
206 }
+
207
+
208 if (fsel_cnt[idx]) {
+
209 *reventsp |= POLLIN;
+
210 printf("POLL %X cnt=%u polled_zero=%u\n",
+
211 idx, fsel_cnt[idx], polled_zero);
+
212 polled_zero = 0;
+
213 } else
+
214 polled_zero++;
+
215
+
216 pthread_mutex_unlock(&fsel_mutex);
+
217 return 0;
+
218}
+
219
+
220static const struct fuse_operations fsel_oper = {
+
221 .destroy = fsel_destroy,
+
222 .getattr = fsel_getattr,
+
223 .readdir = fsel_readdir,
+
224 .open = fsel_open,
+
225 .release = fsel_release,
+
226 .read = fsel_read,
+
227 .poll = fsel_poll,
+
228};
+
229
+
230static void *fsel_producer(void *data)
+
231{
+
232 const struct timespec interval = { 0, 250000000 };
+
233 unsigned idx = 0, nr = 1;
+
234
+
235 (void) data;
+
236
+
237 while (!fsel_stop) {
+
238 int i, t;
+
239
+
240 pthread_mutex_lock(&fsel_mutex);
+
241
+
242 /*
+
243 * This is the main producer loop which is executed
+
244 * ever 500ms. On each iteration, it fills one byte
+
245 * to 1, 2 or 4 files and sends poll notification if
+
246 * requested.
+
247 */
+
248 for (i = 0, t = idx; i < nr;
+
249 i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
+
250 if (fsel_cnt[t] == FSEL_CNT_MAX)
+
251 continue;
+
252
+
253 fsel_cnt[t]++;
+
254 if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
+
255 struct fuse_pollhandle *ph;
+
256
+
257 printf("NOTIFY %X\n", t);
+
258 ph = fsel_poll_handle[t];
+
259 fuse_notify_poll(ph);
+ +
261 fsel_poll_notify_mask &= ~(1 << t);
+
262 fsel_poll_handle[t] = NULL;
+
263 }
+
264 }
+
265
+
266 idx = (idx + 1) % FSEL_FILES;
+
267 if (idx == 0)
+
268 nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
+
269
+
270 pthread_mutex_unlock(&fsel_mutex);
+
271
+
272 nanosleep(&interval, NULL);
+
273 }
+
274
+
275 return NULL;
+
276}
+
277
+
278int main(int argc, char *argv[])
+
279{
+
280 pthread_attr_t attr;
+
281 int ret;
+
282
+
283 errno = pthread_mutex_init(&fsel_mutex, NULL);
+
284 if (errno) {
+
285 perror("pthread_mutex_init");
+
286 return 1;
+
287 }
+
288
+
289 errno = pthread_attr_init(&attr);
+
290 if (errno) {
+
291 perror("pthread_attr_init");
+
292 return 1;
+
293 }
+
294
+
295 errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
+
296 if (errno) {
+
297 perror("pthread_create");
+
298 return 1;
+
299 }
+
300
+
301 ret = fuse_main(argc, argv, &fsel_oper, NULL);
+
302
+
303 return ret;
+
304}
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+ +
struct fuse * fuse
Definition fuse.h:862
+ + +
uint32_t nonseekable
Definition fuse_common.h:78
+
uint32_t direct_io
Definition fuse_common.h:63
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2poll__client_8c.html b/doc/html/fuse-3_818_82_2example_2poll__client_8c.html new file mode 100644 index 0000000..f23ea25 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2poll__client_8c.html @@ -0,0 +1,145 @@ + + + + + + + +libfuse: fuse-3.18.2/example/poll_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
poll_client.c File Reference
+
+
+
#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the poll.c example file systsem.

+

Compile with:

 gcc -Wall poll_client.c -o poll_client
+

+Source code

+
/*
+
FUSE fselclient: FUSE select example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#include <sys/select.h>
+
#include <sys/time.h>
+
#include <sys/types.h>
+
#include <sys/stat.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <ctype.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <errno.h>
+
+
#define FSEL_FILES 16
+
+
int main(void)
+
{
+
static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
+
int fds[FSEL_FILES];
+
int i, nfds, tries;
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
char name[] = { hex_map[i], '\0' };
+
fds[i] = open(name, O_RDONLY);
+
if (fds[i] < 0) {
+
perror("open");
+
return 1;
+
}
+
}
+
nfds = fds[FSEL_FILES - 1] + 1;
+
+
for(tries=0; tries < 16; tries++) {
+
static char buf[4096];
+
fd_set rfds;
+
int rc;
+
+
FD_ZERO(&rfds);
+
for (i = 0; i < FSEL_FILES; i++)
+
FD_SET(fds[i], &rfds);
+
+
rc = select(nfds, &rfds, NULL, NULL, NULL);
+
+
if (rc < 0) {
+
perror("select");
+
return 1;
+
}
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
if (!FD_ISSET(fds[i], &rfds)) {
+
printf("_: ");
+
continue;
+
}
+
printf("%X:", i);
+
rc = read(fds[i], buf, sizeof(buf));
+
if (rc < 0) {
+
perror("read");
+
return 1;
+
}
+
printf("%02d ", rc);
+
}
+
printf("\n");
+
}
+
return 0;
+
}
+
+

Definition in file poll_client.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2poll__client_8c_source.html b/doc/html/fuse-3_818_82_2example_2poll__client_8c_source.html new file mode 100644 index 0000000..f1b55ab --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2poll__client_8c_source.html @@ -0,0 +1,131 @@ + + + + + + + +libfuse: fuse-3.18.2/example/poll_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
poll_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fselclient: FUSE select example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
23#include <sys/select.h>
+
24#include <sys/time.h>
+
25#include <sys/types.h>
+
26#include <sys/stat.h>
+
27#include <fcntl.h>
+
28#include <unistd.h>
+
29#include <ctype.h>
+
30#include <stdio.h>
+
31#include <stdlib.h>
+
32#include <errno.h>
+
33
+
34#define FSEL_FILES 16
+
35
+
36int main(void)
+
37{
+
38 static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
+
39 int fds[FSEL_FILES];
+
40 int i, nfds, tries;
+
41
+
42 for (i = 0; i < FSEL_FILES; i++) {
+
43 char name[] = { hex_map[i], '\0' };
+
44 fds[i] = open(name, O_RDONLY);
+
45 if (fds[i] < 0) {
+
46 perror("open");
+
47 return 1;
+
48 }
+
49 }
+
50 nfds = fds[FSEL_FILES - 1] + 1;
+
51
+
52 for(tries=0; tries < 16; tries++) {
+
53 static char buf[4096];
+
54 fd_set rfds;
+
55 int rc;
+
56
+
57 FD_ZERO(&rfds);
+
58 for (i = 0; i < FSEL_FILES; i++)
+
59 FD_SET(fds[i], &rfds);
+
60
+
61 rc = select(nfds, &rfds, NULL, NULL, NULL);
+
62
+
63 if (rc < 0) {
+
64 perror("select");
+
65 return 1;
+
66 }
+
67
+
68 for (i = 0; i < FSEL_FILES; i++) {
+
69 if (!FD_ISSET(fds[i], &rfds)) {
+
70 printf("_: ");
+
71 continue;
+
72 }
+
73 printf("%X:", i);
+
74 rc = read(fds[i], buf, sizeof(buf));
+
75 if (rc < 0) {
+
76 perror("read");
+
77 return 1;
+
78 }
+
79 printf("%02d ", rc);
+
80 }
+
81 printf("\n");
+
82 }
+
83 return 0;
+
84}
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2printcap_8c.html b/doc/html/fuse-3_818_82_2example_2printcap_8c.html new file mode 100644 index 0000000..3a866d6 --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2printcap_8c.html @@ -0,0 +1,240 @@ + + + + + + + +libfuse: fuse-3.18.2/example/printcap.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
printcap.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem that prints out all capabilities supported by the kernel and then exits.

+

Compile with:

gcc -Wall printcap.c `pkg-config fuse3 --cflags --libs` -o printcap
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <unistd.h>
+
#include <string.h>
+
#include <stdlib.h>
+
+
struct fuse_session *se;
+
+
// Define a structure to hold capability information
+
struct cap_info {
+
uint64_t flag;
+
const char *name;
+
};
+
+
// Define an array of all capabilities
+
static const struct cap_info capabilities[] = {
+
{FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
+
{FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
+
{FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
+
{FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
+
{FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
+
{FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
+
{FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
+
{FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
+
{FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
+
{FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
+
{FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
+
{FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
+
{FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
+
{FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
+
{FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
+
{FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
+
{FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
+
{FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
+
{FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
+
{FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
+
{FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
+
{FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
+
{FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
+
{FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
+
{FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
+
{FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
+
{FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
+
{FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
+
// Add any new capabilities here
+
{0, NULL} // Sentinel to mark the end of the array
+
};
+
+
static void print_capabilities(struct fuse_conn_info *conn)
+
{
+
printf("Capabilities:\n");
+
for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
+
if (fuse_get_feature_flag(conn, cap->flag)) {
+
printf("\t%s\n", cap->name);
+
}
+
}
+
}
+
+
static void pc_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void) userdata;
+
+
printf("Protocol version: %d.%d\n", conn->proto_major,
+
conn->proto_minor);
+
print_capabilities(conn);
+ +
}
+
+
+
static const struct fuse_lowlevel_ops pc_oper = {
+
.init = pc_init,
+
};
+
+
int main(int argc, char **argv)
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
char *mountpoint;
+
int ret = -1;
+
+
mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
+
if(mkdtemp(mountpoint) == NULL) {
+
perror("mkdtemp");
+
return 1;
+
}
+
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
+
se = fuse_session_new(&args, &pc_oper,
+
sizeof(pc_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, mountpoint) != 0)
+
goto err_out3;
+
+
ret = fuse_session_loop(se);
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
rmdir(mountpoint);
+
free(mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t proto_major
+
uint32_t proto_minor
+ +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file printcap.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2example_2printcap_8c_source.html b/doc/html/fuse-3_818_82_2example_2printcap_8c_source.html new file mode 100644 index 0000000..c34f6ad --- /dev/null +++ b/doc/html/fuse-3_818_82_2example_2printcap_8c_source.html @@ -0,0 +1,231 @@ + + + + + + + +libfuse: fuse-3.18.2/example/printcap.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
printcap.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
22#define FUSE_USE_VERSION 31
+
23
+
24#include <fuse_lowlevel.h>
+
25#include <stdio.h>
+
26#include <unistd.h>
+
27#include <string.h>
+
28#include <stdlib.h>
+
29
+
30struct fuse_session *se;
+
31
+
32// Define a structure to hold capability information
+
33struct cap_info {
+
34 uint64_t flag;
+
35 const char *name;
+
36};
+
37
+
38// Define an array of all capabilities
+
39static const struct cap_info capabilities[] = {
+
40 {FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
+
41 {FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
+
42 {FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
+
43 {FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
+
44 {FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
+
45 {FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
+
46 {FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
+
47 {FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
+
48 {FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
+
49 {FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
+
50 {FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
+
51 {FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
+
52 {FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
+
53 {FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
+
54 {FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
+
55 {FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
+
56 {FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
+
57 {FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
+
58 {FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
+
59 {FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
+
60 {FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
+
61 {FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
+
62 {FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
+
63 {FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
+
64 {FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
+
65 {FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
+
66 {FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
+
67 {FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
+
68 // Add any new capabilities here
+
69 {0, NULL} // Sentinel to mark the end of the array
+
70};
+
71
+
72static void print_capabilities(struct fuse_conn_info *conn)
+
73{
+
74 printf("Capabilities:\n");
+
75 for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
+
76 if (fuse_get_feature_flag(conn, cap->flag)) {
+
77 printf("\t%s\n", cap->name);
+
78 }
+
79 }
+
80}
+
81
+
82static void pc_init(void *userdata, struct fuse_conn_info *conn)
+
83{
+
84 (void) userdata;
+
85
+
86 printf("Protocol version: %d.%d\n", conn->proto_major,
+
87 conn->proto_minor);
+
88 print_capabilities(conn);
+ +
90}
+
91
+
92
+
93static const struct fuse_lowlevel_ops pc_oper = {
+
94 .init = pc_init,
+
95};
+
96
+
97int main(int argc, char **argv)
+
98{
+
99 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
100 char *mountpoint;
+
101 int ret = -1;
+
102
+
103 mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
+
104 if(mkdtemp(mountpoint) == NULL) {
+
105 perror("mkdtemp");
+
106 return 1;
+
107 }
+
108
+
109 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
111
+
112 se = fuse_session_new(&args, &pc_oper,
+
113 sizeof(pc_oper), NULL);
+
114 if (se == NULL)
+
115 goto err_out1;
+
116
+
117 if (fuse_set_signal_handlers(se) != 0)
+
118 goto err_out2;
+
119
+
120 if (fuse_session_mount(se, mountpoint) != 0)
+
121 goto err_out3;
+
122
+
123 ret = fuse_session_loop(se);
+
124
+ +
126err_out3:
+ +
128err_out2:
+ +
130err_out1:
+
131 rmdir(mountpoint);
+
132 free(mountpoint);
+
133 fuse_opt_free_args(&args);
+
134
+
135 return ret ? 1 : 0;
+
136}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t proto_major
+
uint32_t proto_minor
+ +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2include_2cuse__lowlevel_8h_source.html b/doc/html/fuse-3_818_82_2include_2cuse__lowlevel_8h_source.html new file mode 100644 index 0000000..f21a3a1 --- /dev/null +++ b/doc/html/fuse-3_818_82_2include_2cuse__lowlevel_8h_source.html @@ -0,0 +1,152 @@ + + + + + + + +libfuse: fuse-3.18.2/include/cuse_lowlevel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_lowlevel.h
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file LGPL2.txt.
+
8
+
9 Read example/cusexmp.c for usages.
+
10*/
+
11
+
12#ifndef CUSE_LOWLEVEL_H_
+
13#define CUSE_LOWLEVEL_H_
+
14
+
15#ifndef FUSE_USE_VERSION
+
16#define FUSE_USE_VERSION 29
+
17#endif
+
18
+
19#include "fuse_lowlevel.h"
+
20
+
21#include <fcntl.h>
+
22#include <sys/types.h>
+
23#include <sys/uio.h>
+
24
+
25#ifdef __cplusplus
+
26extern "C" {
+
27#endif
+
28
+
29#define CUSE_UNRESTRICTED_IOCTL (1 << 0) /* use unrestricted ioctl */
+
30
+
31struct fuse_session;
+
32
+
33struct cuse_info {
+
34 unsigned dev_major;
+
35 unsigned dev_minor;
+
36 unsigned dev_info_argc;
+
37 const char **dev_info_argv;
+
38 unsigned flags;
+
39};
+
40
+
41/*
+
42 * Most ops behave almost identically to the matching fuse_lowlevel
+
43 * ops except that they don't take @ino.
+
44 *
+
45 * init_done : called after initialization is complete
+
46 * read/write : always direct IO, simultaneous operations allowed
+
47 * ioctl : might be in unrestricted mode depending on ci->flags
+
48 */
+
49struct cuse_lowlevel_ops {
+
50 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
51 void (*init_done) (void *userdata);
+
52 void (*destroy) (void *userdata);
+
53 void (*open) (fuse_req_t req, struct fuse_file_info *fi);
+
54 void (*read) (fuse_req_t req, size_t size, off_t off,
+
55 struct fuse_file_info *fi);
+
56 void (*write) (fuse_req_t req, const char *buf, size_t size, off_t off,
+
57 struct fuse_file_info *fi);
+
58 void (*flush) (fuse_req_t req, struct fuse_file_info *fi);
+
59 void (*release) (fuse_req_t req, struct fuse_file_info *fi);
+
60 void (*fsync) (fuse_req_t req, int datasync, struct fuse_file_info *fi);
+
61 void (*ioctl) (fuse_req_t req, int cmd, void *arg,
+
62 struct fuse_file_info *fi, unsigned int flags,
+
63 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
64 void (*poll) (fuse_req_t req, struct fuse_file_info *fi,
+
65 struct fuse_pollhandle *ph);
+
66};
+
67
+
68struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
69 const struct cuse_info *ci,
+
70 const struct cuse_lowlevel_ops *clop,
+
71 void *userdata);
+
72
+
73struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
74 const struct cuse_info *ci,
+
75 const struct cuse_lowlevel_ops *clop,
+
76 int *multithreaded, void *userdata);
+
77
+
78void cuse_lowlevel_teardown(struct fuse_session *se);
+
79
+
80int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
81 const struct cuse_lowlevel_ops *clop, void *userdata);
+
82
+
83#ifdef __cplusplus
+
84}
+
85#endif
+
86
+
87#endif /* CUSE_LOWLEVEL_H_ */
+
struct fuse_req * fuse_req_t
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_818_82_2include_2fuse_8h.html b/doc/html/fuse-3_818_82_2include_2fuse_8h.html new file mode 100644 index 0000000..8e7486b --- /dev/null +++ b/doc/html/fuse-3_818_82_2include_2fuse_8h.html @@ -0,0 +1,839 @@ + + + + + + + +libfuse: fuse-3.18.2/include/fuse.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse.h File Reference
+
+
+
#include "fuse_common.h"
+#include <fcntl.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + + + +

+Data Structures

struct  fuse_config
 
struct  fuse_operations
 
struct  fuse_context
 
+ + + +

+Macros

#define FUSE_REGISTER_MODULE(name_, factory_)    fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
 
+ + + + + +

+Typedefs

typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
 
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
 
+ + + + + +

+Enumerations

enum  fuse_readdir_flags { FUSE_READDIR_DEFAULTS = 0 +, FUSE_READDIR_PLUS = (1 << 0) + }
 
enum  fuse_fill_dir_flags { FUSE_FILL_DIR_DEFAULTS = 0 +, FUSE_FILL_DIR_PLUS = (1 << 1) + }
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

int fuse_main_real_versioned (int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
 
void fuse_lib_help (struct fuse_args *args)
 
int fuse_mount (struct fuse *f, const char *mountpoint)
 
void fuse_unmount (struct fuse *f)
 
void fuse_destroy (struct fuse *f)
 
int fuse_loop (struct fuse *f)
 
void fuse_exit (struct fuse *f)
 
struct fuse_contextfuse_get_context (void)
 
int fuse_getgroups (int size, gid_t list[])
 
int fuse_interrupted (void)
 
int fuse_invalidate_path (struct fuse *f, const char *path)
 
int fuse_start_cleanup_thread (struct fuse *fuse)
 
void fuse_stop_cleanup_thread (struct fuse *fuse)
 
int fuse_clean_cache (struct fuse *fuse)
 
struct fuse_fs * fuse_fs_new (const struct fuse_operations *op, size_t op_size, void *private_data)
 
struct fuse_session * fuse_get_session (struct fuse *f)
 
int fuse_open_channel (const char *mountpoint, const char *options)
 
+

Detailed Description

+

This file defines the library interface of FUSE

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header.

+ +

Definition in file fuse.h.

+

Macro Definition Documentation

+ +

◆ FUSE_REGISTER_MODULE

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_REGISTER_MODULE( name_,
 factory_ 
)    fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+

Register filesystem module

+

If the "-omodules=*name*_:..." option is present, filesystem objects are created and pushed onto the stack with the factory_ function.

+
Parameters
+ + + +
name_the name of this filesystem module
factory_the factory function for this filesystem module
+
+
+ +

Definition at line 1414 of file fuse.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_fill_dir_t

+ +
+
+ + + + +
typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
+
+

Function to add an entry in a readdir() operation

+

The off parameter can be any non-zero value that enables the filesystem to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to indicate that seeking in directories is not supported.

+
Parameters
+ + + + + + +
bufthe buffer passed to the readdir() operation
namethe file name of the directory entry
stbuffile attributes, can be NULL
offoffset of the next entry or zero
flagsfill flags
+
+
+
Returns
1 if buffer is full, zero otherwise
+ +

Definition at line 93 of file fuse.h.

+ +
+
+ +

◆ fuse_module_factory_t

+ +
+
+ + + + +
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
+
+

Factory for creating filesystem objects

+

The function may use and remove options from 'args' that belong to this module.

+

For now the 'fs' vector always contains exactly one filesystem. This is the filesystem which will be below the newly created filesystem in the stack.

+
Parameters
+ + + +
argsthe command line arguments
fsNULL terminated filesystem object vector
+
+
+
Returns
the new filesystem object
+ +

Definition at line 1385 of file fuse.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_fill_dir_flags

+ +
+
+ + + + +
enum fuse_fill_dir_flags
+
+

Readdir flags, passed to fuse_fill_dir_t callback.

+ + +
Enumerator
FUSE_FILL_DIR_DEFAULTS 

"Plus" mode: file attributes are valid

+

The attributes are used by the kernel to prefill the inode cache during a readdir.

+

It is okay to set FUSE_FILL_DIR_PLUS if FUSE_READDIR_PLUS is not set and vice versa.

+

This does not make libfuse honor the 'st_ino' field. That is controlled by the 'use_ino' option instead.

+
+ +

Definition at line 61 of file fuse.h.

+ +
+
+ +

◆ fuse_readdir_flags

+ +
+
+ + + + +
enum fuse_readdir_flags
+
+

Readdir flags, passed to ->readdir()

+ + +
Enumerator
FUSE_READDIR_DEFAULTS 

"Plus" mode.

+

The kernel wants to prefill the inode cache during readdir. The filesystem may honour this by filling in the attributes and setting FUSE_FILL_DIR_FLAGS for the filler function. The filesystem may also just ignore this flag completely.

+
+ +

Definition at line 45 of file fuse.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_clean_cache()

+ +
+
+ + + + + + + + +
int fuse_clean_cache (struct fuse * fuse)
+
+

Iterate over cache removing stale entries use in conjunction with "-oremember"

+

NOTE: This is already done for the standard sessions

+
Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
the number of seconds until the next cleanup
+ +

Definition at line 4433 of file fuse.c.

+ +
+
+ +

◆ fuse_destroy()

+ +
+
+ + + + + + + + +
void fuse_destroy (struct fuse * f)
+
+

Destroy the FUSE handle.

+

NOTE: This function does not unmount the filesystem. If this is needed, call fuse_unmount() before calling this function.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5146 of file fuse.c.

+ +
+
+ +

◆ fuse_exit()

+ +
+
+ + + + + + + + +
void fuse_exit (struct fuse * f)
+
+

Flag session as terminated

+

This function will cause any running event loops to exit on the next opportunity.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 4639 of file fuse.c.

+ +
+
+ +

◆ fuse_fs_new()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
struct fuse_fs * fuse_fs_new (const struct fuse_operationsop,
size_t op_size,
void * private_data 
)
+
+

Create a new fuse filesystem object

+

This is usually called from the factory of a fuse module to create a new instance of a filesystem.

+
Parameters
+ + + + +
opthe filesystem operations
op_sizethe size of the fuse_operations structure
private_dataInitial value for the private_data field of struct fuse_context. May be overridden by the struct fuse_operations.init handler.
+
+
+
Returns
a new filesystem object
+ +

Definition at line 4854 of file fuse.c.

+ +
+
+ +

◆ fuse_get_context()

+ +
+
+ + + + + + + + +
struct fuse_context * fuse_get_context (void )
+
+

Get the current context

+

The context is only valid for the duration of a filesystem operation, and thus must not be stored and used later.

+
Returns
the context
+ +

Definition at line 4644 of file fuse.c.

+ +
+
+ +

◆ fuse_get_session()

+ +
+
+ + + + + + + + +
struct fuse_session * fuse_get_session (struct fuse * f)
+
+

Get session from fuse object

+ +

Definition at line 4520 of file fuse.c.

+ +
+
+ +

◆ fuse_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_getgroups (int size,
gid_t list[] 
)
+
+

Get the current supplementary group IDs for the current request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + +
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 4654 of file fuse.c.

+ +
+
+ +

◆ fuse_interrupted()

+ +
+
+ + + + + + + + +
int fuse_interrupted (void )
+
+

Check if the current request has already been interrupted

+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 4663 of file fuse.c.

+ +
+
+ +

◆ fuse_invalidate_path()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_invalidate_path (struct fuse * f,
const char * path 
)
+
+

Invalidates cache for the given path.

+

This calls fuse_lowlevel_notify_inval_inode internally.

+
Returns
0 on successful invalidation, negative error value otherwise. This routine may return -ENOENT to indicate that there was no entry to be invalidated, e.g., because the path has not been seen before or has been forgotten; this should not be considered to be an error.
+ +

Definition at line 4673 of file fuse.c.

+ +
+
+ +

◆ fuse_lib_help()

+ +
+
+ + + + + + + + +
void fuse_lib_help (struct fuse_argsargs)
+
+

Print available options (high- and low-level) to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+

The function looks at the argument vector only to determine if there are additional modules to be loaded (module=foo option), and attempts to call their help functions as well.

+
Parameters
+ + +
argsthe argument vector.
+
+
+ +

Definition at line 4744 of file fuse.c.

+ +
+
+ +

◆ fuse_loop()

+ +
+
+ + + + + + + + +
int fuse_loop (struct fuse * f)
+
+

FUSE event loop.

+

Requests from the kernel are processed, and the appropriate operations are called.

+

For a description of the return value and the conditions when the event loop exits, refer to the documentation of fuse_session_loop().

+
Parameters
+ + +
fthe FUSE handle
+
+
+
Returns
see fuse_session_loop()
+

See also: fuse_loop_mt()

+ +

Definition at line 4577 of file fuse.c.

+ +
+
+ +

◆ fuse_main_real_versioned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_main_real_versioned (int argc,
char * argv[],
const struct fuse_operationsop,
size_t op_size,
struct libfuse_versionversion,
void * user_data 
)
+
+

The real main function

+

Do not call this directly, use fuse_main()

+ +

Definition at line 307 of file helper.c.

+ +
+
+ +

◆ fuse_mount()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_mount (struct fuse * f,
const char * mountpoint 
)
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
fthe FUSE handle
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 5197 of file fuse.c.

+ +
+
+ +

◆ fuse_open_channel()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_open_channel (const char * mountpoint,
const char * options 
)
+
+

Open a FUSE file descriptor and set up the mount for the given mountpoint and flags.

+
Parameters
+ + + +
mountpointreference to the mount in the file system
optionsmount options
+
+
+
Returns
the FUSE file descriptor or -1 upon error
+ +

Definition at line 482 of file helper.c.

+ +
+
+ +

◆ fuse_start_cleanup_thread()

+ +
+
+ + + + + + + + +
int fuse_start_cleanup_thread (struct fuse * fuse)
+
+

Start the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
0 on success and -1 on error
+ +

Definition at line 4906 of file fuse.c.

+ +
+
+ +

◆ fuse_stop_cleanup_thread()

+ +
+
+ + + + + + + + +
void fuse_stop_cleanup_thread (struct fuse * fuse)
+
+

Stop the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+ +

Definition at line 4914 of file fuse.c.

+ +
+
+ +

◆ fuse_unmount()

+ +
+
+ + + + + + + + +
void fuse_unmount (struct fuse * f)
+
+

Unmount a FUSE file system.

+

See fuse_session_unmount() for additional information.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5202 of file fuse.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2include_2fuse_8h_source.html b/doc/html/fuse-3_818_82_2include_2fuse_8h_source.html new file mode 100644 index 0000000..7384aef --- /dev/null +++ b/doc/html/fuse-3_818_82_2include_2fuse_8h_source.html @@ -0,0 +1,650 @@ + + + + + + + +libfuse: fuse-3.18.2/include/fuse.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#ifndef FUSE_H_
+
10#define FUSE_H_
+
11
+
19#include "fuse_common.h"
+
20
+
21#include <fcntl.h>
+
22#include <time.h>
+
23#include <sys/types.h>
+
24#include <sys/stat.h>
+
25#include <sys/statvfs.h>
+
26#include <sys/uio.h>
+
27
+
28#ifdef __cplusplus
+
29extern "C" {
+
30#endif
+
31
+
32/* ----------------------------------------------------------- *
+
33 * Basic FUSE API *
+
34 * ----------------------------------------------------------- */
+
35
+
36/* Forward declaration */
+
37struct statx;
+
38
+
40struct fuse;
+
41
+
+ + +
55 FUSE_READDIR_PLUS = (1 << 0)
+
56};
+
+
57
+
+ + +
75 FUSE_FILL_DIR_PLUS = (1 << 1)
+
76};
+
+
77
+
93typedef int (*fuse_fill_dir_t) (void *buf, const char *name,
+
94 const struct stat *stbuf, off_t off,
+
95 enum fuse_fill_dir_flags flags);
+
107struct fuse_config {
+
112 int32_t set_gid;
+
113 uint32_t gid;
+
114
+
119 int32_t set_uid;
+
120 uint32_t uid;
+
121
+
126 int32_t set_mode;
+
127 uint32_t umask;
+
128
+
133 double entry_timeout;
+
134
+
143 double negative_timeout;
+
144
+
149 double attr_timeout;
+
150
+
154 int32_t intr;
+
155
+
161 int32_t intr_signal;
+
162
+
173 int32_t remember;
+
174
+
191 int32_t hard_remove;
+
192
+
204 int32_t use_ino;
+
205
+
213 int32_t readdir_ino;
+
214
+
232 int32_t direct_io;
+
233
+
251 int32_t kernel_cache;
+
252
+
259 int32_t auto_cache;
+
260
+
261 /*
+
262 * The timeout in seconds for which file attributes are cached
+
263 * for the purpose of checking if auto_cache should flush the
+
264 * file data on open.
+
265 */
+
266 int32_t ac_attr_timeout_set;
+
267 double ac_attr_timeout;
+
268
+
279 int32_t nullpath_ok;
+
280
+
285 int32_t show_help;
+
286 char *modules;
+
287 int32_t debug;
+
288
+
294 uint32_t fmask;
+
295 uint32_t dmask;
+
296
+
303 int32_t no_rofd_flush;
+
304
+ +
319
+
320
+
324 uint32_t flags;
+
325
+
329 uint64_t reserved[48];
+
330};
+
331
+
332
+
355struct fuse_operations {
+
367 int (*getattr) (const char *, struct stat *, struct fuse_file_info *fi);
+
368
+
377 int (*readlink) (const char *, char *, size_t);
+
378
+
385 int (*mknod) (const char *, mode_t, dev_t);
+
386
+
393 int (*mkdir) (const char *, mode_t);
+
394
+
396 int (*unlink) (const char *);
+
397
+
399 int (*rmdir) (const char *);
+
400
+
402 int (*symlink) (const char *, const char *);
+
403
+
413 int (*rename) (const char *, const char *, unsigned int flags);
+
414
+
416 int (*link) (const char *, const char *);
+
417
+
423 int (*chmod) (const char *, mode_t, struct fuse_file_info *fi);
+
424
+
433 int (*chown) (const char *, uid_t, gid_t, struct fuse_file_info *fi);
+
434
+
443 int (*truncate) (const char *, off_t, struct fuse_file_info *fi);
+
444
+
492 int (*open) (const char *, struct fuse_file_info *);
+
493
+
503 int (*read) (const char *, char *, size_t, off_t,
+
504 struct fuse_file_info *);
+
505
+
515 int (*write) (const char *, const char *, size_t, off_t,
+
516 struct fuse_file_info *);
+
517
+
522 int (*statfs) (const char *, struct statvfs *);
+
523
+
552 int (*flush) (const char *, struct fuse_file_info *);
+
553
+
566 int (*release) (const char *, struct fuse_file_info *);
+
567
+
573 int (*fsync) (const char *, int, struct fuse_file_info *);
+
574
+
576 int (*setxattr) (const char *, const char *, const char *, size_t, int);
+
577
+
579 int (*getxattr) (const char *, const char *, char *, size_t);
+
580
+
582 int (*listxattr) (const char *, char *, size_t);
+
583
+
585 int (*removexattr) (const char *, const char *);
+
586
+
595 int (*opendir) (const char *, struct fuse_file_info *);
+
596
+
619 int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t,
+
620 struct fuse_file_info *, enum fuse_readdir_flags);
+
621
+
627 int (*releasedir) (const char *, struct fuse_file_info *);
+
628
+
637 int (*fsyncdir) (const char *, int, struct fuse_file_info *);
+
638
+
647 void *(*init) (struct fuse_conn_info *conn,
+
648 struct fuse_config *cfg);
+
649
+
655 void (*destroy) (void *private_data);
+
656
+
666 int (*access) (const char *, int);
+
667
+
678 int (*create) (const char *, mode_t, struct fuse_file_info *);
+
679
+
710 int (*lock) (const char *, struct fuse_file_info *, int cmd,
+
711 struct flock *);
+
712
+
725 int (*utimens) (const char *, const struct timespec tv[2],
+
726 struct fuse_file_info *fi);
+
727
+
734 int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
+
735
+
736#if FUSE_USE_VERSION < 35
+
737 int (*ioctl) (const char *, int cmd, void *arg,
+
738 struct fuse_file_info *, unsigned int flags, void *data);
+
739#else
+
756 int (*ioctl) (const char *, unsigned int cmd, void *arg,
+
757 struct fuse_file_info *, unsigned int flags, void *data);
+
758#endif
+
759
+
775 int (*poll) (const char *, struct fuse_file_info *,
+
776 struct fuse_pollhandle *ph, unsigned *reventsp);
+
777
+
787 int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off,
+
788 struct fuse_file_info *);
+
789
+
804 int (*read_buf) (const char *, struct fuse_bufvec **bufp,
+
805 size_t size, off_t off, struct fuse_file_info *);
+
824 int (*flock) (const char *, struct fuse_file_info *, int op);
+
825
+
834 int (*fallocate) (const char *, int, off_t, off_t,
+
835 struct fuse_file_info *);
+
836
+
849 ssize_t (*copy_file_range) (const char *path_in,
+
850 struct fuse_file_info *fi_in,
+
851 off_t offset_in, const char *path_out,
+
852 struct fuse_file_info *fi_out,
+
853 off_t offset_out, size_t size, int flags);
+
854
+
858 off_t (*lseek) (const char *, off_t off, int whence, struct fuse_file_info *);
+
859
+
868 int (*statx)(const char *path, int flags, int mask, struct statx *stxbuf,
+
869 struct fuse_file_info *fi);
+
870};
+
871
+
877struct fuse_context {
+
879 struct fuse *fuse;
+
880
+
882 uid_t uid;
+
883
+
885 gid_t gid;
+
886
+
888 pid_t pid;
+
889
+
891 void *private_data;
+
892
+
894 mode_t umask;
+
895};
+
896
+
902int fuse_main_real_versioned(int argc, char *argv[],
+
903 const struct fuse_operations *op, size_t op_size,
+
904 struct libfuse_version *version, void *user_data);
+
905static inline int fuse_main_real(int argc, char *argv[],
+
906 const struct fuse_operations *op,
+
907 size_t op_size, void *user_data)
+
908{
+
909 struct libfuse_version version = { .major = FUSE_MAJOR_VERSION,
+
910 .minor = FUSE_MINOR_VERSION,
+
911 .hotfix = FUSE_HOTFIX_VERSION,
+
912 .padding = 0 };
+
913
+
914 fuse_log(FUSE_LOG_ERR,
+
915 "%s is a libfuse internal function, please use fuse_main()\n",
+
916 __func__);
+
917
+
918 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
919 user_data);
+
920}
+
921
+
976static inline int fuse_main_fn(int argc, char *argv[],
+
977 const struct fuse_operations *op,
+
978 void *user_data)
+
979{
+
980 struct libfuse_version version = {
+
981 .major = FUSE_MAJOR_VERSION,
+
982 .minor = FUSE_MINOR_VERSION,
+
983 .hotfix = FUSE_HOTFIX_VERSION,
+
984 .padding = 0
+
985 };
+
986
+
987 return fuse_main_real_versioned(argc, argv, op, sizeof(*(op)), &version,
+
988 user_data);
+
989}
+
990#define fuse_main(argc, argv, op, user_data) \
+
991 fuse_main_fn(argc, argv, op, user_data)
+
992
+
993/* ----------------------------------------------------------- *
+
994 * More detailed API *
+
995 * ----------------------------------------------------------- */
+
996
+
1008void fuse_lib_help(struct fuse_args *args);
+
1009
+
1010/* Do not call this directly, use fuse_new() instead */
+
1011struct fuse *_fuse_new_30(struct fuse_args *args,
+
1012 const struct fuse_operations *op, size_t op_size,
+
1013 struct libfuse_version *version, void *user_data);
+
1014struct fuse *_fuse_new_31(struct fuse_args *args,
+
1015 const struct fuse_operations *op, size_t op_size,
+
1016 struct libfuse_version *version, void *user_data);
+
1017
+
1045#if FUSE_USE_VERSION == 30
+
1046static inline struct fuse *fuse_new_fn(struct fuse_args *args,
+
1047 const struct fuse_operations *op,
+
1048 size_t op_size, void *user_data)
+
1049{
+
1050 struct libfuse_version version = {
+
1051 .major = FUSE_MAJOR_VERSION,
+
1052 .minor = FUSE_MINOR_VERSION,
+
1053 .hotfix = FUSE_HOTFIX_VERSION,
+
1054 .padding = 0
+
1055 };
+
1056
+
1057 return _fuse_new_30(args, op, op_size, &version, user_data);
+
1058}
+
1059#else /* FUSE_USE_VERSION */
+
1060static inline struct fuse *fuse_new_fn(struct fuse_args *args,
+
1061 const struct fuse_operations *op,
+
1062 size_t op_size, void *user_data)
+
1063{
+
1064 struct libfuse_version version = {
+
1065 .major = FUSE_MAJOR_VERSION,
+
1066 .minor = FUSE_MINOR_VERSION,
+
1067 .hotfix = FUSE_HOTFIX_VERSION,
+
1068 .padding = 0
+
1069 };
+
1070
+
1071 return _fuse_new_31(args, op, op_size, &version, user_data);
+
1072}
+
1073#endif
+
1074#define fuse_new(args, op, size, data) fuse_new_fn(args, op, size, data)
+
1075
+
1084int fuse_mount(struct fuse *f, const char *mountpoint);
+
1085
+
1093void fuse_unmount(struct fuse *f);
+
1094
+
1103void fuse_destroy(struct fuse *f);
+
1104
+
1120int fuse_loop(struct fuse *f);
+
1121
+
1130void fuse_exit(struct fuse *f);
+
1131
+
1132#if FUSE_USE_VERSION < 32
+
1133int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
1134#define fuse_loop_mt(f, clone_fd) fuse_loop_mt_31(f, clone_fd)
+
1135#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
1136int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config *config);
+
1137#define fuse_loop_mt(f, config) fuse_loop_mt_32(f, config)
+
1138#else
+
1170#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
1171int fuse_loop_mt(struct fuse *f, struct fuse_loop_config *config);
+
1172#else
+
1173#define fuse_loop_mt(f, config) fuse_loop_mt_312(f, config)
+
1174#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
1175#endif
+
1176
+
1177
+
1186struct fuse_context *fuse_get_context(void);
+
1187
+
1206int fuse_getgroups(int size, gid_t list[]);
+
1207
+
1213int fuse_interrupted(void);
+
1214
+
1226int fuse_invalidate_path(struct fuse *f, const char *path);
+
1227
+ +
1236
+
1243void fuse_stop_cleanup_thread(struct fuse *fuse);
+
1244
+
1254int fuse_clean_cache(struct fuse *fuse);
+
1255
+
1256/*
+
1257 * Stacking API
+
1258 */
+
1259
+
1265struct fuse_fs;
+
1266
+
1267/*
+
1268 * These functions call the relevant filesystem operation, and return
+
1269 * the result.
+
1270 *
+
1271 * If the operation is not defined, they return -ENOSYS, with the
+
1272 * exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir,
+
1273 * fuse_fs_releasedir and fuse_fs_statfs, which return 0.
+
1274 */
+
1275
+
1276int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1277 struct fuse_file_info *fi);
+
1278int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1279 const char *newpath, unsigned int flags);
+
1280int fuse_fs_unlink(struct fuse_fs *fs, const char *path);
+
1281int fuse_fs_rmdir(struct fuse_fs *fs, const char *path);
+
1282int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname,
+
1283 const char *path);
+
1284int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath);
+
1285int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1286 struct fuse_file_info *fi);
+
1287int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1288 struct fuse_file_info *fi);
+
1289int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size,
+
1290 off_t off, struct fuse_file_info *fi);
+
1291int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1292 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1293 struct fuse_file_info *fi);
+
1294int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf,
+
1295 size_t size, off_t off, struct fuse_file_info *fi);
+
1296int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1297 struct fuse_bufvec *buf, off_t off,
+
1298 struct fuse_file_info *fi);
+
1299int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1300 struct fuse_file_info *fi);
+
1301int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1302 struct fuse_file_info *fi);
+
1303int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf);
+
1304int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1305 struct fuse_file_info *fi);
+
1306int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
1307 fuse_fill_dir_t filler, off_t off,
+
1308 struct fuse_file_info *fi, enum fuse_readdir_flags flags);
+
1309int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1310 struct fuse_file_info *fi);
+
1311int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1312 struct fuse_file_info *fi);
+
1313int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
1314 struct fuse_file_info *fi);
+
1315int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
1316 struct fuse_file_info *fi, int cmd, struct flock *lock);
+
1317int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
1318 struct fuse_file_info *fi, int op);
+
1319int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1320 struct fuse_file_info *fi);
+
1321int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid,
+
1322 struct fuse_file_info *fi);
+
1323int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
1324 struct fuse_file_info *fi);
+
1325int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
1326 const struct timespec tv[2], struct fuse_file_info *fi);
+
1327int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask);
+
1328int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
1329 size_t len);
+
1330int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1331 dev_t rdev);
+
1332int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode);
+
1333int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1334 const char *value, size_t size, int flags);
+
1335int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1336 char *value, size_t size);
+
1337int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
1338 size_t size);
+
1339int fuse_fs_removexattr(struct fuse_fs *fs, const char *path,
+
1340 const char *name);
+
1341int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
1342 uint64_t *idx);
+
1343#if FUSE_USE_VERSION < 35
+
1344int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd,
+
1345 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1346 void *data);
+
1347#else
+
1348int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
1349 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1350 void *data);
+
1351#endif
+
1352int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
1353 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
1354 unsigned *reventsp);
+
1355int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
1356 off_t offset, off_t length, struct fuse_file_info *fi);
+
1357ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
1358 struct fuse_file_info *fi_in, off_t off_in,
+
1359 const char *path_out,
+
1360 struct fuse_file_info *fi_out, off_t off_out,
+
1361 size_t len, int flags);
+
1362off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
1363 struct fuse_file_info *fi);
+
1364int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask,
+
1365 struct statx *stxbuf, struct fuse_file_info *fi);
+
1366void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
1367 struct fuse_config *cfg);
+
1368void fuse_fs_destroy(struct fuse_fs *fs);
+
1369
+
1370int fuse_notify_poll(struct fuse_pollhandle *ph);
+
1371
+
1385struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
1386 void *private_data);
+
1387
+
1402typedef struct fuse_fs *(*fuse_module_factory_t)(struct fuse_args *args,
+
1403 struct fuse_fs *fs[]);
+
+
1414#define FUSE_REGISTER_MODULE(name_, factory_) \
+
1415 fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+
1416
+
1418struct fuse_session *fuse_get_session(struct fuse *f);
+
1419
+
1428int fuse_open_channel(const char *mountpoint, const char *options);
+
1429
+
1430#ifdef __cplusplus
+
1431}
+
1432#endif
+
1433
+
1434#endif /* FUSE_H_ */
+
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4654
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5197
+
int fuse_interrupted(void)
Definition fuse.c:4663
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5146
+
int fuse_main_real_versioned(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
Definition helper.c:307
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4906
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4639
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4433
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:482
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5202
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4914
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
@ FUSE_READDIR_DEFAULTS
Definition fuse.h:51
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:93
+
fuse_readdir_flags
Definition fuse.h:45
+ + + + +
uint32_t fmask
Definition fuse.h:288
+
int32_t show_help
Definition fuse.h:279
+
uint32_t flags
Definition fuse.h:318
+
int32_t direct_io
Definition fuse.h:226
+
int32_t no_rofd_flush
Definition fuse.h:297
+
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t intr_signal
Definition fuse.h:155
+
int32_t remember
Definition fuse.h:167
+
int32_t kernel_cache
Definition fuse.h:245
+
int32_t readdir_ino
Definition fuse.h:207
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t set_mode
Definition fuse.h:120
+
int32_t auto_cache
Definition fuse.h:253
+
uint64_t reserved[48]
Definition fuse.h:323
+
int32_t intr
Definition fuse.h:148
+
double negative_timeout
Definition fuse.h:137
+
int32_t set_gid
Definition fuse.h:106
+
int32_t set_uid
Definition fuse.h:113
+
int32_t hard_remove
Definition fuse.h:185
+
double attr_timeout
Definition fuse.h:143
+ + +
void * private_data
Definition fuse.h:874
+
uid_t uid
Definition fuse.h:865
+
pid_t pid
Definition fuse.h:871
+
struct fuse * fuse
Definition fuse.h:862
+
gid_t gid
Definition fuse.h:868
+
mode_t umask
Definition fuse.h:877
+ + + +
int(* mkdir)(const char *, mode_t)
Definition fuse.h:387
+
int(* truncate)(const char *, off_t, struct fuse_file_info *fi)
Definition fuse.h:437
+
int(* mknod)(const char *, mode_t, dev_t)
Definition fuse.h:379
+
int(* utimens)(const char *, const struct timespec tv[2], struct fuse_file_info *fi)
Definition fuse.h:719
+
int(* open)(const char *, struct fuse_file_info *)
Definition fuse.h:486
+
int(* opendir)(const char *, struct fuse_file_info *)
Definition fuse.h:589
+
int(* link)(const char *, const char *)
Definition fuse.h:410
+
int(* lock)(const char *, struct fuse_file_info *, int cmd, struct flock *)
Definition fuse.h:704
+
int(* read_buf)(const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *)
Definition fuse.h:798
+
int(* access)(const char *, int)
Definition fuse.h:660
+
int(* read)(const char *, char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:497
+
int(* poll)(const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp)
Definition fuse.h:769
+
void(* destroy)(void *private_data)
Definition fuse.h:649
+
int(* statfs)(const char *, struct statvfs *)
Definition fuse.h:516
+
int(* fallocate)(const char *, int, off_t, off_t, struct fuse_file_info *)
Definition fuse.h:828
+
int(* removexattr)(const char *, const char *)
Definition fuse.h:579
+
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
int(* releasedir)(const char *, struct fuse_file_info *)
Definition fuse.h:621
+
int(* rename)(const char *, const char *, unsigned int flags)
Definition fuse.h:407
+
off_t(* lseek)(const char *, off_t off, int whence, struct fuse_file_info *)
Definition fuse.h:852
+
int(* write)(const char *, const char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:509
+
int(* write_buf)(const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *)
Definition fuse.h:781
+
int(* unlink)(const char *)
Definition fuse.h:390
+
ssize_t(* copy_file_range)(const char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags)
Definition fuse.h:843
+
int(* fsync)(const char *, int, struct fuse_file_info *)
Definition fuse.h:567
+
int(* create)(const char *, mode_t, struct fuse_file_info *)
Definition fuse.h:672
+
int(* setxattr)(const char *, const char *, const char *, size_t, int)
Definition fuse.h:570
+
int(* statx)(const char *path, int flags, int mask, struct statx *stxbuf, struct fuse_file_info *fi)
Definition fuse.h:868
+
int(* listxattr)(const char *, char *, size_t)
Definition fuse.h:576
+
int(* readlink)(const char *, char *, size_t)
Definition fuse.h:371
+
int(* symlink)(const char *, const char *)
Definition fuse.h:396
+
int(* fsyncdir)(const char *, int, struct fuse_file_info *)
Definition fuse.h:631
+
int(* release)(const char *, struct fuse_file_info *)
Definition fuse.h:560
+
int(* chown)(const char *, uid_t, gid_t, struct fuse_file_info *fi)
Definition fuse.h:427
+
int(* chmod)(const char *, mode_t, struct fuse_file_info *fi)
Definition fuse.h:417
+
int(* rmdir)(const char *)
Definition fuse.h:393
+
int(* flush)(const char *, struct fuse_file_info *)
Definition fuse.h:546
+
int(* flock)(const char *, struct fuse_file_info *, int op)
Definition fuse.h:818
+
int(* ioctl)(const char *, unsigned int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data)
Definition fuse.h:750
+
int(* readdir)(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags)
Definition fuse.h:613
+
int(* getxattr)(const char *, const char *, char *, size_t)
Definition fuse.h:573
+
int(* bmap)(const char *, size_t blocksize, uint64_t *idx)
Definition fuse.h:728
+ +
+ + + + diff --git a/doc/html/fuse-3_818_82_2include_2fuse__common_8h.html b/doc/html/fuse-3_818_82_2include_2fuse__common_8h.html new file mode 100644 index 0000000..ceb06a7 --- /dev/null +++ b/doc/html/fuse-3_818_82_2include_2fuse__common_8h.html @@ -0,0 +1,1303 @@ + + + + + + + +libfuse: fuse-3.18.2/include/fuse_common.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_common.h File Reference
+
+
+
#include "libfuse_config.h"
+#include "fuse_opt.h"
+#include "fuse_log.h"
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+ + + + + + + + + + + + + + +

+Data Structures

struct  fuse_file_info
 
struct  fuse_loop_config
 
struct  fuse_conn_info
 
struct  fuse_buf
 
struct  fuse_bufvec
 
struct  libfuse_version
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Macros

#define FUSE_CAP_ASYNC_READ   (1UL << 0)
 
#define FUSE_CAP_POSIX_LOCKS   (1UL << 1)
 
#define FUSE_CAP_ATOMIC_O_TRUNC   (1UL << 3)
 
#define FUSE_CAP_EXPORT_SUPPORT   (1UL << 4)
 
#define FUSE_CAP_DONT_MASK   (1UL << 6)
 
#define FUSE_CAP_SPLICE_WRITE   (1UL << 7)
 
#define FUSE_CAP_SPLICE_MOVE   (1UL << 8)
 
#define FUSE_CAP_SPLICE_READ   (1UL << 9)
 
#define FUSE_CAP_FLOCK_LOCKS   (1UL << 10)
 
#define FUSE_CAP_IOCTL_DIR   (1UL << 11)
 
#define FUSE_CAP_AUTO_INVAL_DATA   (1UL << 12)
 
#define FUSE_CAP_READDIRPLUS   (1UL << 13)
 
#define FUSE_CAP_READDIRPLUS_AUTO   (1UL << 14)
 
#define FUSE_CAP_ASYNC_DIO   (1UL << 15)
 
#define FUSE_CAP_WRITEBACK_CACHE   (1UL << 16)
 
#define FUSE_CAP_NO_OPEN_SUPPORT   (1UL << 17)
 
#define FUSE_CAP_PARALLEL_DIROPS   (1UL << 18)
 
#define FUSE_CAP_POSIX_ACL   (1UL << 19)
 
#define FUSE_CAP_HANDLE_KILLPRIV   (1UL << 20)
 
#define FUSE_CAP_HANDLE_KILLPRIV_V2   (1UL << 21)
 
#define FUSE_CAP_CACHE_SYMLINKS   (1UL << 23)
 
#define FUSE_CAP_NO_OPENDIR_SUPPORT   (1UL << 24)
 
#define FUSE_CAP_EXPLICIT_INVAL_DATA   (1UL << 25)
 
#define FUSE_CAP_EXPIRE_ONLY   (1UL << 26)
 
#define FUSE_CAP_SETXATTR_EXT   (1UL << 27)
 
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP   (1UL << 28)
 
#define FUSE_CAP_PASSTHROUGH   (1UL << 29)
 
#define FUSE_CAP_NO_EXPORT_SUPPORT   (1UL << 30)
 
#define FUSE_CAP_OVER_IO_URING   (1UL << 31)
 
#define FUSE_IOCTL_COMPAT   (1 << 0)
 
#define FUSE_BACKING_STACKED_UNDER   (0)
 
+ + + + + +

+Enumerations

enum  fuse_buf_flags { FUSE_BUF_IS_FD = (1 << 1) +, FUSE_BUF_FD_SEEK = (1 << 2) +, FUSE_BUF_FD_RETRY = (1 << 3) + }
 
enum  fuse_buf_copy_flags { FUSE_BUF_NO_SPLICE = (1 << 1) +, FUSE_BUF_FORCE_SPLICE = (1 << 2) +, FUSE_BUF_SPLICE_MOVE = (1 << 3) +, FUSE_BUF_SPLICE_NONBLOCK = (1 << 4) + }
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_args *args)
 
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
 
int fuse_daemonize (int foreground)
 
int fuse_version (void)
 
const char * fuse_pkgversion (void)
 
void fuse_pollhandle_destroy (struct fuse_pollhandle *ph)
 
size_t fuse_buf_size (const struct fuse_bufvec *bufv)
 
ssize_t fuse_buf_copy (struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
 
int fuse_set_signal_handlers (struct fuse_session *se)
 
int fuse_set_fail_signal_handlers (struct fuse_session *se)
 
void fuse_remove_signal_handlers (struct fuse_session *se)
 
bool fuse_set_feature_flag (struct fuse_conn_info *conn, uint64_t flag)
 
void fuse_unset_feature_flag (struct fuse_conn_info *conn, uint64_t flag)
 
bool fuse_get_feature_flag (struct fuse_conn_info *conn, uint64_t flag)
 
int fuse_convert_to_conn_want_ext (struct fuse_conn_info *conn)
 
+

Macro Definition Documentation

+ +

◆ FUSE_BACKING_STACKED_UNDER

+ +
+
+ + + + +
#define FUSE_BACKING_STACKED_UNDER   (0)
+
+

When FUSE_CAP_PASSTHROUGH is enabled, this is the maximum allowed stacking depth of the backing files. In current kernel, the maximum allowed stack depth if FILESYSTEM_MAX_STACK_DEPTH (2), which includes the FUSE passthrough layer, so the maximum stacking depth for backing files is 1.

+

The default is FUSE_BACKING_STACKED_UNDER (0), meaning that the backing files cannot be on a stacked filesystem, but another stacked filesystem can be stacked over this FUSE passthrough filesystem.

+

Set this to FUSE_BACKING_STACKED_OVER (1) if backing files may be on a stacked filesystem, such as overlayfs or another FUSE passthrough. In this configuration, another stacked filesystem cannot be stacked over this FUSE passthrough filesystem.

+ +

Definition at line 670 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ASYNC_DIO

+ +
+
+ + + + +
#define FUSE_CAP_ASYNC_DIO   (1UL << 15)
+
+

Indicates that the filesystem supports asynchronous direct I/O submission.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read and one pending write request per direct I/O file-handle at any time.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 328 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ASYNC_READ

+ +
+
+ + + + +
#define FUSE_CAP_ASYNC_READ   (1UL << 0)
+
+

Indicates that the filesystem supports asynchronous read requests.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read request per file-handle at any time, and will attempt to order read requests by increasing offset.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 177 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ATOMIC_O_TRUNC

+ +
+
+ + + + +
#define FUSE_CAP_ATOMIC_O_TRUNC   (1UL << 3)
+
+

Indicates that the filesystem supports the O_TRUNC open flag. If disabled, and an application specifies O_TRUNC, fuse first calls truncate() and then open() with O_TRUNC filtered out.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 194 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_AUTO_INVAL_DATA

+ +
+
+ + + + +
#define FUSE_CAP_AUTO_INVAL_DATA   (1UL << 12)
+
+

Traditionally, while a file is open the FUSE kernel module only asks the filesystem for an update of the file's attributes when a client attempts to read beyond EOF. This is unsuitable for e.g. network filesystems, where the file contents may change without the kernel knowing about it.

+

If this flag is set, FUSE will check the validity of the attributes on every read. If the attributes are no longer valid (i.e., if the attr_timeout passed to fuse_reply_attr() or set in struct fuse_entry_param has passed), it will first issue a getattr request. If the new mtime differs from the previous value, any cached file contents will be invalidated as well.

+

This flag should always be set when available. If all file changes go through the kernel, attr_timeout should be set to a very large number to avoid unnecessary getattr() calls.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 281 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_CACHE_SYMLINKS

+ +
+
+ + + + +
#define FUSE_CAP_CACHE_SYMLINKS   (1UL << 23)
+
+

Indicates that the kernel supports caching symlinks in its page cache.

+

When this feature is enabled, symlink targets are saved in the page cache. You can invalidate a cached link by calling: fuse_lowlevel_notify_inval_inode(se, ino, 0, 0);

+

This feature is disabled by default. If the kernel supports it (>= 4.20), you can enable this feature by setting this flag in the want field of the fuse_conn_info structure.

+ +

Definition at line 418 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_DIRECT_IO_ALLOW_MMAP

+ +
+
+ + + + +
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP   (1UL << 28)
+
+

Files opened with FUSE_DIRECT_IO do not support MAP_SHARED mmap. This restriction is relaxed through FUSE_CAP_DIRECT_IO_RELAX (kernel flag: FUSE_DIRECT_IO_RELAX). MAP_SHARED is disabled by default for FUSE_DIRECT_IO, as this flag can be used to ensure coherency between mount points (or network clients) and with kernel page cache as enforced by mmap that cannot be guaranteed anymore.

+ +

Definition at line 488 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_DONT_MASK

+ +
+
+ + + + +
#define FUSE_CAP_DONT_MASK   (1UL << 6)
+
+

Indicates that the kernel should not apply the umask to the file mode on create operations.

+

This feature is disabled by default.

+ +

Definition at line 214 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPIRE_ONLY

+ +
+
+ + + + +
#define FUSE_CAP_EXPIRE_ONLY   (1UL << 26)
+
+

Indicates support that dentries can be expired.

+

Expiring dentries, instead of invalidating them, makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+ +

Definition at line 472 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPLICIT_INVAL_DATA

+ +
+
+ + + + +
#define FUSE_CAP_EXPLICIT_INVAL_DATA   (1UL << 25)
+
+

Indicates support for invalidating cached pages only on explicit request.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports invalidating cached pages only on explicit request by the filesystem through fuse_lowlevel_notify_inval_inode() or fuse_invalidate_path().

+

By setting this flag in the want field of the fuse_conn_info structure, the filesystem is responsible for invalidating cached pages through explicit requests to the kernel.

+

Note that setting this flag does not prevent the cached pages from being flushed by OS itself and/or through user actions.

+

Note that if both FUSE_CAP_EXPLICIT_INVAL_DATA and FUSE_CAP_AUTO_INVAL_DATA are set in the capable field of the fuse_conn_info structure then FUSE_CAP_AUTO_INVAL_DATA takes precedence.

+

This feature is disabled by default.

+ +

Definition at line 456 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPORT_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_EXPORT_SUPPORT   (1UL << 4)
+
+

Indicates that the filesystem supports lookups of "." and "..".

+

When this flag is set, the filesystem must be prepared to receive requests for invalid inodes (i.e., for which a FORGET request was received or which have been used in a previous instance of the filesystem daemon) and must not reuse node-ids (even when setting generation numbers).

+

This feature is disabled by default.

+ +

Definition at line 206 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_FLOCK_LOCKS

+ +
+
+ + + + +
#define FUSE_CAP_FLOCK_LOCKS   (1UL << 10)
+
+

If set, the calls to flock(2) will be emulated using POSIX locks and must then be handled by the filesystem's setlock() handler.

+

If not set, flock(2) calls will be handled by the FUSE kernel module internally (so any access that does not go through the kernel cannot be taken into account).

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a flock() handler.

+ +

Definition at line 252 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_HANDLE_KILLPRIV

+ +
+
+ + + + +
#define FUSE_CAP_HANDLE_KILLPRIV   (1UL << 20)
+
+

Indicates that the filesystem is responsible for unsetting setuid and setgid bits when a file is written, truncated, or its owner is changed.

+

This feature is disabled by default.

+ +

Definition at line 388 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_HANDLE_KILLPRIV_V2

+ +
+
+ + + + +
#define FUSE_CAP_HANDLE_KILLPRIV_V2   (1UL << 21)
+
+

Indicates that the filesystem is responsible for unsetting setuid and setgid bit and additionally cap (stored as xattr) when a file is written, truncated, or its owner is changed. Upon write/truncate suid/sgid is only killed if caller does not have CAP_FSETID. Additionally upon write/truncate sgid is killed only if file has group execute permission. (Same as Linux VFS behavior). KILLPRIV_V2 requires handling of

    +
  • FUSE_OPEN_KILL_SUIDGID (set in struct fuse_create_in::open_flags)
  • +
  • FATTR_KILL_SUIDGID (set in struct fuse_setattr_in::valid)
  • +
  • FUSE_WRITE_KILL_SUIDGID (set in struct fuse_write_in::write_flags)
  • +
+

This feature is disabled by default.

+ +

Definition at line 405 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_IOCTL_DIR

+ +
+
+ + + + +
#define FUSE_CAP_IOCTL_DIR   (1UL << 11)
+
+

Indicates that the filesystem supports ioctl's on directories.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 259 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_EXPORT_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_EXPORT_SUPPORT   (1UL << 30)
+
+

Indicates that the file system cannot handle NFS export

+

If this flag is set NFS export and name_to_handle_at is not going to work at all and will fail with EOPNOTSUPP.

+ +

Definition at line 508 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_OPEN_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_OPEN_SUPPORT   (1UL << 17)
+
+

Indicates support for zero-message opens. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the open() handler to indicate success. Further attempts to open files will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signaled to the caller).

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users wish to have this behavior you must return ENOSYS from the open() handler on supporting kernels.

+ +

Definition at line 352 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_OPENDIR_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_OPENDIR_SUPPORT   (1UL << 24)
+
+

Indicates support for zero-message opendirs. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the opendir() handler to indicate success. Further opendir and releasedir messages will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signalled to the caller.)

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users with to have this behavior you must return ENOSYS from the opendir() handler on supporting kernels.

+ +

Definition at line 433 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_OVER_IO_URING

+ +
+
+ + + + +
#define FUSE_CAP_OVER_IO_URING   (1UL << 31)
+
+

Indicates support for io-uring between fuse-server and fuse-client

+ +

Definition at line 513 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_PARALLEL_DIROPS

+ +
+
+ + + + +
#define FUSE_CAP_PARALLEL_DIROPS   (1UL << 18)
+
+

Indicates support for parallel directory operations. If this flag is unset, the FUSE kernel module will ensure that lookup() and readdir() requests are never issued concurrently for the same directory.

+ +

Definition at line 360 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_PASSTHROUGH

+ +
+
+ + + + +
#define FUSE_CAP_PASSTHROUGH   (1UL << 29)
+
+

Indicates support for passthrough mode access for read/write operations.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports redirecting read/write operations to the backing file instead of letting them to be handled by the FUSE daemon.

+

This feature is disabled by default.

+ +

Definition at line 500 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_POSIX_ACL

+ +
+
+ + + + +
#define FUSE_CAP_POSIX_ACL   (1UL << 19)
+
+

Indicates support for POSIX ACLs.

+

If this feature is enabled, the kernel will cache and have responsibility for enforcing ACLs. ACL will be stored as xattrs and passed to userspace, which is responsible for updating the ACLs in the filesystem, keeping the file mode in sync with the ACL, and ensuring inheritance of default ACLs when new filesystem nodes are created. Note that this requires that the file system is able to parse and interpret the xattr representation of ACLs.

+

Enabling this feature implicitly turns on the default_permissions mount option (even if it was not passed to mount(2)).

+

This feature is disabled by default.

+ +

Definition at line 379 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_POSIX_LOCKS

+ +
+
+ + + + +
#define FUSE_CAP_POSIX_LOCKS   (1UL << 1)
+
+

Indicates that the filesystem supports "remote" locking.

+

This feature is enabled by default when supported by the kernel, and if getlk() and setlk() handlers are implemented.

+ +

Definition at line 185 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_READDIRPLUS

+ +
+
+ + + + +
#define FUSE_CAP_READDIRPLUS   (1UL << 13)
+
+

Indicates that the filesystem supports readdirplus.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a readdirplus() handler.

+ +

Definition at line 289 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_READDIRPLUS_AUTO

+ +
+
+ + + + +
#define FUSE_CAP_READDIRPLUS_AUTO   (1UL << 14)
+
+

Indicates that the filesystem supports adaptive readdirplus.

+

If FUSE_CAP_READDIRPLUS is not set, this flag has no effect.

+

If FUSE_CAP_READDIRPLUS is set and this flag is not set, the kernel will always issue readdirplus() requests to retrieve directory contents.

+

If FUSE_CAP_READDIRPLUS is set and this flag is set, the kernel will issue both readdir() and readdirplus() requests, depending on how much information is expected to be required.

+

As of Linux 4.20, the algorithm is as follows: when userspace starts to read directory entries, issue a READDIRPLUS request to the filesystem. If any entry attributes have been looked up by the time userspace requests the next batch of entries continue with READDIRPLUS, otherwise switch to plain READDIR. This will reasult in eg plain "ls" triggering READDIRPLUS first then READDIR after that because it doesn't do lookups. "ls -l" should result in all READDIRPLUS, except if dentries are already cached.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements both a readdirplus() and a readdir() handler.

+ +

Definition at line 317 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SETXATTR_EXT

+ +
+
+ + + + +
#define FUSE_CAP_SETXATTR_EXT   (1UL << 27)
+
+

Indicates that an extended 'struct fuse_setxattr' is used by the kernel side - extra_flags are passed, which are used (as of now by acl) processing. For example FUSE_SETXATTR_ACL_KILL_SGID might be set.

+ +

Definition at line 479 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_MOVE

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_MOVE   (1UL << 8)
+
+

Indicates that libfuse should try to move pages instead of copying when writing to / reading from the fuse device. This may improve performance.

+

This feature is disabled by default.

+ +

Definition at line 230 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_READ

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_READ   (1UL << 9)
+
+

Indicates that libfuse should try to use splice() when reading from the fuse device. This may improve performance.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a write_buf() handler.

+ +

Definition at line 239 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_WRITE

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_WRITE   (1UL << 7)
+
+

Indicates that libfuse should try to use splice() when writing to the fuse device. This may improve performance.

+

This feature is disabled by default.

+ +

Definition at line 222 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_WRITEBACK_CACHE

+ +
+
+ + + + +
#define FUSE_CAP_WRITEBACK_CACHE   (1UL << 16)
+
+

Indicates that writeback caching should be enabled. This means that individual write request may be buffered and merged in the kernel before they are send to the filesystem.

+

This feature is disabled by default.

+ +

Definition at line 337 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_IOCTL_COMPAT

+ +
+
+ + + + +
#define FUSE_IOCTL_COMPAT   (1 << 0)
+
+

Ioctl flags

+

FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed FUSE_IOCTL_RETRY: retry with new iovecs FUSE_IOCTL_DIR: is a directory

+

FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs

+ +

Definition at line 525 of file fuse_common.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_buf_copy_flags

+ +
+
+ + + + +
enum fuse_buf_copy_flags
+
+

Buffer copy flags

+ + + + + +
Enumerator
FUSE_BUF_NO_SPLICE 

Don't use splice(2)

+

Always fall back to using read and write instead of splice(2) to copy data from one file descriptor to another.

+

If this flag is not set, then only fall back if splice is unavailable.

+
FUSE_BUF_FORCE_SPLICE 

Force splice

+

Always use splice(2) to copy data from one file descriptor to another. If splice is not available, return -EINVAL.

+
FUSE_BUF_SPLICE_MOVE 

Try to move data with splice.

+

If splice is used, try to move pages from the source to the destination instead of copying. See documentation of SPLICE_F_MOVE in splice(2) man page.

+
FUSE_BUF_SPLICE_NONBLOCK 

Don't block on the pipe when copying data with splice

+

Makes the operations on the pipe non-blocking (if the pipe is full or empty). See SPLICE_F_NONBLOCK in the splice(2) man page.

+
+ +

Definition at line 840 of file fuse_common.h.

+ +
+
+ +

◆ fuse_buf_flags

+ +
+
+ + + + +
enum fuse_buf_flags
+
+

Buffer flags

+ + + + +
Enumerator
FUSE_BUF_IS_FD 

Buffer contains a file descriptor

+

If this flag is set, the .fd field is valid, otherwise the .mem fields is valid.

+
FUSE_BUF_FD_SEEK 

Seek on the file descriptor

+

If this flag is set then the .pos field is valid and is used to seek to the given offset before performing operation on file descriptor.

+
FUSE_BUF_FD_RETRY 

Retry operation on file descriptor

+

If this flag is set then retry operation on file descriptor until .size bytes have been copied or an error or EOF is detected.

+
+ +

Definition at line 809 of file fuse_common.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_apply_conn_info_opts()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts * opts,
struct fuse_conn_infoconn 
)
+
+

This function applies the (parsed) parameters in opts to the conn pointer. It may modify the following fields: wants, max_write, max_readahead, congestion_threshold, max_background, time_gran. A field is only set (or unset) if the corresponding option has been explicitly set.

+ +

Definition at line 412 of file helper.c.

+ +
+
+ +

◆ fuse_buf_copy()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
ssize_t fuse_buf_copy (struct fuse_bufvecdst,
struct fuse_bufvecsrc,
enum fuse_buf_copy_flags flags 
)
+
+

Copy data from one buffer vector to another

+
Parameters
+ + + + +
dstdestination buffer vector
srcsource buffer vector
flagsflags controlling the copy
+
+
+
Returns
actual number of bytes copied or -errno on error
+ +

Definition at line 284 of file buffer.c.

+ +
+
+ +

◆ fuse_buf_size()

+ +
+
+ + + + + + + + +
size_t fuse_buf_size (const struct fuse_bufvecbufv)
+
+

Get total size of data in a fuse buffer vector

+
Parameters
+ + +
bufvbuffer vector
+
+
+
Returns
size of data
+ +

Definition at line 22 of file buffer.c.

+ +
+
+ +

◆ fuse_convert_to_conn_want_ext()

+ +
+
+ + + + + + + + +
int fuse_convert_to_conn_want_ext (struct fuse_conn_infoconn)
+
+

Get the wanted capability flags, converting from old format if necessary

+ +

Definition at line 2000 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_daemonize()

+ +
+
+ + + + + + + + +
int fuse_daemonize (int foreground)
+
+

Go into the background

+
Parameters
+ + +
foregroundif true, stay in the foreground
+
+
+
Returns
0 on success, -1 on failure
+ +

Definition at line 253 of file helper.c.

+ +
+
+ +

◆ fuse_get_feature_flag()

+ +
+
+ + + + + + + + + + + + + + + + + + +
bool fuse_get_feature_flag (struct fuse_conn_infoconn,
uint64_t flag 
)
+
+

Get the value of a feature flag in the want_ext field of fuse_conn_info.

+
Parameters
+ + + +
connconnection information
flagfeature flag to be checked
+
+
+
Returns
true if the flag is set, false otherwise
+ +

Definition at line 2061 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_parse_conn_info_opts()

+ +
+
+ + + + + + + + +
struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_argsargs)
+
+

This function parses several command-line options that can be used to override elements of struct fuse_conn_info. The pointer returned by this function should be passed to the fuse_apply_conn_info_opts() method by the file system's init() handler.

+

Before using this function, think twice if you really want these parameters to be adjustable from the command line. In most cases, they should be determined by the file system internally.

+

The following options are recognized:

+

-o max_write=N sets conn->max_write -o max_readahead=N sets conn->max_readahead -o max_background=N sets conn->max_background -o congestion_threshold=N sets conn->congestion_threshold -o async_read sets FUSE_CAP_ASYNC_READ in conn->want -o sync_read unsets FUSE_CAP_ASYNC_READ in conn->want -o atomic_o_trunc sets FUSE_CAP_ATOMIC_O_TRUNC in conn->want -o no_remote_lock Equivalent to -o no_remote_flock,no_remote_posix_lock -o no_remote_flock Unsets FUSE_CAP_FLOCK_LOCKS in conn->want -o no_remote_posix_lock Unsets FUSE_CAP_POSIX_LOCKS in conn->want -o [no_]splice_write (un-)sets FUSE_CAP_SPLICE_WRITE in conn->want -o [no_]splice_move (un-)sets FUSE_CAP_SPLICE_MOVE in conn->want -o [no_]splice_read (un-)sets FUSE_CAP_SPLICE_READ in conn->want -o [no_]auto_inval_data (un-)sets FUSE_CAP_AUTO_INVAL_DATA in conn->want -o readdirplus=no unsets FUSE_CAP_READDIRPLUS in conn->want -o readdirplus=yes sets FUSE_CAP_READDIRPLUS and unsets FUSE_CAP_READDIRPLUS_AUTO in conn->want -o readdirplus=auto sets FUSE_CAP_READDIRPLUS and FUSE_CAP_READDIRPLUS_AUTO in conn->want -o [no_]async_dio (un-)sets FUSE_CAP_ASYNC_DIO in conn->want -o [no_]writeback_cache (un-)sets FUSE_CAP_WRITEBACK_CACHE in conn->want -o time_gran=N sets conn->time_gran

+

Known options will be removed from args, unknown options will be passed through unchanged.

+
Parameters
+ + +
argsargument vector (input+output)
+
+
+
Returns
parsed options
+ +

Definition at line 466 of file helper.c.

+ +
+
+ +

◆ fuse_pkgversion()

+ +
+
+ + + + + + + + +
const char * fuse_pkgversion (void )
+
+

Get the full package version string of the library

+
Returns
the package version
+ +

Definition at line 5211 of file fuse.c.

+ +
+
+ +

◆ fuse_pollhandle_destroy()

+ +
+
+ + + + + + + + +
void fuse_pollhandle_destroy (struct fuse_pollhandle * ph)
+
+

Destroy poll handle

+
Parameters
+ + +
phthe poll handle
+
+
+ +

Definition at line 1903 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_remove_signal_handlers()

+ +
+
+ + + + + + + + +
void fuse_remove_signal_handlers (struct fuse_session * se)
+
+

Restore default signal handlers

+

Resets global session. After this fuse_set_signal_handlers() may be called again.

+
Parameters
+ + +
sethe same session as given in fuse_set_signal_handlers()
+
+
+

See also: fuse_set_signal_handlers()

+ +

Definition at line 187 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_fail_signal_handlers()

+ +
+
+ + + + + + + + +
int fuse_set_fail_signal_handlers (struct fuse_session * se)
+
+

Print a stack backtrace diagnostic on critical signals ()

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 165 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_feature_flag()

+ +
+
+ + + + + + + + + + + + + + + + + + +
bool fuse_set_feature_flag (struct fuse_conn_infoconn,
uint64_t flag 
)
+
+

Config operations. These APIs are introduced in version 312 (FUSE_MAKE_VERSION(3, 12)). Using them in earlier versions will result in errors. Set a feature flag in the want_ext field of fuse_conn_info.

+
Parameters
+ + + +
connconnection information
flagfeature flag to be set
+
+
+
Returns
true if the flag was set, false if the flag is not supported
+ +

Definition at line 2035 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_set_signal_handlers()

+ +
+
+ + + + + + + + +
int fuse_set_signal_handlers (struct fuse_session * se)
+
+

Exit session on HUP, TERM and INT signals and ignore PIPE signal

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 140 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_unset_feature_flag()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_unset_feature_flag (struct fuse_conn_infoconn,
uint64_t flag 
)
+
+

Unset a feature flag in the want_ext field of fuse_conn_info.

+
Parameters
+ + + +
connconnection information
flagfeature flag to be unset
+
+
+ +

Definition at line 2050 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_version()

+ +
+
+ + + + + + + + +
int fuse_version (void )
+
+

Get the version of the library

+
Returns
the version
+ +

Definition at line 5206 of file fuse.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2include_2fuse__common_8h_source.html b/doc/html/fuse-3_818_82_2include_2fuse__common_8h_source.html new file mode 100644 index 0000000..de0d66e --- /dev/null +++ b/doc/html/fuse-3_818_82_2include_2fuse__common_8h_source.html @@ -0,0 +1,469 @@ + + + + + + + +libfuse: fuse-3.18.2/include/fuse_common.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_common.h
+
+
+Go to the documentation of this file.
1/* FUSE: Filesystem in Userspace
+
2 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
3
+
4 This program can be distributed under the terms of the GNU LGPLv2.
+
5 See the file LGPL2.txt.
+
6*/
+
7
+
10#if !defined(FUSE_H_) && !defined(FUSE_LOWLEVEL_H_)
+
11#error "Never include <fuse_common.h> directly; use <fuse.h> or <fuse_lowlevel.h> instead."
+
12#endif
+
13
+
14#ifndef FUSE_COMMON_H_
+
15#define FUSE_COMMON_H_
+
16
+
17#ifdef HAVE_LIBFUSE_PRIVATE_CONFIG_H
+
18#include "fuse_config.h"
+
19#endif
+
20
+
21#include "libfuse_config.h"
+
22
+
23#include "fuse_opt.h"
+
24#include "fuse_log.h"
+
25#include <stdint.h>
+
26#include <stdbool.h>
+
27#include <sys/types.h>
+
28#include <assert.h>
+
29
+
30#define FUSE_MAKE_VERSION(maj, min) ((maj) * 100 + (min))
+
31#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION)
+
32
+
33#ifdef __cplusplus
+
34extern "C" {
+
35#endif
+
36
+
50struct fuse_file_info {
+
52 int32_t flags;
+
53
+
60 uint32_t writepage : 1;
+
61
+
63 uint32_t direct_io : 1;
+
64
+
69 uint32_t keep_cache : 1;
+
70
+
74 uint32_t flush : 1;
+
75
+
78 uint32_t nonseekable : 1;
+
79
+
80 /* Indicates that flock locks for this file should be
+
81 released. If set, lock_owner shall contain a valid value.
+
82 May only be set in ->release(). */
+
83 uint32_t flock_release : 1;
+
84
+
89 uint32_t cache_readdir : 1;
+
90
+
93 uint32_t noflush : 1;
+
94
+
97 uint32_t parallel_direct_writes : 1;
+
98
+
100 uint32_t padding : 23;
+
101 uint32_t padding2 : 32;
+
102 uint32_t padding3 : 32;
+
103
+
107 uint64_t fh;
+
108
+
110 uint64_t lock_owner;
+
111
+
114 uint32_t poll_events;
+
115
+
119 int32_t backing_id;
+
120
+
122 uint64_t compat_flags;
+
123
+
124 uint64_t reserved[2];
+
125};
+
126
+
137#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
138struct fuse_loop_config_v1; /* forward declaration */
+
139struct fuse_loop_config {
+
140#else
+
141struct fuse_loop_config_v1 {
+
142#endif
+
147 int clone_fd;
+
148
+
159 unsigned int max_idle_threads;
+
160};
+
161
+
162
+
163/**************************************************************************
+
164 * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want' *
+
165 **************************************************************************/
+
166
+
177#define FUSE_CAP_ASYNC_READ (1UL << 0)
+
178
+
185#define FUSE_CAP_POSIX_LOCKS (1UL << 1)
+
186
+
194#define FUSE_CAP_ATOMIC_O_TRUNC (1UL << 3)
+
195
+
206#define FUSE_CAP_EXPORT_SUPPORT (1UL << 4)
+
207
+
214#define FUSE_CAP_DONT_MASK (1UL << 6)
+
215
+
222#define FUSE_CAP_SPLICE_WRITE (1UL << 7)
+
223
+
230#define FUSE_CAP_SPLICE_MOVE (1UL << 8)
+
231
+
239#define FUSE_CAP_SPLICE_READ (1UL << 9)
+
240
+
252#define FUSE_CAP_FLOCK_LOCKS (1UL << 10)
+
253
+
259#define FUSE_CAP_IOCTL_DIR (1UL << 11)
+
260
+
281#define FUSE_CAP_AUTO_INVAL_DATA (1UL << 12)
+
282
+
289#define FUSE_CAP_READDIRPLUS (1UL << 13)
+
290
+
317#define FUSE_CAP_READDIRPLUS_AUTO (1UL << 14)
+
318
+
328#define FUSE_CAP_ASYNC_DIO (1UL << 15)
+
329
+
337#define FUSE_CAP_WRITEBACK_CACHE (1UL << 16)
+
338
+
352#define FUSE_CAP_NO_OPEN_SUPPORT (1UL << 17)
+
353
+
360#define FUSE_CAP_PARALLEL_DIROPS (1UL << 18)
+
361
+
379#define FUSE_CAP_POSIX_ACL (1UL << 19)
+
380
+
388#define FUSE_CAP_HANDLE_KILLPRIV (1UL << 20)
+
389
+
405#define FUSE_CAP_HANDLE_KILLPRIV_V2 (1UL << 21)
+
406
+
418#define FUSE_CAP_CACHE_SYMLINKS (1UL << 23)
+
419
+
433#define FUSE_CAP_NO_OPENDIR_SUPPORT (1UL << 24)
+
434
+
456#define FUSE_CAP_EXPLICIT_INVAL_DATA (1UL << 25)
+
457
+
472#define FUSE_CAP_EXPIRE_ONLY (1UL << 26)
+
473
+
479#define FUSE_CAP_SETXATTR_EXT (1UL << 27)
+
480
+
488#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP (1UL << 28)
+
489
+
500#define FUSE_CAP_PASSTHROUGH (1UL << 29)
+
501
+
508#define FUSE_CAP_NO_EXPORT_SUPPORT (1UL << 30)
+
509
+
513#define FUSE_CAP_OVER_IO_URING (1UL << 31)
+
514
+
525#define FUSE_IOCTL_COMPAT (1 << 0)
+
526#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
527#define FUSE_IOCTL_RETRY (1 << 2)
+
528#define FUSE_IOCTL_DIR (1 << 4)
+
529
+
530#define FUSE_IOCTL_MAX_IOV 256
+
531
+
543struct fuse_conn_info {
+
547 uint32_t proto_major;
+
548
+
552 uint32_t proto_minor;
+
553
+
557 uint32_t max_write;
+
558
+
571 uint32_t max_read;
+
572
+
576 uint32_t max_readahead;
+
577
+
583 uint32_t capable;
+
584
+
595 uint32_t want;
+
596
+
625 uint32_t max_background;
+
626
+
635 uint32_t congestion_threshold;
+
636
+
652 uint32_t time_gran;
+
653
+
670#define FUSE_BACKING_STACKED_UNDER (0)
+
671#define FUSE_BACKING_STACKED_OVER (1)
+
672 uint32_t max_backing_stack_depth;
+
673
+
682 uint32_t no_interrupt : 1;
+
683
+
684 /* reserved bits for future use */
+
685 uint32_t padding : 31;
+
686
+
691 uint64_t capable_ext;
+
692
+
701 uint64_t want_ext;
+
702
+
707 uint16_t request_timeout;
+
708
+
712 uint16_t reserved[31];
+
713};
+
714
+
715struct fuse_session;
+
716struct fuse_pollhandle;
+
717struct fuse_conn_info_opts;
+
718
+
761struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args);
+
762
+
770void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
771 struct fuse_conn_info *conn);
+
772
+
779int fuse_daemonize(int foreground);
+
780
+
786int fuse_version(void);
+
787
+
793const char *fuse_pkgversion(void);
+
794
+
800void fuse_pollhandle_destroy(struct fuse_pollhandle *ph);
+
801
+
802/* ----------------------------------------------------------- *
+
803 * Data buffer *
+
804 * ----------------------------------------------------------- */
+
805
+
+ +
816 FUSE_BUF_IS_FD = (1 << 1),
+
817
+ +
826
+
834 FUSE_BUF_FD_RETRY = (1 << 3)
+ +
+
836
+
+ + +
851
+ +
859
+ +
868
+ + +
+
878
+
885struct fuse_buf {
+
889 size_t size;
+
890
+
894 enum fuse_buf_flags flags;
+
895
+
901 void *mem;
+
902
+
908 int fd;
+
909
+
915 off_t pos;
+
916
+
923 size_t mem_size;
+
924};
+
925
+
934struct fuse_bufvec {
+
938 size_t count;
+
939
+
943 size_t idx;
+
944
+
948 size_t off;
+
949
+
953 struct fuse_buf buf[1];
+
954};
+
955
+
960struct libfuse_version
+
961{
+
962 uint32_t major;
+
963 uint32_t minor;
+
964 uint32_t hotfix;
+
965 uint32_t padding;
+
966};
+
967
+
968/* Initialize bufvec with a single buffer of given size */
+
969#define FUSE_BUFVEC_INIT(size__) \
+
970 ((struct fuse_bufvec) { \
+
971 /* .count= */ 1, \
+
972 /* .idx = */ 0, \
+
973 /* .off = */ 0, \
+
974 /* .buf = */ { /* [0] = */ { \
+
975 /* .size = */ (size__), \
+
976 /* .flags = */ (enum fuse_buf_flags) 0, \
+
977 /* .mem = */ NULL, \
+
978 /* .fd = */ -1, \
+
979 /* .pos = */ 0, \
+
980 /* .mem_size = */ 0, \
+
981 } } \
+
982 } )
+
983
+
990size_t fuse_buf_size(const struct fuse_bufvec *bufv);
+
991
+
1000ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src,
+
1001 enum fuse_buf_copy_flags flags);
+
1002
+
1003/* ----------------------------------------------------------- *
+
1004 * Signal handling *
+
1005 * ----------------------------------------------------------- */
+
1006
+
1022int fuse_set_signal_handlers(struct fuse_session *se);
+
1023
+
1039int fuse_set_fail_signal_handlers(struct fuse_session *se);
+
1040
+
1052void fuse_remove_signal_handlers(struct fuse_session *se);
+
1053
+
1059#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
1065struct fuse_loop_config *fuse_loop_cfg_create(void);
+
1066
+
1070void fuse_loop_cfg_destroy(struct fuse_loop_config *config);
+
1071
+
1075void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
1076 unsigned int value);
+
1077
+
1081void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
1082 unsigned int value);
+
1083
+
1087void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
1088 unsigned int value);
+
1089
+
1096void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
1097 struct fuse_loop_config_v1 *v1_conf);
+
1098#endif
+
1099
+
1107bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag);
+
1108
+
1115void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag);
+
1116
+
1124bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag);
+
1125
+
1126/*
+
1127 * DO NOT USE: Not part of public API, for internal test use only.
+
1128 * The function signature or any use of it is not guaranteeed to
+
1129 * remain stable. And neither are results of what this function does.
+
1130 */
+ +
1132
+
1133
+
1134
+
1135/* ----------------------------------------------------------- *
+
1136 * Compatibility stuff *
+
1137 * ----------------------------------------------------------- */
+
1138
+
1139#if !defined(FUSE_USE_VERSION) || FUSE_USE_VERSION < 30
+
1140# error only API version 30 or greater is supported
+
1141#endif
+
1142
+
1143#ifdef __cplusplus
+
1144}
+
1145#endif
+
1146
+
1147
+
1148/*
+
1149 * This interface uses 64 bit off_t.
+
1150 *
+
1151 * On 32bit systems please add -D_FILE_OFFSET_BITS=64 to your compile flags!
+
1152 */
+
1153
+
1154#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
+
1155_Static_assert(sizeof(off_t) == 8, "fuse: off_t must be 64bit");
+
1156#else
+
1157struct _fuse_off_t_must_be_64bit_dummy_struct \
+
1158 { unsigned _fuse_off_t_must_be_64bit:((sizeof(off_t) == 8) ? 1 : -1); };
+
1159#endif
+
1160
+
1161#endif /* FUSE_COMMON_H_ */
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:412
+
fuse_buf_flags
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:466
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
int fuse_version(void)
Definition fuse.c:5206
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+ + + + + + + +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:60
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t nonseekable
Definition fuse_common.h:78
+
int32_t backing_id
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t padding
+
uint32_t noflush
Definition fuse_common.h:93
+
uint64_t compat_flags
+
uint32_t flush
Definition fuse_common.h:74
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
+ + + + diff --git a/doc/html/fuse-3_818_82_2include_2fuse__kernel_8h_source.html b/doc/html/fuse-3_818_82_2include_2fuse__kernel_8h_source.html new file mode 100644 index 0000000..c6f4289 --- /dev/null +++ b/doc/html/fuse-3_818_82_2include_2fuse__kernel_8h_source.html @@ -0,0 +1,1180 @@ + + + + + + + +libfuse: fuse-3.18.2/include/fuse_kernel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_kernel.h
+
+
+
1/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */
+
2/*
+
3 This file defines the kernel interface of FUSE
+
4 Copyright (C) 2001-2008 Miklos Szeredi <miklos@szeredi.hu>
+
5
+
6 This program can be distributed under the terms of the GNU GPL.
+
7 See the file COPYING.
+
8
+
9 This -- and only this -- header file may also be distributed under
+
10 the terms of the BSD Licence as follows:
+
11
+
12 Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
+
13
+
14 Redistribution and use in source and binary forms, with or without
+
15 modification, are permitted provided that the following conditions
+
16 are met:
+
17 1. Redistributions of source code must retain the above copyright
+
18 notice, this list of conditions and the following disclaimer.
+
19 2. Redistributions in binary form must reproduce the above copyright
+
20 notice, this list of conditions and the following disclaimer in the
+
21 documentation and/or other materials provided with the distribution.
+
22
+
23 THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+
24 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
25 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
26 ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+
27 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
28 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
29 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
30 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
32 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
33 SUCH DAMAGE.
+
34*/
+
35
+
36/*
+
37 * This file defines the kernel interface of FUSE
+
38 *
+
39 * Protocol changelog:
+
40 *
+
41 * 7.1:
+
42 * - add the following messages:
+
43 * FUSE_SETATTR, FUSE_SYMLINK, FUSE_MKNOD, FUSE_MKDIR, FUSE_UNLINK,
+
44 * FUSE_RMDIR, FUSE_RENAME, FUSE_LINK, FUSE_OPEN, FUSE_READ, FUSE_WRITE,
+
45 * FUSE_RELEASE, FUSE_FSYNC, FUSE_FLUSH, FUSE_SETXATTR, FUSE_GETXATTR,
+
46 * FUSE_LISTXATTR, FUSE_REMOVEXATTR, FUSE_OPENDIR, FUSE_READDIR,
+
47 * FUSE_RELEASEDIR
+
48 * - add padding to messages to accommodate 32-bit servers on 64-bit kernels
+
49 *
+
50 * 7.2:
+
51 * - add FOPEN_DIRECT_IO and FOPEN_KEEP_CACHE flags
+
52 * - add FUSE_FSYNCDIR message
+
53 *
+
54 * 7.3:
+
55 * - add FUSE_ACCESS message
+
56 * - add FUSE_CREATE message
+
57 * - add filehandle to fuse_setattr_in
+
58 *
+
59 * 7.4:
+
60 * - add frsize to fuse_kstatfs
+
61 * - clean up request size limit checking
+
62 *
+
63 * 7.5:
+
64 * - add flags and max_write to fuse_init_out
+
65 *
+
66 * 7.6:
+
67 * - add max_readahead to fuse_init_in and fuse_init_out
+
68 *
+
69 * 7.7:
+
70 * - add FUSE_INTERRUPT message
+
71 * - add POSIX file lock support
+
72 *
+
73 * 7.8:
+
74 * - add lock_owner and flags fields to fuse_release_in
+
75 * - add FUSE_BMAP message
+
76 * - add FUSE_DESTROY message
+
77 *
+
78 * 7.9:
+
79 * - new fuse_getattr_in input argument of GETATTR
+
80 * - add lk_flags in fuse_lk_in
+
81 * - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in
+
82 * - add blksize field to fuse_attr
+
83 * - add file flags field to fuse_read_in and fuse_write_in
+
84 * - Add ATIME_NOW and MTIME_NOW flags to fuse_setattr_in
+
85 *
+
86 * 7.10
+
87 * - add nonseekable open flag
+
88 *
+
89 * 7.11
+
90 * - add IOCTL message
+
91 * - add unsolicited notification support
+
92 * - add POLL message and NOTIFY_POLL notification
+
93 *
+
94 * 7.12
+
95 * - add umask flag to input argument of create, mknod and mkdir
+
96 * - add notification messages for invalidation of inodes and
+
97 * directory entries
+
98 *
+
99 * 7.13
+
100 * - make max number of background requests and congestion threshold
+
101 * tunables
+
102 *
+
103 * 7.14
+
104 * - add splice support to fuse device
+
105 *
+
106 * 7.15
+
107 * - add store notify
+
108 * - add retrieve notify
+
109 *
+
110 * 7.16
+
111 * - add BATCH_FORGET request
+
112 * - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct
+
113 * fuse_ioctl_iovec' instead of ambiguous 'struct iovec'
+
114 * - add FUSE_IOCTL_32BIT flag
+
115 *
+
116 * 7.17
+
117 * - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK
+
118 *
+
119 * 7.18
+
120 * - add FUSE_IOCTL_DIR flag
+
121 * - add FUSE_NOTIFY_DELETE
+
122 *
+
123 * 7.19
+
124 * - add FUSE_FALLOCATE
+
125 *
+
126 * 7.20
+
127 * - add FUSE_AUTO_INVAL_DATA
+
128 *
+
129 * 7.21
+
130 * - add FUSE_READDIRPLUS
+
131 * - send the requested events in POLL request
+
132 *
+
133 * 7.22
+
134 * - add FUSE_ASYNC_DIO
+
135 *
+
136 * 7.23
+
137 * - add FUSE_WRITEBACK_CACHE
+
138 * - add time_gran to fuse_init_out
+
139 * - add reserved space to fuse_init_out
+
140 * - add FATTR_CTIME
+
141 * - add ctime and ctimensec to fuse_setattr_in
+
142 * - add FUSE_RENAME2 request
+
143 * - add FUSE_NO_OPEN_SUPPORT flag
+
144 *
+
145 * 7.24
+
146 * - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support
+
147 *
+
148 * 7.25
+
149 * - add FUSE_PARALLEL_DIROPS
+
150 *
+
151 * 7.26
+
152 * - add FUSE_HANDLE_KILLPRIV
+
153 * - add FUSE_POSIX_ACL
+
154 *
+
155 * 7.27
+
156 * - add FUSE_ABORT_ERROR
+
157 *
+
158 * 7.28
+
159 * - add FUSE_COPY_FILE_RANGE
+
160 * - add FOPEN_CACHE_DIR
+
161 * - add FUSE_MAX_PAGES, add max_pages to init_out
+
162 * - add FUSE_CACHE_SYMLINKS
+
163 *
+
164 * 7.29
+
165 * - add FUSE_NO_OPENDIR_SUPPORT flag
+
166 *
+
167 * 7.30
+
168 * - add FUSE_EXPLICIT_INVAL_DATA
+
169 * - add FUSE_IOCTL_COMPAT_X32
+
170 *
+
171 * 7.31
+
172 * - add FUSE_WRITE_KILL_PRIV flag
+
173 * - add FUSE_SETUPMAPPING and FUSE_REMOVEMAPPING
+
174 * - add map_alignment to fuse_init_out, add FUSE_MAP_ALIGNMENT flag
+
175 *
+
176 * 7.32
+
177 * - add flags to fuse_attr, add FUSE_ATTR_SUBMOUNT, add FUSE_SUBMOUNTS
+
178 *
+
179 * 7.33
+
180 * - add FUSE_HANDLE_KILLPRIV_V2, FUSE_WRITE_KILL_SUIDGID, FATTR_KILL_SUIDGID
+
181 * - add FUSE_OPEN_KILL_SUIDGID
+
182 * - extend fuse_setxattr_in, add FUSE_SETXATTR_EXT
+
183 * - add FUSE_SETXATTR_ACL_KILL_SGID
+
184 *
+
185 * 7.34
+
186 * - add FUSE_SYNCFS
+
187 *
+
188 * 7.35
+
189 * - add FOPEN_NOFLUSH
+
190 *
+
191 * 7.36
+
192 * - extend fuse_init_in with reserved fields, add FUSE_INIT_EXT init flag
+
193 * - add flags2 to fuse_init_in and fuse_init_out
+
194 * - add FUSE_SECURITY_CTX init flag
+
195 * - add security context to create, mkdir, symlink, and mknod requests
+
196 * - add FUSE_HAS_INODE_DAX, FUSE_ATTR_DAX
+
197 *
+
198 * 7.37
+
199 * - add FUSE_TMPFILE
+
200 *
+
201 * 7.38
+
202 * - add FUSE_EXPIRE_ONLY flag to fuse_notify_inval_entry
+
203 * - add FOPEN_PARALLEL_DIRECT_WRITES
+
204 * - add total_extlen to fuse_in_header
+
205 * - add FUSE_MAX_NR_SECCTX
+
206 * - add extension header
+
207 * - add FUSE_EXT_GROUPS
+
208 * - add FUSE_CREATE_SUPP_GROUP
+
209 * - add FUSE_HAS_EXPIRE_ONLY
+
210 *
+
211 * 7.39
+
212 * - add FUSE_DIRECT_IO_ALLOW_MMAP
+
213 * - add FUSE_STATX and related structures
+
214 *
+
215 * 7.40
+
216 * - add max_stack_depth to fuse_init_out, add FUSE_PASSTHROUGH init flag
+
217 * - add backing_id to fuse_open_out, add FOPEN_PASSTHROUGH open flag
+
218 * - add FUSE_NO_EXPORT_SUPPORT init flag
+
219 * - add FUSE_NOTIFY_RESEND, add FUSE_HAS_RESEND init flag
+
220 *
+
221 * 7.41
+
222 * - add FUSE_ALLOW_IDMAP
+
223 * 7.42
+
224 * - Add FUSE_OVER_IO_URING and all other io-uring related flags and data
+
225 * structures:
+
226 * - struct fuse_uring_ent_in_out
+
227 * - struct fuse_uring_req_header
+
228 * - struct fuse_uring_cmd_req
+
229 * - FUSE_URING_IN_OUT_HEADER_SZ
+
230 * - FUSE_URING_OP_IN_OUT_SZ
+
231 * - enum fuse_uring_cmd
+
232 *
+
233 * 7.43
+
234 * - add FUSE_REQUEST_TIMEOUT
+
235 *
+
236 * 7.44
+
237 * - add FUSE_NOTIFY_INC_EPOCH
+
238 *
+
239 * 7.45
+
240 * - add FUSE_COPY_FILE_RANGE_64
+
241 * - add struct fuse_copy_file_range_out
+
242 */
+
243
+
244#ifndef _LINUX_FUSE_H
+
245#define _LINUX_FUSE_H
+
246
+
247#ifdef __KERNEL__
+
248#include <linux/types.h>
+
249#else
+
250#include <stdint.h>
+
251#endif
+
252
+
253/*
+
254 * Version negotiation:
+
255 *
+
256 * Both the kernel and userspace send the version they support in the
+
257 * INIT request and reply respectively.
+
258 *
+
259 * If the major versions match then both shall use the smallest
+
260 * of the two minor versions for communication.
+
261 *
+
262 * If the kernel supports a larger major version, then userspace shall
+
263 * reply with the major version it supports, ignore the rest of the
+
264 * INIT message and expect a new INIT message from the kernel with a
+
265 * matching major version.
+
266 *
+
267 * If the library supports a larger major version, then it shall fall
+
268 * back to the major protocol version sent by the kernel for
+
269 * communication and reply with that major version (and an arbitrary
+
270 * supported minor version).
+
271 */
+
272
+
274#define FUSE_KERNEL_VERSION 7
+
275
+
277#define FUSE_KERNEL_MINOR_VERSION 45
+
278
+
280#define FUSE_ROOT_ID 1
+
281
+
282/* Make sure all structures are padded to 64bit boundary, so 32bit
+
283 userspace works under 64bit kernels */
+
284
+
285struct fuse_attr {
+
286 uint64_t ino;
+
287 uint64_t size;
+
288 uint64_t blocks;
+
289 uint64_t atime;
+
290 uint64_t mtime;
+
291 uint64_t ctime;
+
292 uint32_t atimensec;
+
293 uint32_t mtimensec;
+
294 uint32_t ctimensec;
+
295 uint32_t mode;
+
296 uint32_t nlink;
+
297 uint32_t uid;
+
298 uint32_t gid;
+
299 uint32_t rdev;
+
300 uint32_t blksize;
+
301 uint32_t flags;
+
302};
+
303
+
304/*
+
305 * The following structures are bit-for-bit compatible with the statx(2) ABI in
+
306 * Linux.
+
307 */
+
308struct fuse_sx_time {
+
309 int64_t tv_sec;
+
310 uint32_t tv_nsec;
+
311 int32_t __reserved;
+
312};
+
313
+
314struct fuse_statx {
+
315 uint32_t mask;
+
316 uint32_t blksize;
+
317 uint64_t attributes;
+
318 uint32_t nlink;
+
319 uint32_t uid;
+
320 uint32_t gid;
+
321 uint16_t mode;
+
322 uint16_t __spare0[1];
+
323 uint64_t ino;
+
324 uint64_t size;
+
325 uint64_t blocks;
+
326 uint64_t attributes_mask;
+
327 struct fuse_sx_time atime;
+
328 struct fuse_sx_time btime;
+
329 struct fuse_sx_time ctime;
+
330 struct fuse_sx_time mtime;
+
331 uint32_t rdev_major;
+
332 uint32_t rdev_minor;
+
333 uint32_t dev_major;
+
334 uint32_t dev_minor;
+
335 uint64_t __spare2[14];
+
336};
+
337
+
338struct fuse_kstatfs {
+
339 uint64_t blocks;
+
340 uint64_t bfree;
+
341 uint64_t bavail;
+
342 uint64_t files;
+
343 uint64_t ffree;
+
344 uint32_t bsize;
+
345 uint32_t namelen;
+
346 uint32_t frsize;
+
347 uint32_t padding;
+
348 uint32_t spare[6];
+
349};
+
350
+
351struct fuse_file_lock {
+
352 uint64_t start;
+
353 uint64_t end;
+
354 uint32_t type;
+
355 uint32_t pid; /* tgid */
+
356};
+
357
+
361#define FATTR_MODE (1 << 0)
+
362#define FATTR_UID (1 << 1)
+
363#define FATTR_GID (1 << 2)
+
364#define FATTR_SIZE (1 << 3)
+
365#define FATTR_ATIME (1 << 4)
+
366#define FATTR_MTIME (1 << 5)
+
367#define FATTR_FH (1 << 6)
+
368#define FATTR_ATIME_NOW (1 << 7)
+
369#define FATTR_MTIME_NOW (1 << 8)
+
370#define FATTR_LOCKOWNER (1 << 9)
+
371#define FATTR_CTIME (1 << 10)
+
372#define FATTR_KILL_SUIDGID (1 << 11)
+
373
+
386#define FOPEN_DIRECT_IO (1 << 0)
+
387#define FOPEN_KEEP_CACHE (1 << 1)
+
388#define FOPEN_NONSEEKABLE (1 << 2)
+
389#define FOPEN_CACHE_DIR (1 << 3)
+
390#define FOPEN_STREAM (1 << 4)
+
391#define FOPEN_NOFLUSH (1 << 5)
+
392#define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6)
+
393#define FOPEN_PASSTHROUGH (1 << 7)
+
394
+
451#define FUSE_ASYNC_READ (1 << 0)
+
452#define FUSE_POSIX_LOCKS (1 << 1)
+
453#define FUSE_FILE_OPS (1 << 2)
+
454#define FUSE_ATOMIC_O_TRUNC (1 << 3)
+
455#define FUSE_EXPORT_SUPPORT (1 << 4)
+
456#define FUSE_BIG_WRITES (1 << 5)
+
457#define FUSE_DONT_MASK (1 << 6)
+
458#define FUSE_SPLICE_WRITE (1 << 7)
+
459#define FUSE_SPLICE_MOVE (1 << 8)
+
460#define FUSE_SPLICE_READ (1 << 9)
+
461#define FUSE_FLOCK_LOCKS (1 << 10)
+
462#define FUSE_HAS_IOCTL_DIR (1 << 11)
+
463#define FUSE_AUTO_INVAL_DATA (1 << 12)
+
464#define FUSE_DO_READDIRPLUS (1 << 13)
+
465#define FUSE_READDIRPLUS_AUTO (1 << 14)
+
466#define FUSE_ASYNC_DIO (1 << 15)
+
467#define FUSE_WRITEBACK_CACHE (1 << 16)
+
468#define FUSE_NO_OPEN_SUPPORT (1 << 17)
+
469#define FUSE_PARALLEL_DIROPS (1 << 18)
+
470#define FUSE_HANDLE_KILLPRIV (1 << 19)
+
471#define FUSE_POSIX_ACL (1 << 20)
+
472#define FUSE_ABORT_ERROR (1 << 21)
+
473#define FUSE_MAX_PAGES (1 << 22)
+
474#define FUSE_CACHE_SYMLINKS (1 << 23)
+
475#define FUSE_NO_OPENDIR_SUPPORT (1 << 24)
+
476#define FUSE_EXPLICIT_INVAL_DATA (1 << 25)
+
477#define FUSE_MAP_ALIGNMENT (1 << 26)
+
478#define FUSE_SUBMOUNTS (1 << 27)
+
479#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28)
+
480#define FUSE_SETXATTR_EXT (1 << 29)
+
481#define FUSE_INIT_EXT (1 << 30)
+
482#define FUSE_INIT_RESERVED (1 << 31)
+
483/* bits 32..63 get shifted down 32 bits into the flags2 field */
+
484#define FUSE_SECURITY_CTX (1ULL << 32)
+
485#define FUSE_HAS_INODE_DAX (1ULL << 33)
+
486#define FUSE_CREATE_SUPP_GROUP (1ULL << 34)
+
487#define FUSE_HAS_EXPIRE_ONLY (1ULL << 35)
+
488#define FUSE_DIRECT_IO_ALLOW_MMAP (1ULL << 36)
+
489#define FUSE_PASSTHROUGH (1ULL << 37)
+
490#define FUSE_NO_EXPORT_SUPPORT (1ULL << 38)
+
491#define FUSE_HAS_RESEND (1ULL << 39)
+
492/* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */
+
493#define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP
+
494#define FUSE_ALLOW_IDMAP (1ULL << 40)
+
495#define FUSE_OVER_IO_URING (1ULL << 41)
+
496#define FUSE_REQUEST_TIMEOUT (1ULL << 42)
+
497
+
503#define CUSE_UNRESTRICTED_IOCTL (1 << 0)
+
504
+
508#define FUSE_RELEASE_FLUSH (1 << 0)
+
509#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1)
+
510
+
514#define FUSE_GETATTR_FH (1 << 0)
+
515
+
519#define FUSE_LK_FLOCK (1 << 0)
+
520
+
528#define FUSE_WRITE_CACHE (1 << 0)
+
529#define FUSE_WRITE_LOCKOWNER (1 << 1)
+
530#define FUSE_WRITE_KILL_SUIDGID (1 << 2)
+
531
+
532/* Obsolete alias; this flag implies killing suid/sgid only. */
+
533#define FUSE_WRITE_KILL_PRIV FUSE_WRITE_KILL_SUIDGID
+
534
+
538#define FUSE_READ_LOCKOWNER (1 << 1)
+
539
+
552#define FUSE_IOCTL_COMPAT (1 << 0)
+
553#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
554#define FUSE_IOCTL_RETRY (1 << 2)
+
555#define FUSE_IOCTL_32BIT (1 << 3)
+
556#define FUSE_IOCTL_DIR (1 << 4)
+
557#define FUSE_IOCTL_COMPAT_X32 (1 << 5)
+
558
+
559#define FUSE_IOCTL_MAX_IOV 256
+
560
+
566#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
+
567
+
573#define FUSE_FSYNC_FDATASYNC (1 << 0)
+
574
+
581#define FUSE_ATTR_SUBMOUNT (1 << 0)
+
582#define FUSE_ATTR_DAX (1 << 1)
+
583
+
588#define FUSE_OPEN_KILL_SUIDGID (1 << 0)
+
589
+
594#define FUSE_SETXATTR_ACL_KILL_SGID (1 << 0)
+
595
+
600#define FUSE_EXPIRE_ONLY (1 << 0)
+
601
+
607enum fuse_ext_type {
+
608 /* Types 0..31 are reserved for fuse_secctx_header */
+
609 FUSE_MAX_NR_SECCTX = 31,
+
610 FUSE_EXT_GROUPS = 32,
+
611};
+
612
+
613enum fuse_opcode {
+
614 FUSE_LOOKUP = 1,
+
615 FUSE_FORGET = 2, /* no reply */
+
616 FUSE_GETATTR = 3,
+
617 FUSE_SETATTR = 4,
+
618 FUSE_READLINK = 5,
+
619 FUSE_SYMLINK = 6,
+
620 FUSE_MKNOD = 8,
+
621 FUSE_MKDIR = 9,
+
622 FUSE_UNLINK = 10,
+
623 FUSE_RMDIR = 11,
+
624 FUSE_RENAME = 12,
+
625 FUSE_LINK = 13,
+
626 FUSE_OPEN = 14,
+
627 FUSE_READ = 15,
+
628 FUSE_WRITE = 16,
+
629 FUSE_STATFS = 17,
+
630 FUSE_RELEASE = 18,
+
631 FUSE_FSYNC = 20,
+
632 FUSE_SETXATTR = 21,
+
633 FUSE_GETXATTR = 22,
+
634 FUSE_LISTXATTR = 23,
+
635 FUSE_REMOVEXATTR = 24,
+
636 FUSE_FLUSH = 25,
+
637 FUSE_INIT = 26,
+
638 FUSE_OPENDIR = 27,
+
639 FUSE_READDIR = 28,
+
640 FUSE_RELEASEDIR = 29,
+
641 FUSE_FSYNCDIR = 30,
+
642 FUSE_GETLK = 31,
+
643 FUSE_SETLK = 32,
+
644 FUSE_SETLKW = 33,
+
645 FUSE_ACCESS = 34,
+
646 FUSE_CREATE = 35,
+
647 FUSE_INTERRUPT = 36,
+
648 FUSE_BMAP = 37,
+
649 FUSE_DESTROY = 38,
+
650 FUSE_IOCTL = 39,
+
651 FUSE_POLL = 40,
+
652 FUSE_NOTIFY_REPLY = 41,
+
653 FUSE_BATCH_FORGET = 42,
+
654 FUSE_FALLOCATE = 43,
+
655 FUSE_READDIRPLUS = 44,
+
656 FUSE_RENAME2 = 45,
+
657 FUSE_LSEEK = 46,
+
658 FUSE_COPY_FILE_RANGE = 47,
+
659 FUSE_SETUPMAPPING = 48,
+
660 FUSE_REMOVEMAPPING = 49,
+
661 FUSE_SYNCFS = 50,
+
662 FUSE_TMPFILE = 51,
+
663 FUSE_STATX = 52,
+
664 FUSE_COPY_FILE_RANGE_64 = 53,
+
665
+
666 /* CUSE specific operations */
+
667 CUSE_INIT = 4096,
+
668
+
669 /* Reserved opcodes: helpful to detect structure endian-ness */
+
670 CUSE_INIT_BSWAP_RESERVED = 1048576, /* CUSE_INIT << 8 */
+
671 FUSE_INIT_BSWAP_RESERVED = 436207616, /* FUSE_INIT << 24 */
+
672};
+
673
+
674enum fuse_notify_code {
+
675 FUSE_NOTIFY_POLL = 1,
+
676 FUSE_NOTIFY_INVAL_INODE = 2,
+
677 FUSE_NOTIFY_INVAL_ENTRY = 3,
+
678 FUSE_NOTIFY_STORE = 4,
+
679 FUSE_NOTIFY_RETRIEVE = 5,
+
680 FUSE_NOTIFY_DELETE = 6,
+
681 FUSE_NOTIFY_RESEND = 7,
+
682 FUSE_NOTIFY_INC_EPOCH = 8,
+
683 FUSE_NOTIFY_CODE_MAX,
+
684};
+
685
+
686/* The read buffer is required to be at least 8k, but may be much larger */
+
687#define FUSE_MIN_READ_BUFFER 8192
+
688
+
689#define FUSE_COMPAT_ENTRY_OUT_SIZE 120
+
690
+
691struct fuse_entry_out {
+
692 uint64_t nodeid; /* Inode ID */
+
693 uint64_t generation; /* Inode generation: nodeid:gen must
+
694 be unique for the fs's lifetime */
+
695 uint64_t entry_valid; /* Cache timeout for the name */
+
696 uint64_t attr_valid; /* Cache timeout for the attributes */
+
697 uint32_t entry_valid_nsec;
+
698 uint32_t attr_valid_nsec;
+
699 struct fuse_attr attr;
+
700};
+
701
+
702struct fuse_forget_in {
+
703 uint64_t nlookup;
+
704};
+
705
+
706struct fuse_forget_one {
+
707 uint64_t nodeid;
+
708 uint64_t nlookup;
+
709};
+
710
+
711struct fuse_batch_forget_in {
+
712 uint32_t count;
+
713 uint32_t dummy;
+
714};
+
715
+
716struct fuse_getattr_in {
+
717 uint32_t getattr_flags;
+
718 uint32_t dummy;
+
719 uint64_t fh;
+
720};
+
721
+
722#define FUSE_COMPAT_ATTR_OUT_SIZE 96
+
723
+
724struct fuse_attr_out {
+
725 uint64_t attr_valid; /* Cache timeout for the attributes */
+
726 uint32_t attr_valid_nsec;
+
727 uint32_t dummy;
+
728 struct fuse_attr attr;
+
729};
+
730
+
731struct fuse_statx_in {
+
732 uint32_t getattr_flags;
+
733 uint32_t reserved;
+
734 uint64_t fh;
+
735 uint32_t sx_flags;
+
736 uint32_t sx_mask;
+
737};
+
738
+
739struct fuse_statx_out {
+
740 uint64_t attr_valid; /* Cache timeout for the attributes */
+
741 uint32_t attr_valid_nsec;
+
742 uint32_t flags;
+
743 uint64_t spare[2];
+
744 struct fuse_statx stat;
+
745};
+
746
+
747#define FUSE_COMPAT_MKNOD_IN_SIZE 8
+
748
+
749struct fuse_mknod_in {
+
750 uint32_t mode;
+
751 uint32_t rdev;
+
752 uint32_t umask;
+
753 uint32_t padding;
+
754};
+
755
+
756struct fuse_mkdir_in {
+
757 uint32_t mode;
+
758 uint32_t umask;
+
759};
+
760
+
761struct fuse_rename_in {
+
762 uint64_t newdir;
+
763};
+
764
+
765struct fuse_rename2_in {
+
766 uint64_t newdir;
+
767 uint32_t flags;
+
768 uint32_t padding;
+
769};
+
770
+
771struct fuse_link_in {
+
772 uint64_t oldnodeid;
+
773};
+
774
+
775struct fuse_setattr_in {
+
776 uint32_t valid;
+
777 uint32_t padding;
+
778 uint64_t fh;
+
779 uint64_t size;
+
780 uint64_t lock_owner;
+
781 uint64_t atime;
+
782 uint64_t mtime;
+
783 uint64_t ctime;
+
784 uint32_t atimensec;
+
785 uint32_t mtimensec;
+
786 uint32_t ctimensec;
+
787 uint32_t mode;
+
788 uint32_t unused4;
+
789 uint32_t uid;
+
790 uint32_t gid;
+
791 uint32_t unused5;
+
792};
+
793
+
794struct fuse_open_in {
+
795 uint32_t flags;
+
796 uint32_t open_flags; /* FUSE_OPEN_... */
+
797};
+
798
+
799struct fuse_create_in {
+
800 uint32_t flags;
+
801 uint32_t mode;
+
802 uint32_t umask;
+
803 uint32_t open_flags; /* FUSE_OPEN_... */
+
804};
+
805
+
806struct fuse_open_out {
+
807 uint64_t fh;
+
808 uint32_t open_flags;
+
809 int32_t backing_id;
+
810};
+
811
+
812struct fuse_release_in {
+
813 uint64_t fh;
+
814 uint32_t flags;
+
815 uint32_t release_flags;
+
816 uint64_t lock_owner;
+
817};
+
818
+
819struct fuse_flush_in {
+
820 uint64_t fh;
+
821 uint32_t unused;
+
822 uint32_t padding;
+
823 uint64_t lock_owner;
+
824};
+
825
+
826struct fuse_read_in {
+
827 uint64_t fh;
+
828 uint64_t offset;
+
829 uint32_t size;
+
830 uint32_t read_flags;
+
831 uint64_t lock_owner;
+
832 uint32_t flags;
+
833 uint32_t padding;
+
834};
+
835
+
836#define FUSE_COMPAT_WRITE_IN_SIZE 24
+
837
+
838struct fuse_write_in {
+
839 uint64_t fh;
+
840 uint64_t offset;
+
841 uint32_t size;
+
842 uint32_t write_flags;
+
843 uint64_t lock_owner;
+
844 uint32_t flags;
+
845 uint32_t padding;
+
846};
+
847
+
848struct fuse_write_out {
+
849 uint32_t size;
+
850 uint32_t padding;
+
851};
+
852
+
853#define FUSE_COMPAT_STATFS_SIZE 48
+
854
+
855struct fuse_statfs_out {
+
856 struct fuse_kstatfs st;
+
857};
+
858
+
859struct fuse_fsync_in {
+
860 uint64_t fh;
+
861 uint32_t fsync_flags;
+
862 uint32_t padding;
+
863};
+
864
+
865#define FUSE_COMPAT_SETXATTR_IN_SIZE 8
+
866
+
867struct fuse_setxattr_in {
+
868 uint32_t size;
+
869 uint32_t flags;
+
870 uint32_t setxattr_flags;
+
871 uint32_t padding;
+
872};
+
873
+
874struct fuse_getxattr_in {
+
875 uint32_t size;
+
876 uint32_t padding;
+
877};
+
878
+
879struct fuse_getxattr_out {
+
880 uint32_t size;
+
881 uint32_t padding;
+
882};
+
883
+
884struct fuse_lk_in {
+
885 uint64_t fh;
+
886 uint64_t owner;
+
887 struct fuse_file_lock lk;
+
888 uint32_t lk_flags;
+
889 uint32_t padding;
+
890};
+
891
+
892struct fuse_lk_out {
+
893 struct fuse_file_lock lk;
+
894};
+
895
+
896struct fuse_access_in {
+
897 uint32_t mask;
+
898 uint32_t padding;
+
899};
+
900
+
901struct fuse_init_in {
+
902 uint32_t major;
+
903 uint32_t minor;
+
904 uint32_t max_readahead;
+
905 uint32_t flags;
+
906 uint32_t flags2;
+
907 uint32_t unused[11];
+
908};
+
909
+
910#define FUSE_COMPAT_INIT_OUT_SIZE 8
+
911#define FUSE_COMPAT_22_INIT_OUT_SIZE 24
+
912
+
913struct fuse_init_out {
+
914 uint32_t major;
+
915 uint32_t minor;
+
916 uint32_t max_readahead;
+
917 uint32_t flags;
+
918 uint16_t max_background;
+
919 uint16_t congestion_threshold;
+
920 uint32_t max_write;
+
921 uint32_t time_gran;
+
922 uint16_t max_pages;
+
923 uint16_t map_alignment;
+
924 uint32_t flags2;
+
925 uint32_t max_stack_depth;
+
926 uint16_t request_timeout;
+
927 uint16_t unused[11];
+
928};
+
929
+
930#define CUSE_INIT_INFO_MAX 4096
+
931
+
932struct cuse_init_in {
+
933 uint32_t major;
+
934 uint32_t minor;
+
935 uint32_t unused;
+
936 uint32_t flags;
+
937};
+
938
+
939struct cuse_init_out {
+
940 uint32_t major;
+
941 uint32_t minor;
+
942 uint32_t unused;
+
943 uint32_t flags;
+
944 uint32_t max_read;
+
945 uint32_t max_write;
+
946 uint32_t dev_major; /* chardev major */
+
947 uint32_t dev_minor; /* chardev minor */
+
948 uint32_t spare[10];
+
949};
+
950
+
951struct fuse_interrupt_in {
+
952 uint64_t unique;
+
953};
+
954
+
955struct fuse_bmap_in {
+
956 uint64_t block;
+
957 uint32_t blocksize;
+
958 uint32_t padding;
+
959};
+
960
+
961struct fuse_bmap_out {
+
962 uint64_t block;
+
963};
+
964
+
965struct fuse_ioctl_in {
+
966 uint64_t fh;
+
967 uint32_t flags;
+
968 uint32_t cmd;
+
969 uint64_t arg;
+
970 uint32_t in_size;
+
971 uint32_t out_size;
+
972};
+
973
+
974struct fuse_ioctl_iovec {
+
975 uint64_t base;
+
976 uint64_t len;
+
977};
+
978
+
979struct fuse_ioctl_out {
+
980 int32_t result;
+
981 uint32_t flags;
+
982 uint32_t in_iovs;
+
983 uint32_t out_iovs;
+
984};
+
985
+
986struct fuse_poll_in {
+
987 uint64_t fh;
+
988 uint64_t kh;
+
989 uint32_t flags;
+
990 uint32_t events;
+
991};
+
992
+
993struct fuse_poll_out {
+
994 uint32_t revents;
+
995 uint32_t padding;
+
996};
+
997
+
998struct fuse_notify_poll_wakeup_out {
+
999 uint64_t kh;
+
1000};
+
1001
+
1002struct fuse_fallocate_in {
+
1003 uint64_t fh;
+
1004 uint64_t offset;
+
1005 uint64_t length;
+
1006 uint32_t mode;
+
1007 uint32_t padding;
+
1008};
+
1009
+
1016#define FUSE_UNIQUE_RESEND (1ULL << 63)
+
1017
+
1031#define FUSE_INVALID_UIDGID ((uint32_t)(-1))
+
1032
+
1033struct fuse_in_header {
+
1034 uint32_t len;
+
1035 uint32_t opcode;
+
1036 uint64_t unique;
+
1037 uint64_t nodeid;
+
1038 uint32_t uid;
+
1039 uint32_t gid;
+
1040 uint32_t pid;
+
1041 uint16_t total_extlen; /* length of extensions in 8byte units */
+
1042 uint16_t padding;
+
1043};
+
1044
+
1045struct fuse_out_header {
+
1046 uint32_t len;
+
1047 int32_t error;
+
1048 uint64_t unique;
+
1049};
+
1050
+
1051struct fuse_dirent {
+
1052 uint64_t ino;
+
1053 uint64_t off;
+
1054 uint32_t namelen;
+
1055 uint32_t type;
+
1056 char name[];
+
1057};
+
1058
+
1059/* Align variable length records to 64bit boundary */
+
1060#define FUSE_REC_ALIGN(x) \
+
1061 (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1))
+
1062
+
1063#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name)
+
1064#define FUSE_DIRENT_ALIGN(x) FUSE_REC_ALIGN(x)
+
1065#define FUSE_DIRENT_SIZE(d) \
+
1066 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen)
+
1067
+
1068struct fuse_direntplus {
+
1069 struct fuse_entry_out entry_out;
+
1070 struct fuse_dirent dirent;
+
1071};
+
1072
+
1073#define FUSE_NAME_OFFSET_DIRENTPLUS \
+
1074 offsetof(struct fuse_direntplus, dirent.name)
+
1075#define FUSE_DIRENTPLUS_SIZE(d) \
+
1076 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen)
+
1077
+
1078struct fuse_notify_inval_inode_out {
+
1079 uint64_t ino;
+
1080 int64_t off;
+
1081 int64_t len;
+
1082};
+
1083
+
1084struct fuse_notify_inval_entry_out {
+
1085 uint64_t parent;
+
1086 uint32_t namelen;
+
1087 uint32_t flags;
+
1088};
+
1089
+
1090struct fuse_notify_delete_out {
+
1091 uint64_t parent;
+
1092 uint64_t child;
+
1093 uint32_t namelen;
+
1094 uint32_t padding;
+
1095};
+
1096
+
1097struct fuse_notify_store_out {
+
1098 uint64_t nodeid;
+
1099 uint64_t offset;
+
1100 uint32_t size;
+
1101 uint32_t padding;
+
1102};
+
1103
+
1104struct fuse_notify_retrieve_out {
+
1105 uint64_t notify_unique;
+
1106 uint64_t nodeid;
+
1107 uint64_t offset;
+
1108 uint32_t size;
+
1109 uint32_t padding;
+
1110};
+
1111
+
1112/* Matches the size of fuse_write_in */
+
1113struct fuse_notify_retrieve_in {
+
1114 uint64_t dummy1;
+
1115 uint64_t offset;
+
1116 uint32_t size;
+
1117 uint32_t dummy2;
+
1118 uint64_t dummy3;
+
1119 uint64_t dummy4;
+
1120};
+
1121
+
1122struct fuse_backing_map {
+
1123 int32_t fd;
+
1124 uint32_t flags;
+
1125 uint64_t padding;
+
1126};
+
1127
+
1128/* Device ioctls: */
+
1129#define FUSE_DEV_IOC_MAGIC 229
+
1130#define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t)
+
1131#define FUSE_DEV_IOC_BACKING_OPEN _IOW(FUSE_DEV_IOC_MAGIC, 1, \
+
1132 struct fuse_backing_map)
+
1133#define FUSE_DEV_IOC_BACKING_CLOSE _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t)
+
1134
+
1135struct fuse_lseek_in {
+
1136 uint64_t fh;
+
1137 uint64_t offset;
+
1138 uint32_t whence;
+
1139 uint32_t padding;
+
1140};
+
1141
+
1142struct fuse_lseek_out {
+
1143 uint64_t offset;
+
1144};
+
1145
+
1146struct fuse_copy_file_range_in {
+
1147 uint64_t fh_in;
+
1148 uint64_t off_in;
+
1149 uint64_t nodeid_out;
+
1150 uint64_t fh_out;
+
1151 uint64_t off_out;
+
1152 uint64_t len;
+
1153 uint64_t flags;
+
1154};
+
1155
+
1156/* For FUSE_COPY_FILE_RANGE_64 */
+
1157struct fuse_copy_file_range_out {
+
1158 uint64_t bytes_copied;
+
1159};
+
1160
+
1161#define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0)
+
1162#define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1)
+
1163struct fuse_setupmapping_in {
+
1164 /* An already open handle */
+
1165 uint64_t fh;
+
1166 /* Offset into the file to start the mapping */
+
1167 uint64_t foffset;
+
1168 /* Length of mapping required */
+
1169 uint64_t len;
+
1170 /* Flags, FUSE_SETUPMAPPING_FLAG_* */
+
1171 uint64_t flags;
+
1172 /* Offset in Memory Window */
+
1173 uint64_t moffset;
+
1174};
+
1175
+
1176struct fuse_removemapping_in {
+
1177 /* number of fuse_removemapping_one follows */
+
1178 uint32_t count;
+
1179};
+
1180
+
1181struct fuse_removemapping_one {
+
1182 /* Offset into the dax window start the unmapping */
+
1183 uint64_t moffset;
+
1184 /* Length of mapping required */
+
1185 uint64_t len;
+
1186};
+
1187
+
1188#define FUSE_REMOVEMAPPING_MAX_ENTRY \
+
1189 (PAGE_SIZE / sizeof(struct fuse_removemapping_one))
+
1190
+
1191struct fuse_syncfs_in {
+
1192 uint64_t padding;
+
1193};
+
1194
+
1195/*
+
1196 * For each security context, send fuse_secctx with size of security context
+
1197 * fuse_secctx will be followed by security context name and this in turn
+
1198 * will be followed by actual context label.
+
1199 * fuse_secctx, name, context
+
1200 */
+
1201struct fuse_secctx {
+
1202 uint32_t size;
+
1203 uint32_t padding;
+
1204};
+
1205
+
1206/*
+
1207 * Contains the information about how many fuse_secctx structures are being
+
1208 * sent and what's the total size of all security contexts (including
+
1209 * size of fuse_secctx_header).
+
1210 *
+
1211 */
+
1212struct fuse_secctx_header {
+
1213 uint32_t size;
+
1214 uint32_t nr_secctx;
+
1215};
+
1216
+
1225struct fuse_ext_header {
+
1226 uint32_t size;
+
1227 uint32_t type;
+
1228};
+
1229
+
1235struct fuse_supp_groups {
+
1236 uint32_t nr_groups;
+
1237 uint32_t groups[];
+
1238};
+
1239
+
1243#define FUSE_URING_IN_OUT_HEADER_SZ 128
+
1244#define FUSE_URING_OP_IN_OUT_SZ 128
+
1245
+
1246/* Used as part of the fuse_uring_req_header */
+
1247struct fuse_uring_ent_in_out {
+
1248 uint64_t flags;
+
1249
+
1250 /*
+
1251 * commit ID to be used in a reply to a ring request (see also
+
1252 * struct fuse_uring_cmd_req)
+
1253 */
+
1254 uint64_t commit_id;
+
1255
+
1256 /* size of user payload buffer */
+
1257 uint32_t payload_sz;
+
1258 uint32_t padding;
+
1259
+
1260 uint64_t reserved;
+
1261};
+
1262
+
1266struct fuse_uring_req_header {
+
1267 /* struct fuse_in_header / struct fuse_out_header */
+
1268 char in_out[FUSE_URING_IN_OUT_HEADER_SZ];
+
1269
+
1270 /* per op code header */
+
1271 char op_in[FUSE_URING_OP_IN_OUT_SZ];
+
1272
+
1273 struct fuse_uring_ent_in_out ring_ent_in_out;
+
1274};
+
1275
+
1279enum fuse_uring_cmd {
+
1280 FUSE_IO_URING_CMD_INVALID = 0,
+
1281
+
1282 /* register the request buffer and fetch a fuse request */
+
1283 FUSE_IO_URING_CMD_REGISTER = 1,
+
1284
+
1285 /* commit fuse request result and fetch next request */
+
1286 FUSE_IO_URING_CMD_COMMIT_AND_FETCH = 2,
+
1287};
+
1288
+
1292struct fuse_uring_cmd_req {
+
1293 uint64_t flags;
+
1294
+
1295 /* entry identifier for commits */
+
1296 uint64_t commit_id;
+
1297
+
1298 /* queue the command is for (queue index) */
+
1299 uint16_t qid;
+
1300 uint8_t padding[6];
+
1301};
+
1302
+
1303#endif /* _LINUX_FUSE_H */
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_818_82_2include_2fuse__log_8h.html b/doc/html/fuse-3_818_82_2include_2fuse__log_8h.html new file mode 100644 index 0000000..fa7d6d4 --- /dev/null +++ b/doc/html/fuse-3_818_82_2include_2fuse__log_8h.html @@ -0,0 +1,268 @@ + + + + + + + +libfuse: fuse-3.18.2/include/fuse_log.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_log.h File Reference
+
+
+
#include <stdarg.h>
+
+

Go to the source code of this file.

+ + + + +

+Typedefs

typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
 
+ + + +

+Enumerations

enum  fuse_log_level
 
+ + + + + + + + + +

+Functions

void fuse_set_log_func (fuse_log_func_t func)
 
void fuse_log (enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
 
void void fuse_log_enable_syslog (const char *ident, int option, int facility)
 
void fuse_log_close_syslog (void)
 
+

Detailed Description

+

This file defines the logging interface of FUSE

+ +

Definition in file fuse_log.h.

+

Typedef Documentation

+ +

◆ fuse_log_func_t

+ +
+
+ + + + +
typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
+
+

Log message handler function.

+

This function must be thread-safe. It may be called from any libfuse function, including fuse_parse_cmdline() and other functions invoked before a FUSE filesystem is created.

+

Install a custom log message handler function using fuse_set_log_func().

+
Parameters
+ + + + +
levellog severity level
fmtsprintf-style format string including newline
apformat string arguments
+
+
+ +

Definition at line 52 of file fuse_log.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_log_level

+ +
+
+ + + + +
enum fuse_log_level
+
+

Log severity level

+

These levels correspond to syslog(2) log levels since they are widely used.

+ +

Definition at line 28 of file fuse_log.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_log()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_log (enum fuse_log_level level,
const char * fmt,
 ... 
)
+
+

Emit a log message

+
Parameters
+ + + +
levelseverity level (FUSE_LOG_ERR, FUSE_LOG_DEBUG, etc)
fmtsprintf-style format string including newline
+
+
+ +

Definition at line 76 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_close_syslog()

+ +
+
+ + + + + + + + +
void fuse_log_close_syslog (void )
+
+

To be called at teardown to close syslog.

+ +

Definition at line 93 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_enable_syslog()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void void fuse_log_enable_syslog (const char * ident,
int option,
int facility 
)
+
+

Switch default log handler from stderr to syslog

+

Passed options are according to 'man 3 openlog'

+ +

Definition at line 86 of file fuse_log.c.

+ +
+
+ +

◆ fuse_set_log_func()

+ +
+
+ + + + + + + + +
void fuse_set_log_func (fuse_log_func_t func)
+
+

Install a custom log handler function.

+

Log messages are emitted by libfuse functions to report errors and debug information. Messages are printed to stderr by default but this can be overridden by installing a custom log message handler function.

+

The log message handler function is global and affects all FUSE filesystems created within this process.

+
Parameters
+ + +
funca custom log message handler function or NULL to revert to the default
+
+
+ +

Definition at line 69 of file fuse_log.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2include_2fuse__log_8h_source.html b/doc/html/fuse-3_818_82_2include_2fuse__log_8h_source.html new file mode 100644 index 0000000..02ca681 --- /dev/null +++ b/doc/html/fuse-3_818_82_2include_2fuse__log_8h_source.html @@ -0,0 +1,113 @@ + + + + + + + +libfuse: fuse-3.18.2/include/fuse_log.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_log.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#ifndef FUSE_LOG_H_
+
10#define FUSE_LOG_H_
+
11
+
17#include <stdarg.h>
+
18
+
19#ifdef __cplusplus
+
20extern "C" {
+
21#endif
+
22
+
+ +
29 FUSE_LOG_EMERG,
+
30 FUSE_LOG_ALERT,
+
31 FUSE_LOG_CRIT,
+
32 FUSE_LOG_ERR,
+
33 FUSE_LOG_WARNING,
+
34 FUSE_LOG_NOTICE,
+
35 FUSE_LOG_INFO,
+
36 FUSE_LOG_DEBUG
+
37};
+
+
38
+
52typedef void (*fuse_log_func_t)(enum fuse_log_level level,
+
53 const char *fmt, va_list ap);
+
54
+ +
69
+
76void fuse_log(enum fuse_log_level level, const char *fmt, ...)
+
77 __attribute__((format(printf, 2, 3)));
+
78
+
84void fuse_log_enable_syslog(const char *ident, int option, int facility);
+
85
+
89void fuse_log_close_syslog(void);
+
90
+
91#ifdef __cplusplus
+
92}
+
93#endif
+
94
+
95#endif /* FUSE_LOG_H_ */
+
void fuse_log_close_syslog(void)
Definition fuse_log.c:93
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:86
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:69
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2include_2fuse__lowlevel_8h.html b/doc/html/fuse-3_818_82_2include_2fuse__lowlevel_8h.html new file mode 100644 index 0000000..6791cf5 --- /dev/null +++ b/doc/html/fuse-3_818_82_2include_2fuse__lowlevel_8h.html @@ -0,0 +1,2530 @@ + + + + + + + +libfuse: fuse-3.18.2/include/fuse_lowlevel.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_lowlevel.h File Reference
+
+
+
#include "fuse_common.h"
+#include <stddef.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + + + + + +

+Data Structures

struct  fuse_entry_param
 
struct  fuse_ctx
 
struct  fuse_lowlevel_ops
 
struct  fuse_cmdline_opts
 
+ + + +

+Macros

#define FUSE_ROOT_ID   1
 
+ + + + + + + +

+Typedefs

typedef uint64_t fuse_ino_t
 
typedef struct fuse_req * fuse_req_t
 
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
 
+ + + +

+Enumerations

enum  fuse_notify_entry_flags
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

int fuse_reply_err (fuse_req_t req, int err)
 
void fuse_reply_none (fuse_req_t req)
 
int fuse_reply_entry (fuse_req_t req, const struct fuse_entry_param *e)
 
int fuse_reply_create (fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
 
int fuse_reply_attr (fuse_req_t req, const struct stat *attr, double attr_timeout)
 
int fuse_reply_readlink (fuse_req_t req, const char *link)
 
int fuse_passthrough_open (fuse_req_t req, int fd)
 
int fuse_reply_open (fuse_req_t req, const struct fuse_file_info *fi)
 
int fuse_reply_write (fuse_req_t req, size_t count)
 
int fuse_reply_buf (fuse_req_t req, const char *buf, size_t size)
 
int fuse_reply_data (fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
 
int fuse_reply_iov (fuse_req_t req, const struct iovec *iov, int count)
 
int fuse_reply_statfs (fuse_req_t req, const struct statvfs *stbuf)
 
int fuse_reply_xattr (fuse_req_t req, size_t count)
 
int fuse_reply_lock (fuse_req_t req, const struct flock *lock)
 
int fuse_reply_bmap (fuse_req_t req, uint64_t idx)
 
size_t fuse_add_direntry (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
 
size_t fuse_add_direntry_plus (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
 
int fuse_reply_ioctl_retry (fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
 
int fuse_reply_ioctl (fuse_req_t req, int result, const void *buf, size_t size)
 
int fuse_reply_ioctl_iov (fuse_req_t req, int result, const struct iovec *iov, int count)
 
int fuse_reply_poll (fuse_req_t req, unsigned revents)
 
int fuse_reply_lseek (fuse_req_t req, off_t off)
 
int fuse_reply_statx (fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
 
int fuse_lowlevel_notify_poll (struct fuse_pollhandle *ph)
 
int fuse_lowlevel_notify_inval_inode (struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
 
int fuse_lowlevel_notify_increment_epoch (struct fuse_session *se)
 
int fuse_lowlevel_notify_inval_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_expire_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_delete (struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_store (struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
 
int fuse_lowlevel_notify_retrieve (struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
 
void * fuse_req_userdata (fuse_req_t req)
 
const struct fuse_ctxfuse_req_ctx (fuse_req_t req)
 
int fuse_req_getgroups (fuse_req_t req, int size, gid_t list[])
 
void fuse_req_interrupt_func (fuse_req_t req, fuse_interrupt_func_t func, void *data)
 
int fuse_req_interrupted (fuse_req_t req)
 
void fuse_lowlevel_version (void)
 
void fuse_lowlevel_help (void)
 
void fuse_cmdline_help (void)
 
int fuse_parse_cmdline_30 (struct fuse_args *args, struct fuse_cmdline_opts *opts)
 
int fuse_session_mount (struct fuse_session *se, const char *mountpoint)
 
int fuse_session_loop (struct fuse_session *se)
 
void fuse_session_exit (struct fuse_session *se)
 
void fuse_session_reset (struct fuse_session *se)
 
int fuse_session_exited (struct fuse_session *se)
 
void fuse_session_unmount (struct fuse_session *se)
 
void fuse_session_destroy (struct fuse_session *se)
 
int fuse_session_fd (struct fuse_session *se)
 
void fuse_session_process_buf (struct fuse_session *se, const struct fuse_buf *buf)
 
int fuse_session_receive_buf (struct fuse_session *se, struct fuse_buf *buf)
 
bool fuse_req_is_uring (fuse_req_t req)
 
int fuse_req_get_payload (fuse_req_t req, char **payload, size_t *payload_sz, void **mr)
 
+

Detailed Description

+

Low level API

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header. To use the newest API define it to 35 (recommended for any new application).

+ +

Definition in file fuse_lowlevel.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ROOT_ID

+ +
+
+ + + + +
#define FUSE_ROOT_ID   1
+
+

The node ID of the root inode

+ +

Definition at line 44 of file fuse_lowlevel.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_ino_t

+ +
+
+ + + + +
typedef uint64_t fuse_ino_t
+
+

Inode number type

+ +

Definition at line 47 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_interrupt_func_t

+ +
+
+ + + + +
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
+
+

Callback function for an interrupt

+
Parameters
+ + + +
reqinterrupted request
datauser data
+
+
+ +

Definition at line 1992 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_req_t

+ +
+
+ + + + +
typedef struct fuse_req* fuse_req_t
+
+

Request pointer type

+ +

Definition at line 50 of file fuse_lowlevel.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_notify_entry_flags

+ +
+
+ + + + +
enum fuse_notify_entry_flags
+
+

Flags for fuse_lowlevel_notify_entry() 0 = invalidate entry FUSE_LL_EXPIRE_ONLY = expire entry

+ +

Definition at line 151 of file fuse_lowlevel.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_add_direntry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct stat * stbuf,
off_t off 
)
+
+

Add a directory entry to the buffer

+

Buffer needs to be large enough to hold the entry. If it's not, then the entry is not filled in but the size of the entry is still returned. The caller can check this by comparing the bufsize parameter with the returned entry size. If the entry size is larger than the buffer size, the operation failed.

+

From the 'stbuf' argument the st_ino field and bits 12-15 of the st_mode field are used. The other fields are ignored.

+

off should be any non-zero value that the filesystem can use to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to mean "from the beginning", and should therefore never be used (the first call to fuse_add_direntry should be passed the offset of the second directory entry).

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
stbufthe file attributes
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 286 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_add_direntry_plus()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry_plus (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct fuse_entry_parame,
off_t off 
)
+
+

Add a directory entry to the buffer with the attributes

+

See documentation of fuse_add_direntry() for more details.

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
ethe directory entry
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 376 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_cmdline_help()

+ +
+
+ + + + + + + + +
void fuse_cmdline_help (void )
+
+

Print available options for fuse_parse_cmdline().

+ +

Definition at line 130 of file helper.c.

+ +
+
+ +

◆ fuse_lowlevel_help()

+ +
+
+ + + + + + + + +
void fuse_lowlevel_help (void )
+
+

Print available low-level options to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+ +

Definition at line 3000 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_delete()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_delete (struct fuse_session * se,
fuse_ino_t parent,
fuse_ino_t child,
const char * name,
size_t namelen 
)
+
+

This function behaves like fuse_lowlevel_notify_inval_entry() with the following additional effect (at least as of Linux kernel 4.8):

+

If the provided child inode matches the inode that is currently associated with the cached dentry, and if there are any inotify watches registered for the dentry, then the watchers are informed that the dentry has been deleted.

+

To avoid a deadlock this function must not be called while executing a related filesystem operation or while holding a lock that could be needed to execute such an operation (see the description of fuse_lowlevel_notify_inval_entry() for more details).

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.18. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
parentinode number
childinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2562 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_expire_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_expire_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen 
)
+
+

Notify to expire parent attributes and the dentry matching parent/name

+

Same restrictions apply as for fuse_lowlevel_notify_inval_entry()

+

Compared to invalidating an entry, expiring the entry results not in a forceful removal of that entry from kernel cache but instead the next access to it forces a lookup from the filesystem.

+

This makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+

Added in FUSE protocol version 7.38. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure, -enosys if no kernel support
+ +

Definition at line 2549 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_increment_epoch()

+ +
+
+ + + + + + + + +
int fuse_lowlevel_notify_increment_epoch (struct fuse_session * se)
+
+

Notify to increment the epoch for the current

+

Each fuse connection has an 'epoch', which is initialized during INIT. Caching will then be validated against the epoch value: if the current epoch is higher than an object being revalidated, the object is invalid.

+

This function simply increment the current epoch value.

+
Parameters
+ + +
sethe session object
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 3116 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen 
)
+
+

Notify to invalidate parent attributes and the dentry matching parent/name

+

To avoid a deadlock this function must not be called in the execution path of a related filesystem operation or within any code that could hold a lock that could be needed to execute such an operation. As of kernel 4.18, a "related operation" is a lookup(), symlink(), mknod(), mkdir(), unlink(), rename(), link() or create() request for the parent, and a setattr(), unlink(), rmdir(), rename(), setxattr(), removexattr(), readdir() or readdirplus() request for the inode itself.

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2543 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_inode()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_inode (struct fuse_session * se,
fuse_ino_t ino,
off_t off,
off_t len 
)
+
+

Notify to invalidate cache for an inode.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+

If the filesystem has writeback caching enabled, invalidating an inode will first trigger a writeback of all dirty pages. The call will block until all writeback requests have completed and the inode has been invalidated. It will, however, not wait for completion of pending writeback requests that have been issued before.

+

If there are no dirty pages, this function will never block.

+
Parameters
+ + + + + +
sethe session object
inothe inode number
offthe offset in the inode where to start invalidating or negative to invalidate attributes only
lenthe amount of cache to invalidate or 0 for all
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2475 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_poll()

+ +
+
+ + + + + + + + +
int fuse_lowlevel_notify_poll (struct fuse_pollhandle * ph)
+
+

Notify IO readiness event

+

For more information, please read comment for poll operation.

+
Parameters
+ + +
phpoll handle to notify IO readiness event for
+
+
+ +

Definition at line 2458 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_retrieve()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_retrieve (struct fuse_session * se,
fuse_ino_t ino,
size_t size,
off_t offset,
void * cookie 
)
+
+

Retrieve data from the kernel buffers

+

Retrieve data in the kernel buffers belonging to the given inode. If successful then the retrieve_reply() method will be called with the returned data.

+

Only present pages are returned in the retrieve reply. Retrieving stops when it finds a non-present page and only data prior to that is returned.

+

If this function returns an error, then the retrieve will not be completed and no reply will be sent.

+

This function doesn't change the dirty state of pages in the kernel buffer. For dirty pages the write() method will be called regardless of having been retrieved previously.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
sizethe number of bytes to retrieve
offsetthe starting offset into the file to retrieve from
cookieuser data to supply to the reply callback
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2668 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_store()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_store (struct fuse_session * se,
fuse_ino_t ino,
off_t offset,
struct fuse_bufvecbufv,
enum fuse_buf_copy_flags flags 
)
+
+

Store data to the kernel buffers

+

Synchronously store data in the kernel buffers belonging to the given inode. The stored data is marked up-to-date (no read will be performed against it, unless it's invalidated or evicted from the cache).

+

If the stored data overflows the current file size, then the size is extended, similarly to a write(2) on the filesystem.

+

If this function returns an error, then the store wasn't fully completed, but it may have been partially completed.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
offsetthe starting offset into the file to store to
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2588 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_version()

+ +
+
+ + + + + + + + +
void fuse_lowlevel_version (void )
+
+

Print low-level version information to stdout.

+ +

Definition at line 2993 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_parse_cmdline_30()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_parse_cmdline_30 (struct fuse_argsargs,
struct fuse_cmdline_optsopts 
)
+
+

Utility function to parse common options for simple file systems using the low-level API. A help text that describes the available options can be printed with fuse_cmdline_help. A single non-option argument is treated as the mountpoint. Multiple non-option arguments will result in an error.

+

If neither -o subtype= or -o fsname= options are given, a new subtype option will be added and set to the basename of the program (the fsname will remain unset, and then defaults to "fuse").

+

Known options will be removed from args, unknown options will remain.

+
Parameters
+ + + +
argsargument vector (input+output)
optsoutput argument for parsed options
+
+
+
Returns
0 on success, -1 on failure
+

struct fuse_cmdline_opts got extended in libfuse-3.12

+ +

Definition at line 237 of file helper.c.

+ +
+
+ +

◆ fuse_passthrough_open()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_passthrough_open (fuse_req_t req,
int fd 
)
+
+

Setup passthrough backing file for open reply

+

Currently there should be only one backing id per node / backing file.

+

Possible requests: open, opendir, create

+
Parameters
+ + + +
reqrequest handle
fdbacking file descriptor
+
+
+
Returns
positive backing id for success, 0 for failure
+ +

Definition at line 480 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_attr()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_attr (fuse_req_t req,
const struct stat * attr,
double attr_timeout 
)
+
+

Reply with attributes

+

Possible requests: getattr, setattr

+
Parameters
+ + + + +
reqrequest handle
attrthe attributes
attr_timeoutvalidity timeout (in seconds) for the attributes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 460 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_bmap()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_bmap (fuse_req_t req,
uint64_t idx 
)
+
+

Reply with block index

+

Possible requests: bmap

+
Parameters
+ + + +
reqrequest handle
idxblock index within device
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 973 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_buf (fuse_req_t req,
const char * buf,
size_t size 
)
+
+

Reply with data

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
bufbuffer containing data
sizethe size of data in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 524 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_create()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_create (fuse_req_t req,
const struct fuse_entry_parame,
const struct fuse_file_infofi 
)
+
+

Reply with a directory entry and open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes

+

Possible requests: create

+

Side effects: increments the lookup count on success

+
Parameters
+ + + + +
reqrequest handle
ethe entry parameters
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 444 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_data()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_data (fuse_req_t req,
struct fuse_bufvecbufv,
enum fuse_buf_copy_flags flags 
)
+
+

Reply with data copied/moved from buffer(s)

+

Zero copy data transfer ("splicing") will be used under the following circumstances:

+
    +
  1. FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports splicing from the fuse device (FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.capable), and
  4. +
  5. flags does not contain FUSE_BUF_NO_SPLICE
  6. +
  7. The amount of data that is provided in file-descriptor backed buffers (i.e., buffers for which bufv[n].flags == FUSE_BUF_FD) is at least twice the page size.
  8. +
+

In order for SPLICE_F_MOVE to be used, the following additional conditions have to be fulfilled:

+
    +
  1. FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports it (i.e, FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.capable), and
  4. +
  5. flags contains FUSE_BUF_SPLICE_MOVE
  6. +
+

Note that, if splice is used, the data is actually spliced twice: once into a temporary pipe (to prepend header data), and then again into the kernel. If some of the provided buffers are memory-backed, the data in them is copied in step one and spliced in step two.

+

The FUSE_BUF_SPLICE_FORCE_SPLICE and FUSE_BUF_SPLICE_NONBLOCK flags are silently ignored.

+

Possible requests: read, readdir, getxattr, listxattr

+

Side effects: when used to return data from a readdirplus() (but not readdir()) call, increments the lookup count of each returned entry by one on success.

+
Parameters
+ + + + +
reqrequest handle
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 912 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_entry (fuse_req_t req,
const struct fuse_entry_parame 
)
+
+

Reply with a directory entry

+

Possible requests: lookup, mknod, mkdir, symlink, link

+

Side effects: increments the lookup count on success

+
Parameters
+ + + +
reqrequest handle
ethe entry parameters
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 428 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_err()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_err (fuse_req_t req,
int err 
)
+
+

Reply with an error code or success.

+

Possible requests: all except forget, forget_multi, retrieve_reply

+

Wherever possible, error codes should be chosen from the list of documented error conditions in the corresponding system calls manpage.

+

An error code of ENOSYS is sometimes treated specially. This is indicated in the documentation of the affected handler functions.

+

The following requests may be answered with a zero error code: unlink, rmdir, rename, flush, release, fsync, fsyncdir, setxattr, removexattr, setlk.

+
Parameters
+ + + +
reqrequest handle
errthe positive error value, or zero for success
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 331 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl (fuse_req_t req,
int result,
const void * buf,
size_t size 
)
+
+

Reply to finish ioctl

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
bufbuffer containing output data
sizelength of output data
+
+
+ +

Definition at line 1071 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_iov (fuse_req_t req,
int result,
const struct iovec * iov,
int count 
)
+
+

Reply to finish ioctl with iov buffer

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
iovthe vector containing the data
countthe size of vector
+
+
+ +

Definition at line 1092 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_retry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_retry (fuse_req_t req,
const struct iovec * in_iov,
size_t in_count,
const struct iovec * out_iov,
size_t out_count 
)
+
+

Reply to ask for data fetch and output buffer preparation. ioctl will be retried with the specified input data fetched and output buffer prepared.

+

Possible requests: ioctl

+
Parameters
+ + + + + + +
reqrequest handle
in_ioviovec specifying data to fetch from the caller
in_countnumber of entries in in_iov
out_ioviovec specifying addresses to write output to
out_countnumber of entries in out_iov
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1001 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_iov (fuse_req_t req,
const struct iovec * iov,
int count 
)
+
+

Reply with data vector

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
iovthe vector containing the data
countthe size of vector
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 265 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lock()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_lock (fuse_req_t req,
const struct flock * lock 
)
+
+

Reply with file lock information

+

Possible requests: getlk

+
Parameters
+ + + +
reqrequest handle
lockthe lock information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 956 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lseek()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_lseek (fuse_req_t req,
off_t off 
)
+
+

Reply with offset

+

Possible requests: lseek

+
Parameters
+ + + +
reqrequest handle
offoffset of next data or hole
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1126 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_none()

+ +
+
+ + + + + + + + +
void fuse_reply_none (fuse_req_t req)
+
+

Don't send reply

+

Possible requests: forget forget_multi retrieve_reply

+
Parameters
+ + +
reqrequest handle
+
+
+ +

Definition at line 336 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_open()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_open (fuse_req_t req,
const struct fuse_file_infofi 
)
+
+

Reply with open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes,

+

Possible requests: open, opendir

+
Parameters
+ + + +
reqrequest handle
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 505 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_poll()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_poll (fuse_req_t req,
unsigned revents 
)
+
+

Reply with poll result event mask

+
Parameters
+ + + +
reqrequest handle
reventspoll result event mask
+
+
+ +

Definition at line 1116 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_readlink()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_readlink (fuse_req_t req,
const char * link 
)
+
+

Reply with the contents of a symbolic link

+

Possible requests: readlink

+
Parameters
+ + + +
reqrequest handle
linksymbolic link contents
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 475 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_statfs()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_statfs (fuse_req_t req,
const struct statvfs * stbuf 
)
+
+

Reply with filesystem statistics

+

Possible requests: statfs

+
Parameters
+ + + +
reqrequest handle
stbuffilesystem statistics
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 934 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_statx()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_statx (fuse_req_t req,
int flags,
struct statx * statx,
double attr_timeout 
)
+
+

Reply with extended file attributes.

+

Possible requests: statx

+
Parameters
+ + + + + +
reqrequest handle
flagsstatx flags
statxthe attributes
attr_timeoutvalidity timeout (in seconds) for the attributes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1260 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_write()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_write (fuse_req_t req,
size_t count 
)
+
+

Reply with number of bytes written

+

Possible requests: write

+
Parameters
+ + + +
reqrequest handle
countthe number of bytes written
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 514 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_xattr()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_xattr (fuse_req_t req,
size_t count 
)
+
+

Reply with needed buffer size

+

Possible requests: getxattr, listxattr

+
Parameters
+ + + +
reqrequest handle
countthe buffer size needed in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 946 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_ctx()

+ +
+
+ + + + + + + + +
const struct fuse_ctx * fuse_req_ctx (fuse_req_t req)
+
+

Get the context from the request

+

The pointer returned by this function will only be valid for the request's lifetime

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the context structure
+ +

Definition at line 2718 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_get_payload()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_req_get_payload (fuse_req_t req,
char ** payload,
size_t * payload_sz,
void ** mr 
)
+
+

Get the payload of a request (for requests submitted through fuse-io-uring only)

+

This is useful for a file system that wants to write data directly to the request buffer. With io-uring the req is the buffer owner and the file system can write directly to the buffer and avoid extra copying. For example useful for network file systems.

+
Parameters
+ + + + + +
reqthe request
payloadpointer to the payload
payload_szsize of the payload
mrmemory registration handle, currently unused
+
+
+
Returns
0 on success, -errno on failure
+ +

Definition at line 3386 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_req_getgroups (fuse_req_t req,
int size,
gid_t list[] 
)
+
+

Get the current supplementary group IDs for the specified request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + + +
reqrequest handle
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 3614 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupt_func()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_req_interrupt_func (fuse_req_t req,
fuse_interrupt_func_t func,
void * data 
)
+
+

Register/unregister callback for an interrupt

+

If an interrupt has already happened, then the callback function is called from within this function, hence it's not possible for interrupts to be lost.

+
Parameters
+ + + + +
reqrequest handle
functhe callback function or NULL for unregister
datauser data passed to the callback function
+
+
+ +

Definition at line 2723 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupted()

+ +
+
+ + + + + + + + +
int fuse_req_interrupted (fuse_req_t req)
+
+

Check if a request has already been interrupted

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 2736 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_is_uring()

+ +
+
+ + + + + + + + +
bool fuse_req_is_uring (fuse_req_t req)
+
+

Check if the request is submitted through fuse-io-uring

+ +

Definition at line 3380 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_userdata()

+ +
+
+ + + + + + + + +
void * fuse_req_userdata (fuse_req_t req)
+
+

Get the userdata from the request

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the user data passed to fuse_session_new()
+ +

Definition at line 2713 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_destroy()

+ +
+
+ + + + + + + + +
void fuse_session_destroy (struct fuse_session * se)
+
+

Destroy a session

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 3010 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_exit()

+ +
+
+ + + + + + + + +
void fuse_session_exit (struct fuse_session * se)
+
+

Flag a session as terminated.

+

This will cause any running event loops to terminate on the next opportunity. If this function is called by a thread that is not a FUSE worker thread, the next opportunity will be when FUSE a request is received (which may be far in the future if the filesystem is not currently being used by any clients). One way to avoid this delay is to afterwards sent a signal to the main thread (if fuse_set_signal_handlers() is used, SIGPIPE will cause the main thread to wake-up but otherwise be ignored).

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_exited()

+ +
+
+ + + + + + + + +
int fuse_session_exited (struct fuse_session * se)
+
+

Query the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+
Returns
1 if exited, 0 if not exited
+ +
+
+ +

◆ fuse_session_fd()

+ +
+
+ + + + + + + + +
int fuse_session_fd (struct fuse_session * se)
+
+

Return file descriptor for communication with kernel.

+

The file selector can be used to integrate FUSE with a custom event loop. Whenever data is available for reading on the provided fd, the event loop should call fuse_session_receive_buf followed by fuse_session_process_buf to process the request.

+

The returned file descriptor is valid until fuse_session_unmount is called.

+
Parameters
+ + +
sethe session
+
+
+
Returns
a file descriptor
+ +

Definition at line 3534 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_loop()

+ +
+
+ + + + + + + + +
int fuse_session_loop (struct fuse_session * se)
+
+

Enter a single threaded, blocking event loop.

+

When the event loop terminates because the connection to the FUSE kernel module has been closed, this function returns zero. This happens when the filesystem is unmounted regularly (by the filesystem owner or root running the umount(8) or fusermount(1) command), or if connection is explicitly severed by writing 1 to theabort file in /sys/fs/fuse/connections/NNN. The only way to distinguish between these two conditions is to check if the filesystem is still mounted after the session loop returns.

+

When some error occurs during request processing, the function returns a negated errno(3) value.

+

If the loop has been terminated because of a signal handler installed by fuse_set_signal_handlers(), this function returns the (positive) signal value that triggered the exit.

+
Parameters
+ + +
sethe session
+
+
+
Returns
0, -errno, or a signal value
+ +

Definition at line 19 of file fuse_loop.c.

+ +
+
+ +

◆ fuse_session_mount()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_session_mount (struct fuse_session * se,
const char * mountpoint 
)
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
sesession object
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 3473 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_process_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_session_process_buf (struct fuse_session * se,
const struct fuse_bufbuf 
)
+
+

Process a raw request supplied in a generic buffer

+

The fuse_buf may contain a memory buffer or a pipe file descriptor.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf containing the request
+
+
+ +

Definition at line 2830 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_receive_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_session_receive_buf (struct fuse_session * se,
struct fuse_bufbuf 
)
+
+

Read a raw request from the kernel into the supplied buffer.

+

Depending on file system options, system capabilities, and request size the request is either read into a memory buffer or spliced into a temporary pipe.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf to store the request in
+
+
+
Returns
the actual size of the raw request, or -errno on error
+ +

Definition at line 3285 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_reset()

+ +
+
+ + + + + + + + +
void fuse_session_reset (struct fuse_session * se)
+
+

Reset the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_unmount()

+ +
+
+ + + + + + + + +
void fuse_session_unmount (struct fuse_session * se)
+
+

Ensure that file system is unmounted.

+

In regular operation, the file system is typically unmounted by the user calling umount(8) or fusermount(1), which then terminates the FUSE session loop. However, the session loop may also terminate as a result of an explicit call to fuse_session_exit() (e.g. by a signal handler installed by fuse_set_signal_handler()). In this case the filesystem remains mounted, but any attempt to access it will block (while the filesystem process is still running) or give an ESHUTDOWN error (after the filesystem process has terminated).

+

If the communication channel with the FUSE kernel module is still open (i.e., if the session loop was terminated by an explicit call to fuse_session_exit()), this function will close it and unmount the filesystem. If the communication channel has been closed by the kernel, this method will do (almost) nothing.

+

NOTE: The above semantics mean that if the connection to the kernel is terminated via the /sys/fs/fuse/connections/NNN/abort file, this method will not unmount the filesystem.

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 3539 of file fuse_lowlevel.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2include_2fuse__lowlevel_8h_source.html b/doc/html/fuse-3_818_82_2include_2fuse__lowlevel_8h_source.html new file mode 100644 index 0000000..0003aab --- /dev/null +++ b/doc/html/fuse-3_818_82_2include_2fuse__lowlevel_8h_source.html @@ -0,0 +1,691 @@ + + + + + + + +libfuse: fuse-3.18.2/include/fuse_lowlevel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_lowlevel.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#ifndef FUSE_LOWLEVEL_H_
+
10#define FUSE_LOWLEVEL_H_
+
11
+
21#ifndef FUSE_USE_VERSION
+
22#error FUSE_USE_VERSION not defined
+
23#endif
+
24
+
25#include "fuse_common.h"
+
26
+
27#include <stddef.h>
+
28#include <utime.h>
+
29#include <fcntl.h>
+
30#include <sys/types.h>
+
31#include <sys/stat.h>
+
32#include <sys/statvfs.h>
+
33#include <sys/uio.h>
+
34
+
35#ifdef __cplusplus
+
36extern "C" {
+
37#endif
+
38
+
39/* ----------------------------------------------------------- *
+
40 * Miscellaneous definitions *
+
41 * ----------------------------------------------------------- */
+
42
+
44#define FUSE_ROOT_ID 1
+
45
+
47typedef uint64_t fuse_ino_t;
+
48
+
50typedef struct fuse_req *fuse_req_t;
+
51
+
52/* Forward declaration */
+
53struct statx;
+
54
+
60struct fuse_session;
+
61
+
63struct fuse_entry_param {
+ +
72
+
83 uint64_t generation;
+
84
+
92 struct stat attr;
+
93
+
98 double attr_timeout;
+
99
+
104 double entry_timeout;
+
105};
+
106
+
115struct fuse_ctx {
+
117 uid_t uid;
+
118
+
120 gid_t gid;
+
121
+
123 pid_t pid;
+
124
+
126 mode_t umask;
+
127};
+
128
+
129struct fuse_forget_data {
+
130 fuse_ino_t ino;
+
131 uint64_t nlookup;
+
132};
+
133
+
134struct fuse_custom_io {
+
135 ssize_t (*writev)(int fd, struct iovec *iov, int count, void *userdata);
+
136 ssize_t (*read)(int fd, void *buf, size_t buf_len, void *userdata);
+
137 ssize_t (*splice_receive)(int fdin, off_t *offin, int fdout,
+
138 off_t *offout, size_t len,
+
139 unsigned int flags, void *userdata);
+
140 ssize_t (*splice_send)(int fdin, off_t *offin, int fdout,
+
141 off_t *offout, size_t len,
+
142 unsigned int flags, void *userdata);
+
143 int (*clone_fd)(int master_fd);
+
144};
+
145
+
+ +
152 FUSE_LL_INVALIDATE = 0,
+
153 FUSE_LL_EXPIRE_ONLY = (1 << 0),
+
154};
+
+
155
+
156/* 'to_set' flags in setattr */
+
157#define FUSE_SET_ATTR_MODE (1 << 0)
+
158#define FUSE_SET_ATTR_UID (1 << 1)
+
159#define FUSE_SET_ATTR_GID (1 << 2)
+
160#define FUSE_SET_ATTR_SIZE (1 << 3)
+
161#define FUSE_SET_ATTR_ATIME (1 << 4)
+
162#define FUSE_SET_ATTR_MTIME (1 << 5)
+
163#define FUSE_SET_ATTR_ATIME_NOW (1 << 7)
+
164#define FUSE_SET_ATTR_MTIME_NOW (1 << 8)
+
165#define FUSE_SET_ATTR_FORCE (1 << 9)
+
166#define FUSE_SET_ATTR_CTIME (1 << 10)
+
167#define FUSE_SET_ATTR_KILL_SUID (1 << 11)
+
168#define FUSE_SET_ATTR_KILL_SGID (1 << 12)
+
169#define FUSE_SET_ATTR_FILE (1 << 13)
+
170#define FUSE_SET_ATTR_KILL_PRIV (1 << 14)
+
171#define FUSE_SET_ATTR_OPEN (1 << 15)
+
172#define FUSE_SET_ATTR_TIMES_SET (1 << 16)
+
173#define FUSE_SET_ATTR_TOUCH (1 << 17)
+
174
+
175/* ----------------------------------------------------------- *
+
176 * Request methods and replies *
+
177 * ----------------------------------------------------------- */
+
178
+
208struct fuse_lowlevel_ops {
+
225 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
226
+
238 void (*destroy) (void *userdata);
+
239
+
251 void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
252
+
289 void (*forget) (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup);
+
290
+
310 void (*getattr) (fuse_req_t req, fuse_ino_t ino,
+
311 struct fuse_file_info *fi);
+
312
+
347 void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
348 int to_set, struct fuse_file_info *fi);
+
349
+
360 void (*readlink) (fuse_req_t req, fuse_ino_t ino);
+
361
+
378 void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
379 mode_t mode, dev_t rdev);
+
380
+
393 void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
394 mode_t mode);
+
395
+
411 void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
412
+
428 void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
429
+
442 void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent,
+
443 const char *name);
+
444
+
474 void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
475 fuse_ino_t newparent, const char *newname,
+
476 unsigned int flags);
+
477
+
490 void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
491 const char *newname);
+
492
+
556 void (*open) (fuse_req_t req, fuse_ino_t ino,
+
557 struct fuse_file_info *fi);
+
558
+
584 void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
585 struct fuse_file_info *fi);
+
586
+
613 void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf,
+
614 size_t size, off_t off, struct fuse_file_info *fi);
+
615
+
654 void (*flush) (fuse_req_t req, fuse_ino_t ino,
+
655 struct fuse_file_info *fi);
+
656
+
682 void (*release) (fuse_req_t req, fuse_ino_t ino,
+
683 struct fuse_file_info *fi);
+
684
+
704 void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
705 struct fuse_file_info *fi);
+
706
+
736 void (*opendir) (fuse_req_t req, fuse_ino_t ino,
+
737 struct fuse_file_info *fi);
+
738
+
782 void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
783 struct fuse_file_info *fi);
+
784
+
801 void (*releasedir) (fuse_req_t req, fuse_ino_t ino,
+
802 struct fuse_file_info *fi);
+
803
+
826 void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
827 struct fuse_file_info *fi);
+
828
+
839 void (*statfs) (fuse_req_t req, fuse_ino_t ino);
+
840
+
852 void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
853 const char *value, size_t size, int flags);
+
854
+
883 void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
884 size_t size);
+
885
+
914 void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size);
+
915
+
931 void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name);
+
932
+
953 void (*access) (fuse_req_t req, fuse_ino_t ino, int mask);
+
954
+
982 void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
983 mode_t mode, struct fuse_file_info *fi);
+
984
+
997 void (*getlk) (fuse_req_t req, fuse_ino_t ino,
+
998 struct fuse_file_info *fi, struct flock *lock);
+
999
+
1022 void (*setlk) (fuse_req_t req, fuse_ino_t ino,
+
1023 struct fuse_file_info *fi,
+
1024 struct flock *lock, int sleep);
+
1025
+
1046 void (*bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
1047 uint64_t idx);
+
1048
+
1049#if FUSE_USE_VERSION < 35
+
1050 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, int cmd,
+
1051 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1052 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1053#else
+
1082 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
1083 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1084 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1085#endif
+
1086
+
1119 void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
1120 struct fuse_pollhandle *ph);
+
1121
+
1149 void (*write_buf) (fuse_req_t req, fuse_ino_t ino,
+
1150 struct fuse_bufvec *bufv, off_t off,
+
1151 struct fuse_file_info *fi);
+
1152
+
1165 void (*retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino,
+
1166 off_t offset, struct fuse_bufvec *bufv);
+
1167
+
1179 void (*forget_multi) (fuse_req_t req, size_t count,
+
1180 struct fuse_forget_data *forgets);
+
1181
+
1197 void (*flock) (fuse_req_t req, fuse_ino_t ino,
+
1198 struct fuse_file_info *fi, int op);
+
1199
+
1220 void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode,
+
1221 off_t offset, off_t length, struct fuse_file_info *fi);
+
1222
+
1248 void (*readdirplus) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
1249 struct fuse_file_info *fi);
+
1250
+
1281 void (*copy_file_range) (fuse_req_t req, fuse_ino_t ino_in,
+
1282 off_t off_in, struct fuse_file_info *fi_in,
+
1283 fuse_ino_t ino_out, off_t off_out,
+
1284 struct fuse_file_info *fi_out, size_t len,
+
1285 int flags);
+
1286
+
1305 void (*lseek) (fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
1306 struct fuse_file_info *fi);
+
1307
+
1326 void (*tmpfile) (fuse_req_t req, fuse_ino_t parent,
+
1327 mode_t mode, struct fuse_file_info *fi);
+
1328
+
1342 void (*statx)(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
+
1343 struct fuse_file_info *fi);
+
1344};
+
1345
+
1367int fuse_reply_err(fuse_req_t req, int err);
+
1368
+
1379void fuse_reply_none(fuse_req_t req);
+
1380
+
1394int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e);
+
1395
+
1414int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+
1415 const struct fuse_file_info *fi);
+
1416
+
1428int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
1429 double attr_timeout);
+
1430
+
1441int fuse_reply_readlink(fuse_req_t req, const char *link);
+
1442
+
1455int fuse_passthrough_open(fuse_req_t req, int fd);
+
1456int fuse_passthrough_close(fuse_req_t req, int backing_id);
+
1457
+
1472int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi);
+
1473
+
1484int fuse_reply_write(fuse_req_t req, size_t count);
+
1485
+
1497int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size);
+
1498
+
1542int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+ +
1544
+
1556int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count);
+
1557
+
1568int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf);
+
1569
+
1580int fuse_reply_xattr(fuse_req_t req, size_t count);
+
1581
+
1592int fuse_reply_lock(fuse_req_t req, const struct flock *lock);
+
1593
+
1604int fuse_reply_bmap(fuse_req_t req, uint64_t idx);
+
1605
+
1606/* ----------------------------------------------------------- *
+
1607 * Filling a buffer in readdir *
+
1608 * ----------------------------------------------------------- */
+
1609
+
1637size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
1638 const char *name, const struct stat *stbuf,
+
1639 off_t off);
+
1640
+
1654size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
1655 const char *name,
+
1656 const struct fuse_entry_param *e, off_t off);
+
1657
+ +
1674 const struct iovec *in_iov, size_t in_count,
+
1675 const struct iovec *out_iov, size_t out_count);
+
1676
+
1688int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size);
+
1689
+
1701int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1702 int count);
+
1703
+
1710int fuse_reply_poll(fuse_req_t req, unsigned revents);
+
1711
+
1722int fuse_reply_lseek(fuse_req_t req, off_t off);
+
1723
+
1736int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout);
+
1737
+
1738/* ----------------------------------------------------------- *
+
1739 * Notification *
+
1740 * ----------------------------------------------------------- */
+
1741
+
1749int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph);
+
1750
+
1774int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
1775 off_t off, off_t len);
+
1776
+
1789int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se);
+
1790
+
1815int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
1816 const char *name, size_t namelen);
+
1817
+
1846int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
1847 const char *name, size_t namelen);
+
1848
+
1877int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
1878 fuse_ino_t parent, fuse_ino_t child,
+
1879 const char *name, size_t namelen);
+
1880
+
1906int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
1907 off_t offset, struct fuse_bufvec *bufv,
+ +
1938int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
1939 size_t size, off_t offset, void *cookie);
+
1940
+
1941
+
1942/* ----------------------------------------------------------- *
+
1943 * Utility functions *
+
1944 * ----------------------------------------------------------- */
+
1945
+
1952void *fuse_req_userdata(fuse_req_t req);
+
1953
+
1963const struct fuse_ctx *fuse_req_ctx(fuse_req_t req);
+
1964
+
1984int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]);
+
1985
+
1992typedef void (*fuse_interrupt_func_t)(fuse_req_t req, void *data);
+
1993
+ +
2006 void *data);
+
2007
+ +
2015
+
2016
+
2017/* ----------------------------------------------------------- *
+
2018 * Inquiry functions *
+
2019 * ----------------------------------------------------------- */
+
2020
+
2024void fuse_lowlevel_version(void);
+
2025
+
2031void fuse_lowlevel_help(void);
+
2032
+
2036void fuse_cmdline_help(void);
+
2037
+
2038/* ----------------------------------------------------------- *
+
2039 * Filesystem setup & teardown *
+
2040 * ----------------------------------------------------------- */
+
2041
+
2047struct fuse_cmdline_opts {
+
2048 int singlethread;
+
2049 int foreground;
+
2050 int debug;
+
2051 int nodefault_subtype;
+
2052 char *mountpoint;
+
2053 int show_version;
+
2054 int show_help;
+
2055 int clone_fd;
+
2056 unsigned int max_idle_threads; /* discouraged, due to thread
+
2057 * destruct overhead */
+
2058
+
2059 /* Added in libfuse-3.12 */
+
2060 unsigned int max_threads;
+
2061};
+
2062
+
2081#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2082int fuse_parse_cmdline(struct fuse_args *args,
+
2083 struct fuse_cmdline_opts *opts);
+
2084#else
+
2085#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2086int fuse_parse_cmdline_30(struct fuse_args *args,
+
2087 struct fuse_cmdline_opts *opts);
+
2088#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_30(args, opts)
+
2089#else
+
2090int fuse_parse_cmdline_312(struct fuse_args *args,
+
2091 struct fuse_cmdline_opts *opts);
+
2092#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_312(args, opts)
+
2093#endif
+
2094#endif
+
2095
+
2096/* Do not call this directly, use fuse_session_new() instead */
+
2097struct fuse_session *
+
2098fuse_session_new_versioned(struct fuse_args *args,
+
2099 const struct fuse_lowlevel_ops *op, size_t op_size,
+
2100 struct libfuse_version *version, void *userdata);
+
2101
+
2132static inline struct fuse_session *
+
2133fuse_session_new_fn(struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
2134 size_t op_size, void *userdata)
+
2135{
+
2136 struct libfuse_version version = {
+
2137 .major = FUSE_MAJOR_VERSION,
+
2138 .minor = FUSE_MINOR_VERSION,
+
2139 .hotfix = FUSE_HOTFIX_VERSION,
+
2140 .padding = 0
+
2141 };
+
2142
+
2143 return fuse_session_new_versioned(args, op, op_size, &version,
+
2144 userdata);
+
2145}
+
2146#define fuse_session_new(args, op, op_size, userdata) \
+
2147 fuse_session_new_fn(args, op, op_size, userdata)
+
2148
+
2149/*
+
2150 * This should mostly not be called directly, but instead the
+
2151 * fuse_session_custom_io() should be used.
+
2152 */
+
2153int fuse_session_custom_io_317(struct fuse_session *se,
+
2154 const struct fuse_custom_io *io, size_t op_size, int fd);
+
2155
+
2183#if FUSE_MAKE_VERSION(3, 17) <= FUSE_USE_VERSION
+
2184static inline int fuse_session_custom_io(struct fuse_session *se,
+
2185 const struct fuse_custom_io *io, size_t op_size, int fd)
+
2186{
+
2187 return fuse_session_custom_io_317(se, io, op_size, fd);
+
2188}
+
2189#else
+
2190static inline int fuse_session_custom_io(struct fuse_session *se,
+
2191 const struct fuse_custom_io *io, int fd)
+
2192{
+
2193 return fuse_session_custom_io_317(se, io,
+
2194 offsetof(struct fuse_custom_io, clone_fd), fd);
+
2195}
+
2196#endif
+
2197
+
2206int fuse_session_mount(struct fuse_session *se, const char *mountpoint);
+
2207
+
2230int fuse_session_loop(struct fuse_session *se);
+
2231
+
2232#if FUSE_USE_VERSION < 32
+
2233 int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
2234 #define fuse_session_loop_mt(se, clone_fd) fuse_session_loop_mt_31(se, clone_fd)
+
2235#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2236 int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config *config);
+
2237 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_32(se, config)
+
2238#else
+
2239 #if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2251 int fuse_session_loop_mt(struct fuse_session *se, struct fuse_loop_config *config);
+
2252 #else
+
2253 int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
2254 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_312(se, config)
+
2255 #endif
+
2256#endif
+
2257
+
2270void fuse_session_exit(struct fuse_session *se);
+
2271
+
2277void fuse_session_reset(struct fuse_session *se);
+
2278
+
2285int fuse_session_exited(struct fuse_session *se);
+
2286
+
2311void fuse_session_unmount(struct fuse_session *se);
+
2312
+
2318void fuse_session_destroy(struct fuse_session *se);
+
2319
+
2320/* ----------------------------------------------------------- *
+
2321 * Custom event loop support *
+
2322 * ----------------------------------------------------------- */
+
2323
+
2338int fuse_session_fd(struct fuse_session *se);
+
2339
+
2348void fuse_session_process_buf(struct fuse_session *se,
+
2349 const struct fuse_buf *buf);
+
2350
+
2362int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf);
+
2363
+ +
2368
+
2384int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz,
+
2385 void **mr);
+
2386
+
2387#ifdef __cplusplus
+
2388}
+
2389#endif
+
2390
+
2391#endif /* FUSE_LOWLEVEL_H_ */
+
fuse_buf_copy_flags
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
bool fuse_req_is_uring(fuse_req_t req)
+
int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, void **mr)
+
int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+ + + + + + + + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
int32_t backing_id
+ + + +
void(* flock)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op)
+
void(* write)(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* lseek)(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi)
+
void(* listxattr)(fuse_req_t req, fuse_ino_t ino, size_t size)
+
void(* removexattr)(fuse_req_t req, fuse_ino_t ino, const char *name)
+
void(* forget_multi)(fuse_req_t req, size_t count, struct fuse_forget_data *forgets)
+
void(* retrieve_reply)(fuse_req_t req, void *cookie, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv)
+
void(* create)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi)
+
void(* mkdir)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
+
void(* symlink)(fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
+
void(* write_buf)(fuse_req_t req, fuse_ino_t ino, struct fuse_bufvec *bufv, off_t off, struct fuse_file_info *fi)
+
void(* rmdir)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* link)(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
+
void(* rename)(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
+
void(* copy_file_range)(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags)
+
void(* poll)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
void(* opendir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* mknod)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
+
void(* fallocate)(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi)
+
void(* setattr)(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
+
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* fsync)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* destroy)(void *userdata)
+
void(* forget)(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
void(* getattr)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* ioctl)(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
void(* open)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* getxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
+
void(* fsyncdir)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* read)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* setxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags)
+
void(* release)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* access)(fuse_req_t req, fuse_ino_t ino, int mask)
+
void(* releasedir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* statx)(fuse_req_t req, fuse_ino_t ino, int flags, int mask, struct fuse_file_info *fi)
+
void(* tmpfile)(fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi)
+
void(* bmap)(fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx)
+
void(* readlink)(fuse_req_t req, fuse_ino_t ino)
+
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* statfs)(fuse_req_t req, fuse_ino_t ino)
+
void(* readdir)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+
void(* readdirplus)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* flush)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* unlink)(fuse_req_t req, fuse_ino_t parent, const char *name)
+ +
+ + + + diff --git a/doc/html/fuse-3_818_82_2include_2fuse__mount__compat_8h_source.html b/doc/html/fuse-3_818_82_2include_2fuse__mount__compat_8h_source.html new file mode 100644 index 0000000..82897fc --- /dev/null +++ b/doc/html/fuse-3_818_82_2include_2fuse__mount__compat_8h_source.html @@ -0,0 +1,109 @@ + + + + + + + +libfuse: fuse-3.18.2/include/fuse_mount_compat.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_mount_compat.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2023 Giulio Benetti <giulio.benetti@benettiengineering.com>
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LICENSE
+
9*/
+
10
+
11#ifndef FUSE_MOUNT_COMPAT_H_
+
12#define FUSE_MOUNT_COMPAT_H_
+
13
+
14#include <sys/mount.h>
+
15
+
16/* Some libc don't define MS_*, so define them manually
+
17 * (values taken from https://elixir.bootlin.com/linux/v6.10/source/include/uapi/linux/mount.h#L13 on)
+
18 */
+
19#ifndef MS_DIRSYNC
+
20#define MS_DIRSYNC 128
+
21#endif
+
22
+
23#ifndef MS_NOSYMFOLLOW
+
24#define MS_NOSYMFOLLOW 256
+
25#endif
+
26
+
27#ifndef MS_REC
+
28#define MS_REC 16384
+
29#endif
+
30
+
31#ifndef MS_PRIVATE
+
32#define MS_PRIVATE (1<<18)
+
33#endif
+
34
+
35#ifndef MS_LAZYTIME
+
36#define MS_LAZYTIME (1<<25)
+
37#endif
+
38
+
39#ifndef UMOUNT_DETACH
+
40#define UMOUNT_DETACH 0x00000002 /* Just detach from the tree */
+
41#endif
+
42#ifndef UMOUNT_NOFOLLOW
+
43#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */
+
44#endif
+
45#ifndef UMOUNT_UNUSED
+
46#define UMOUNT_UNUSED 0x80000000 /* Flag guaranteed to be unused */
+
47#endif
+
48
+
49#endif /* FUSE_MOUNT_COMPAT_H_ */
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2include_2fuse__opt_8h.html b/doc/html/fuse-3_818_82_2include_2fuse__opt_8h.html new file mode 100644 index 0000000..a7c4ce4 --- /dev/null +++ b/doc/html/fuse-3_818_82_2include_2fuse__opt_8h.html @@ -0,0 +1,587 @@ + + + + + + + +libfuse: fuse-3.18.2/include/fuse_opt.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_opt.h File Reference
+
+
+ +

Go to the source code of this file.

+ + + + + + +

+Data Structures

struct  fuse_opt
 
struct  fuse_args
 
+ + + + + + + + + + + + + + + +

+Macros

#define FUSE_OPT_KEY(templ, key)   { templ, -1U, key }
 
#define FUSE_OPT_END   { NULL, 0, 0 }
 
#define FUSE_ARGS_INIT(argc, argv)   { argc, argv, 0 }
 
#define FUSE_OPT_KEY_OPT   -1
 
#define FUSE_OPT_KEY_NONOPT   -2
 
#define FUSE_OPT_KEY_KEEP   -3
 
#define FUSE_OPT_KEY_DISCARD   -4
 
+ + + +

+Typedefs

typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
 
+ + + + + + + + + + + + + + + +

+Functions

int fuse_opt_parse (struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
 
int fuse_opt_add_opt (char **opts, const char *opt)
 
int fuse_opt_add_opt_escaped (char **opts, const char *opt)
 
int fuse_opt_add_arg (struct fuse_args *args, const char *arg)
 
int fuse_opt_insert_arg (struct fuse_args *args, int pos, const char *arg)
 
void fuse_opt_free_args (struct fuse_args *args)
 
int fuse_opt_match (const struct fuse_opt opts[], const char *opt)
 
+

Detailed Description

+

This file defines the option parsing interface of FUSE

+ +

Definition in file fuse_opt.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ARGS_INIT

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_ARGS_INIT( argc,
 argv 
)   { argc, argv, 0 }
+
+

Initializer for 'struct fuse_args'

+ +

Definition at line 123 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_END

+ +
+
+ + + + +
#define FUSE_OPT_END   { NULL, 0, 0 }
+
+

Last option. An array of 'struct fuse_opt' must end with a NULL template value

+ +

Definition at line 104 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_OPT_KEY( templ,
 key 
)   { templ, -1U, key }
+
+

Key option. In case of a match, the processing function will be called with the specified key.

+ +

Definition at line 98 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_DISCARD

+ +
+
+ + + + +
#define FUSE_OPT_KEY_DISCARD   -4
+
+

Special key value for options to discard

+

Argument is not passed to processing function, but behave as if the processing function returned zero

+ +

Definition at line 153 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_KEEP

+ +
+
+ + + + +
#define FUSE_OPT_KEY_KEEP   -3
+
+

Special key value for options to keep

+

Argument is not passed to processing function, but behave as if the processing function returned 1

+ +

Definition at line 145 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_NONOPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_NONOPT   -2
+
+

Key value passed to the processing function for all non-options

+

Non-options are the arguments beginning with a character other than '-' or all arguments after the special '–' option

+ +

Definition at line 137 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_OPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_OPT   -1
+
+

Key value passed to the processing function if an option did not match any template

+ +

Definition at line 129 of file fuse_opt.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_opt_proc_t

+ +
+
+ + + + +
typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
+
+

Processing function

+

This function is called if

    +
  • option did not match any 'struct fuse_opt'
  • +
  • argument is a non-option
  • +
  • option did match and offset was set to -1
  • +
+

The 'arg' parameter will always contain the whole argument or option including the parameter if exists. A two-argument option ("-x foo") is always converted to single argument option of the form "-xfoo" before this function is called.

+

Options of the form '-ofoo' are passed to this function without the '-o' prefix.

+

The return value of this function determines whether this argument is to be inserted into the output argument vector, or discarded.

+
Parameters
+ + + + + +
datais the user data passed to the fuse_opt_parse() function
argis the whole argument or option
keydetermines why the processing function was called
outargsthe current output argument list
+
+
+
Returns
-1 on error, 0 if arg is to be discarded, 1 if arg should be kept
+ +

Definition at line 180 of file fuse_opt.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_opt_add_arg()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_arg (struct fuse_argsargs,
const char * arg 
)
+
+

Add an argument to a NULL terminated argument vector

+
Parameters
+ + + +
argsis the structure containing the current argument list
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 55 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_opt (char ** opts,
const char * opt 
)
+
+

Add an option to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 139 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt_escaped()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_opt_escaped (char ** opts,
const char * opt 
)
+
+

Add an option, escaping commas, to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 144 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_free_args()

+ +
+
+ + + + + + + + +
void fuse_opt_free_args (struct fuse_argsargs)
+
+

Free the contents of argument list

+

The structure itself is not freed

+
Parameters
+ + +
argsis the structure containing the argument list
+
+
+ +

Definition at line 34 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_insert_arg()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_insert_arg (struct fuse_argsargs,
int pos,
const char * arg 
)
+
+

Add an argument at the specified position in a NULL terminated argument vector

+

Adds the argument to the N-th position. This is useful for adding options at the beginning of the array which must not come after the special '–' option.

+
Parameters
+ + + + +
argsis the structure containing the current argument list
posis the position at which to add the argument
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 95 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_match()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_match (const struct fuse_opt opts[],
const char * opt 
)
+
+

Check if an option matches

+
Parameters
+ + + +
optsis the option description array
optis the option to match
+
+
+
Returns
1 if a match is found, 0 if not
+ +
+
+ +

◆ fuse_opt_parse()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_parse (struct fuse_argsargs,
void * data,
const struct fuse_opt opts[],
fuse_opt_proc_t proc 
)
+
+

Option parsing function

+

If 'args' was returned from a previous call to fuse_opt_parse() or it was constructed from

+

A NULL 'args' is equivalent to an empty argument vector

+

A NULL 'opts' is equivalent to an 'opts' array containing a single end marker

+

A NULL 'proc' is equivalent to a processing function always returning '1'

+
Parameters
+ + + + + +
argsis the input and output argument list
datais the user data
optsis the option description array
procis the processing function
+
+
+
Returns
-1 on error, 0 on success
+ +

Definition at line 398 of file fuse_opt.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2include_2fuse__opt_8h_source.html b/doc/html/fuse-3_818_82_2include_2fuse__opt_8h_source.html new file mode 100644 index 0000000..f398d39 --- /dev/null +++ b/doc/html/fuse-3_818_82_2include_2fuse__opt_8h_source.html @@ -0,0 +1,145 @@ + + + + + + + +libfuse: fuse-3.18.2/include/fuse_opt.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_opt.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#ifndef FUSE_OPT_H_
+
10#define FUSE_OPT_H_
+
11
+
17#ifdef __cplusplus
+
18extern "C" {
+
19#endif
+
20
+
77struct fuse_opt {
+
79 const char *templ;
+
80
+
85 unsigned long offset;
+
86
+
91 int value;
+
92};
+
93
+
98#define FUSE_OPT_KEY(templ, key) { templ, -1U, key }
+
99
+
104#define FUSE_OPT_END { NULL, 0, 0 }
+
105
+
109struct fuse_args {
+
111 int argc;
+
112
+
114 char **argv;
+
115
+
117 int allocated;
+
118};
+
119
+
123#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
+
124
+
129#define FUSE_OPT_KEY_OPT -1
+
130
+
137#define FUSE_OPT_KEY_NONOPT -2
+
138
+
145#define FUSE_OPT_KEY_KEEP -3
+
146
+
153#define FUSE_OPT_KEY_DISCARD -4
+
154
+
180typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key,
+
181 struct fuse_args *outargs);
+
182
+
203int fuse_opt_parse(struct fuse_args *args, void *data,
+
204 const struct fuse_opt opts[], fuse_opt_proc_t proc);
+
205
+
213int fuse_opt_add_opt(char **opts, const char *opt);
+
214
+
222int fuse_opt_add_opt_escaped(char **opts, const char *opt);
+
223
+
231int fuse_opt_add_arg(struct fuse_args *args, const char *arg);
+
232
+
246int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg);
+
247
+
255void fuse_opt_free_args(struct fuse_args *args);
+
256
+
257
+
265int fuse_opt_match(const struct fuse_opt opts[], const char *opt);
+
266
+
267#ifdef __cplusplus
+
268}
+
269#endif
+
270
+
271#endif /* FUSE_OPT_H_ */
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
unsigned long offset
Definition fuse_opt.h:85
+
const char * templ
Definition fuse_opt.h:79
+
int value
Definition fuse_opt.h:91
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2buffer_8c_source.html b/doc/html/fuse-3_818_82_2lib_2buffer_8c_source.html new file mode 100644 index 0000000..9907a12 --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2buffer_8c_source.html @@ -0,0 +1,406 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/buffer.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
buffer.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2010 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Functions for dealing with `struct fuse_buf` and `struct
+
6 fuse_bufvec`.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file LGPL2.txt
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_lowlevel.h"
+
17#include <string.h>
+
18#include <unistd.h>
+
19#include <errno.h>
+
20#include <assert.h>
+
21
+
22size_t fuse_buf_size(const struct fuse_bufvec *bufv)
+
23{
+
24 size_t i;
+
25 size_t size = 0;
+
26
+
27 for (i = 0; i < bufv->count; i++) {
+
28 if (bufv->buf[i].size >= SIZE_MAX - size)
+
29 return SIZE_MAX;
+
30
+
31 size += bufv->buf[i].size;
+
32 }
+
33
+
34 return size;
+
35}
+
36
+
37static size_t min_size(size_t s1, size_t s2)
+
38{
+
39 return s1 < s2 ? s1 : s2;
+
40}
+
41
+
42static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off,
+
43 const struct fuse_buf *src, size_t src_off,
+
44 size_t len)
+
45{
+
46 ssize_t res = 0;
+
47 size_t copied = 0;
+
48
+
49 while (len) {
+
50 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
51 res = pwrite(dst->fd, (char *)src->mem + src_off, len,
+
52 dst->pos + dst_off);
+
53 } else {
+
54 res = write(dst->fd, (char *)src->mem + src_off, len);
+
55 }
+
56 if (res == -1) {
+
57 if (!copied)
+
58 return -errno;
+
59 break;
+
60 }
+
61 if (res == 0)
+
62 break;
+
63
+
64 copied += res;
+
65 if (!(dst->flags & FUSE_BUF_FD_RETRY))
+
66 break;
+
67
+
68 src_off += res;
+
69 dst_off += res;
+
70 len -= res;
+
71 }
+
72
+
73 return copied;
+
74}
+
75
+
76static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off,
+
77 const struct fuse_buf *src, size_t src_off,
+
78 size_t len)
+
79{
+
80 ssize_t res = 0;
+
81 size_t copied = 0;
+
82
+
83 while (len) {
+
84 if (src->flags & FUSE_BUF_FD_SEEK) {
+
85 res = pread(src->fd, (char *)dst->mem + dst_off, len,
+
86 src->pos + src_off);
+
87 } else {
+
88 res = read(src->fd, (char *)dst->mem + dst_off, len);
+
89 }
+
90 if (res == -1) {
+
91 if (!copied)
+
92 return -errno;
+
93 break;
+
94 }
+
95 if (res == 0)
+
96 break;
+
97
+
98 copied += res;
+
99 if (!(src->flags & FUSE_BUF_FD_RETRY))
+
100 break;
+
101
+
102 dst_off += res;
+
103 src_off += res;
+
104 len -= res;
+
105 }
+
106
+
107 return copied;
+
108}
+
109
+
110static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
+
111 const struct fuse_buf *src, size_t src_off,
+
112 size_t len)
+
113{
+
114 char buf[4096];
+
115 struct fuse_buf tmp = {
+
116 .size = sizeof(buf),
+
117 .flags = 0,
+
118 };
+
119 ssize_t res;
+
120 size_t copied = 0;
+
121
+
122 tmp.mem = buf;
+
123
+
124 while (len) {
+
125 size_t this_len = min_size(tmp.size, len);
+
126 size_t read_len;
+
127
+
128 res = fuse_buf_read(&tmp, 0, src, src_off, this_len);
+
129 if (res < 0) {
+
130 if (!copied)
+
131 return res;
+
132 break;
+
133 }
+
134 if (res == 0)
+
135 break;
+
136
+
137 read_len = res;
+
138 res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len);
+
139 if (res < 0) {
+
140 if (!copied)
+
141 return res;
+
142 break;
+
143 }
+
144 if (res == 0)
+
145 break;
+
146
+
147 copied += res;
+
148
+
149 if (res < this_len)
+
150 break;
+
151
+
152 dst_off += res;
+
153 src_off += res;
+
154 len -= res;
+
155 }
+
156
+
157 return copied;
+
158}
+
159
+
160#ifdef HAVE_SPLICE
+
161static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
162 const struct fuse_buf *src, size_t src_off,
+
163 size_t len, enum fuse_buf_copy_flags flags)
+
164{
+
165 int splice_flags = 0;
+
166 off_t *srcpos = NULL;
+
167 off_t *dstpos = NULL;
+
168 off_t srcpos_val;
+
169 off_t dstpos_val;
+
170 ssize_t res;
+
171 size_t copied = 0;
+
172
+ +
174 splice_flags |= SPLICE_F_MOVE;
+ +
176 splice_flags |= SPLICE_F_NONBLOCK;
+
177
+
178 if (src->flags & FUSE_BUF_FD_SEEK) {
+
179 srcpos_val = src->pos + src_off;
+
180 srcpos = &srcpos_val;
+
181 }
+
182 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
183 dstpos_val = dst->pos + dst_off;
+
184 dstpos = &dstpos_val;
+
185 }
+
186
+
187 while (len) {
+
188 res = splice(src->fd, srcpos, dst->fd, dstpos, len,
+
189 splice_flags);
+
190 if (res == -1) {
+
191 if (copied)
+
192 break;
+
193
+
194 if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE))
+
195 return -errno;
+
196
+
197 /* Maybe splice is not supported for this combination */
+
198 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off,
+
199 len);
+
200 }
+
201 if (res == 0)
+
202 break;
+
203
+
204 copied += res;
+
205 if (!(src->flags & FUSE_BUF_FD_RETRY) &&
+
206 !(dst->flags & FUSE_BUF_FD_RETRY)) {
+
207 break;
+
208 }
+
209
+
210 len -= res;
+
211 }
+
212
+
213 return copied;
+
214}
+
215#else
+
216static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
217 const struct fuse_buf *src, size_t src_off,
+
218 size_t len, enum fuse_buf_copy_flags flags)
+
219{
+
220 (void) flags;
+
221
+
222 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
223}
+
224#endif
+
225
+
226
+
227static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
+
228 const struct fuse_buf *src, size_t src_off,
+
229 size_t len, enum fuse_buf_copy_flags flags)
+
230{
+
231 int src_is_fd = src->flags & FUSE_BUF_IS_FD;
+
232 int dst_is_fd = dst->flags & FUSE_BUF_IS_FD;
+
233
+
234 if (!src_is_fd && !dst_is_fd) {
+
235 char *dstmem = (char *)dst->mem + dst_off;
+
236 char *srcmem = (char *)src->mem + src_off;
+
237
+
238 if (dstmem != srcmem) {
+
239 if (dstmem + len <= srcmem || srcmem + len <= dstmem)
+
240 memcpy(dstmem, srcmem, len);
+
241 else
+
242 memmove(dstmem, srcmem, len);
+
243 }
+
244
+
245 return len;
+
246 } else if (!src_is_fd) {
+
247 return fuse_buf_write(dst, dst_off, src, src_off, len);
+
248 } else if (!dst_is_fd) {
+
249 return fuse_buf_read(dst, dst_off, src, src_off, len);
+
250 } else if (flags & FUSE_BUF_NO_SPLICE) {
+
251 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
252 } else {
+
253 return fuse_buf_splice(dst, dst_off, src, src_off, len, flags);
+
254 }
+
255}
+
256
+
257static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv)
+
258{
+
259 if (bufv->idx < bufv->count)
+
260 return &bufv->buf[bufv->idx];
+
261 else
+
262 return NULL;
+
263}
+
264
+
265static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len)
+
266{
+
267 const struct fuse_buf *buf = fuse_bufvec_current(bufv);
+
268
+
269 if (!buf)
+
270 return 0;
+
271
+
272 bufv->off += len;
+
273 assert(bufv->off <= buf->size);
+
274 if (bufv->off == buf->size) {
+
275 assert(bufv->idx < bufv->count);
+
276 bufv->idx++;
+
277 if (bufv->idx == bufv->count)
+
278 return 0;
+
279 bufv->off = 0;
+
280 }
+
281 return 1;
+
282}
+
283
+
284ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
+ +
286{
+
287 size_t copied = 0;
+
288
+
289 if (dstv == srcv)
+
290 return fuse_buf_size(dstv);
+
291
+
292 for (;;) {
+
293 const struct fuse_buf *src = fuse_bufvec_current(srcv);
+
294 const struct fuse_buf *dst = fuse_bufvec_current(dstv);
+
295 size_t src_len;
+
296 size_t dst_len;
+
297 size_t len;
+
298 ssize_t res;
+
299
+
300 if (src == NULL || dst == NULL)
+
301 break;
+
302
+
303 src_len = src->size - srcv->off;
+
304 dst_len = dst->size - dstv->off;
+
305 len = min_size(src_len, dst_len);
+
306
+
307 res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
+
308 if (res < 0) {
+
309 if (!copied)
+
310 return res;
+
311 break;
+
312 }
+
313 copied += res;
+
314
+
315 if (!fuse_bufvec_advance(srcv, res) ||
+
316 !fuse_bufvec_advance(dstv, res))
+
317 break;
+
318
+
319 if (res < len)
+
320 break;
+
321 }
+
322
+
323 return copied;
+
324}
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+ +
enum fuse_buf_flags flags
+ +
off_t pos
+
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2compat_8c_source.html b/doc/html/fuse-3_818_82_2lib_2compat_8c_source.html new file mode 100644 index 0000000..979dacf --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2compat_8c_source.html @@ -0,0 +1,148 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/compat.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
compat.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file LGPL2.txt.
+
11*/
+
12
+
13/* Description:
+
14 This file has compatibility symbols for platforms that do not
+
15 support version symboling
+
16*/
+
17
+
18#include "libfuse_config.h"
+
19
+
20#include <stddef.h>
+
21#include <stdint.h>
+
22
+
23struct fuse_args;
+ + +
26struct fuse_session;
+
27struct fuse_custom_io;
+
28struct fuse_operations;
+ +
30
+
34#if (!defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
35/* With current libfuse fuse_parse_cmdline is a macro pointing to the
+
36 * versioned function. Here in this file we need to provide the ABI symbol
+
37 * and the redirecting macro is conflicting.
+
38 */
+
39#ifdef fuse_parse_cmdline
+
40#undef fuse_parse_cmdline
+
41#endif
+
42int fuse_parse_cmdline_30(struct fuse_args *args,
+
43 struct fuse_cmdline_opts *opts);
+
44int fuse_parse_cmdline(struct fuse_args *args,
+
45 struct fuse_cmdline_opts *opts);
+
46int fuse_parse_cmdline(struct fuse_args *args,
+
47 struct fuse_cmdline_opts *opts)
+
48{
+
49 return fuse_parse_cmdline_30(args, opts);
+
50}
+
51
+
52int fuse_session_custom_io_30(struct fuse_session *se,
+
53 const struct fuse_custom_io *io, int fd);
+
54int fuse_session_custom_io(struct fuse_session *se,
+
55 const struct fuse_custom_io *io, int fd);
+
56int fuse_session_custom_io(struct fuse_session *se,
+
57 const struct fuse_custom_io *io, int fd)
+
58
+
59{
+
60 return fuse_session_custom_io_30(se, io, fd);
+
61}
+
62
+
63#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
64
+
65int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
66 size_t op_size, void *user_data);
+
67int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
68 size_t op_size, void *user_data);
+
69int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
70 size_t op_size, void *user_data)
+
71{
+
72 return fuse_main_real_30(argc, argv, op, op_size, user_data);
+
73}
+
74
+
75struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
76 const struct fuse_lowlevel_ops *op,
+
77 size_t op_size, void *userdata);
+
78struct fuse_session *fuse_session_new(struct fuse_args *args,
+
79 const struct fuse_lowlevel_ops *op,
+
80 size_t op_size, void *userdata);
+
81struct fuse_session *fuse_session_new(struct fuse_args *args,
+
82 const struct fuse_lowlevel_ops *op,
+
83 size_t op_size, void *userdata)
+
84{
+
85 return fuse_session_new_30(args, op, op_size, userdata);
+
86}
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2cuse__lowlevel_8c_source.html b/doc/html/fuse-3_818_82_2lib_2cuse__lowlevel_8c_source.html new file mode 100644 index 0000000..9ef97b1 --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2cuse__lowlevel_8c_source.html @@ -0,0 +1,458 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/cuse_lowlevel.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_lowlevel.c
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file LGPL2.txt.
+
8*/
+
9
+
10#include "fuse_config.h"
+
11#include "cuse_lowlevel.h"
+
12#include "fuse_kernel.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15
+
16#include <stdio.h>
+
17#include <string.h>
+
18#include <stdlib.h>
+
19#include <stddef.h>
+
20#include <errno.h>
+
21#include <unistd.h>
+
22
+
23struct cuse_data {
+
24 struct cuse_lowlevel_ops clop;
+
25 unsigned max_read;
+
26 unsigned dev_major;
+
27 unsigned dev_minor;
+
28 unsigned flags;
+
29 unsigned dev_info_len;
+
30 char dev_info[];
+
31};
+
32
+
33static struct cuse_lowlevel_ops *req_clop(fuse_req_t req)
+
34{
+
35 return &req->se->cuse_data->clop;
+
36}
+
37
+
38static void cuse_fll_open(fuse_req_t req, fuse_ino_t ino,
+
39 struct fuse_file_info *fi)
+
40{
+
41 (void)ino;
+
42 req_clop(req)->open(req, fi);
+
43}
+
44
+
45static void cuse_fll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
46 off_t off, struct fuse_file_info *fi)
+
47{
+
48 (void)ino;
+
49 req_clop(req)->read(req, size, off, fi);
+
50}
+
51
+
52static void cuse_fll_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+
53 size_t size, off_t off, struct fuse_file_info *fi)
+
54{
+
55 (void)ino;
+
56 req_clop(req)->write(req, buf, size, off, fi);
+
57}
+
58
+
59static void cuse_fll_flush(fuse_req_t req, fuse_ino_t ino,
+
60 struct fuse_file_info *fi)
+
61{
+
62 (void)ino;
+
63 req_clop(req)->flush(req, fi);
+
64}
+
65
+
66static void cuse_fll_release(fuse_req_t req, fuse_ino_t ino,
+
67 struct fuse_file_info *fi)
+
68{
+
69 (void)ino;
+
70 req_clop(req)->release(req, fi);
+
71}
+
72
+
73static void cuse_fll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
74 struct fuse_file_info *fi)
+
75{
+
76 (void)ino;
+
77 req_clop(req)->fsync(req, datasync, fi);
+
78}
+
79
+
80static void cuse_fll_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg,
+
81 struct fuse_file_info *fi, unsigned int flags,
+
82 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
83{
+
84 (void)ino;
+
85 req_clop(req)->ioctl(req, cmd, arg, fi, flags, in_buf, in_bufsz,
+
86 out_bufsz);
+
87}
+
88
+
89static void cuse_fll_poll(fuse_req_t req, fuse_ino_t ino,
+
90 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
91{
+
92 (void)ino;
+
93 req_clop(req)->poll(req, fi, ph);
+
94}
+
95
+
96static size_t cuse_pack_info(int argc, const char **argv, char *buf)
+
97{
+
98 size_t size = 0;
+
99 int i;
+
100
+
101 for (i = 0; i < argc; i++) {
+
102 size_t len;
+
103
+
104 len = strlen(argv[i]) + 1;
+
105 size += len;
+
106 if (buf) {
+
107 memcpy(buf, argv[i], len);
+
108 buf += len;
+
109 }
+
110 }
+
111
+
112 return size;
+
113}
+
114
+
115static struct cuse_data *cuse_prep_data(const struct cuse_info *ci,
+
116 const struct cuse_lowlevel_ops *clop)
+
117{
+
118 struct cuse_data *cd;
+
119 size_t dev_info_len;
+
120
+
121 dev_info_len = cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv,
+
122 NULL);
+
123
+
124 if (dev_info_len > CUSE_INIT_INFO_MAX) {
+
125 fuse_log(FUSE_LOG_ERR, "cuse: dev_info (%zu) too large, limit=%u\n",
+
126 dev_info_len, CUSE_INIT_INFO_MAX);
+
127 return NULL;
+
128 }
+
129
+
130 cd = calloc(1, sizeof(*cd) + dev_info_len);
+
131 if (!cd) {
+
132 fuse_log(FUSE_LOG_ERR, "cuse: failed to allocate cuse_data\n");
+
133 return NULL;
+
134 }
+
135
+
136 memcpy(&cd->clop, clop, sizeof(cd->clop));
+
137 cd->max_read = 131072;
+
138 cd->dev_major = ci->dev_major;
+
139 cd->dev_minor = ci->dev_minor;
+
140 cd->dev_info_len = dev_info_len;
+
141 cd->flags = ci->flags;
+
142 cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info);
+
143
+
144 return cd;
+
145}
+
146
+
147struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
148 const struct cuse_info *ci,
+
149 const struct cuse_lowlevel_ops *clop,
+
150 void *userdata)
+
151{
+
152 struct fuse_lowlevel_ops lop;
+
153 struct cuse_data *cd;
+
154 struct fuse_session *se;
+
155
+
156 cd = cuse_prep_data(ci, clop);
+
157 if (!cd)
+
158 return NULL;
+
159
+
160 memset(&lop, 0, sizeof(lop));
+
161 lop.init = clop->init;
+
162 lop.destroy = clop->destroy;
+
163 lop.open = clop->open ? cuse_fll_open : NULL;
+
164 lop.read = clop->read ? cuse_fll_read : NULL;
+
165 lop.write = clop->write ? cuse_fll_write : NULL;
+
166 lop.flush = clop->flush ? cuse_fll_flush : NULL;
+
167 lop.release = clop->release ? cuse_fll_release : NULL;
+
168 lop.fsync = clop->fsync ? cuse_fll_fsync : NULL;
+
169 lop.ioctl = clop->ioctl ? cuse_fll_ioctl : NULL;
+
170 lop.poll = clop->poll ? cuse_fll_poll : NULL;
+
171
+
172 se = fuse_session_new(args, &lop, sizeof(lop), userdata);
+
173 if (!se) {
+
174 free(cd);
+
175 return NULL;
+
176 }
+
177 se->cuse_data = cd;
+
178
+
179 return se;
+
180}
+
181
+
182static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg,
+
183 char *dev_info, unsigned dev_info_len)
+
184{
+
185 struct iovec iov[3];
+
186
+
187 iov[1].iov_base = arg;
+
188 iov[1].iov_len = sizeof(struct cuse_init_out);
+
189 iov[2].iov_base = dev_info;
+
190 iov[2].iov_len = dev_info_len;
+
191
+
192 return fuse_send_reply_iov_nofree(req, 0, iov, 3);
+
193}
+
194
+
195void _cuse_lowlevel_init(fuse_req_t req, const fuse_ino_t nodeid,
+
196 const void *op_in, const void *req_payload)
+
197{
+
198 const struct fuse_init_in *arg = op_in;
+
199 (void)req_payload;
+
200 struct cuse_init_out outarg;
+
201 struct fuse_session *se = req->se;
+
202 struct cuse_data *cd = se->cuse_data;
+
203 size_t bufsize = se->bufsize;
+
204 struct cuse_lowlevel_ops *clop = req_clop(req);
+
205
+
206 (void) nodeid;
+
207 if (se->debug) {
+
208 fuse_log(FUSE_LOG_DEBUG, "CUSE_INIT: %u.%u\n", arg->major, arg->minor);
+
209 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
210 }
+
211 se->conn.proto_major = arg->major;
+
212 se->conn.proto_minor = arg->minor;
+
213
+
214 /* XXX This is not right.*/
+
215 se->conn.capable_ext = 0;
+
216 se->conn.want_ext = 0;
+
217
+
218 if (arg->major < 7) {
+
219 fuse_log(FUSE_LOG_ERR, "cuse: unsupported protocol version: %u.%u\n",
+
220 arg->major, arg->minor);
+
221 fuse_reply_err(req, EPROTO);
+
222 return;
+
223 }
+
224
+
225 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
226 fuse_log(FUSE_LOG_ERR, "cuse: warning: buffer size too small: %zu\n",
+
227 bufsize);
+
228 bufsize = FUSE_MIN_READ_BUFFER;
+
229 }
+
230
+
231 bufsize -= 4096;
+
232 if (bufsize < se->conn.max_write)
+
233 se->conn.max_write = bufsize;
+
234
+
235 se->got_init = 1;
+
236 if (se->op.init)
+
237 se->op.init(se->userdata, &se->conn);
+
238
+
239 memset(&outarg, 0, sizeof(outarg));
+
240 outarg.major = FUSE_KERNEL_VERSION;
+
241 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
242 outarg.flags = cd->flags;
+
243 outarg.max_read = cd->max_read;
+
244 outarg.max_write = se->conn.max_write;
+
245 outarg.dev_major = cd->dev_major;
+
246 outarg.dev_minor = cd->dev_minor;
+
247
+
248 if (se->debug) {
+
249 fuse_log(FUSE_LOG_DEBUG, " CUSE_INIT: %u.%u\n",
+
250 outarg.major, outarg.minor);
+
251 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
252 fuse_log(FUSE_LOG_DEBUG, " max_read=0x%08x\n", outarg.max_read);
+
253 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
254 fuse_log(FUSE_LOG_DEBUG, " dev_major=%u\n", outarg.dev_major);
+
255 fuse_log(FUSE_LOG_DEBUG, " dev_minor=%u\n", outarg.dev_minor);
+
256 fuse_log(FUSE_LOG_DEBUG, " dev_info: %.*s\n", cd->dev_info_len,
+
257 cd->dev_info);
+
258 }
+
259
+
260 cuse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len);
+
261
+
262 if (clop->init_done)
+
263 clop->init_done(se->userdata);
+
264
+
265 fuse_free_req(req);
+
266}
+
267
+
268void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
269{
+
270 _cuse_lowlevel_init(req, nodeid, inarg, NULL);
+
271}
+
272
+
273struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
274 const struct cuse_info *ci,
+
275 const struct cuse_lowlevel_ops *clop,
+
276 int *multithreaded, void *userdata)
+
277{
+
278 const char *devname = "/dev/cuse";
+
279 static const struct fuse_opt kill_subtype_opts[] = {
+ + +
282 };
+
283 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
284 struct fuse_session *se;
+
285 struct fuse_cmdline_opts opts;
+
286 int fd;
+
287 int res;
+
288
+
289 if (fuse_parse_cmdline(&args, &opts) == -1)
+
290 return NULL;
+
291 *multithreaded = !opts.singlethread;
+
292
+
293 /* Remove subtype= option */
+
294 res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL);
+
295 if (res == -1)
+
296 goto out1;
+
297
+
298 /*
+
299 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
300 * would ensue.
+
301 */
+
302 do {
+
303 fd = open("/dev/null", O_RDWR);
+
304 if (fd > 2)
+
305 close(fd);
+
306 } while (fd >= 0 && fd <= 2);
+
307
+
308 se = cuse_lowlevel_new(&args, ci, clop, userdata);
+
309 if (se == NULL)
+
310 goto out1;
+
311
+
312 fd = open(devname, O_RDWR);
+
313 if (fd == -1) {
+
314 if (errno == ENODEV || errno == ENOENT)
+
315 fuse_log(FUSE_LOG_ERR, "cuse: device not found, try 'modprobe cuse' first\n");
+
316 else
+
317 fuse_log(FUSE_LOG_ERR, "cuse: failed to open %s: %s\n",
+
318 devname, strerror(errno));
+
319 goto err_se;
+
320 }
+
321 se->fd = fd;
+
322
+
323 res = fuse_set_signal_handlers(se);
+
324 if (res == -1)
+
325 goto err_se;
+
326
+
327 res = fuse_daemonize(opts.foreground);
+
328 if (res == -1)
+
329 goto err_sig;
+
330
+
331 fuse_opt_free_args(&args);
+
332 return se;
+
333
+
334err_sig:
+ +
336err_se:
+ +
338out1:
+
339 free(opts.mountpoint);
+
340 fuse_opt_free_args(&args);
+
341 return NULL;
+
342}
+
343
+
344void cuse_lowlevel_teardown(struct fuse_session *se)
+
345{
+ + +
348}
+
349
+
350int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
351 const struct cuse_lowlevel_ops *clop, void *userdata)
+
352{
+
353 struct fuse_session *se;
+
354 int multithreaded;
+
355 int res;
+
356
+
357 se = cuse_lowlevel_setup(argc, argv, ci, clop, &multithreaded,
+
358 userdata);
+
359 if (se == NULL)
+
360 return 1;
+
361
+
362 if (multithreaded) {
+
363 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
364 res = fuse_session_loop_mt(se, config);
+
365 fuse_loop_cfg_destroy(config);
+
366 }
+
367 else
+
368 res = fuse_session_loop(se);
+
369
+
370 cuse_lowlevel_teardown(se);
+
371 if (res == -1)
+
372 return 1;
+
373
+
374 return 0;
+
375}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
uint64_t fuse_ino_t
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + + + +
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2fuse_8c_source.html b/doc/html/fuse-3_818_82_2lib_2fuse_8c_source.html new file mode 100644 index 0000000..f7ebd32 --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2fuse_8c_source.html @@ -0,0 +1,5449 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/fuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the high-level FUSE API on top of the low-level
+
6 API.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file LGPL2.txt
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13#include "fuse.h"
+
14#include <pthread.h>
+
15
+
16#include "fuse_config.h"
+
17#include "fuse_i.h"
+
18#include "fuse_lowlevel.h"
+
19#include "fuse_opt.h"
+
20#include "fuse_misc.h"
+
21#include "fuse_kernel.h"
+
22#include "util.h"
+
23
+
24#include <stdint.h>
+
25#include <stdio.h>
+
26#include <string.h>
+
27#include <stdlib.h>
+
28#include <stddef.h>
+
29#include <stdbool.h>
+
30#include <unistd.h>
+
31#include <time.h>
+
32#include <fcntl.h>
+
33#include <limits.h>
+
34#include <errno.h>
+
35#include <signal.h>
+
36#include <dlfcn.h>
+
37#include <assert.h>
+
38#include <poll.h>
+
39#include <sys/param.h>
+
40#include <sys/uio.h>
+
41#include <sys/time.h>
+
42#include <sys/mman.h>
+
43#include <sys/file.h>
+
44
+
45#define FUSE_NODE_SLAB 1
+
46
+
47#ifndef MAP_ANONYMOUS
+
48#undef FUSE_NODE_SLAB
+
49#endif
+
50
+
51#ifndef RENAME_EXCHANGE
+
52#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */
+
53#endif
+
54
+
55#define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1
+
56
+
57#define FUSE_UNKNOWN_INO 0xffffffff
+
58#define OFFSET_MAX 0x7fffffffffffffffLL
+
59
+
60#define NODE_TABLE_MIN_SIZE 8192
+
61
+
62struct fuse_fs {
+
63 struct fuse_operations op;
+
64 void *user_data;
+
65 int debug;
+
66};
+
67
+
68struct fusemod_so {
+
69 void *handle;
+
70 int ctr;
+
71};
+
72
+
73struct lock_queue_element {
+
74 struct lock_queue_element *next;
+
75 pthread_cond_t cond;
+
76 fuse_ino_t nodeid1;
+
77 const char *name1;
+
78 char **path1;
+
79 struct node **wnode1;
+
80 fuse_ino_t nodeid2;
+
81 const char *name2;
+
82 char **path2;
+
83 struct node **wnode2;
+
84 int err;
+
85 bool done : 1;
+
86};
+
87
+
88struct node_table {
+
89 struct node **array;
+
90 size_t use;
+
91 size_t size;
+
92 size_t split;
+
93};
+
94
+
95#define list_entry(ptr, type, member) \
+
96 container_of(ptr, type, member)
+
97
+
98struct list_head {
+
99 struct list_head *next;
+
100 struct list_head *prev;
+
101};
+
102
+
103struct node_slab {
+
104 struct list_head list; /* must be the first member */
+
105 struct list_head freelist;
+
106 int used;
+
107};
+
108
+
109struct fuse {
+
110 struct fuse_session *se;
+
111 struct node_table name_table;
+
112 struct node_table id_table;
+
113 struct list_head lru_table;
+
114 fuse_ino_t ctr;
+
115 unsigned int generation;
+
116 unsigned int hidectr;
+
117 pthread_mutex_t lock;
+
118 struct fuse_config conf;
+
119 int intr_installed;
+
120 struct fuse_fs *fs;
+
121 struct lock_queue_element *lockq;
+
122 int pagesize;
+
123 struct list_head partial_slabs;
+
124 struct list_head full_slabs;
+
125 pthread_t prune_thread;
+
126};
+
127
+
128struct lock {
+
129 int type;
+
130 off_t start;
+
131 off_t end;
+
132 pid_t pid;
+
133 uint64_t owner;
+
134 struct lock *next;
+
135};
+
136
+
137struct node {
+
138 struct node *name_next;
+
139 struct node *id_next;
+
140 fuse_ino_t nodeid;
+
141 unsigned int generation;
+
142 int refctr;
+
143 struct node *parent;
+
144 char *name;
+
145 uint64_t nlookup;
+
146 int open_count;
+
147 struct timespec stat_updated;
+
148 struct timespec mtime;
+
149 off_t size;
+
150 struct lock *locks;
+
151 unsigned int is_hidden : 1;
+
152 unsigned int cache_valid : 1;
+
153 int treelock;
+
154 char inline_name[32];
+
155};
+
156
+
157#define TREELOCK_WRITE -1
+
158#define TREELOCK_WAIT_OFFSET INT_MIN
+
159
+
160struct node_lru {
+
161 struct node node;
+
162 struct list_head lru;
+
163 struct timespec forget_time;
+
164};
+
165
+
166struct fuse_direntry {
+
167 struct stat stat;
+
168 enum fuse_fill_dir_flags flags;
+
169 char *name;
+
170 struct fuse_direntry *next;
+
171};
+
172
+
173struct fuse_dh {
+
174 pthread_mutex_t lock;
+
175 struct fuse *fuse;
+
176 fuse_req_t req;
+
177 char *contents;
+
178 struct fuse_direntry *first;
+
179 struct fuse_direntry **last;
+
180 unsigned len;
+
181 unsigned size;
+
182 unsigned needlen;
+
183 int filled;
+
184 uint64_t fh;
+
185 int error;
+
186 fuse_ino_t nodeid;
+
187};
+
188
+
189struct fuse_context_i {
+
190 struct fuse_context ctx;
+
191 fuse_req_t req;
+
192};
+
193
+
194/* Defined by FUSE_REGISTER_MODULE() in lib/modules/subdir.c and iconv.c. */
+
195extern fuse_module_factory_t fuse_module_subdir_factory;
+
196#ifdef HAVE_ICONV
+
197extern fuse_module_factory_t fuse_module_iconv_factory;
+
198#endif
+
199
+
200static pthread_key_t fuse_context_key;
+
201static pthread_mutex_t fuse_context_lock = PTHREAD_MUTEX_INITIALIZER;
+
202static int fuse_context_ref;
+
203static struct fuse_module *fuse_modules = NULL;
+
204
+
205static int fuse_register_module(const char *name,
+
206 fuse_module_factory_t factory,
+
207 struct fusemod_so *so)
+
208{
+
209 struct fuse_module *mod;
+
210
+
211 mod = calloc(1, sizeof(struct fuse_module));
+
212 if (!mod) {
+
213 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module\n");
+
214 return -1;
+
215 }
+
216 mod->name = strdup(name);
+
217 if (!mod->name) {
+
218 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module name\n");
+
219 free(mod);
+
220 return -1;
+
221 }
+
222 mod->factory = factory;
+
223 mod->ctr = 0;
+
224 mod->so = so;
+
225 if (mod->so)
+
226 mod->so->ctr++;
+
227 mod->next = fuse_modules;
+
228 fuse_modules = mod;
+
229
+
230 return 0;
+
231}
+
232
+
233static void fuse_unregister_module(struct fuse_module *m)
+
234{
+
235 struct fuse_module **mp;
+
236 for (mp = &fuse_modules; *mp; mp = &(*mp)->next) {
+
237 if (*mp == m) {
+
238 *mp = (*mp)->next;
+
239 break;
+
240 }
+
241 }
+
242 free(m->name);
+
243 free(m);
+
244}
+
245
+
246static int fuse_load_so_module(const char *module)
+
247{
+
248 int ret = -1;
+
249 char *tmp;
+
250 struct fusemod_so *so;
+
251 fuse_module_factory_t *factory;
+
252
+
253 tmp = malloc(strlen(module) + 64);
+
254 if (!tmp) {
+
255 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
256 return -1;
+
257 }
+
258 sprintf(tmp, "libfusemod_%s.so", module);
+
259 so = calloc(1, sizeof(struct fusemod_so));
+
260 if (!so) {
+
261 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module so\n");
+
262 goto out;
+
263 }
+
264
+
265 so->handle = dlopen(tmp, RTLD_NOW);
+
266 if (so->handle == NULL) {
+
267 fuse_log(FUSE_LOG_ERR, "fuse: dlopen(%s) failed: %s\n",
+
268 tmp, dlerror());
+
269 goto out_free_so;
+
270 }
+
271
+
272 sprintf(tmp, "fuse_module_%s_factory", module);
+
273 factory = (fuse_module_factory_t*)dlsym(so->handle, tmp);
+
274 if (factory == NULL) {
+
275 fuse_log(FUSE_LOG_ERR, "fuse: symbol <%s> not found in module: %s\n",
+
276 tmp, dlerror());
+
277 goto out_dlclose;
+
278 }
+
279 ret = fuse_register_module(module, *factory, so);
+
280 if (ret)
+
281 goto out_dlclose;
+
282
+
283out:
+
284 free(tmp);
+
285 return ret;
+
286
+
287out_dlclose:
+
288 dlclose(so->handle);
+
289out_free_so:
+
290 free(so);
+
291 goto out;
+
292}
+
293
+
294static struct fuse_module *fuse_find_module(const char *module)
+
295{
+
296 struct fuse_module *m;
+
297 for (m = fuse_modules; m; m = m->next) {
+
298 if (strcmp(module, m->name) == 0) {
+
299 m->ctr++;
+
300 break;
+
301 }
+
302 }
+
303 return m;
+
304}
+
305
+
306static struct fuse_module *fuse_get_module(const char *module)
+
307{
+
308 struct fuse_module *m;
+
309
+
310 pthread_mutex_lock(&fuse_context_lock);
+
311 m = fuse_find_module(module);
+
312 if (!m) {
+
313 int err = fuse_load_so_module(module);
+
314 if (!err)
+
315 m = fuse_find_module(module);
+
316 }
+
317 pthread_mutex_unlock(&fuse_context_lock);
+
318 return m;
+
319}
+
320
+
321static void fuse_put_module(struct fuse_module *m)
+
322{
+
323 pthread_mutex_lock(&fuse_context_lock);
+
324 if (m->so)
+
325 assert(m->ctr > 0);
+
326 /* Builtin modules may already have m->ctr == 0 */
+
327 if (m->ctr > 0)
+
328 m->ctr--;
+
329 if (!m->ctr && m->so) {
+
330 struct fusemod_so *so = m->so;
+
331 assert(so->ctr > 0);
+
332 so->ctr--;
+
333 if (!so->ctr) {
+
334 struct fuse_module **mp;
+
335 for (mp = &fuse_modules; *mp;) {
+
336 if ((*mp)->so == so)
+
337 fuse_unregister_module(*mp);
+
338 else
+
339 mp = &(*mp)->next;
+
340 }
+
341 dlclose(so->handle);
+
342 free(so);
+
343 }
+
344 } else if (!m->ctr) {
+
345 fuse_unregister_module(m);
+
346 }
+
347 pthread_mutex_unlock(&fuse_context_lock);
+
348}
+
349
+
350static void init_list_head(struct list_head *list)
+
351{
+
352 list->next = list;
+
353 list->prev = list;
+
354}
+
355
+
356static int list_empty(const struct list_head *head)
+
357{
+
358 return head->next == head;
+
359}
+
360
+
361static void list_add(struct list_head *new, struct list_head *prev,
+
362 struct list_head *next)
+
363{
+
364 next->prev = new;
+
365 new->next = next;
+
366 new->prev = prev;
+
367 prev->next = new;
+
368}
+
369
+
370static inline void list_add_head(struct list_head *new, struct list_head *head)
+
371{
+
372 list_add(new, head, head->next);
+
373}
+
374
+
375static inline void list_add_tail(struct list_head *new, struct list_head *head)
+
376{
+
377 list_add(new, head->prev, head);
+
378}
+
379
+
380static inline void list_del(struct list_head *entry)
+
381{
+
382 struct list_head *prev = entry->prev;
+
383 struct list_head *next = entry->next;
+
384
+
385 next->prev = prev;
+
386 prev->next = next;
+
387}
+
388
+
389static inline int lru_enabled(struct fuse *f)
+
390{
+
391 return f->conf.remember > 0;
+
392}
+
393
+
394static struct node_lru *node_lru(struct node *node)
+
395{
+
396 return (struct node_lru *) node;
+
397}
+
398
+
399static size_t get_node_size(struct fuse *f)
+
400{
+
401 if (lru_enabled(f))
+
402 return sizeof(struct node_lru);
+
403 else
+
404 return sizeof(struct node);
+
405}
+
406
+
407#ifdef FUSE_NODE_SLAB
+
408static struct node_slab *list_to_slab(struct list_head *head)
+
409{
+
410 return (struct node_slab *) head;
+
411}
+
412
+
413static struct node_slab *node_to_slab(struct fuse *f, struct node *node)
+
414{
+
415 return (struct node_slab *) (((uintptr_t) node) & ~((uintptr_t) f->pagesize - 1));
+
416}
+
417
+
418static int alloc_slab(struct fuse *f)
+
419{
+
420 void *mem;
+
421 struct node_slab *slab;
+
422 char *start;
+
423 size_t num;
+
424 size_t i;
+
425 size_t node_size = get_node_size(f);
+
426
+
427 mem = mmap(NULL, f->pagesize, PROT_READ | PROT_WRITE,
+
428 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
429
+
430 if (mem == MAP_FAILED)
+
431 return -1;
+
432
+
433 slab = mem;
+
434 init_list_head(&slab->freelist);
+
435 slab->used = 0;
+
436 num = (f->pagesize - sizeof(struct node_slab)) / node_size;
+
437
+
438 start = (char *) mem + f->pagesize - num * node_size;
+
439 for (i = 0; i < num; i++) {
+
440 struct list_head *n;
+
441
+
442 n = (struct list_head *) (start + i * node_size);
+
443 list_add_tail(n, &slab->freelist);
+
444 }
+
445 list_add_tail(&slab->list, &f->partial_slabs);
+
446
+
447 return 0;
+
448}
+
449
+
450static struct node *alloc_node(struct fuse *f)
+
451{
+
452 struct node_slab *slab;
+
453 struct list_head *node;
+
454
+
455 if (list_empty(&f->partial_slabs)) {
+
456 int res = alloc_slab(f);
+
457 if (res != 0)
+
458 return NULL;
+
459 }
+
460 slab = list_to_slab(f->partial_slabs.next);
+
461 slab->used++;
+
462 node = slab->freelist.next;
+
463 list_del(node);
+
464 if (list_empty(&slab->freelist)) {
+
465 list_del(&slab->list);
+
466 list_add_tail(&slab->list, &f->full_slabs);
+
467 }
+
468 memset(node, 0, sizeof(struct node));
+
469
+
470 return (struct node *) node;
+
471}
+
472
+
473static void free_slab(struct fuse *f, struct node_slab *slab)
+
474{
+
475 int res;
+
476
+
477 list_del(&slab->list);
+
478 res = munmap(slab, f->pagesize);
+
479 if (res == -1)
+
480 fuse_log(FUSE_LOG_WARNING, "fuse warning: munmap(%p) failed\n",
+
481 slab);
+
482}
+
483
+
484static void free_node_mem(struct fuse *f, struct node *node)
+
485{
+
486 struct node_slab *slab = node_to_slab(f, node);
+
487 struct list_head *n = (struct list_head *) node;
+
488
+
489 slab->used--;
+
490 if (slab->used) {
+
491 if (list_empty(&slab->freelist)) {
+
492 list_del(&slab->list);
+
493 list_add_tail(&slab->list, &f->partial_slabs);
+
494 }
+
495 list_add_head(n, &slab->freelist);
+
496 } else {
+
497 free_slab(f, slab);
+
498 }
+
499}
+
500#else
+
501static struct node *alloc_node(struct fuse *f)
+
502{
+
503 return (struct node *) calloc(1, get_node_size(f));
+
504}
+
505
+
506static void free_node_mem(struct fuse *f, struct node *node)
+
507{
+
508 (void) f;
+
509 free(node);
+
510}
+
511#endif
+
512
+
513static size_t id_hash(struct fuse *f, fuse_ino_t ino)
+
514{
+
515 uint64_t hash = ((uint32_t) ino * 2654435761U) % f->id_table.size;
+
516 uint64_t oldhash = hash % (f->id_table.size / 2);
+
517
+
518 if (oldhash >= f->id_table.split)
+
519 return oldhash;
+
520 else
+
521 return hash;
+
522}
+
523
+
524static struct node *get_node_nocheck(struct fuse *f, fuse_ino_t nodeid)
+
525{
+
526 size_t hash = id_hash(f, nodeid);
+
527 struct node *node;
+
528
+
529 for (node = f->id_table.array[hash]; node != NULL; node = node->id_next)
+
530 if (node->nodeid == nodeid)
+
531 return node;
+
532
+
533 return NULL;
+
534}
+
535
+
536static struct node *get_node(struct fuse *f, fuse_ino_t nodeid)
+
537{
+
538 struct node *node = get_node_nocheck(f, nodeid);
+
539 if (!node) {
+
540 fuse_log(FUSE_LOG_ERR, "fuse internal error: node %llu not found\n",
+
541 (unsigned long long) nodeid);
+
542 abort();
+
543 }
+
544 return node;
+
545}
+
546
+
547static void curr_time(struct timespec *now);
+
548static double diff_timespec(const struct timespec *t1,
+
549 const struct timespec *t2);
+
550
+
551static void remove_node_lru(struct node *node)
+
552{
+
553 struct node_lru *lnode = node_lru(node);
+
554 list_del(&lnode->lru);
+
555 init_list_head(&lnode->lru);
+
556}
+
557
+
558static void set_forget_time(struct fuse *f, struct node *node)
+
559{
+
560 struct node_lru *lnode = node_lru(node);
+
561
+
562 list_del(&lnode->lru);
+
563 list_add_tail(&lnode->lru, &f->lru_table);
+
564 curr_time(&lnode->forget_time);
+
565}
+
566
+
567static void free_node(struct fuse *f, struct node *node)
+
568{
+
569 if (node->name != node->inline_name)
+
570 free(node->name);
+
571 free_node_mem(f, node);
+
572}
+
573
+
574static void node_table_reduce(struct node_table *t)
+
575{
+
576 size_t newsize = t->size / 2;
+
577 void *newarray;
+
578
+
579 if (newsize < NODE_TABLE_MIN_SIZE)
+
580 return;
+
581
+
582 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
583 if (newarray != NULL)
+
584 t->array = newarray;
+
585
+
586 t->size = newsize;
+
587 t->split = t->size / 2;
+
588}
+
589
+
590static void remerge_id(struct fuse *f)
+
591{
+
592 struct node_table *t = &f->id_table;
+
593 int iter;
+
594
+
595 if (t->split == 0)
+
596 node_table_reduce(t);
+
597
+
598 for (iter = 8; t->split > 0 && iter; iter--) {
+
599 struct node **upper;
+
600
+
601 t->split--;
+
602 upper = &t->array[t->split + t->size / 2];
+
603 if (*upper) {
+
604 struct node **nodep;
+
605
+
606 for (nodep = &t->array[t->split]; *nodep;
+
607 nodep = &(*nodep)->id_next);
+
608
+
609 *nodep = *upper;
+
610 *upper = NULL;
+
611 break;
+
612 }
+
613 }
+
614}
+
615
+
616static void unhash_id(struct fuse *f, struct node *node)
+
617{
+
618 struct node **nodep = &f->id_table.array[id_hash(f, node->nodeid)];
+
619
+
620 for (; *nodep != NULL; nodep = &(*nodep)->id_next)
+
621 if (*nodep == node) {
+
622 *nodep = node->id_next;
+
623 f->id_table.use--;
+
624
+
625 if(f->id_table.use < f->id_table.size / 4)
+
626 remerge_id(f);
+
627 return;
+
628 }
+
629}
+
630
+
631static int node_table_resize(struct node_table *t)
+
632{
+
633 size_t newsize = t->size * 2;
+
634 void *newarray;
+
635
+
636 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
637 if (newarray == NULL)
+
638 return -1;
+
639
+
640 t->array = newarray;
+
641 memset(t->array + t->size, 0, t->size * sizeof(struct node *));
+
642 t->size = newsize;
+
643 t->split = 0;
+
644
+
645 return 0;
+
646}
+
647
+
648static void rehash_id(struct fuse *f)
+
649{
+
650 struct node_table *t = &f->id_table;
+
651 struct node **nodep;
+
652 struct node **next;
+
653 size_t hash;
+
654
+
655 if (t->split == t->size / 2)
+
656 return;
+
657
+
658 hash = t->split;
+
659 t->split++;
+
660 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
661 struct node *node = *nodep;
+
662 size_t newhash = id_hash(f, node->nodeid);
+
663
+
664 if (newhash != hash) {
+
665 next = nodep;
+
666 *nodep = node->id_next;
+
667 node->id_next = t->array[newhash];
+
668 t->array[newhash] = node;
+
669 } else {
+
670 next = &node->id_next;
+
671 }
+
672 }
+
673 if (t->split == t->size / 2)
+
674 node_table_resize(t);
+
675}
+
676
+
677static void hash_id(struct fuse *f, struct node *node)
+
678{
+
679 size_t hash = id_hash(f, node->nodeid);
+
680 node->id_next = f->id_table.array[hash];
+
681 f->id_table.array[hash] = node;
+
682 f->id_table.use++;
+
683
+
684 if (f->id_table.use >= f->id_table.size / 2)
+
685 rehash_id(f);
+
686}
+
687
+
688static size_t name_hash(struct fuse *f, fuse_ino_t parent,
+
689 const char *name)
+
690{
+
691 uint64_t hash = parent;
+
692 uint64_t oldhash;
+
693
+
694 for (; *name; name++)
+
695 hash = hash * 31 + (unsigned char) *name;
+
696
+
697 hash %= f->name_table.size;
+
698 oldhash = hash % (f->name_table.size / 2);
+
699 if (oldhash >= f->name_table.split)
+
700 return oldhash;
+
701 else
+
702 return hash;
+
703}
+
704
+
705static void unref_node(struct fuse *f, struct node *node);
+
706
+
707static void remerge_name(struct fuse *f)
+
708{
+
709 struct node_table *t = &f->name_table;
+
710 int iter;
+
711
+
712 if (t->split == 0)
+
713 node_table_reduce(t);
+
714
+
715 for (iter = 8; t->split > 0 && iter; iter--) {
+
716 struct node **upper;
+
717
+
718 t->split--;
+
719 upper = &t->array[t->split + t->size / 2];
+
720 if (*upper) {
+
721 struct node **nodep;
+
722
+
723 for (nodep = &t->array[t->split]; *nodep;
+
724 nodep = &(*nodep)->name_next);
+
725
+
726 *nodep = *upper;
+
727 *upper = NULL;
+
728 break;
+
729 }
+
730 }
+
731}
+
732
+
733static void unhash_name(struct fuse *f, struct node *node)
+
734{
+
735 if (node->name) {
+
736 size_t hash = name_hash(f, node->parent->nodeid, node->name);
+
737 struct node **nodep = &f->name_table.array[hash];
+
738
+
739 for (; *nodep != NULL; nodep = &(*nodep)->name_next)
+
740 if (*nodep == node) {
+
741 *nodep = node->name_next;
+
742 node->name_next = NULL;
+
743 unref_node(f, node->parent);
+
744 if (node->name != node->inline_name)
+
745 free(node->name);
+
746 node->name = NULL;
+
747 node->parent = NULL;
+
748 f->name_table.use--;
+
749
+
750 if (f->name_table.use < f->name_table.size / 4)
+
751 remerge_name(f);
+
752 return;
+
753 }
+
754 fuse_log(FUSE_LOG_ERR,
+
755 "fuse internal error: unable to unhash node: %llu\n",
+
756 (unsigned long long) node->nodeid);
+
757 abort();
+
758 }
+
759}
+
760
+
761static void rehash_name(struct fuse *f)
+
762{
+
763 struct node_table *t = &f->name_table;
+
764 struct node **nodep;
+
765 struct node **next;
+
766 size_t hash;
+
767
+
768 if (t->split == t->size / 2)
+
769 return;
+
770
+
771 hash = t->split;
+
772 t->split++;
+
773 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
774 struct node *node = *nodep;
+
775 size_t newhash = name_hash(f, node->parent->nodeid, node->name);
+
776
+
777 if (newhash != hash) {
+
778 next = nodep;
+
779 *nodep = node->name_next;
+
780 node->name_next = t->array[newhash];
+
781 t->array[newhash] = node;
+
782 } else {
+
783 next = &node->name_next;
+
784 }
+
785 }
+
786 if (t->split == t->size / 2)
+
787 node_table_resize(t);
+
788}
+
789
+
790static int hash_name(struct fuse *f, struct node *node, fuse_ino_t parentid,
+
791 const char *name)
+
792{
+
793 size_t hash = name_hash(f, parentid, name);
+
794 struct node *parent = get_node(f, parentid);
+
795 if (strlen(name) < sizeof(node->inline_name)) {
+
796 strcpy(node->inline_name, name);
+
797 node->name = node->inline_name;
+
798 } else {
+
799 node->name = strdup(name);
+
800 if (node->name == NULL)
+
801 return -1;
+
802 }
+
803
+
804 parent->refctr ++;
+
805 node->parent = parent;
+
806 node->name_next = f->name_table.array[hash];
+
807 f->name_table.array[hash] = node;
+
808 f->name_table.use++;
+
809
+
810 if (f->name_table.use >= f->name_table.size / 2)
+
811 rehash_name(f);
+
812
+
813 return 0;
+
814}
+
815
+
816static void delete_node(struct fuse *f, struct node *node)
+
817{
+
818 if (f->conf.debug)
+
819 fuse_log(FUSE_LOG_DEBUG, "DELETE: %llu\n",
+
820 (unsigned long long) node->nodeid);
+
821
+
822 assert(node->treelock == 0);
+
823 unhash_name(f, node);
+
824 if (lru_enabled(f))
+
825 remove_node_lru(node);
+
826 unhash_id(f, node);
+
827 free_node(f, node);
+
828}
+
829
+
830static void unref_node(struct fuse *f, struct node *node)
+
831{
+
832 assert(node->refctr > 0);
+
833 node->refctr --;
+
834 if (!node->refctr)
+
835 delete_node(f, node);
+
836}
+
837
+
838static fuse_ino_t next_id(struct fuse *f)
+
839{
+
840 do {
+
841 f->ctr = (f->ctr + 1) & 0xffffffff;
+
842 if (!f->ctr)
+
843 f->generation ++;
+
844 } while (f->ctr == 0 || f->ctr == FUSE_UNKNOWN_INO ||
+
845 get_node_nocheck(f, f->ctr) != NULL);
+
846 return f->ctr;
+
847}
+
848
+
849static struct node *lookup_node(struct fuse *f, fuse_ino_t parent,
+
850 const char *name)
+
851{
+
852 size_t hash = name_hash(f, parent, name);
+
853 struct node *node;
+
854
+
855 for (node = f->name_table.array[hash]; node != NULL; node = node->name_next)
+
856 if (node->parent->nodeid == parent &&
+
857 strcmp(node->name, name) == 0)
+
858 return node;
+
859
+
860 return NULL;
+
861}
+
862
+
863static void inc_nlookup(struct node *node)
+
864{
+
865 if (!node->nlookup)
+
866 node->refctr++;
+
867 node->nlookup++;
+
868}
+
869
+
870static struct node *find_node(struct fuse *f, fuse_ino_t parent,
+
871 const char *name)
+
872{
+
873 struct node *node;
+
874
+
875 pthread_mutex_lock(&f->lock);
+
876 if (!name)
+
877 node = get_node(f, parent);
+
878 else
+
879 node = lookup_node(f, parent, name);
+
880 if (node == NULL) {
+
881 node = alloc_node(f);
+
882 if (node == NULL)
+
883 goto out_err;
+
884
+
885 node->nodeid = next_id(f);
+
886 node->generation = f->generation;
+
887 if (f->conf.remember)
+
888 inc_nlookup(node);
+
889
+
890 if (hash_name(f, node, parent, name) == -1) {
+
891 free_node(f, node);
+
892 node = NULL;
+
893 goto out_err;
+
894 }
+
895 hash_id(f, node);
+
896 if (lru_enabled(f)) {
+
897 struct node_lru *lnode = node_lru(node);
+
898 init_list_head(&lnode->lru);
+
899 }
+
900 } else if (lru_enabled(f) && node->nlookup == 1) {
+
901 remove_node_lru(node);
+
902 }
+
903 inc_nlookup(node);
+
904out_err:
+
905 pthread_mutex_unlock(&f->lock);
+
906 return node;
+
907}
+
908
+
909static int lookup_path_in_cache(struct fuse *f,
+
910 const char *path, fuse_ino_t *inop)
+
911{
+
912 char *tmp = strdup(path);
+
913 if (!tmp)
+
914 return -ENOMEM;
+
915
+
916 pthread_mutex_lock(&f->lock);
+
917 fuse_ino_t ino = FUSE_ROOT_ID;
+
918
+
919 int err = 0;
+
920 char *save_ptr;
+
921 char *path_element = strtok_r(tmp, "/", &save_ptr);
+
922 while (path_element != NULL) {
+
923 struct node *node = lookup_node(f, ino, path_element);
+
924 if (node == NULL) {
+
925 err = -ENOENT;
+
926 break;
+
927 }
+
928 ino = node->nodeid;
+
929 path_element = strtok_r(NULL, "/", &save_ptr);
+
930 }
+
931 pthread_mutex_unlock(&f->lock);
+
932 free(tmp);
+
933
+
934 if (!err)
+
935 *inop = ino;
+
936 return err;
+
937}
+
938
+
939static char *add_name(char **buf, unsigned *bufsize, char *s, const char *name)
+
940{
+
941 size_t len = strlen(name);
+
942
+
943 if (s - len <= *buf) {
+
944 unsigned pathlen = *bufsize - (s - *buf);
+
945 unsigned newbufsize = *bufsize;
+
946 char *newbuf;
+
947
+
948 while (newbufsize < pathlen + len + 1) {
+
949 if (newbufsize >= 0x80000000)
+
950 newbufsize = 0xffffffff;
+
951 else
+
952 newbufsize *= 2;
+
953 }
+
954
+
955 newbuf = realloc(*buf, newbufsize);
+
956 if (newbuf == NULL)
+
957 return NULL;
+
958
+
959 *buf = newbuf;
+
960 s = newbuf + newbufsize - pathlen;
+
961 memmove(s, newbuf + *bufsize - pathlen, pathlen);
+
962 *bufsize = newbufsize;
+
963 }
+
964 s -= len;
+
965 memcpy(s, name, len);
+
966 s--;
+
967 *s = '/';
+
968
+
969 return s;
+
970}
+
971
+
972static void unlock_path(struct fuse *f, fuse_ino_t nodeid, struct node *wnode,
+
973 struct node *end)
+
974{
+
975 struct node *node;
+
976
+
977 if (wnode) {
+
978 assert(wnode->treelock == TREELOCK_WRITE);
+
979 wnode->treelock = 0;
+
980 }
+
981
+
982 for (node = get_node(f, nodeid);
+
983 node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) {
+
984 assert(node->treelock != 0);
+
985 assert(node->treelock != TREELOCK_WAIT_OFFSET);
+
986 assert(node->treelock != TREELOCK_WRITE);
+
987 node->treelock--;
+
988 if (node->treelock == TREELOCK_WAIT_OFFSET)
+
989 node->treelock = 0;
+
990 }
+
991}
+
992
+
993static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
994 char **path, struct node **wnodep, bool need_lock)
+
995{
+
996 unsigned bufsize = 256;
+
997 char *buf;
+
998 char *s;
+
999 struct node *node;
+
1000 struct node *wnode = NULL;
+
1001 int err;
+
1002
+
1003 *path = NULL;
+
1004
+
1005 err = -ENOMEM;
+
1006 buf = malloc(bufsize);
+
1007 if (buf == NULL)
+
1008 goto out_err;
+
1009
+
1010 s = buf + bufsize - 1;
+
1011 *s = '\0';
+
1012
+
1013 if (name != NULL) {
+
1014 s = add_name(&buf, &bufsize, s, name);
+
1015 err = -ENOMEM;
+
1016 if (s == NULL)
+
1017 goto out_free;
+
1018 }
+
1019
+
1020 if (wnodep) {
+
1021 assert(need_lock);
+
1022 wnode = lookup_node(f, nodeid, name);
+
1023 if (wnode) {
+
1024 if (wnode->treelock != 0) {
+
1025 if (wnode->treelock > 0)
+
1026 wnode->treelock += TREELOCK_WAIT_OFFSET;
+
1027 err = -EAGAIN;
+
1028 goto out_free;
+
1029 }
+
1030 wnode->treelock = TREELOCK_WRITE;
+
1031 }
+
1032 }
+
1033
+
1034 for (node = get_node(f, nodeid); node->nodeid != FUSE_ROOT_ID;
+
1035 node = node->parent) {
+
1036 err = -ESTALE;
+
1037 if (node->name == NULL || node->parent == NULL)
+
1038 goto out_unlock;
+
1039
+
1040 err = -ENOMEM;
+
1041 s = add_name(&buf, &bufsize, s, node->name);
+
1042 if (s == NULL)
+
1043 goto out_unlock;
+
1044
+
1045 if (need_lock) {
+
1046 err = -EAGAIN;
+
1047 if (node->treelock < 0)
+
1048 goto out_unlock;
+
1049
+
1050 node->treelock++;
+
1051 }
+
1052 }
+
1053
+
1054 if (s[0])
+
1055 memmove(buf, s, bufsize - (s - buf));
+
1056 else
+
1057 strcpy(buf, "/");
+
1058
+
1059 *path = buf;
+
1060 if (wnodep)
+
1061 *wnodep = wnode;
+
1062
+
1063 return 0;
+
1064
+
1065 out_unlock:
+
1066 if (need_lock)
+
1067 unlock_path(f, nodeid, wnode, node);
+
1068 out_free:
+
1069 free(buf);
+
1070
+
1071 out_err:
+
1072 return err;
+
1073}
+
1074
+
1075static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1076 fuse_ino_t nodeid2, const char *name2,
+
1077 char **path1, char **path2,
+
1078 struct node **wnode1, struct node **wnode2)
+
1079{
+
1080 int err;
+
1081
+
1082 /* FIXME: locking two paths needs deadlock checking */
+
1083 err = try_get_path(f, nodeid1, name1, path1, wnode1, true);
+
1084 if (!err) {
+
1085 err = try_get_path(f, nodeid2, name2, path2, wnode2, true);
+
1086 if (err) {
+
1087 struct node *wn1 = wnode1 ? *wnode1 : NULL;
+
1088
+
1089 unlock_path(f, nodeid1, wn1, NULL);
+
1090 free(*path1);
+
1091 }
+
1092 }
+
1093 return err;
+
1094}
+
1095
+
1096static void queue_element_wakeup(struct fuse *f, struct lock_queue_element *qe)
+
1097{
+
1098 int err;
+
1099
+
1100 if (!qe->path1) {
+
1101 /* Just waiting for it to be unlocked */
+
1102 if (get_node(f, qe->nodeid1)->treelock == 0)
+
1103 pthread_cond_signal(&qe->cond);
+
1104
+
1105 return;
+
1106 }
+
1107
+
1108 if (qe->done)
+
1109 return; // Don't try to double-lock the element
+
1110
+
1111 if (!qe->path2) {
+
1112 err = try_get_path(f, qe->nodeid1, qe->name1, qe->path1,
+
1113 qe->wnode1, true);
+
1114 } else {
+
1115 err = try_get_path2(f, qe->nodeid1, qe->name1, qe->nodeid2,
+
1116 qe->name2, qe->path1, qe->path2, qe->wnode1,
+
1117 qe->wnode2);
+
1118 }
+
1119
+
1120 if (err == -EAGAIN)
+
1121 return; /* keep trying */
+
1122
+
1123 qe->err = err;
+
1124 qe->done = true;
+
1125 pthread_cond_signal(&qe->cond);
+
1126}
+
1127
+
1128static void wake_up_queued(struct fuse *f)
+
1129{
+
1130 struct lock_queue_element *qe;
+
1131
+
1132 for (qe = f->lockq; qe != NULL; qe = qe->next)
+
1133 queue_element_wakeup(f, qe);
+
1134}
+
1135
+
1136static void debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid,
+
1137 const char *name, bool wr)
+
1138{
+
1139 if (f->conf.debug) {
+
1140 struct node *wnode = NULL;
+
1141
+
1142 if (wr)
+
1143 wnode = lookup_node(f, nodeid, name);
+
1144
+
1145 if (wnode) {
+
1146 fuse_log(FUSE_LOG_DEBUG, "%s %llu (w)\n",
+
1147 msg, (unsigned long long) wnode->nodeid);
+
1148 } else {
+
1149 fuse_log(FUSE_LOG_DEBUG, "%s %llu\n",
+
1150 msg, (unsigned long long) nodeid);
+
1151 }
+
1152 }
+
1153}
+
1154
+
1155static void queue_path(struct fuse *f, struct lock_queue_element *qe)
+
1156{
+
1157 struct lock_queue_element **qp;
+
1158
+
1159 qe->done = false;
+
1160 pthread_cond_init(&qe->cond, NULL);
+
1161 qe->next = NULL;
+
1162 for (qp = &f->lockq; *qp != NULL; qp = &(*qp)->next);
+
1163 *qp = qe;
+
1164}
+
1165
+
1166static void dequeue_path(struct fuse *f, struct lock_queue_element *qe)
+
1167{
+
1168 struct lock_queue_element **qp;
+
1169
+
1170 pthread_cond_destroy(&qe->cond);
+
1171 for (qp = &f->lockq; *qp != qe; qp = &(*qp)->next);
+
1172 *qp = qe->next;
+
1173}
+
1174
+
1175static int wait_path(struct fuse *f, struct lock_queue_element *qe)
+
1176{
+
1177 queue_path(f, qe);
+
1178
+
1179 do {
+
1180 pthread_cond_wait(&qe->cond, &f->lock);
+
1181 } while (!qe->done);
+
1182
+
1183 dequeue_path(f, qe);
+
1184
+
1185 return qe->err;
+
1186}
+
1187
+
1188static int get_path_common(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1189 char **path, struct node **wnode)
+
1190{
+
1191 int err;
+
1192
+
1193 pthread_mutex_lock(&f->lock);
+
1194 err = try_get_path(f, nodeid, name, path, wnode, true);
+
1195 if (err == -EAGAIN) {
+
1196 struct lock_queue_element qe = {
+
1197 .nodeid1 = nodeid,
+
1198 .name1 = name,
+
1199 .path1 = path,
+
1200 .wnode1 = wnode,
+
1201 };
+
1202 debug_path(f, "QUEUE PATH", nodeid, name, !!wnode);
+
1203 err = wait_path(f, &qe);
+
1204 debug_path(f, "DEQUEUE PATH", nodeid, name, !!wnode);
+
1205 }
+
1206 pthread_mutex_unlock(&f->lock);
+
1207
+
1208 return err;
+
1209}
+
1210
+
1211static int get_path(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1212{
+
1213 return get_path_common(f, nodeid, NULL, path, NULL);
+
1214}
+
1215
+
1216static int get_path_nullok(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1217{
+
1218 int err = 0;
+
1219
+
1220 if (f->conf.nullpath_ok) {
+
1221 *path = NULL;
+
1222 } else {
+
1223 err = get_path_common(f, nodeid, NULL, path, NULL);
+
1224 if (err == -ESTALE)
+
1225 err = 0;
+
1226 }
+
1227
+
1228 return err;
+
1229}
+
1230
+
1231static int get_path_name(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1232 char **path)
+
1233{
+
1234 return get_path_common(f, nodeid, name, path, NULL);
+
1235}
+
1236
+
1237static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1238 char **path, struct node **wnode)
+
1239{
+
1240 return get_path_common(f, nodeid, name, path, wnode);
+
1241}
+
1242
+
1243#if defined(__FreeBSD__)
+
1244#define CHECK_DIR_LOOP
+
1245#endif
+
1246
+
1247#if defined(CHECK_DIR_LOOP)
+
1248static int check_dir_loop(struct fuse *f,
+
1249 fuse_ino_t nodeid1, const char *name1,
+
1250 fuse_ino_t nodeid2, const char *name2)
+
1251{
+
1252 struct node *node, *node1, *node2;
+
1253 fuse_ino_t id1, id2;
+
1254
+
1255 node1 = lookup_node(f, nodeid1, name1);
+
1256 id1 = node1 ? node1->nodeid : nodeid1;
+
1257
+
1258 node2 = lookup_node(f, nodeid2, name2);
+
1259 id2 = node2 ? node2->nodeid : nodeid2;
+
1260
+
1261 for (node = get_node(f, id2); node->nodeid != FUSE_ROOT_ID;
+
1262 node = node->parent) {
+
1263 if (node->name == NULL || node->parent == NULL)
+
1264 break;
+
1265
+
1266 if (node->nodeid != id2 && node->nodeid == id1)
+
1267 return -EINVAL;
+
1268 }
+
1269
+
1270 if (node2)
+
1271 {
+
1272 for (node = get_node(f, id1); node->nodeid != FUSE_ROOT_ID;
+
1273 node = node->parent) {
+
1274 if (node->name == NULL || node->parent == NULL)
+
1275 break;
+
1276
+
1277 if (node->nodeid != id1 && node->nodeid == id2)
+
1278 return -ENOTEMPTY;
+
1279 }
+
1280 }
+
1281
+
1282 return 0;
+
1283}
+
1284#endif
+
1285
+
1286static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1287 fuse_ino_t nodeid2, const char *name2,
+
1288 char **path1, char **path2,
+
1289 struct node **wnode1, struct node **wnode2)
+
1290{
+
1291 int err;
+
1292
+
1293 pthread_mutex_lock(&f->lock);
+
1294
+
1295#if defined(CHECK_DIR_LOOP)
+
1296 if (name1)
+
1297 {
+
1298 // called during rename; perform dir loop check
+
1299 err = check_dir_loop(f, nodeid1, name1, nodeid2, name2);
+
1300 if (err)
+
1301 goto out_unlock;
+
1302 }
+
1303#endif
+
1304
+
1305 err = try_get_path2(f, nodeid1, name1, nodeid2, name2,
+
1306 path1, path2, wnode1, wnode2);
+
1307 if (err == -EAGAIN) {
+
1308 struct lock_queue_element qe = {
+
1309 .nodeid1 = nodeid1,
+
1310 .name1 = name1,
+
1311 .path1 = path1,
+
1312 .wnode1 = wnode1,
+
1313 .nodeid2 = nodeid2,
+
1314 .name2 = name2,
+
1315 .path2 = path2,
+
1316 .wnode2 = wnode2,
+
1317 };
+
1318
+
1319 debug_path(f, "QUEUE PATH1", nodeid1, name1, !!wnode1);
+
1320 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1321 err = wait_path(f, &qe);
+
1322 debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1);
+
1323 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1324 }
+
1325
+
1326#if defined(CHECK_DIR_LOOP)
+
1327out_unlock:
+
1328#endif
+
1329 pthread_mutex_unlock(&f->lock);
+
1330
+
1331 return err;
+
1332}
+
1333
+
1334static void free_path_wrlock(struct fuse *f, fuse_ino_t nodeid,
+
1335 struct node *wnode, char *path)
+
1336{
+
1337 pthread_mutex_lock(&f->lock);
+
1338 unlock_path(f, nodeid, wnode, NULL);
+
1339 if (f->lockq)
+
1340 wake_up_queued(f);
+
1341 pthread_mutex_unlock(&f->lock);
+
1342 free(path);
+
1343}
+
1344
+
1345static void free_path(struct fuse *f, fuse_ino_t nodeid, char *path)
+
1346{
+
1347 if (path)
+
1348 free_path_wrlock(f, nodeid, NULL, path);
+
1349}
+
1350
+
1351static void free_path2(struct fuse *f, fuse_ino_t nodeid1, fuse_ino_t nodeid2,
+
1352 struct node *wnode1, struct node *wnode2,
+
1353 char *path1, char *path2)
+
1354{
+
1355 pthread_mutex_lock(&f->lock);
+
1356 unlock_path(f, nodeid1, wnode1, NULL);
+
1357 unlock_path(f, nodeid2, wnode2, NULL);
+
1358 wake_up_queued(f);
+
1359 pthread_mutex_unlock(&f->lock);
+
1360 free(path1);
+
1361 free(path2);
+
1362}
+
1363
+
1364static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup)
+
1365{
+
1366 struct node *node;
+
1367 if (nodeid == FUSE_ROOT_ID)
+
1368 return;
+
1369 pthread_mutex_lock(&f->lock);
+
1370 node = get_node(f, nodeid);
+
1371
+
1372 /*
+
1373 * Node may still be locked due to interrupt idiocy in open,
+
1374 * create and opendir
+
1375 */
+
1376 while (node->nlookup == nlookup && node->treelock) {
+
1377 struct lock_queue_element qe = {
+
1378 .nodeid1 = nodeid,
+
1379 };
+
1380
+
1381 debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false);
+
1382 queue_path(f, &qe);
+
1383
+
1384 do {
+
1385 pthread_cond_wait(&qe.cond, &f->lock);
+
1386 } while (node->nlookup == nlookup && node->treelock);
+
1387
+
1388 dequeue_path(f, &qe);
+
1389 debug_path(f, "DEQUEUE_PATH (forget)", nodeid, NULL, false);
+
1390 }
+
1391
+
1392 assert(node->nlookup >= nlookup);
+
1393 node->nlookup -= nlookup;
+
1394 if (!node->nlookup) {
+
1395 unref_node(f, node);
+
1396 } else if (lru_enabled(f) && node->nlookup == 1) {
+
1397 set_forget_time(f, node);
+
1398 }
+
1399 pthread_mutex_unlock(&f->lock);
+
1400}
+
1401
+
1402static void unlink_node(struct fuse *f, struct node *node)
+
1403{
+
1404 if (f->conf.remember) {
+
1405 assert(node->nlookup > 1);
+
1406 node->nlookup--;
+
1407 }
+
1408 unhash_name(f, node);
+
1409}
+
1410
+
1411static void remove_node(struct fuse *f, fuse_ino_t dir, const char *name)
+
1412{
+
1413 struct node *node;
+
1414
+
1415 pthread_mutex_lock(&f->lock);
+
1416 node = lookup_node(f, dir, name);
+
1417 if (node != NULL)
+
1418 unlink_node(f, node);
+
1419 pthread_mutex_unlock(&f->lock);
+
1420}
+
1421
+
1422static int rename_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1423 fuse_ino_t newdir, const char *newname, int hide)
+
1424{
+
1425 struct node *node;
+
1426 struct node *newnode;
+
1427 int err = 0;
+
1428
+
1429 pthread_mutex_lock(&f->lock);
+
1430 node = lookup_node(f, olddir, oldname);
+
1431 newnode = lookup_node(f, newdir, newname);
+
1432 if (node == NULL)
+
1433 goto out;
+
1434
+
1435 if (newnode != NULL) {
+
1436 if (hide) {
+
1437 fuse_log(FUSE_LOG_ERR, "fuse: hidden file got created during hiding\n");
+
1438 err = -EBUSY;
+
1439 goto out;
+
1440 }
+
1441 unlink_node(f, newnode);
+
1442 }
+
1443
+
1444 unhash_name(f, node);
+
1445 if (hash_name(f, node, newdir, newname) == -1) {
+
1446 err = -ENOMEM;
+
1447 goto out;
+
1448 }
+
1449
+
1450 if (hide)
+
1451 node->is_hidden = 1;
+
1452
+
1453out:
+
1454 pthread_mutex_unlock(&f->lock);
+
1455 return err;
+
1456}
+
1457
+
1458static int exchange_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1459 fuse_ino_t newdir, const char *newname)
+
1460{
+
1461 struct node *oldnode;
+
1462 struct node *newnode;
+
1463 int err;
+
1464
+
1465 pthread_mutex_lock(&f->lock);
+
1466 oldnode = lookup_node(f, olddir, oldname);
+
1467 newnode = lookup_node(f, newdir, newname);
+
1468
+
1469 if (oldnode)
+
1470 unhash_name(f, oldnode);
+
1471 if (newnode)
+
1472 unhash_name(f, newnode);
+
1473
+
1474 err = -ENOMEM;
+
1475 if (oldnode) {
+
1476 if (hash_name(f, oldnode, newdir, newname) == -1)
+
1477 goto out;
+
1478 }
+
1479 if (newnode) {
+
1480 if (hash_name(f, newnode, olddir, oldname) == -1)
+
1481 goto out;
+
1482 }
+
1483 err = 0;
+
1484out:
+
1485 pthread_mutex_unlock(&f->lock);
+
1486 return err;
+
1487}
+
1488
+
1489static void set_stat(struct fuse *f, fuse_ino_t nodeid, struct stat *stbuf)
+
1490{
+
1491 if (!f->conf.use_ino)
+
1492 stbuf->st_ino = nodeid;
+
1493 if (f->conf.set_mode) {
+
1494 if (f->conf.dmask && S_ISDIR(stbuf->st_mode))
+
1495 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1496 (0777 & ~f->conf.dmask);
+
1497 else if (f->conf.fmask)
+
1498 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1499 (0777 & ~f->conf.fmask);
+
1500 else
+
1501 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1502 (0777 & ~f->conf.umask);
+
1503 }
+
1504 if (f->conf.set_uid)
+
1505 stbuf->st_uid = f->conf.uid;
+
1506 if (f->conf.set_gid)
+
1507 stbuf->st_gid = f->conf.gid;
+
1508}
+
1509
+
1510#ifdef HAVE_STATX
+
1511static void set_statx(struct fuse *f, fuse_ino_t nodeid, struct statx *stxbuf)
+
1512{
+
1513 if (!f->conf.use_ino)
+
1514 stxbuf->stx_ino = nodeid;
+
1515 if (f->conf.set_mode) {
+
1516 if (f->conf.dmask && S_ISDIR(stxbuf->stx_mode))
+
1517 stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) |
+
1518 (0777 & ~f->conf.dmask);
+
1519 else if (f->conf.fmask)
+
1520 stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) |
+
1521 (0777 & ~f->conf.fmask);
+
1522 else
+
1523 stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) |
+
1524 (0777 & ~f->conf.umask);
+
1525 }
+
1526 if (f->conf.set_uid)
+
1527 stxbuf->stx_uid = f->conf.uid;
+
1528 if (f->conf.set_gid)
+
1529 stxbuf->stx_gid = f->conf.gid;
+
1530}
+
1531#endif
+
1532
+
1533static struct fuse *req_fuse(fuse_req_t req)
+
1534{
+
1535 return (struct fuse *) fuse_req_userdata(req);
+
1536}
+
1537
+
1538static void fuse_intr_sighandler(int sig)
+
1539{
+
1540 (void) sig;
+
1541 /* Nothing to do */
+
1542}
+
1543
+
1544struct fuse_intr_data {
+
1545 pthread_t id;
+
1546 pthread_cond_t cond;
+
1547 int finished;
+
1548};
+
1549
+
1550static void fuse_interrupt(fuse_req_t req, void *d_)
+
1551{
+
1552 struct fuse_intr_data *d = d_;
+
1553 struct fuse *f = req_fuse(req);
+
1554
+
1555 if (d->id == pthread_self())
+
1556 return;
+
1557
+
1558 pthread_mutex_lock(&f->lock);
+
1559 while (!d->finished) {
+
1560 struct timeval now;
+
1561 struct timespec timeout;
+
1562
+
1563 pthread_kill(d->id, f->conf.intr_signal);
+
1564 gettimeofday(&now, NULL);
+
1565 timeout.tv_sec = now.tv_sec + 1;
+
1566 timeout.tv_nsec = now.tv_usec * 1000;
+
1567 pthread_cond_timedwait(&d->cond, &f->lock, &timeout);
+
1568 }
+
1569 pthread_mutex_unlock(&f->lock);
+
1570}
+
1571
+
1572static void fuse_do_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1573 struct fuse_intr_data *d)
+
1574{
+
1575 pthread_mutex_lock(&f->lock);
+
1576 d->finished = 1;
+
1577 pthread_cond_broadcast(&d->cond);
+
1578 pthread_mutex_unlock(&f->lock);
+
1579 fuse_req_interrupt_func(req, NULL, NULL);
+
1580 pthread_cond_destroy(&d->cond);
+
1581}
+
1582
+
1583static void fuse_do_prepare_interrupt(fuse_req_t req, struct fuse_intr_data *d)
+
1584{
+
1585 d->id = pthread_self();
+
1586 pthread_cond_init(&d->cond, NULL);
+
1587 d->finished = 0;
+
1588 fuse_req_interrupt_func(req, fuse_interrupt, d);
+
1589}
+
1590
+
1591static inline void fuse_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1592 struct fuse_intr_data *d)
+
1593{
+
1594 if (f->conf.intr)
+
1595 fuse_do_finish_interrupt(f, req, d);
+
1596}
+
1597
+
1598static inline void fuse_prepare_interrupt(struct fuse *f, fuse_req_t req,
+
1599 struct fuse_intr_data *d)
+
1600{
+
1601 if (f->conf.intr)
+
1602 fuse_do_prepare_interrupt(req, d);
+
1603}
+
1604
+
1605static const char* file_info_string(struct fuse_file_info *fi,
+
1606 char* buf, size_t len)
+
1607{
+
1608 if(fi == NULL)
+
1609 return "NULL";
+
1610 snprintf(buf, len, "%llu", (unsigned long long) fi->fh);
+
1611 return buf;
+
1612}
+
1613
+
1614int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1615 struct fuse_file_info *fi)
+
1616{
+
1617 fuse_get_context()->private_data = fs->user_data;
+
1618 if (!fs->op.getattr)
+
1619 return -ENOSYS;
+
1620
+
1621 if (fs->debug) {
+
1622 char buf[10];
+
1623
+
1624 fuse_log(FUSE_LOG_DEBUG, "getattr[%s] %s\n",
+
1625 file_info_string(fi, buf, sizeof(buf)),
+
1626 path);
+
1627 }
+
1628 return fs->op.getattr(path, buf, fi);
+
1629}
+
1630
+
1631int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1632 const char *newpath, unsigned int flags)
+
1633{
+
1634 fuse_get_context()->private_data = fs->user_data;
+
1635 if (!fs->op.rename)
+
1636 return -ENOSYS;
+
1637 if (fs->debug)
+
1638 fuse_log(FUSE_LOG_DEBUG, "rename %s %s 0x%x\n", oldpath, newpath,
+
1639 flags);
+
1640
+
1641 return fs->op.rename(oldpath, newpath, flags);
+
1642}
+
1643
+
1644int fuse_fs_unlink(struct fuse_fs *fs, const char *path)
+
1645{
+
1646 fuse_get_context()->private_data = fs->user_data;
+
1647 if (!fs->op.unlink)
+
1648 return -ENOSYS;
+
1649 if (fs->debug)
+
1650 fuse_log(FUSE_LOG_DEBUG, "unlink %s\n", path);
+
1651
+
1652 return fs->op.unlink(path);
+
1653}
+
1654
+
1655int fuse_fs_rmdir(struct fuse_fs *fs, const char *path)
+
1656{
+
1657 fuse_get_context()->private_data = fs->user_data;
+
1658 if (!fs->op.rmdir)
+
1659 return -ENOSYS;
+
1660 if (fs->debug)
+
1661 fuse_log(FUSE_LOG_DEBUG, "rmdir %s\n", path);
+
1662
+
1663 return fs->op.rmdir(path);
+
1664}
+
1665
+
1666int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path)
+
1667{
+
1668 fuse_get_context()->private_data = fs->user_data;
+
1669 if (!fs->op.symlink)
+
1670 return -ENOSYS;
+
1671 if (fs->debug)
+
1672 fuse_log(FUSE_LOG_DEBUG, "symlink %s %s\n", linkname, path);
+
1673
+
1674 return fs->op.symlink(linkname, path);
+
1675}
+
1676
+
1677int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath)
+
1678{
+
1679 fuse_get_context()->private_data = fs->user_data;
+
1680 if (!fs->op.link)
+
1681 return -ENOSYS;
+
1682 if (fs->debug)
+
1683 fuse_log(FUSE_LOG_DEBUG, "link %s %s\n", oldpath, newpath);
+
1684
+
1685 return fs->op.link(oldpath, newpath);
+
1686}
+
1687
+
1688int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1689 struct fuse_file_info *fi)
+
1690{
+
1691 fuse_get_context()->private_data = fs->user_data;
+
1692 if (!fs->op.release)
+
1693 return 0;
+
1694
+
1695 if (fs->debug)
+
1696 fuse_log(FUSE_LOG_DEBUG, "release%s[%llu] flags: 0x%x\n",
+
1697 fi->flush ? "+flush" : "",
+
1698 (unsigned long long) fi->fh, fi->flags);
+
1699
+
1700 return fs->op.release(path, fi);
+
1701}
+
1702
+
1703int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1704 struct fuse_file_info *fi)
+
1705{
+
1706 int err;
+
1707
+
1708 fuse_get_context()->private_data = fs->user_data;
+
1709 if (!fs->op.opendir)
+
1710 return 0;
+
1711
+
1712 if (fs->debug)
+
1713 fuse_log(FUSE_LOG_DEBUG, "opendir flags: 0x%x %s\n", fi->flags,
+
1714 path);
+
1715
+
1716 err = fs->op.opendir(path, fi);
+
1717
+
1718 if (fs->debug && !err)
+
1719 fuse_log(FUSE_LOG_DEBUG, " opendir[%llu] flags: 0x%x %s\n",
+
1720 (unsigned long long) fi->fh, fi->flags, path);
+
1721
+
1722 return err;
+
1723}
+
1724
+
1725int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1726 struct fuse_file_info *fi)
+
1727{
+
1728 int err;
+
1729
+
1730 fuse_get_context()->private_data = fs->user_data;
+
1731 if (!fs->op.open)
+
1732 return 0;
+
1733
+
1734 if (fs->debug)
+
1735 fuse_log(FUSE_LOG_DEBUG, "open flags: 0x%x %s\n", fi->flags,
+
1736 path);
+
1737
+
1738 err = fs->op.open(path, fi);
+
1739
+
1740 if (fs->debug && !err)
+
1741 fuse_log(FUSE_LOG_DEBUG, " open[%llu] flags: 0x%x %s\n",
+
1742 (unsigned long long) fi->fh, fi->flags, path);
+
1743
+
1744 return err;
+
1745}
+
1746
+
1747static void fuse_free_buf(struct fuse_bufvec *buf)
+
1748{
+
1749 if (buf != NULL) {
+
1750 size_t i;
+
1751
+
1752 for (i = 0; i < buf->count; i++)
+
1753 if (!(buf->buf[i].flags & FUSE_BUF_IS_FD))
+
1754 free(buf->buf[i].mem);
+
1755 free(buf);
+
1756 }
+
1757}
+
1758
+
1759int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1760 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1761 struct fuse_file_info *fi)
+
1762{
+
1763 int res;
+
1764
+
1765 fuse_get_context()->private_data = fs->user_data;
+
1766 if (!fs->op.read && !fs->op.read_buf)
+
1767 return -ENOSYS;
+
1768
+
1769 if (fs->debug)
+
1770 fuse_log(FUSE_LOG_DEBUG,
+
1771 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1772 (unsigned long long) fi->fh,
+
1773 size, (unsigned long long) off, fi->flags);
+
1774
+
1775 if (fs->op.read_buf) {
+
1776 res = fs->op.read_buf(path, bufp, size, off, fi);
+
1777 } else {
+
1778 struct fuse_bufvec *buf;
+
1779 void *mem;
+
1780
+
1781 buf = malloc(sizeof(struct fuse_bufvec));
+
1782 if (buf == NULL)
+
1783 return -ENOMEM;
+
1784
+
1785 mem = malloc(size);
+
1786 if (mem == NULL) {
+
1787 free(buf);
+
1788 return -ENOMEM;
+
1789 }
+
1790 *buf = FUSE_BUFVEC_INIT(size);
+
1791 buf->buf[0].mem = mem;
+
1792 *bufp = buf;
+
1793
+
1794 res = fs->op.read(path, mem, size, off, fi);
+
1795 if (res >= 0)
+
1796 buf->buf[0].size = res;
+
1797 }
+
1798
+
1799 if (fs->debug && res >= 0)
+
1800 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %zu bytes from %llu\n",
+
1801 (unsigned long long) fi->fh,
+
1802 fuse_buf_size(*bufp),
+
1803 (unsigned long long) off);
+
1804 if (res >= 0 && fuse_buf_size(*bufp) > size)
+
1805 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1806
+
1807 if (res < 0)
+
1808 return res;
+
1809
+
1810 return 0;
+
1811}
+
1812
+
1813int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size,
+
1814 off_t off, struct fuse_file_info *fi)
+
1815{
+
1816 int res;
+
1817
+
1818 fuse_get_context()->private_data = fs->user_data;
+
1819 if (!fs->op.read && !fs->op.read_buf)
+
1820 return -ENOSYS;
+
1821
+
1822 if (fs->debug)
+
1823 fuse_log(FUSE_LOG_DEBUG,
+
1824 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1825 (unsigned long long) fi->fh,
+
1826 size, (unsigned long long) off, fi->flags);
+
1827
+
1828 if (fs->op.read_buf) {
+
1829 struct fuse_bufvec *buf = NULL;
+
1830
+
1831 res = fs->op.read_buf(path, &buf, size, off, fi);
+
1832 if (res == 0) {
+
1833 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size);
+
1834
+
1835 dst.buf[0].mem = mem;
+
1836 res = fuse_buf_copy(&dst, buf, 0);
+
1837 }
+
1838 fuse_free_buf(buf);
+
1839 } else {
+
1840 res = fs->op.read(path, mem, size, off, fi);
+
1841 }
+
1842
+
1843 if (fs->debug && res >= 0)
+
1844 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %u bytes from %llu\n",
+
1845 (unsigned long long) fi->fh,
+
1846 res,
+
1847 (unsigned long long) off);
+
1848 if (res >= 0 && res > (int) size)
+
1849 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1850
+
1851 return res;
+
1852}
+
1853
+
1854int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1855 struct fuse_bufvec *buf, off_t off,
+
1856 struct fuse_file_info *fi)
+
1857{
+
1858 int res;
+
1859 size_t size;
+
1860
+
1861 fuse_get_context()->private_data = fs->user_data;
+
1862 if (!fs->op.write_buf && !fs->op.write)
+
1863 return -ENOSYS;
+
1864
+
1865 size = fuse_buf_size(buf);
+
1866 assert(buf->idx == 0 && buf->off == 0);
+
1867 if (fs->debug)
+
1868 fuse_log(FUSE_LOG_DEBUG,
+
1869 "write%s[%llu] %zu bytes to %llu flags: 0x%x\n",
+
1870 fi->writepage ? "page" : "",
+
1871 (unsigned long long) fi->fh,
+
1872 size,
+
1873 (unsigned long long) off,
+
1874 fi->flags);
+
1875
+
1876 if (fs->op.write_buf) {
+
1877 res = fs->op.write_buf(path, buf, off, fi);
+
1878 } else {
+
1879 void *mem = NULL;
+
1880 struct fuse_buf *flatbuf;
+
1881 struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size);
+
1882
+
1883 if (buf->count == 1 &&
+
1884 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
1885 flatbuf = &buf->buf[0];
+
1886 } else {
+
1887 res = -ENOMEM;
+
1888 mem = malloc(size);
+
1889 if (mem == NULL)
+
1890 goto out;
+
1891
+
1892 tmp.buf[0].mem = mem;
+
1893 res = fuse_buf_copy(&tmp, buf, 0);
+
1894 if (res <= 0)
+
1895 goto out_free;
+
1896
+
1897 tmp.buf[0].size = res;
+
1898 flatbuf = &tmp.buf[0];
+
1899 }
+
1900
+
1901 res = fs->op.write(path, flatbuf->mem, flatbuf->size,
+
1902 off, fi);
+
1903out_free:
+
1904 free(mem);
+
1905 }
+
1906out:
+
1907 if (fs->debug && res >= 0)
+
1908 fuse_log(FUSE_LOG_DEBUG, " write%s[%llu] %u bytes to %llu\n",
+
1909 fi->writepage ? "page" : "",
+
1910 (unsigned long long) fi->fh, res,
+
1911 (unsigned long long) off);
+
1912 if (res > (int) size)
+
1913 fuse_log(FUSE_LOG_ERR, "fuse: wrote too many bytes\n");
+
1914
+
1915 return res;
+
1916}
+
1917
+
1918int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem,
+
1919 size_t size, off_t off, struct fuse_file_info *fi)
+
1920{
+
1921 struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(size);
+
1922
+
1923 bufv.buf[0].mem = (void *) mem;
+
1924
+
1925 return fuse_fs_write_buf(fs, path, &bufv, off, fi);
+
1926}
+
1927
+
1928int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1929 struct fuse_file_info *fi)
+
1930{
+
1931 fuse_get_context()->private_data = fs->user_data;
+
1932 if (!fs->op.fsync)
+
1933 return -ENOSYS;
+
1934
+
1935 if (fs->debug)
+
1936 fuse_log(FUSE_LOG_DEBUG, "fsync[%llu] datasync: %i\n",
+
1937 (unsigned long long) fi->fh, datasync);
+
1938
+
1939 return fs->op.fsync(path, datasync, fi);
+
1940}
+
1941
+
1942int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1943 struct fuse_file_info *fi)
+
1944{
+
1945 fuse_get_context()->private_data = fs->user_data;
+
1946 if (!fs->op.fsyncdir)
+
1947 return -ENOSYS;
+
1948
+
1949 if (fs->debug)
+
1950 fuse_log(FUSE_LOG_DEBUG, "fsyncdir[%llu] datasync: %i\n",
+
1951 (unsigned long long) fi->fh, datasync);
+
1952
+
1953 return fs->op.fsyncdir(path, datasync, fi);
+
1954}
+
1955
+
1956int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1957 struct fuse_file_info *fi)
+
1958{
+
1959 fuse_get_context()->private_data = fs->user_data;
+
1960 if (!fs->op.flush)
+
1961 return -ENOSYS;
+
1962 if (fs->debug)
+
1963 fuse_log(FUSE_LOG_DEBUG, "flush[%llu]\n",
+
1964 (unsigned long long) fi->fh);
+
1965
+
1966 return fs->op.flush(path, fi);
+
1967}
+
1968
+
1969int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf)
+
1970{
+
1971 fuse_get_context()->private_data = fs->user_data;
+
1972 if (fs->op.statfs) {
+
1973 if (fs->debug)
+
1974 fuse_log(FUSE_LOG_DEBUG, "statfs %s\n", path);
+
1975
+
1976 return fs->op.statfs(path, buf);
+
1977 } else {
+
1978 buf->f_namemax = 255;
+
1979 buf->f_bsize = 512;
+
1980 return 0;
+
1981 }
+
1982}
+
1983
+
1984int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1985 struct fuse_file_info *fi)
+
1986{
+
1987 fuse_get_context()->private_data = fs->user_data;
+
1988 if (!fs->op.releasedir)
+
1989 return 0;
+
1990
+
1991 if (fs->debug)
+
1992 fuse_log(FUSE_LOG_DEBUG, "releasedir[%llu] flags: 0x%x\n",
+
1993 (unsigned long long) fi->fh, fi->flags);
+
1994
+
1995 return fs->op.releasedir(path, fi);
+
1996}
+
1997
+
1998int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
1999 fuse_fill_dir_t filler, off_t off,
+
2000 struct fuse_file_info *fi,
+
2001 enum fuse_readdir_flags flags)
+
2002{
+
2003 fuse_get_context()->private_data = fs->user_data;
+
2004 if (!fs->op.readdir)
+
2005 return -ENOSYS;
+
2006 if (fs->debug) {
+
2007 fuse_log(FUSE_LOG_DEBUG, "readdir%s[%llu] from %llu\n",
+
2008 (flags & FUSE_READDIR_PLUS) ? "plus" : "",
+
2009 (unsigned long long) fi->fh,
+
2010 (unsigned long long) off);
+
2011 }
+
2012
+
2013 return fs->op.readdir(path, buf, filler, off, fi, flags);
+
2014}
+
2015
+
2016int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
2017 struct fuse_file_info *fi)
+
2018{
+
2019 int err;
+
2020
+
2021 fuse_get_context()->private_data = fs->user_data;
+
2022 if (!fs->op.create)
+
2023 return -ENOSYS;
+
2024
+
2025 if (fs->debug)
+
2026 fuse_log(FUSE_LOG_DEBUG,
+
2027 "create flags: 0x%x %s 0%o umask=0%03o\n",
+
2028 fi->flags, path, mode,
+
2029 fuse_get_context()->umask);
+
2030
+
2031 err = fs->op.create(path, mode, fi);
+
2032
+
2033 if (fs->debug && !err)
+
2034 fuse_log(FUSE_LOG_DEBUG, " create[%llu] flags: 0x%x %s\n",
+
2035 (unsigned long long) fi->fh, fi->flags, path);
+
2036
+
2037 return err;
+
2038}
+
2039
+
2040int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
2041 struct fuse_file_info *fi, int cmd, struct flock *lock)
+
2042{
+
2043 fuse_get_context()->private_data = fs->user_data;
+
2044 if (!fs->op.lock)
+
2045 return -ENOSYS;
+
2046
+
2047 if (fs->debug)
+
2048 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n",
+
2049 (unsigned long long) fi->fh,
+
2050 (cmd == F_GETLK ? "F_GETLK" :
+
2051 (cmd == F_SETLK ? "F_SETLK" :
+
2052 (cmd == F_SETLKW ? "F_SETLKW" : "???"))),
+
2053 (lock->l_type == F_RDLCK ? "F_RDLCK" :
+
2054 (lock->l_type == F_WRLCK ? "F_WRLCK" :
+
2055 (lock->l_type == F_UNLCK ? "F_UNLCK" :
+
2056 "???"))),
+
2057 (unsigned long long) lock->l_start,
+
2058 (unsigned long long) lock->l_len,
+
2059 (unsigned long long) lock->l_pid);
+
2060
+
2061 return fs->op.lock(path, fi, cmd, lock);
+
2062}
+
2063
+
2064int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
2065 struct fuse_file_info *fi, int op)
+
2066{
+
2067 fuse_get_context()->private_data = fs->user_data;
+
2068 if (!fs->op.flock)
+
2069 return -ENOSYS;
+
2070
+
2071 if (fs->debug) {
+
2072 int xop = op & ~LOCK_NB;
+
2073
+
2074 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s%s\n",
+
2075 (unsigned long long) fi->fh,
+
2076 xop == LOCK_SH ? "LOCK_SH" :
+
2077 (xop == LOCK_EX ? "LOCK_EX" :
+
2078 (xop == LOCK_UN ? "LOCK_UN" : "???")),
+
2079 (op & LOCK_NB) ? "|LOCK_NB" : "");
+
2080 }
+
2081 return fs->op.flock(path, fi, op);
+
2082}
+
2083
+
2084int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid,
+
2085 gid_t gid, struct fuse_file_info *fi)
+
2086{
+
2087 fuse_get_context()->private_data = fs->user_data;
+
2088 if (!fs->op.chown)
+
2089 return -ENOSYS;
+
2090 if (fs->debug) {
+
2091 char buf[10];
+
2092
+
2093 fuse_log(FUSE_LOG_DEBUG, "chown[%s] %s %lu %lu\n",
+
2094 file_info_string(fi, buf, sizeof(buf)),
+
2095 path, (unsigned long) uid, (unsigned long) gid);
+
2096 }
+
2097 return fs->op.chown(path, uid, gid, fi);
+
2098}
+
2099
+
2100int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
2101 struct fuse_file_info *fi)
+
2102{
+
2103 fuse_get_context()->private_data = fs->user_data;
+
2104 if (!fs->op.truncate)
+
2105 return -ENOSYS;
+
2106 if (fs->debug) {
+
2107 char buf[10];
+
2108
+
2109 fuse_log(FUSE_LOG_DEBUG, "truncate[%s] %llu\n",
+
2110 file_info_string(fi, buf, sizeof(buf)),
+
2111 (unsigned long long) size);
+
2112 }
+
2113 return fs->op.truncate(path, size, fi);
+
2114}
+
2115
+
2116int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
2117 const struct timespec tv[2], struct fuse_file_info *fi)
+
2118{
+
2119 fuse_get_context()->private_data = fs->user_data;
+
2120 if (!fs->op.utimens)
+
2121 return -ENOSYS;
+
2122 if (fs->debug) {
+
2123 char buf[10];
+
2124
+
2125 fuse_log(FUSE_LOG_DEBUG, "utimens[%s] %s %jd.%09ld %jd.%09ld\n",
+
2126 file_info_string(fi, buf, sizeof(buf)),
+
2127 path, (intmax_t)tv[0].tv_sec, tv[0].tv_nsec,
+
2128 (intmax_t)tv[1].tv_sec, tv[1].tv_nsec);
+
2129 }
+
2130 return fs->op.utimens(path, tv, fi);
+
2131}
+
2132
+
2133int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask)
+
2134{
+
2135 fuse_get_context()->private_data = fs->user_data;
+
2136 if (!fs->op.access)
+
2137 return -ENOSYS;
+
2138 if (fs->debug)
+
2139 fuse_log(FUSE_LOG_DEBUG, "access %s 0%o\n", path, mask);
+
2140
+
2141 return fs->op.access(path, mask);
+
2142}
+
2143
+
2144int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
2145 size_t len)
+
2146{
+
2147 fuse_get_context()->private_data = fs->user_data;
+
2148 if (!fs->op.readlink)
+
2149 return -ENOSYS;
+
2150 if (fs->debug)
+
2151 fuse_log(FUSE_LOG_DEBUG, "readlink %s %lu\n", path,
+
2152 (unsigned long) len);
+
2153
+
2154 return fs->op.readlink(path, buf, len);
+
2155}
+
2156
+
2157int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2158 dev_t rdev)
+
2159{
+
2160 fuse_get_context()->private_data = fs->user_data;
+
2161 if (!fs->op.mknod)
+
2162 return -ENOSYS;
+
2163 if (fs->debug)
+
2164 fuse_log(FUSE_LOG_DEBUG, "mknod %s 0%o 0x%llx umask=0%03o\n",
+
2165 path, mode, (unsigned long long) rdev,
+
2166 fuse_get_context()->umask);
+
2167
+
2168 return fs->op.mknod(path, mode, rdev);
+
2169}
+
2170
+
2171int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode)
+
2172{
+
2173 fuse_get_context()->private_data = fs->user_data;
+
2174 if (!fs->op.mkdir)
+
2175 return -ENOSYS;
+
2176 if (fs->debug)
+
2177 fuse_log(FUSE_LOG_DEBUG, "mkdir %s 0%o umask=0%03o\n",
+
2178 path, mode, fuse_get_context()->umask);
+
2179
+
2180 return fs->op.mkdir(path, mode);
+
2181}
+
2182
+
2183int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2184 const char *value, size_t size, int flags)
+
2185{
+
2186 fuse_get_context()->private_data = fs->user_data;
+
2187 if (!fs->op.setxattr)
+
2188 return -ENOSYS;
+
2189 if (fs->debug)
+
2190 fuse_log(FUSE_LOG_DEBUG, "setxattr %s %s %lu 0x%x\n",
+
2191 path, name, (unsigned long) size, flags);
+
2192
+
2193 return fs->op.setxattr(path, name, value, size, flags);
+
2194}
+
2195
+
2196int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2197 char *value, size_t size)
+
2198{
+
2199 fuse_get_context()->private_data = fs->user_data;
+
2200 if (!fs->op.getxattr)
+
2201 return -ENOSYS;
+
2202 if (fs->debug)
+
2203 fuse_log(FUSE_LOG_DEBUG, "getxattr %s %s %lu\n",
+
2204 path, name, (unsigned long) size);
+
2205
+
2206 return fs->op.getxattr(path, name, value, size);
+
2207}
+
2208
+
2209int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
2210 size_t size)
+
2211{
+
2212 fuse_get_context()->private_data = fs->user_data;
+
2213 if (!fs->op.listxattr)
+
2214 return -ENOSYS;
+
2215 if (fs->debug)
+
2216 fuse_log(FUSE_LOG_DEBUG, "listxattr %s %lu\n",
+
2217 path, (unsigned long) size);
+
2218
+
2219 return fs->op.listxattr(path, list, size);
+
2220}
+
2221
+
2222int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
2223 uint64_t *idx)
+
2224{
+
2225 fuse_get_context()->private_data = fs->user_data;
+
2226 if (!fs->op.bmap)
+
2227 return -ENOSYS;
+
2228 if (fs->debug)
+
2229 fuse_log(FUSE_LOG_DEBUG, "bmap %s blocksize: %lu index: %llu\n",
+
2230 path, (unsigned long) blocksize,
+
2231 (unsigned long long) *idx);
+
2232
+
2233 return fs->op.bmap(path, blocksize, idx);
+
2234}
+
2235
+
2236int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name)
+
2237{
+
2238 fuse_get_context()->private_data = fs->user_data;
+
2239 if (!fs->op.removexattr)
+
2240 return -ENOSYS;
+
2241 if (fs->debug)
+
2242 fuse_log(FUSE_LOG_DEBUG, "removexattr %s %s\n", path, name);
+
2243
+
2244 return fs->op.removexattr(path, name);
+
2245}
+
2246
+
2247int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
2248 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
2249 void *data)
+
2250{
+
2251 fuse_get_context()->private_data = fs->user_data;
+
2252 if (!fs->op.ioctl)
+
2253 return -ENOSYS;
+
2254 if (fs->debug)
+
2255 fuse_log(FUSE_LOG_DEBUG, "ioctl[%llu] 0x%x flags: 0x%x\n",
+
2256 (unsigned long long) fi->fh, cmd, flags);
+
2257
+
2258 return fs->op.ioctl(path, cmd, arg, fi, flags, data);
+
2259}
+
2260
+
2261int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
2262 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
2263 unsigned *reventsp)
+
2264{
+
2265 int res;
+
2266
+
2267 fuse_get_context()->private_data = fs->user_data;
+
2268 if (!fs->op.poll)
+
2269 return -ENOSYS;
+
2270 if (fs->debug)
+
2271 fuse_log(FUSE_LOG_DEBUG, "poll[%llu] ph: %p, events 0x%x\n",
+
2272 (unsigned long long) fi->fh, ph,
+
2273 fi->poll_events);
+
2274
+
2275 res = fs->op.poll(path, fi, ph, reventsp);
+
2276
+
2277 if (fs->debug && !res)
+
2278 fuse_log(FUSE_LOG_DEBUG, " poll[%llu] revents: 0x%x\n",
+
2279 (unsigned long long) fi->fh, *reventsp);
+
2280
+
2281 return res;
+
2282}
+
2283
+
2284int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
2285 off_t offset, off_t length, struct fuse_file_info *fi)
+
2286{
+
2287 fuse_get_context()->private_data = fs->user_data;
+
2288 if (!fs->op.fallocate)
+
2289 return -ENOSYS;
+
2290 if (fs->debug)
+
2291 fuse_log(FUSE_LOG_DEBUG, "fallocate %s mode %x, offset: %llu, length: %llu\n",
+
2292 path,
+
2293 mode,
+
2294 (unsigned long long) offset,
+
2295 (unsigned long long) length);
+
2296
+
2297 return fs->op.fallocate(path, mode, offset, length, fi);
+
2298}
+
2299
+
2300ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
2301 struct fuse_file_info *fi_in, off_t off_in,
+
2302 const char *path_out,
+
2303 struct fuse_file_info *fi_out, off_t off_out,
+
2304 size_t len, int flags)
+
2305{
+
2306 fuse_get_context()->private_data = fs->user_data;
+
2307 if (!fs->op.copy_file_range)
+
2308 return -ENOSYS;
+
2309 if (fs->debug)
+
2310 fuse_log(FUSE_LOG_DEBUG, "copy_file_range from %s:%llu to "
+
2311 "%s:%llu, length: %llu\n",
+
2312 path_in,
+
2313 (unsigned long long) off_in,
+
2314 path_out,
+
2315 (unsigned long long) off_out,
+
2316 (unsigned long long) len);
+
2317
+
2318 return fs->op.copy_file_range(path_in, fi_in, off_in, path_out,
+
2319 fi_out, off_out, len, flags);
+
2320}
+
2321
+
2322off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
2323 struct fuse_file_info *fi)
+
2324{
+
2325 fuse_get_context()->private_data = fs->user_data;
+
2326 if (!fs->op.lseek)
+
2327 return -ENOSYS;
+
2328 if (fs->debug) {
+
2329 char buf[10];
+
2330
+
2331 fuse_log(FUSE_LOG_DEBUG, "lseek[%s] %llu %d\n",
+
2332 file_info_string(fi, buf, sizeof(buf)),
+
2333 (unsigned long long) off, whence);
+
2334 }
+
2335 return fs->op.lseek(path, off, whence, fi);
+
2336}
+
2337
+
2338#ifdef HAVE_STATX
+
2339int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask,
+
2340 struct statx *stxbuf, struct fuse_file_info *fi)
+
2341{
+
2342 fuse_get_context()->private_data = fs->user_data;
+
2343 if (fs->op.statx) {
+
2344 if (fs->debug) {
+
2345 char buf[10];
+
2346
+
2347 fuse_log(FUSE_LOG_DEBUG, "statx[%s] %s %d %d\n",
+
2348 file_info_string(fi, buf, sizeof(buf)), path,
+
2349 flags, mask);
+
2350 }
+
2351 return fs->op.statx(path, flags, mask, stxbuf, fi);
+
2352 }
+
2353
+
2354 return -ENOSYS;
+
2355}
+
2356#else
+
2357int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask,
+
2358 struct statx *stxbuf, struct fuse_file_info *fi)
+
2359{
+
2360 (void)fs;
+
2361 (void)path;
+
2362 (void)flags;
+
2363 (void)mask;
+
2364 (void)stxbuf;
+
2365 (void)fi;
+
2366
+
2367 return -ENOSYS;
+
2368}
+
2369#endif
+
2370
+
2371static int is_open(struct fuse *f, fuse_ino_t dir, const char *name)
+
2372{
+
2373 struct node *node;
+
2374 int isopen = 0;
+
2375 pthread_mutex_lock(&f->lock);
+
2376 node = lookup_node(f, dir, name);
+
2377 if (node && node->open_count > 0)
+
2378 isopen = 1;
+
2379 pthread_mutex_unlock(&f->lock);
+
2380 return isopen;
+
2381}
+
2382
+
2383static char *hidden_name(struct fuse *f, fuse_ino_t dir, const char *oldname,
+
2384 char *newname, size_t bufsize)
+
2385{
+
2386 struct stat buf;
+
2387 struct node *node;
+
2388 struct node *newnode;
+
2389 char *newpath;
+
2390 int res;
+
2391 int failctr = 10;
+
2392
+
2393 do {
+
2394 pthread_mutex_lock(&f->lock);
+
2395 node = lookup_node(f, dir, oldname);
+
2396 if (node == NULL) {
+
2397 pthread_mutex_unlock(&f->lock);
+
2398 return NULL;
+
2399 }
+
2400 do {
+
2401 f->hidectr ++;
+
2402 snprintf(newname, bufsize, ".fuse_hidden%08x%08x",
+
2403 (unsigned int) node->nodeid, f->hidectr);
+
2404 newnode = lookup_node(f, dir, newname);
+
2405 } while(newnode);
+
2406
+
2407 res = try_get_path(f, dir, newname, &newpath, NULL, false);
+
2408 pthread_mutex_unlock(&f->lock);
+
2409 if (res)
+
2410 break;
+
2411
+
2412 memset(&buf, 0, sizeof(buf));
+
2413 res = fuse_fs_getattr(f->fs, newpath, &buf, NULL);
+
2414 if (res == -ENOENT)
+
2415 break;
+
2416 free(newpath);
+
2417 newpath = NULL;
+
2418 } while(res == 0 && --failctr);
+
2419
+
2420 return newpath;
+
2421}
+
2422
+
2423static int hide_node(struct fuse *f, const char *oldpath,
+
2424 fuse_ino_t dir, const char *oldname)
+
2425{
+
2426 char newname[64];
+
2427 char *newpath;
+
2428 int err = -EBUSY;
+
2429
+
2430 newpath = hidden_name(f, dir, oldname, newname, sizeof(newname));
+
2431 if (newpath) {
+
2432 err = fuse_fs_rename(f->fs, oldpath, newpath, 0);
+
2433 if (!err)
+
2434 err = rename_node(f, dir, oldname, dir, newname, 1);
+
2435 free(newpath);
+
2436 }
+
2437 return err;
+
2438}
+
2439
+
2440static int mtime_eq(const struct stat *stbuf, const struct timespec *ts)
+
2441{
+
2442 return stbuf->st_mtime == ts->tv_sec &&
+
2443 ST_MTIM_NSEC(stbuf) == ts->tv_nsec;
+
2444}
+
2445
+
2446#ifndef CLOCK_MONOTONIC
+
2447#define CLOCK_MONOTONIC CLOCK_REALTIME
+
2448#endif
+
2449
+
2450static void curr_time(struct timespec *now)
+
2451{
+
2452 static clockid_t clockid = CLOCK_MONOTONIC;
+
2453 int res = clock_gettime(clockid, now);
+
2454 if (res == -1 && errno == EINVAL) {
+
2455 clockid = CLOCK_REALTIME;
+
2456 res = clock_gettime(clockid, now);
+
2457 }
+
2458 if (res == -1) {
+
2459 perror("fuse: clock_gettime");
+
2460 abort();
+
2461 }
+
2462}
+
2463
+
2464static void update_stat(struct node *node, const struct stat *stbuf)
+
2465{
+
2466 if (node->cache_valid && (!mtime_eq(stbuf, &node->mtime) ||
+
2467 stbuf->st_size != node->size))
+
2468 node->cache_valid = 0;
+
2469 node->mtime.tv_sec = stbuf->st_mtime;
+
2470 node->mtime.tv_nsec = ST_MTIM_NSEC(stbuf);
+
2471 node->size = stbuf->st_size;
+
2472 curr_time(&node->stat_updated);
+
2473}
+
2474
+
2475static int do_lookup(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
2476 struct fuse_entry_param *e)
+
2477{
+
2478 struct node *node;
+
2479
+
2480 node = find_node(f, nodeid, name);
+
2481 if (node == NULL)
+
2482 return -ENOMEM;
+
2483
+
2484 e->ino = node->nodeid;
+
2485 e->generation = node->generation;
+
2486 e->entry_timeout = f->conf.entry_timeout;
+
2487 e->attr_timeout = f->conf.attr_timeout;
+
2488 if (f->conf.auto_cache) {
+
2489 pthread_mutex_lock(&f->lock);
+
2490 update_stat(node, &e->attr);
+
2491 pthread_mutex_unlock(&f->lock);
+
2492 }
+
2493 set_stat(f, e->ino, &e->attr);
+
2494 return 0;
+
2495}
+
2496
+
2497static int lookup_path(struct fuse *f, fuse_ino_t nodeid,
+
2498 const char *name, const char *path,
+
2499 struct fuse_entry_param *e, struct fuse_file_info *fi)
+
2500{
+
2501 int res;
+
2502
+
2503 memset(e, 0, sizeof(struct fuse_entry_param));
+
2504 res = fuse_fs_getattr(f->fs, path, &e->attr, fi);
+
2505 if (res == 0) {
+
2506 res = do_lookup(f, nodeid, name, e);
+
2507 if (res == 0 && f->conf.debug) {
+
2508 fuse_log(FUSE_LOG_DEBUG, " NODEID: %llu\n",
+
2509 (unsigned long long) e->ino);
+
2510 }
+
2511 }
+
2512 return res;
+
2513}
+
2514
+
2515static struct fuse_context_i *fuse_get_context_internal(void)
+
2516{
+
2517 return (struct fuse_context_i *) pthread_getspecific(fuse_context_key);
+
2518}
+
2519
+
2520static struct fuse_context_i *fuse_create_context(struct fuse *f)
+
2521{
+
2522 struct fuse_context_i *c = fuse_get_context_internal();
+
2523 if (c == NULL) {
+
2524 c = (struct fuse_context_i *)
+
2525 calloc(1, sizeof(struct fuse_context_i));
+
2526 if (c == NULL) {
+
2527 /* This is hard to deal with properly, so just
+
2528 abort. If memory is so low that the
+
2529 context cannot be allocated, there's not
+
2530 much hope for the filesystem anyway */
+
2531 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate thread specific data\n");
+
2532 abort();
+
2533 }
+
2534 pthread_setspecific(fuse_context_key, c);
+
2535 } else {
+
2536 memset(c, 0, sizeof(*c));
+
2537 }
+
2538 c->ctx.fuse = f;
+
2539
+
2540 return c;
+
2541}
+
2542
+
2543static void fuse_freecontext(void *data)
+
2544{
+
2545 free(data);
+
2546}
+
2547
+
2548static int fuse_create_context_key(void)
+
2549{
+
2550 int err = 0;
+
2551 pthread_mutex_lock(&fuse_context_lock);
+
2552 if (!fuse_context_ref) {
+
2553 err = pthread_key_create(&fuse_context_key, fuse_freecontext);
+
2554 if (err) {
+
2555 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
2556 strerror(err));
+
2557 pthread_mutex_unlock(&fuse_context_lock);
+
2558 return -1;
+
2559 }
+
2560 }
+
2561 fuse_context_ref++;
+
2562 pthread_mutex_unlock(&fuse_context_lock);
+
2563 return 0;
+
2564}
+
2565
+
2566static void fuse_delete_context_key(void)
+
2567{
+
2568 pthread_mutex_lock(&fuse_context_lock);
+
2569 fuse_context_ref--;
+
2570 if (!fuse_context_ref) {
+
2571 free(pthread_getspecific(fuse_context_key));
+
2572 pthread_key_delete(fuse_context_key);
+
2573 }
+
2574 pthread_mutex_unlock(&fuse_context_lock);
+
2575}
+
2576
+
2577static struct fuse *req_fuse_prepare(fuse_req_t req)
+
2578{
+
2579 struct fuse_context_i *c = fuse_create_context(req_fuse(req));
+
2580 const struct fuse_ctx *ctx = fuse_req_ctx(req);
+
2581 c->req = req;
+
2582 c->ctx.uid = ctx->uid;
+
2583 c->ctx.gid = ctx->gid;
+
2584 c->ctx.pid = ctx->pid;
+
2585 c->ctx.umask = ctx->umask;
+
2586 return c->ctx.fuse;
+
2587}
+
2588
+
2589static inline void reply_err(fuse_req_t req, int err)
+
2590{
+
2591 /* fuse_reply_err() uses non-negated errno values */
+
2592 fuse_reply_err(req, -err);
+
2593}
+
2594
+
2595static void reply_entry(fuse_req_t req, const struct fuse_entry_param *e,
+
2596 int err)
+
2597{
+
2598 if (!err) {
+
2599 struct fuse *f = req_fuse(req);
+
2600 if (fuse_reply_entry(req, e) == -ENOENT) {
+
2601 /* Skip forget for negative result */
+
2602 if (e->ino != 0)
+
2603 forget_node(f, e->ino, 1);
+
2604 }
+
2605 } else
+
2606 reply_err(req, err);
+
2607}
+
2608
+
2609void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
2610 struct fuse_config *cfg)
+
2611{
+
2612 fuse_get_context()->private_data = fs->user_data;
+
2613 if (!fs->op.write_buf)
+ +
2615 if (!fs->op.lock)
+ +
2617 if (!fs->op.flock)
+ +
2619 if (fs->op.init)
+
2620 fs->user_data = fs->op.init(conn, cfg);
+
2621}
+
2622
+
2623static int fuse_init_intr_signal(int signum, int *installed);
+
2624
+
2625static void fuse_lib_init(void *data, struct fuse_conn_info *conn)
+
2626{
+
2627 struct fuse *f = (struct fuse *) data;
+
2628
+
2629 fuse_create_context(f);
+ +
2631 fuse_fs_init(f->fs, conn, &f->conf);
+
2632
+
2633 if (f->conf.intr) {
+
2634 if (fuse_init_intr_signal(f->conf.intr_signal,
+
2635 &f->intr_installed) == -1)
+
2636 fuse_log(FUSE_LOG_ERR, "fuse: failed to init interrupt signal\n");
+
2637 } else {
+
2638 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
2639 conn->no_interrupt = 1;
+
2640 }
+
2641}
+
2642
+
2643void fuse_fs_destroy(struct fuse_fs *fs)
+
2644{
+
2645 fuse_get_context()->private_data = fs->user_data;
+
2646 if (fs->op.destroy)
+
2647 fs->op.destroy(fs->user_data);
+
2648}
+
2649
+
2650static void fuse_lib_destroy(void *data)
+
2651{
+
2652 struct fuse *f = (struct fuse *) data;
+
2653
+
2654 fuse_create_context(f);
+
2655 fuse_fs_destroy(f->fs);
+
2656}
+
2657
+
2658static void fuse_lib_lookup(fuse_req_t req, fuse_ino_t parent,
+
2659 const char *name)
+
2660{
+
2661 struct fuse *f = req_fuse_prepare(req);
+
2662 struct fuse_entry_param e = { .ino = 0 }; /* invalid ino */
+
2663 char *path;
+
2664 int err;
+
2665 struct node *dot = NULL;
+
2666
+
2667 if (name[0] == '.') {
+
2668 int len = strlen(name);
+
2669
+
2670 if (len == 1 || (name[1] == '.' && len == 2)) {
+
2671 pthread_mutex_lock(&f->lock);
+
2672 if (len == 1) {
+
2673 if (f->conf.debug)
+
2674 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOT\n");
+
2675 dot = get_node_nocheck(f, parent);
+
2676 if (dot == NULL) {
+
2677 pthread_mutex_unlock(&f->lock);
+
2678 reply_entry(req, &e, -ESTALE);
+
2679 return;
+
2680 }
+
2681 dot->refctr++;
+
2682 } else {
+
2683 if (f->conf.debug)
+
2684 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOTDOT\n");
+
2685 parent = get_node(f, parent)->parent->nodeid;
+
2686 }
+
2687 pthread_mutex_unlock(&f->lock);
+
2688 name = NULL;
+
2689 }
+
2690 }
+
2691
+
2692 err = get_path_name(f, parent, name, &path);
+
2693 if (!err) {
+
2694 struct fuse_intr_data d;
+
2695 if (f->conf.debug)
+
2696 fuse_log(FUSE_LOG_DEBUG, "LOOKUP %s\n", path);
+
2697 fuse_prepare_interrupt(f, req, &d);
+
2698 err = lookup_path(f, parent, name, path, &e, NULL);
+
2699 if (err == -ENOENT && f->conf.negative_timeout != 0.0) {
+
2700 e.ino = 0;
+
2701 e.entry_timeout = f->conf.negative_timeout;
+
2702 err = 0;
+
2703 }
+
2704 fuse_finish_interrupt(f, req, &d);
+
2705 free_path(f, parent, path);
+
2706 }
+
2707 if (dot) {
+
2708 pthread_mutex_lock(&f->lock);
+
2709 unref_node(f, dot);
+
2710 pthread_mutex_unlock(&f->lock);
+
2711 }
+
2712 reply_entry(req, &e, err);
+
2713}
+
2714
+
2715static void do_forget(struct fuse *f, fuse_ino_t ino, uint64_t nlookup)
+
2716{
+
2717 if (f->conf.debug)
+
2718 fuse_log(FUSE_LOG_DEBUG, "FORGET %llu/%llu\n", (unsigned long long)ino,
+
2719 (unsigned long long) nlookup);
+
2720 forget_node(f, ino, nlookup);
+
2721}
+
2722
+
2723static void fuse_lib_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
2724{
+
2725 do_forget(req_fuse(req), ino, nlookup);
+
2726 fuse_reply_none(req);
+
2727}
+
2728
+
2729static void fuse_lib_forget_multi(fuse_req_t req, size_t count,
+
2730 struct fuse_forget_data *forgets)
+
2731{
+
2732 struct fuse *f = req_fuse(req);
+
2733 size_t i;
+
2734
+
2735 for (i = 0; i < count; i++)
+
2736 do_forget(f, forgets[i].ino, forgets[i].nlookup);
+
2737
+
2738 fuse_reply_none(req);
+
2739}
+
2740
+
2741
+
2742static void fuse_lib_getattr(fuse_req_t req, fuse_ino_t ino,
+
2743 struct fuse_file_info *fi)
+
2744{
+
2745 struct fuse *f = req_fuse_prepare(req);
+
2746 struct stat buf;
+
2747 char *path;
+
2748 int err;
+
2749
+
2750 memset(&buf, 0, sizeof(buf));
+
2751
+
2752 if (fi != NULL)
+
2753 err = get_path_nullok(f, ino, &path);
+
2754 else
+
2755 err = get_path(f, ino, &path);
+
2756 if (!err) {
+
2757 struct fuse_intr_data d;
+
2758 fuse_prepare_interrupt(f, req, &d);
+
2759 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2760 fuse_finish_interrupt(f, req, &d);
+
2761 free_path(f, ino, path);
+
2762 }
+
2763 if (!err) {
+
2764 struct node *node;
+
2765
+
2766 pthread_mutex_lock(&f->lock);
+
2767 node = get_node(f, ino);
+
2768 if (node->is_hidden && buf.st_nlink > 0)
+
2769 buf.st_nlink--;
+
2770 if (f->conf.auto_cache)
+
2771 update_stat(node, &buf);
+
2772 pthread_mutex_unlock(&f->lock);
+
2773 set_stat(f, ino, &buf);
+
2774 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2775 } else
+
2776 reply_err(req, err);
+
2777}
+
2778
+
2779int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2780 struct fuse_file_info *fi)
+
2781{
+
2782 fuse_get_context()->private_data = fs->user_data;
+
2783 if (!fs->op.chmod)
+
2784 return -ENOSYS;
+
2785
+
2786 if (fs->debug) {
+
2787 char buf[10];
+
2788
+
2789 fuse_log(FUSE_LOG_DEBUG, "chmod[%s] %s %llo\n",
+
2790 file_info_string(fi, buf, sizeof(buf)),
+
2791 path, (unsigned long long) mode);
+
2792 }
+
2793 return fs->op.chmod(path, mode, fi);
+
2794}
+
2795
+
2796static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
2797 int valid, struct fuse_file_info *fi)
+
2798{
+
2799 struct fuse *f = req_fuse_prepare(req);
+
2800 struct stat buf;
+
2801 char *path;
+
2802 int err;
+
2803
+
2804 memset(&buf, 0, sizeof(buf));
+
2805 if (fi != NULL)
+
2806 err = get_path_nullok(f, ino, &path);
+
2807 else
+
2808 err = get_path(f, ino, &path);
+
2809 if (!err) {
+
2810 struct fuse_intr_data d;
+
2811 fuse_prepare_interrupt(f, req, &d);
+
2812 err = 0;
+
2813 if (!err && (valid & FUSE_SET_ATTR_MODE))
+
2814 err = fuse_fs_chmod(f->fs, path, attr->st_mode, fi);
+
2815 if (!err && (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))) {
+
2816 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
2817 attr->st_uid : (uid_t) -1;
+
2818 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
2819 attr->st_gid : (gid_t) -1;
+
2820 err = fuse_fs_chown(f->fs, path, uid, gid, fi);
+
2821 }
+
2822 if (!err && (valid & FUSE_SET_ATTR_SIZE)) {
+
2823 err = fuse_fs_truncate(f->fs, path,
+
2824 attr->st_size, fi);
+
2825 }
+
2826#ifdef HAVE_UTIMENSAT
+
2827 if (!err &&
+
2828 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) {
+
2829 struct timespec tv[2];
+
2830
+
2831 tv[0].tv_sec = 0;
+
2832 tv[1].tv_sec = 0;
+
2833 tv[0].tv_nsec = UTIME_OMIT;
+
2834 tv[1].tv_nsec = UTIME_OMIT;
+
2835
+
2836 if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
2837 tv[0].tv_nsec = UTIME_NOW;
+
2838 else if (valid & FUSE_SET_ATTR_ATIME)
+
2839 tv[0] = attr->st_atim;
+
2840
+
2841 if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
2842 tv[1].tv_nsec = UTIME_NOW;
+
2843 else if (valid & FUSE_SET_ATTR_MTIME)
+
2844 tv[1] = attr->st_mtim;
+
2845
+
2846 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2847 } else
+
2848#endif
+
2849 if (!err &&
+
2850 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) ==
+
2851 (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
2852 struct timespec tv[2];
+
2853 tv[0].tv_sec = attr->st_atime;
+
2854 tv[0].tv_nsec = ST_ATIM_NSEC(attr);
+
2855 tv[1].tv_sec = attr->st_mtime;
+
2856 tv[1].tv_nsec = ST_MTIM_NSEC(attr);
+
2857 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2858 }
+
2859 if (!err) {
+
2860 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2861 }
+
2862 fuse_finish_interrupt(f, req, &d);
+
2863 free_path(f, ino, path);
+
2864 }
+
2865 if (!err) {
+
2866 if (f->conf.auto_cache) {
+
2867 pthread_mutex_lock(&f->lock);
+
2868 update_stat(get_node(f, ino), &buf);
+
2869 pthread_mutex_unlock(&f->lock);
+
2870 }
+
2871 set_stat(f, ino, &buf);
+
2872 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2873 } else
+
2874 reply_err(req, err);
+
2875}
+
2876
+
2877static void fuse_lib_access(fuse_req_t req, fuse_ino_t ino, int mask)
+
2878{
+
2879 struct fuse *f = req_fuse_prepare(req);
+
2880 char *path;
+
2881 int err;
+
2882
+
2883 err = get_path(f, ino, &path);
+
2884 if (!err) {
+
2885 struct fuse_intr_data d;
+
2886
+
2887 fuse_prepare_interrupt(f, req, &d);
+
2888 err = fuse_fs_access(f->fs, path, mask);
+
2889 fuse_finish_interrupt(f, req, &d);
+
2890 free_path(f, ino, path);
+
2891 }
+
2892 reply_err(req, err);
+
2893}
+
2894
+
2895static void fuse_lib_readlink(fuse_req_t req, fuse_ino_t ino)
+
2896{
+
2897 struct fuse *f = req_fuse_prepare(req);
+
2898 char linkname[PATH_MAX + 1];
+
2899 char *path;
+
2900 int err;
+
2901
+
2902 err = get_path(f, ino, &path);
+
2903 if (!err) {
+
2904 struct fuse_intr_data d;
+
2905 fuse_prepare_interrupt(f, req, &d);
+
2906 err = fuse_fs_readlink(f->fs, path, linkname, sizeof(linkname));
+
2907 fuse_finish_interrupt(f, req, &d);
+
2908 free_path(f, ino, path);
+
2909 }
+
2910 if (!err) {
+
2911 linkname[PATH_MAX] = '\0';
+
2912 fuse_reply_readlink(req, linkname);
+
2913 } else
+
2914 reply_err(req, err);
+
2915}
+
2916
+
2917static void fuse_lib_mknod(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2918 mode_t mode, dev_t rdev)
+
2919{
+
2920 struct fuse *f = req_fuse_prepare(req);
+
2921 struct fuse_entry_param e;
+
2922 char *path;
+
2923 int err;
+
2924
+
2925 err = get_path_name(f, parent, name, &path);
+
2926 if (!err) {
+
2927 struct fuse_intr_data d;
+
2928
+
2929 fuse_prepare_interrupt(f, req, &d);
+
2930 err = -ENOSYS;
+
2931 if (S_ISREG(mode)) {
+
2932 struct fuse_file_info fi;
+
2933
+
2934 memset(&fi, 0, sizeof(fi));
+
2935 fi.flags = O_CREAT | O_EXCL | O_WRONLY;
+
2936 err = fuse_fs_create(f->fs, path, mode, &fi);
+
2937 if (!err) {
+
2938 err = lookup_path(f, parent, name, path, &e,
+
2939 &fi);
+
2940 fuse_fs_release(f->fs, path, &fi);
+
2941 }
+
2942 }
+
2943 if (err == -ENOSYS) {
+
2944 err = fuse_fs_mknod(f->fs, path, mode, rdev);
+
2945 if (!err)
+
2946 err = lookup_path(f, parent, name, path, &e,
+
2947 NULL);
+
2948 }
+
2949 fuse_finish_interrupt(f, req, &d);
+
2950 free_path(f, parent, path);
+
2951 }
+
2952 reply_entry(req, &e, err);
+
2953}
+
2954
+
2955static void fuse_lib_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2956 mode_t mode)
+
2957{
+
2958 struct fuse *f = req_fuse_prepare(req);
+
2959 struct fuse_entry_param e;
+
2960 char *path;
+
2961 int err;
+
2962
+
2963 err = get_path_name(f, parent, name, &path);
+
2964 if (!err) {
+
2965 struct fuse_intr_data d;
+
2966
+
2967 fuse_prepare_interrupt(f, req, &d);
+
2968 err = fuse_fs_mkdir(f->fs, path, mode);
+
2969 if (!err)
+
2970 err = lookup_path(f, parent, name, path, &e, NULL);
+
2971 fuse_finish_interrupt(f, req, &d);
+
2972 free_path(f, parent, path);
+
2973 }
+
2974 reply_entry(req, &e, err);
+
2975}
+
2976
+
2977static void fuse_lib_unlink(fuse_req_t req, fuse_ino_t parent,
+
2978 const char *name)
+
2979{
+
2980 struct fuse *f = req_fuse_prepare(req);
+
2981 struct node *wnode;
+
2982 char *path;
+
2983 int err;
+
2984
+
2985 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
2986 if (!err) {
+
2987 struct fuse_intr_data d;
+
2988
+
2989 fuse_prepare_interrupt(f, req, &d);
+
2990 if (!f->conf.hard_remove && is_open(f, parent, name)) {
+
2991 err = hide_node(f, path, parent, name);
+
2992 if (!err) {
+
2993 /* we have hidden the node so now check again under a lock in case it is not used any more */
+
2994 if (!is_open(f, parent, wnode->name)) {
+
2995 char *unlinkpath;
+
2996
+
2997 /* get the hidden file path, to unlink it */
+
2998 if (try_get_path(f, wnode->nodeid, NULL, &unlinkpath, NULL, false) == 0) {
+
2999 err = fuse_fs_unlink(f->fs, unlinkpath);
+
3000 if (!err)
+
3001 remove_node(f, parent, wnode->name);
+
3002 free(unlinkpath);
+
3003 }
+
3004 }
+
3005 }
+
3006 } else {
+
3007 err = fuse_fs_unlink(f->fs, path);
+
3008 if (!err)
+
3009 remove_node(f, parent, name);
+
3010 }
+
3011 fuse_finish_interrupt(f, req, &d);
+
3012 free_path_wrlock(f, parent, wnode, path);
+
3013 }
+
3014 reply_err(req, err);
+
3015}
+
3016
+
3017static void fuse_lib_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
3018{
+
3019 struct fuse *f = req_fuse_prepare(req);
+
3020 struct node *wnode;
+
3021 char *path;
+
3022 int err;
+
3023
+
3024 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
3025 if (!err) {
+
3026 struct fuse_intr_data d;
+
3027
+
3028 fuse_prepare_interrupt(f, req, &d);
+
3029 err = fuse_fs_rmdir(f->fs, path);
+
3030 fuse_finish_interrupt(f, req, &d);
+
3031 if (!err)
+
3032 remove_node(f, parent, name);
+
3033 free_path_wrlock(f, parent, wnode, path);
+
3034 }
+
3035 reply_err(req, err);
+
3036}
+
3037
+
3038static void fuse_lib_symlink(fuse_req_t req, const char *linkname,
+
3039 fuse_ino_t parent, const char *name)
+
3040{
+
3041 struct fuse *f = req_fuse_prepare(req);
+
3042 struct fuse_entry_param e;
+
3043 char *path;
+
3044 int err;
+
3045
+
3046 err = get_path_name(f, parent, name, &path);
+
3047 if (!err) {
+
3048 struct fuse_intr_data d;
+
3049
+
3050 fuse_prepare_interrupt(f, req, &d);
+
3051 err = fuse_fs_symlink(f->fs, linkname, path);
+
3052 if (!err)
+
3053 err = lookup_path(f, parent, name, path, &e, NULL);
+
3054 fuse_finish_interrupt(f, req, &d);
+
3055 free_path(f, parent, path);
+
3056 }
+
3057 reply_entry(req, &e, err);
+
3058}
+
3059
+
3060static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir,
+
3061 const char *oldname, fuse_ino_t newdir,
+
3062 const char *newname, unsigned int flags)
+
3063{
+
3064 struct fuse *f = req_fuse_prepare(req);
+
3065 char *oldpath;
+
3066 char *newpath;
+
3067 struct node *wnode1;
+
3068 struct node *wnode2;
+
3069 int err;
+
3070
+
3071 err = get_path2(f, olddir, oldname, newdir, newname,
+
3072 &oldpath, &newpath, &wnode1, &wnode2);
+
3073 if (!err) {
+
3074 struct fuse_intr_data d;
+
3075 err = 0;
+
3076 fuse_prepare_interrupt(f, req, &d);
+
3077 if (!f->conf.hard_remove && !(flags & RENAME_EXCHANGE) &&
+
3078 is_open(f, newdir, newname))
+
3079 err = hide_node(f, newpath, newdir, newname);
+
3080 if (!err) {
+
3081 err = fuse_fs_rename(f->fs, oldpath, newpath, flags);
+
3082 if (!err) {
+
3083 if (flags & RENAME_EXCHANGE) {
+
3084 err = exchange_node(f, olddir, oldname,
+
3085 newdir, newname);
+
3086 } else {
+
3087 err = rename_node(f, olddir, oldname,
+
3088 newdir, newname, 0);
+
3089 }
+
3090 }
+
3091 }
+
3092 fuse_finish_interrupt(f, req, &d);
+
3093 free_path2(f, olddir, newdir, wnode1, wnode2, oldpath, newpath);
+
3094 }
+
3095 reply_err(req, err);
+
3096}
+
3097
+
3098static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
3099 const char *newname)
+
3100{
+
3101 struct fuse *f = req_fuse_prepare(req);
+
3102 struct fuse_entry_param e;
+
3103 char *oldpath;
+
3104 char *newpath;
+
3105 int err;
+
3106
+
3107 err = get_path2(f, ino, NULL, newparent, newname,
+
3108 &oldpath, &newpath, NULL, NULL);
+
3109 if (!err) {
+
3110 struct fuse_intr_data d;
+
3111
+
3112 fuse_prepare_interrupt(f, req, &d);
+
3113 err = fuse_fs_link(f->fs, oldpath, newpath);
+
3114 if (!err)
+
3115 err = lookup_path(f, newparent, newname, newpath,
+
3116 &e, NULL);
+
3117 fuse_finish_interrupt(f, req, &d);
+
3118 free_path2(f, ino, newparent, NULL, NULL, oldpath, newpath);
+
3119 }
+
3120 reply_entry(req, &e, err);
+
3121}
+
3122
+
3123static void fuse_do_release(struct fuse *f, fuse_ino_t ino, const char *path,
+
3124 struct fuse_file_info *fi)
+
3125{
+
3126 struct node *node;
+
3127 int unlink_hidden = 0;
+
3128
+
3129 fuse_fs_release(f->fs, path, fi);
+
3130
+
3131 pthread_mutex_lock(&f->lock);
+
3132 node = get_node(f, ino);
+
3133 assert(node->open_count > 0);
+
3134 --node->open_count;
+
3135 if (node->is_hidden && !node->open_count) {
+
3136 unlink_hidden = 1;
+
3137 node->is_hidden = 0;
+
3138 }
+
3139 pthread_mutex_unlock(&f->lock);
+
3140
+
3141 if(unlink_hidden) {
+
3142 if (path) {
+
3143 fuse_fs_unlink(f->fs, path);
+
3144 } else if (f->conf.nullpath_ok) {
+
3145 char *unlinkpath;
+
3146
+
3147 if (get_path(f, ino, &unlinkpath) == 0)
+
3148 fuse_fs_unlink(f->fs, unlinkpath);
+
3149
+
3150 free_path(f, ino, unlinkpath);
+
3151 }
+
3152 }
+
3153}
+
3154
+
3155static void fuse_lib_create(fuse_req_t req, fuse_ino_t parent,
+
3156 const char *name, mode_t mode,
+
3157 struct fuse_file_info *fi)
+
3158{
+
3159 struct fuse *f = req_fuse_prepare(req);
+
3160 struct fuse_intr_data d;
+
3161 struct fuse_entry_param e;
+
3162 char *path;
+
3163 int err;
+
3164
+
3165 err = get_path_name(f, parent, name, &path);
+
3166 if (!err) {
+
3167 fuse_prepare_interrupt(f, req, &d);
+
3168 err = fuse_fs_create(f->fs, path, mode, fi);
+
3169 if (!err) {
+
3170 err = lookup_path(f, parent, name, path, &e, fi);
+
3171 if (err)
+
3172 fuse_fs_release(f->fs, path, fi);
+
3173 else if (!S_ISREG(e.attr.st_mode)) {
+
3174 err = -EIO;
+
3175 fuse_fs_release(f->fs, path, fi);
+
3176 forget_node(f, e.ino, 1);
+
3177 } else {
+
3178 if (f->conf.direct_io)
+
3179 fi->direct_io = 1;
+
3180 if (f->conf.kernel_cache)
+
3181 fi->keep_cache = 1;
+
3182 if (fi->direct_io &&
+
3183 f->conf.parallel_direct_writes)
+
3184 fi->parallel_direct_writes = 1;
+
3185 }
+
3186 }
+
3187 fuse_finish_interrupt(f, req, &d);
+
3188 }
+
3189 if (!err) {
+
3190 pthread_mutex_lock(&f->lock);
+
3191 get_node(f, e.ino)->open_count++;
+
3192 pthread_mutex_unlock(&f->lock);
+
3193 if (fuse_reply_create(req, &e, fi) == -ENOENT) {
+
3194 /* The open syscall was interrupted, so it
+
3195 must be cancelled */
+
3196 fuse_do_release(f, e.ino, path, fi);
+
3197 forget_node(f, e.ino, 1);
+
3198 }
+
3199 } else {
+
3200 reply_err(req, err);
+
3201 }
+
3202
+
3203 free_path(f, parent, path);
+
3204}
+
3205
+
3206static double diff_timespec(const struct timespec *t1,
+
3207 const struct timespec *t2)
+
3208{
+
3209 return (t1->tv_sec - t2->tv_sec) +
+
3210 ((double) t1->tv_nsec - (double) t2->tv_nsec) / 1000000000.0;
+
3211}
+
3212
+
3213static void open_auto_cache(struct fuse *f, fuse_ino_t ino, const char *path,
+
3214 struct fuse_file_info *fi)
+
3215{
+
3216 struct node *node;
+
3217
+
3218 pthread_mutex_lock(&f->lock);
+
3219 node = get_node(f, ino);
+
3220 if (node->cache_valid) {
+
3221 struct timespec now;
+
3222
+
3223 curr_time(&now);
+
3224 if (diff_timespec(&now, &node->stat_updated) >
+
3225 f->conf.ac_attr_timeout) {
+
3226 struct stat stbuf;
+
3227 int err;
+
3228 pthread_mutex_unlock(&f->lock);
+
3229 err = fuse_fs_getattr(f->fs, path, &stbuf, fi);
+
3230 pthread_mutex_lock(&f->lock);
+
3231 if (!err)
+
3232 update_stat(node, &stbuf);
+
3233 else
+
3234 node->cache_valid = 0;
+
3235 }
+
3236 }
+
3237 if (node->cache_valid)
+
3238 fi->keep_cache = 1;
+
3239
+
3240 node->cache_valid = 1;
+
3241 pthread_mutex_unlock(&f->lock);
+
3242}
+
3243
+
3244static void fuse_lib_open(fuse_req_t req, fuse_ino_t ino,
+
3245 struct fuse_file_info *fi)
+
3246{
+
3247 struct fuse *f = req_fuse_prepare(req);
+
3248 struct fuse_intr_data d;
+
3249 char *path;
+
3250 int err;
+
3251
+
3252 err = get_path(f, ino, &path);
+
3253 if (!err) {
+
3254 fuse_prepare_interrupt(f, req, &d);
+
3255 err = fuse_fs_open(f->fs, path, fi);
+
3256 if (!err) {
+
3257 if (f->conf.direct_io)
+
3258 fi->direct_io = 1;
+
3259 if (f->conf.kernel_cache)
+
3260 fi->keep_cache = 1;
+
3261
+
3262 if (f->conf.auto_cache)
+
3263 open_auto_cache(f, ino, path, fi);
+
3264
+
3265 if (f->conf.no_rofd_flush &&
+
3266 (fi->flags & O_ACCMODE) == O_RDONLY)
+
3267 fi->noflush = 1;
+
3268
+
3269 if (fi->direct_io && f->conf.parallel_direct_writes)
+
3270 fi->parallel_direct_writes = 1;
+
3271
+
3272 }
+
3273 fuse_finish_interrupt(f, req, &d);
+
3274 }
+
3275 if (!err) {
+
3276 pthread_mutex_lock(&f->lock);
+
3277 get_node(f, ino)->open_count++;
+
3278 pthread_mutex_unlock(&f->lock);
+
3279 if (fuse_reply_open(req, fi) == -ENOENT) {
+
3280 /* The open syscall was interrupted, so it
+
3281 must be cancelled */
+
3282 fuse_do_release(f, ino, path, fi);
+
3283 }
+
3284 } else
+
3285 reply_err(req, err);
+
3286
+
3287 free_path(f, ino, path);
+
3288}
+
3289
+
3290static void fuse_lib_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3291 off_t off, struct fuse_file_info *fi)
+
3292{
+
3293 struct fuse *f = req_fuse_prepare(req);
+
3294 struct fuse_bufvec *buf = NULL;
+
3295 char *path;
+
3296 int res;
+
3297
+
3298 res = get_path_nullok(f, ino, &path);
+
3299 if (res == 0) {
+
3300 struct fuse_intr_data d;
+
3301
+
3302 fuse_prepare_interrupt(f, req, &d);
+
3303 res = fuse_fs_read_buf(f->fs, path, &buf, size, off, fi);
+
3304 fuse_finish_interrupt(f, req, &d);
+
3305 free_path(f, ino, path);
+
3306 }
+
3307
+
3308 if (res == 0)
+ +
3310 else
+
3311 reply_err(req, res);
+
3312
+
3313 fuse_free_buf(buf);
+
3314}
+
3315
+
3316static void fuse_lib_write_buf(fuse_req_t req, fuse_ino_t ino,
+
3317 struct fuse_bufvec *buf, off_t off,
+
3318 struct fuse_file_info *fi)
+
3319{
+
3320 struct fuse *f = req_fuse_prepare(req);
+
3321 char *path;
+
3322 int res;
+
3323
+
3324 res = get_path_nullok(f, ino, &path);
+
3325 if (res == 0) {
+
3326 struct fuse_intr_data d;
+
3327
+
3328 fuse_prepare_interrupt(f, req, &d);
+
3329 res = fuse_fs_write_buf(f->fs, path, buf, off, fi);
+
3330 fuse_finish_interrupt(f, req, &d);
+
3331 free_path(f, ino, path);
+
3332 }
+
3333
+
3334 if (res >= 0)
+
3335 fuse_reply_write(req, res);
+
3336 else
+
3337 reply_err(req, res);
+
3338}
+
3339
+
3340static void fuse_lib_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3341 struct fuse_file_info *fi)
+
3342{
+
3343 struct fuse *f = req_fuse_prepare(req);
+
3344 char *path;
+
3345 int err;
+
3346
+
3347 err = get_path_nullok(f, ino, &path);
+
3348 if (!err) {
+
3349 struct fuse_intr_data d;
+
3350
+
3351 fuse_prepare_interrupt(f, req, &d);
+
3352 err = fuse_fs_fsync(f->fs, path, datasync, fi);
+
3353 fuse_finish_interrupt(f, req, &d);
+
3354 free_path(f, ino, path);
+
3355 }
+
3356 reply_err(req, err);
+
3357}
+
3358
+
3359static struct fuse_dh *get_dirhandle(const struct fuse_file_info *llfi,
+
3360 struct fuse_file_info *fi)
+
3361{
+
3362 struct fuse_dh *dh = (struct fuse_dh *) (uintptr_t) llfi->fh;
+
3363 memset(fi, 0, sizeof(struct fuse_file_info));
+
3364 fi->fh = dh->fh;
+
3365 return dh;
+
3366}
+
3367
+
3368static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino,
+
3369 struct fuse_file_info *llfi)
+
3370{
+
3371 struct fuse *f = req_fuse_prepare(req);
+
3372 struct fuse_intr_data d;
+
3373 struct fuse_dh *dh;
+
3374 struct fuse_file_info fi;
+
3375 char *path;
+
3376 int err;
+
3377
+
3378 dh = (struct fuse_dh *) malloc(sizeof(struct fuse_dh));
+
3379 if (dh == NULL) {
+
3380 reply_err(req, -ENOMEM);
+
3381 return;
+
3382 }
+
3383 memset(dh, 0, sizeof(struct fuse_dh));
+
3384 dh->fuse = f;
+
3385 dh->contents = NULL;
+
3386 dh->first = NULL;
+
3387 dh->len = 0;
+
3388 dh->filled = 0;
+
3389 dh->nodeid = ino;
+
3390 pthread_mutex_init(&dh->lock, NULL);
+
3391
+
3392 llfi->fh = (uintptr_t) dh;
+
3393
+
3394 memset(&fi, 0, sizeof(fi));
+
3395 fi.flags = llfi->flags;
+
3396
+
3397 err = get_path(f, ino, &path);
+
3398 if (!err) {
+
3399 fuse_prepare_interrupt(f, req, &d);
+
3400 err = fuse_fs_opendir(f->fs, path, &fi);
+
3401 fuse_finish_interrupt(f, req, &d);
+
3402 dh->fh = fi.fh;
+
3403 llfi->cache_readdir = fi.cache_readdir;
+
3404 llfi->keep_cache = fi.keep_cache;
+
3405 }
+
3406 if (!err) {
+
3407 if (fuse_reply_open(req, llfi) == -ENOENT) {
+
3408 /* The opendir syscall was interrupted, so it
+
3409 must be cancelled */
+
3410 fuse_fs_releasedir(f->fs, path, &fi);
+
3411 pthread_mutex_destroy(&dh->lock);
+
3412 free(dh);
+
3413 }
+
3414 } else {
+
3415 reply_err(req, err);
+
3416 pthread_mutex_destroy(&dh->lock);
+
3417 free(dh);
+
3418 }
+
3419 free_path(f, ino, path);
+
3420}
+
3421
+
3422static int extend_contents(struct fuse_dh *dh, unsigned minsize)
+
3423{
+
3424 if (minsize > dh->size) {
+
3425 char *newptr;
+
3426 unsigned newsize = dh->size;
+
3427 if (!newsize)
+
3428 newsize = 1024;
+
3429 while (newsize < minsize) {
+
3430 if (newsize >= 0x80000000)
+
3431 newsize = 0xffffffff;
+
3432 else
+
3433 newsize *= 2;
+
3434 }
+
3435
+
3436 newptr = (char *) realloc(dh->contents, newsize);
+
3437 if (!newptr) {
+
3438 dh->error = -ENOMEM;
+
3439 return -1;
+
3440 }
+
3441 dh->contents = newptr;
+
3442 dh->size = newsize;
+
3443 }
+
3444 return 0;
+
3445}
+
3446
+
3447static int fuse_add_direntry_to_dh(struct fuse_dh *dh, const char *name,
+
3448 struct stat *st, enum fuse_fill_dir_flags flags)
+
3449{
+
3450 struct fuse_direntry *de;
+
3451
+
3452 de = malloc(sizeof(struct fuse_direntry));
+
3453 if (!de) {
+
3454 dh->error = -ENOMEM;
+
3455 return -1;
+
3456 }
+
3457 de->name = strdup(name);
+
3458 if (!de->name) {
+
3459 dh->error = -ENOMEM;
+
3460 free(de);
+
3461 return -1;
+
3462 }
+
3463 de->flags = flags;
+
3464 de->stat = *st;
+
3465 de->next = NULL;
+
3466
+
3467 *dh->last = de;
+
3468 dh->last = &de->next;
+
3469
+
3470 return 0;
+
3471}
+
3472
+
3473static fuse_ino_t lookup_nodeid(struct fuse *f, fuse_ino_t parent,
+
3474 const char *name)
+
3475{
+
3476 struct node *node;
+
3477 fuse_ino_t res = FUSE_UNKNOWN_INO;
+
3478
+
3479 pthread_mutex_lock(&f->lock);
+
3480 node = lookup_node(f, parent, name);
+
3481 if (node)
+
3482 res = node->nodeid;
+
3483 pthread_mutex_unlock(&f->lock);
+
3484
+
3485 return res;
+
3486}
+
3487
+
3488static int fill_dir(void *dh_, const char *name, const struct stat *statp,
+
3489 off_t off, enum fuse_fill_dir_flags flags)
+
3490{
+
3491 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3492 struct stat stbuf;
+
3493
+
3494 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3495 dh->error = -EIO;
+
3496 return 1;
+
3497 }
+
3498
+
3499 if (statp)
+
3500 stbuf = *statp;
+
3501 else {
+
3502 memset(&stbuf, 0, sizeof(stbuf));
+
3503 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3504 }
+
3505
+
3506 if (!dh->fuse->conf.use_ino) {
+
3507 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3508 if (dh->fuse->conf.readdir_ino) {
+
3509 stbuf.st_ino = (ino_t)
+
3510 lookup_nodeid(dh->fuse, dh->nodeid, name);
+
3511 }
+
3512 }
+
3513
+
3514 if (off) {
+
3515 size_t newlen;
+
3516
+
3517 if (dh->filled) {
+
3518 dh->error = -EIO;
+
3519 return 1;
+
3520 }
+
3521
+
3522 if (dh->first) {
+
3523 dh->error = -EIO;
+
3524 return 1;
+
3525 }
+
3526
+
3527 if (extend_contents(dh, dh->needlen) == -1)
+
3528 return 1;
+
3529
+
3530 newlen = dh->len +
+
3531 fuse_add_direntry(dh->req, dh->contents + dh->len,
+
3532 dh->needlen - dh->len, name,
+
3533 &stbuf, off);
+
3534 if (newlen > dh->needlen)
+
3535 return 1;
+
3536
+
3537 dh->len = newlen;
+
3538 } else {
+
3539 dh->filled = 1;
+
3540
+
3541 if (fuse_add_direntry_to_dh(dh, name, &stbuf, flags) == -1)
+
3542 return 1;
+
3543 }
+
3544 return 0;
+
3545}
+
3546
+
3547static int is_dot_or_dotdot(const char *name)
+
3548{
+
3549 return name[0] == '.' && (name[1] == '\0' ||
+
3550 (name[1] == '.' && name[2] == '\0'));
+
3551}
+
3552
+
3553static int fill_dir_plus(void *dh_, const char *name, const struct stat *statp,
+
3554 off_t off, enum fuse_fill_dir_flags flags)
+
3555{
+
3556 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3557 struct fuse_entry_param e = {
+
3558 /* ino=0 tells the kernel to ignore readdirplus stat info */
+
3559 .ino = 0,
+
3560 };
+
3561 struct fuse *f = dh->fuse;
+
3562 int res;
+
3563
+
3564 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3565 dh->error = -EIO;
+
3566 return 1;
+
3567 }
+
3568
+
3569 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3570 e.attr = *statp;
+
3571 }
+
3572
+
3573 e.attr.st_ino = FUSE_UNKNOWN_INO;
+
3574 if (statp) {
+
3575 e.attr.st_mode = statp->st_mode;
+
3576 if (f->conf.use_ino)
+
3577 e.attr.st_ino = statp->st_ino;
+
3578 }
+
3579 if (!f->conf.use_ino && f->conf.readdir_ino) {
+
3580 e.attr.st_ino = (ino_t)
+
3581 lookup_nodeid(f, dh->nodeid, name);
+
3582 }
+
3583
+
3584 if (off) {
+
3585 size_t newlen;
+
3586
+
3587 if (dh->filled) {
+
3588 dh->error = -EIO;
+
3589 return 1;
+
3590 }
+
3591
+
3592 if (dh->first) {
+
3593 dh->error = -EIO;
+
3594 return 1;
+
3595 }
+
3596 if (extend_contents(dh, dh->needlen) == -1)
+
3597 return 1;
+
3598
+
3599 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3600 if (!is_dot_or_dotdot(name)) {
+
3601 res = do_lookup(f, dh->nodeid, name, &e);
+
3602 if (res) {
+
3603 dh->error = res;
+
3604 return 1;
+
3605 }
+
3606 }
+
3607 }
+
3608
+
3609 newlen = dh->len +
+
3610 fuse_add_direntry_plus(dh->req, dh->contents + dh->len,
+
3611 dh->needlen - dh->len, name,
+
3612 &e, off);
+
3613 if (newlen > dh->needlen)
+
3614 return 1;
+
3615 dh->len = newlen;
+
3616 } else {
+
3617 dh->filled = 1;
+
3618
+
3619 if (fuse_add_direntry_to_dh(dh, name, &e.attr, flags) == -1)
+
3620 return 1;
+
3621 }
+
3622
+
3623 return 0;
+
3624}
+
3625
+
3626static void free_direntries(struct fuse_direntry *de)
+
3627{
+
3628 while (de) {
+
3629 struct fuse_direntry *next = de->next;
+
3630 free(de->name);
+
3631 free(de);
+
3632 de = next;
+
3633 }
+
3634}
+
3635
+
3636static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3637 size_t size, off_t off, struct fuse_dh *dh,
+
3638 struct fuse_file_info *fi,
+
3639 enum fuse_readdir_flags flags)
+
3640{
+
3641 char *path;
+
3642 int err;
+
3643
+
3644 if (f->fs->op.readdir)
+
3645 err = get_path_nullok(f, ino, &path);
+
3646 else
+
3647 err = get_path(f, ino, &path);
+
3648 if (!err) {
+
3649 struct fuse_intr_data d;
+
3650 fuse_fill_dir_t filler = fill_dir;
+
3651
+
3652 if (flags & FUSE_READDIR_PLUS)
+
3653 filler = fill_dir_plus;
+
3654
+
3655 free_direntries(dh->first);
+
3656 dh->first = NULL;
+
3657 dh->last = &dh->first;
+
3658 dh->len = 0;
+
3659 dh->error = 0;
+
3660 dh->needlen = size;
+
3661 dh->filled = 0;
+
3662 dh->req = req;
+
3663 fuse_prepare_interrupt(f, req, &d);
+
3664 err = fuse_fs_readdir(f->fs, path, dh, filler, off, fi, flags);
+
3665 fuse_finish_interrupt(f, req, &d);
+
3666 dh->req = NULL;
+
3667 if (!err)
+
3668 err = dh->error;
+
3669 if (err)
+
3670 dh->filled = 0;
+
3671 free_path(f, ino, path);
+
3672 }
+
3673 return err;
+
3674}
+
3675
+
3676static int readdir_fill_from_list(fuse_req_t req, struct fuse_dh *dh,
+
3677 off_t off, enum fuse_readdir_flags flags)
+
3678{
+
3679 off_t pos;
+
3680 struct fuse_direntry *de = dh->first;
+
3681 int res;
+
3682
+
3683 dh->len = 0;
+
3684
+
3685 if (extend_contents(dh, dh->needlen) == -1)
+
3686 return dh->error;
+
3687
+
3688 for (pos = 0; pos < off; pos++) {
+
3689 if (!de)
+
3690 break;
+
3691
+
3692 de = de->next;
+
3693 }
+
3694 while (de) {
+
3695 char *p = dh->contents + dh->len;
+
3696 unsigned rem = dh->needlen - dh->len;
+
3697 unsigned thislen;
+
3698 unsigned newlen;
+
3699 pos++;
+
3700
+
3701 if (flags & FUSE_READDIR_PLUS) {
+
3702 struct fuse_entry_param e = {
+
3703 .ino = 0,
+
3704 .attr = de->stat,
+
3705 };
+
3706
+
3707 if (de->flags & FUSE_FILL_DIR_PLUS &&
+
3708 !is_dot_or_dotdot(de->name)) {
+
3709 res = do_lookup(dh->fuse, dh->nodeid,
+
3710 de->name, &e);
+
3711 if (res) {
+
3712 dh->error = res;
+
3713 return 1;
+
3714 }
+
3715 }
+
3716
+
3717 thislen = fuse_add_direntry_plus(req, p, rem,
+
3718 de->name, &e, pos);
+
3719 } else {
+
3720 thislen = fuse_add_direntry(req, p, rem,
+
3721 de->name, &de->stat, pos);
+
3722 }
+
3723 newlen = dh->len + thislen;
+
3724 if (newlen > dh->needlen)
+
3725 break;
+
3726 dh->len = newlen;
+
3727 de = de->next;
+
3728 }
+
3729 return 0;
+
3730}
+
3731
+
3732static void fuse_readdir_common(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3733 off_t off, struct fuse_file_info *llfi,
+
3734 enum fuse_readdir_flags flags)
+
3735{
+
3736 struct fuse *f = req_fuse_prepare(req);
+
3737 struct fuse_file_info fi;
+
3738 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3739 int err;
+
3740
+
3741 pthread_mutex_lock(&dh->lock);
+
3742 /* According to SUS, directory contents need to be refreshed on
+
3743 rewinddir() */
+
3744 if (!off)
+
3745 dh->filled = 0;
+
3746
+
3747 if (!dh->filled) {
+
3748 err = readdir_fill(f, req, ino, size, off, dh, &fi, flags);
+
3749 if (err) {
+
3750 reply_err(req, err);
+
3751 goto out;
+
3752 }
+
3753 }
+
3754 if (dh->filled) {
+
3755 dh->needlen = size;
+
3756 err = readdir_fill_from_list(req, dh, off, flags);
+
3757 if (err) {
+
3758 reply_err(req, err);
+
3759 goto out;
+
3760 }
+
3761 }
+
3762 fuse_reply_buf(req, dh->contents, dh->len);
+
3763out:
+
3764 pthread_mutex_unlock(&dh->lock);
+
3765}
+
3766
+
3767static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3768 off_t off, struct fuse_file_info *llfi)
+
3769{
+
3770 fuse_readdir_common(req, ino, size, off, llfi, 0);
+
3771}
+
3772
+
3773static void fuse_lib_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3774 off_t off, struct fuse_file_info *llfi)
+
3775{
+
3776 fuse_readdir_common(req, ino, size, off, llfi, FUSE_READDIR_PLUS);
+
3777}
+
3778
+
3779static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino,
+
3780 struct fuse_file_info *llfi)
+
3781{
+
3782 struct fuse *f = req_fuse_prepare(req);
+
3783 struct fuse_intr_data d;
+
3784 struct fuse_file_info fi;
+
3785 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3786 char *path;
+
3787
+
3788 get_path_nullok(f, ino, &path);
+
3789
+
3790 fuse_prepare_interrupt(f, req, &d);
+
3791 fuse_fs_releasedir(f->fs, path, &fi);
+
3792 fuse_finish_interrupt(f, req, &d);
+
3793 free_path(f, ino, path);
+
3794
+
3795 pthread_mutex_lock(&dh->lock);
+
3796 pthread_mutex_unlock(&dh->lock);
+
3797 pthread_mutex_destroy(&dh->lock);
+
3798 free_direntries(dh->first);
+
3799 free(dh->contents);
+
3800 free(dh);
+
3801 reply_err(req, 0);
+
3802}
+
3803
+
3804static void fuse_lib_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3805 struct fuse_file_info *llfi)
+
3806{
+
3807 struct fuse *f = req_fuse_prepare(req);
+
3808 struct fuse_file_info fi;
+
3809 char *path;
+
3810 int err;
+
3811
+
3812 get_dirhandle(llfi, &fi);
+
3813
+
3814 err = get_path_nullok(f, ino, &path);
+
3815 if (!err) {
+
3816 struct fuse_intr_data d;
+
3817 fuse_prepare_interrupt(f, req, &d);
+
3818 err = fuse_fs_fsyncdir(f->fs, path, datasync, &fi);
+
3819 fuse_finish_interrupt(f, req, &d);
+
3820 free_path(f, ino, path);
+
3821 }
+
3822 reply_err(req, err);
+
3823}
+
3824
+
3825static void fuse_lib_statfs(fuse_req_t req, fuse_ino_t ino)
+
3826{
+
3827 struct fuse *f = req_fuse_prepare(req);
+
3828 struct statvfs buf;
+
3829 char *path = NULL;
+
3830 int err = 0;
+
3831
+
3832 memset(&buf, 0, sizeof(buf));
+
3833 if (ino)
+
3834 err = get_path(f, ino, &path);
+
3835
+
3836 if (!err) {
+
3837 struct fuse_intr_data d;
+
3838 fuse_prepare_interrupt(f, req, &d);
+
3839 err = fuse_fs_statfs(f->fs, path ? path : "/", &buf);
+
3840 fuse_finish_interrupt(f, req, &d);
+
3841 free_path(f, ino, path);
+
3842 }
+
3843
+
3844 if (!err)
+
3845 fuse_reply_statfs(req, &buf);
+
3846 else
+
3847 reply_err(req, err);
+
3848}
+
3849
+
3850static void fuse_lib_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3851 const char *value, size_t size, int flags)
+
3852{
+
3853 struct fuse *f = req_fuse_prepare(req);
+
3854 char *path;
+
3855 int err;
+
3856
+
3857 err = get_path(f, ino, &path);
+
3858 if (!err) {
+
3859 struct fuse_intr_data d;
+
3860 fuse_prepare_interrupt(f, req, &d);
+
3861 err = fuse_fs_setxattr(f->fs, path, name, value, size, flags);
+
3862 fuse_finish_interrupt(f, req, &d);
+
3863 free_path(f, ino, path);
+
3864 }
+
3865 reply_err(req, err);
+
3866}
+
3867
+
3868static int common_getxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3869 const char *name, char *value, size_t size)
+
3870{
+
3871 int err;
+
3872 char *path;
+
3873
+
3874 err = get_path(f, ino, &path);
+
3875 if (!err) {
+
3876 struct fuse_intr_data d;
+
3877 fuse_prepare_interrupt(f, req, &d);
+
3878 err = fuse_fs_getxattr(f->fs, path, name, value, size);
+
3879 fuse_finish_interrupt(f, req, &d);
+
3880 free_path(f, ino, path);
+
3881 }
+
3882 return err;
+
3883}
+
3884
+
3885static void fuse_lib_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3886 size_t size)
+
3887{
+
3888 struct fuse *f = req_fuse_prepare(req);
+
3889 int res;
+
3890
+
3891 if (size) {
+
3892 char *value = (char *) malloc(size);
+
3893 if (value == NULL) {
+
3894 reply_err(req, -ENOMEM);
+
3895 return;
+
3896 }
+
3897 res = common_getxattr(f, req, ino, name, value, size);
+
3898 if (res > 0)
+
3899 fuse_reply_buf(req, value, res);
+
3900 else
+
3901 reply_err(req, res);
+
3902 free(value);
+
3903 } else {
+
3904 res = common_getxattr(f, req, ino, name, NULL, 0);
+
3905 if (res >= 0)
+
3906 fuse_reply_xattr(req, res);
+
3907 else
+
3908 reply_err(req, res);
+
3909 }
+
3910}
+
3911
+
3912static int common_listxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3913 char *list, size_t size)
+
3914{
+
3915 char *path;
+
3916 int err;
+
3917
+
3918 err = get_path(f, ino, &path);
+
3919 if (!err) {
+
3920 struct fuse_intr_data d;
+
3921 fuse_prepare_interrupt(f, req, &d);
+
3922 err = fuse_fs_listxattr(f->fs, path, list, size);
+
3923 fuse_finish_interrupt(f, req, &d);
+
3924 free_path(f, ino, path);
+
3925 }
+
3926 return err;
+
3927}
+
3928
+
3929static void fuse_lib_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
3930{
+
3931 struct fuse *f = req_fuse_prepare(req);
+
3932 int res;
+
3933
+
3934 if (size) {
+
3935 char *list = (char *) malloc(size);
+
3936 if (list == NULL) {
+
3937 reply_err(req, -ENOMEM);
+
3938 return;
+
3939 }
+
3940 res = common_listxattr(f, req, ino, list, size);
+
3941 if (res > 0)
+
3942 fuse_reply_buf(req, list, res);
+
3943 else
+
3944 reply_err(req, res);
+
3945 free(list);
+
3946 } else {
+
3947 res = common_listxattr(f, req, ino, NULL, 0);
+
3948 if (res >= 0)
+
3949 fuse_reply_xattr(req, res);
+
3950 else
+
3951 reply_err(req, res);
+
3952 }
+
3953}
+
3954
+
3955static void fuse_lib_removexattr(fuse_req_t req, fuse_ino_t ino,
+
3956 const char *name)
+
3957{
+
3958 struct fuse *f = req_fuse_prepare(req);
+
3959 char *path;
+
3960 int err;
+
3961
+
3962 err = get_path(f, ino, &path);
+
3963 if (!err) {
+
3964 struct fuse_intr_data d;
+
3965 fuse_prepare_interrupt(f, req, &d);
+
3966 err = fuse_fs_removexattr(f->fs, path, name);
+
3967 fuse_finish_interrupt(f, req, &d);
+
3968 free_path(f, ino, path);
+
3969 }
+
3970 reply_err(req, err);
+
3971}
+
3972
+
3973static struct lock *locks_conflict(struct node *node, const struct lock *lock)
+
3974{
+
3975 struct lock *l;
+
3976
+
3977 for (l = node->locks; l; l = l->next)
+
3978 if (l->owner != lock->owner &&
+
3979 lock->start <= l->end && l->start <= lock->end &&
+
3980 (l->type == F_WRLCK || lock->type == F_WRLCK))
+
3981 break;
+
3982
+
3983 return l;
+
3984}
+
3985
+
3986static void delete_lock(struct lock **lockp)
+
3987{
+
3988 struct lock *l = *lockp;
+
3989 *lockp = l->next;
+
3990 free(l);
+
3991}
+
3992
+
3993static void insert_lock(struct lock **pos, struct lock *lock)
+
3994{
+
3995 lock->next = *pos;
+
3996 *pos = lock;
+
3997}
+
3998
+
3999static int locks_insert(struct node *node, struct lock *lock)
+
4000{
+
4001 struct lock **lp;
+
4002 struct lock *newl1 = NULL;
+
4003 struct lock *newl2 = NULL;
+
4004
+
4005 if (lock->type != F_UNLCK || lock->start != 0 ||
+
4006 lock->end != OFFSET_MAX) {
+
4007 newl1 = malloc(sizeof(struct lock));
+
4008 newl2 = malloc(sizeof(struct lock));
+
4009
+
4010 if (!newl1 || !newl2) {
+
4011 free(newl1);
+
4012 free(newl2);
+
4013 return -ENOLCK;
+
4014 }
+
4015 }
+
4016
+
4017 for (lp = &node->locks; *lp;) {
+
4018 struct lock *l = *lp;
+
4019 if (l->owner != lock->owner)
+
4020 goto skip;
+
4021
+
4022 if (lock->type == l->type) {
+
4023 if (l->end < lock->start - 1)
+
4024 goto skip;
+
4025 if (lock->end < l->start - 1)
+
4026 break;
+
4027 if (l->start <= lock->start && lock->end <= l->end)
+
4028 goto out;
+
4029 if (l->start < lock->start)
+
4030 lock->start = l->start;
+
4031 if (lock->end < l->end)
+
4032 lock->end = l->end;
+
4033 goto delete;
+
4034 } else {
+
4035 if (l->end < lock->start)
+
4036 goto skip;
+
4037 if (lock->end < l->start)
+
4038 break;
+
4039 if (lock->start <= l->start && l->end <= lock->end)
+
4040 goto delete;
+
4041 if (l->end <= lock->end) {
+
4042 l->end = lock->start - 1;
+
4043 goto skip;
+
4044 }
+
4045 if (lock->start <= l->start) {
+
4046 l->start = lock->end + 1;
+
4047 break;
+
4048 }
+
4049 *newl2 = *l;
+
4050 newl2->start = lock->end + 1;
+
4051 l->end = lock->start - 1;
+
4052 insert_lock(&l->next, newl2);
+
4053 newl2 = NULL;
+
4054 }
+
4055 skip:
+
4056 lp = &l->next;
+
4057 continue;
+
4058
+
4059 delete:
+
4060 delete_lock(lp);
+
4061 }
+
4062 if (lock->type != F_UNLCK) {
+
4063 *newl1 = *lock;
+
4064 insert_lock(lp, newl1);
+
4065 newl1 = NULL;
+
4066 }
+
4067out:
+
4068 free(newl1);
+
4069 free(newl2);
+
4070 return 0;
+
4071}
+
4072
+
4073static void flock_to_lock(struct flock *flock, struct lock *lock)
+
4074{
+
4075 memset(lock, 0, sizeof(struct lock));
+
4076 lock->type = flock->l_type;
+
4077 lock->start = flock->l_start;
+
4078 lock->end =
+
4079 flock->l_len ? flock->l_start + flock->l_len - 1 : OFFSET_MAX;
+
4080 lock->pid = flock->l_pid;
+
4081}
+
4082
+
4083static void lock_to_flock(struct lock *lock, struct flock *flock)
+
4084{
+
4085 flock->l_type = lock->type;
+
4086 flock->l_start = lock->start;
+
4087 flock->l_len =
+
4088 (lock->end == OFFSET_MAX) ? 0 : lock->end - lock->start + 1;
+
4089 flock->l_pid = lock->pid;
+
4090}
+
4091
+
4092static int fuse_flush_common(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
4093 const char *path, struct fuse_file_info *fi)
+
4094{
+
4095 struct fuse_intr_data d;
+
4096 struct flock lock;
+
4097 struct lock l;
+
4098 int err;
+
4099 int errlock;
+
4100
+
4101 fuse_prepare_interrupt(f, req, &d);
+
4102 memset(&lock, 0, sizeof(lock));
+
4103 lock.l_type = F_UNLCK;
+
4104 lock.l_whence = SEEK_SET;
+
4105 err = fuse_fs_flush(f->fs, path, fi);
+
4106 errlock = fuse_fs_lock(f->fs, path, fi, F_SETLK, &lock);
+
4107 fuse_finish_interrupt(f, req, &d);
+
4108
+
4109 if (errlock != -ENOSYS) {
+
4110 flock_to_lock(&lock, &l);
+
4111 l.owner = fi->lock_owner;
+
4112 pthread_mutex_lock(&f->lock);
+
4113 locks_insert(get_node(f, ino), &l);
+
4114 pthread_mutex_unlock(&f->lock);
+
4115
+
4116 /* if op.lock() is defined FLUSH is needed regardless
+
4117 of op.flush() */
+
4118 if (err == -ENOSYS)
+
4119 err = 0;
+
4120 }
+
4121 return err;
+
4122}
+
4123
+
4124static void fuse_lib_release(fuse_req_t req, fuse_ino_t ino,
+
4125 struct fuse_file_info *fi)
+
4126{
+
4127 struct fuse *f = req_fuse_prepare(req);
+
4128 struct fuse_intr_data d;
+
4129 char *path;
+
4130 int err = 0;
+
4131
+
4132 get_path_nullok(f, ino, &path);
+
4133 if (fi->flush) {
+
4134 err = fuse_flush_common(f, req, ino, path, fi);
+
4135 if (err == -ENOSYS)
+
4136 err = 0;
+
4137 }
+
4138
+
4139 fuse_prepare_interrupt(f, req, &d);
+
4140 fuse_do_release(f, ino, path, fi);
+
4141 fuse_finish_interrupt(f, req, &d);
+
4142 free_path(f, ino, path);
+
4143
+
4144 reply_err(req, err);
+
4145}
+
4146
+
4147static void fuse_lib_flush(fuse_req_t req, fuse_ino_t ino,
+
4148 struct fuse_file_info *fi)
+
4149{
+
4150 struct fuse *f = req_fuse_prepare(req);
+
4151 char *path;
+
4152 int err;
+
4153
+
4154 get_path_nullok(f, ino, &path);
+
4155 err = fuse_flush_common(f, req, ino, path, fi);
+
4156 free_path(f, ino, path);
+
4157
+
4158 reply_err(req, err);
+
4159}
+
4160
+
4161static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino,
+
4162 struct fuse_file_info *fi, struct flock *lock,
+
4163 int cmd)
+
4164{
+
4165 struct fuse *f = req_fuse_prepare(req);
+
4166 char *path;
+
4167 int err;
+
4168
+
4169 err = get_path_nullok(f, ino, &path);
+
4170 if (!err) {
+
4171 struct fuse_intr_data d;
+
4172 fuse_prepare_interrupt(f, req, &d);
+
4173 err = fuse_fs_lock(f->fs, path, fi, cmd, lock);
+
4174 fuse_finish_interrupt(f, req, &d);
+
4175 free_path(f, ino, path);
+
4176 }
+
4177 return err;
+
4178}
+
4179
+
4180static void fuse_lib_getlk(fuse_req_t req, fuse_ino_t ino,
+
4181 struct fuse_file_info *fi, struct flock *lock)
+
4182{
+
4183 int err;
+
4184 struct lock l;
+
4185 struct lock *conflict;
+
4186 struct fuse *f = req_fuse(req);
+
4187
+
4188 flock_to_lock(lock, &l);
+
4189 l.owner = fi->lock_owner;
+
4190 pthread_mutex_lock(&f->lock);
+
4191 conflict = locks_conflict(get_node(f, ino), &l);
+
4192 if (conflict)
+
4193 lock_to_flock(conflict, lock);
+
4194 pthread_mutex_unlock(&f->lock);
+
4195 if (!conflict)
+
4196 err = fuse_lock_common(req, ino, fi, lock, F_GETLK);
+
4197 else
+
4198 err = 0;
+
4199
+
4200 if (!err)
+
4201 fuse_reply_lock(req, lock);
+
4202 else
+
4203 reply_err(req, err);
+
4204}
+
4205
+
4206static void fuse_lib_setlk(fuse_req_t req, fuse_ino_t ino,
+
4207 struct fuse_file_info *fi, struct flock *lock,
+
4208 int sleep)
+
4209{
+
4210 int err = fuse_lock_common(req, ino, fi, lock,
+
4211 sleep ? F_SETLKW : F_SETLK);
+
4212 if (!err) {
+
4213 struct fuse *f = req_fuse(req);
+
4214 struct lock l;
+
4215 flock_to_lock(lock, &l);
+
4216 l.owner = fi->lock_owner;
+
4217 pthread_mutex_lock(&f->lock);
+
4218 locks_insert(get_node(f, ino), &l);
+
4219 pthread_mutex_unlock(&f->lock);
+
4220 }
+
4221 reply_err(req, err);
+
4222}
+
4223
+
4224static void fuse_lib_flock(fuse_req_t req, fuse_ino_t ino,
+
4225 struct fuse_file_info *fi, int op)
+
4226{
+
4227 struct fuse *f = req_fuse_prepare(req);
+
4228 char *path;
+
4229 int err;
+
4230
+
4231 err = get_path_nullok(f, ino, &path);
+
4232 if (err == 0) {
+
4233 struct fuse_intr_data d;
+
4234 fuse_prepare_interrupt(f, req, &d);
+
4235 err = fuse_fs_flock(f->fs, path, fi, op);
+
4236 fuse_finish_interrupt(f, req, &d);
+
4237 free_path(f, ino, path);
+
4238 }
+
4239 reply_err(req, err);
+
4240}
+
4241
+
4242static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
4243 uint64_t idx)
+
4244{
+
4245 struct fuse *f = req_fuse_prepare(req);
+
4246 struct fuse_intr_data d;
+
4247 char *path;
+
4248 int err;
+
4249
+
4250 err = get_path(f, ino, &path);
+
4251 if (!err) {
+
4252 fuse_prepare_interrupt(f, req, &d);
+
4253 err = fuse_fs_bmap(f->fs, path, blocksize, &idx);
+
4254 fuse_finish_interrupt(f, req, &d);
+
4255 free_path(f, ino, path);
+
4256 }
+
4257 if (!err)
+
4258 fuse_reply_bmap(req, idx);
+
4259 else
+
4260 reply_err(req, err);
+
4261}
+
4262
+
4263static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
4264 void *arg, struct fuse_file_info *llfi,
+
4265 unsigned int flags, const void *in_buf,
+
4266 size_t in_bufsz, size_t out_bufsz)
+
4267{
+
4268 struct fuse *f = req_fuse_prepare(req);
+
4269 struct fuse_intr_data d;
+
4270 struct fuse_file_info fi;
+
4271 char *path, *out_buf = NULL;
+
4272 int err;
+
4273
+
4274 err = -EPERM;
+
4275 if (flags & FUSE_IOCTL_UNRESTRICTED)
+
4276 goto err;
+
4277
+
4278 if (flags & FUSE_IOCTL_DIR)
+
4279 get_dirhandle(llfi, &fi);
+
4280 else
+
4281 fi = *llfi;
+
4282
+
4283 if (out_bufsz) {
+
4284 err = -ENOMEM;
+
4285 out_buf = malloc(out_bufsz);
+
4286 if (!out_buf)
+
4287 goto err;
+
4288 }
+
4289
+
4290 assert(!in_bufsz || !out_bufsz || in_bufsz == out_bufsz);
+
4291 if (out_buf && in_bufsz)
+
4292 memcpy(out_buf, in_buf, in_bufsz);
+
4293
+
4294 err = get_path_nullok(f, ino, &path);
+
4295 if (err)
+
4296 goto err;
+
4297
+
4298 fuse_prepare_interrupt(f, req, &d);
+
4299
+
4300 err = fuse_fs_ioctl(f->fs, path, cmd, arg, &fi, flags,
+
4301 out_buf ? out_buf : (void *)in_buf);
+
4302
+
4303 fuse_finish_interrupt(f, req, &d);
+
4304 free_path(f, ino, path);
+
4305
+
4306 if (err < 0)
+
4307 goto err;
+
4308 fuse_reply_ioctl(req, err, out_buf, out_bufsz);
+
4309 goto out;
+
4310err:
+
4311 reply_err(req, err);
+
4312out:
+
4313 free(out_buf);
+
4314}
+
4315
+
4316static void fuse_lib_poll(fuse_req_t req, fuse_ino_t ino,
+
4317 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
4318{
+
4319 struct fuse *f = req_fuse_prepare(req);
+
4320 struct fuse_intr_data d;
+
4321 char *path;
+
4322 int err;
+
4323 unsigned revents = 0;
+
4324
+
4325 err = get_path_nullok(f, ino, &path);
+
4326 if (!err) {
+
4327 fuse_prepare_interrupt(f, req, &d);
+
4328 err = fuse_fs_poll(f->fs, path, fi, ph, &revents);
+
4329 fuse_finish_interrupt(f, req, &d);
+
4330 free_path(f, ino, path);
+
4331 }
+
4332 if (!err)
+
4333 fuse_reply_poll(req, revents);
+
4334 else
+
4335 reply_err(req, err);
+
4336}
+
4337
+
4338static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
4339 off_t offset, off_t length, struct fuse_file_info *fi)
+
4340{
+
4341 struct fuse *f = req_fuse_prepare(req);
+
4342 struct fuse_intr_data d;
+
4343 char *path;
+
4344 int err;
+
4345
+
4346 err = get_path_nullok(f, ino, &path);
+
4347 if (!err) {
+
4348 fuse_prepare_interrupt(f, req, &d);
+
4349 err = fuse_fs_fallocate(f->fs, path, mode, offset, length, fi);
+
4350 fuse_finish_interrupt(f, req, &d);
+
4351 free_path(f, ino, path);
+
4352 }
+
4353 reply_err(req, err);
+
4354}
+
4355
+
4356static void fuse_lib_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in,
+
4357 off_t off_in, struct fuse_file_info *fi_in,
+
4358 fuse_ino_t nodeid_out, off_t off_out,
+
4359 struct fuse_file_info *fi_out, size_t len,
+
4360 int flags)
+
4361{
+
4362 struct fuse *f = req_fuse_prepare(req);
+
4363 struct fuse_intr_data d;
+
4364 char *path_in, *path_out;
+
4365 int err;
+
4366 ssize_t res;
+
4367
+
4368 err = get_path_nullok(f, nodeid_in, &path_in);
+
4369 if (err) {
+
4370 reply_err(req, err);
+
4371 return;
+
4372 }
+
4373
+
4374 err = get_path_nullok(f, nodeid_out, &path_out);
+
4375 if (err) {
+
4376 free_path(f, nodeid_in, path_in);
+
4377 reply_err(req, err);
+
4378 return;
+
4379 }
+
4380
+
4381 fuse_prepare_interrupt(f, req, &d);
+
4382 res = fuse_fs_copy_file_range(f->fs, path_in, fi_in, off_in, path_out,
+
4383 fi_out, off_out, len, flags);
+
4384 fuse_finish_interrupt(f, req, &d);
+
4385
+
4386 if (res >= 0)
+
4387 fuse_reply_write(req, res);
+
4388 else
+
4389 reply_err(req, res);
+
4390
+
4391 free_path(f, nodeid_in, path_in);
+
4392 free_path(f, nodeid_out, path_out);
+
4393}
+
4394
+
4395static void fuse_lib_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
4396 struct fuse_file_info *fi)
+
4397{
+
4398 struct fuse *f = req_fuse_prepare(req);
+
4399 struct fuse_intr_data d;
+
4400 char *path;
+
4401 int err;
+
4402 off_t res;
+
4403
+
4404 err = get_path(f, ino, &path);
+
4405 if (err) {
+
4406 reply_err(req, err);
+
4407 return;
+
4408 }
+
4409
+
4410 fuse_prepare_interrupt(f, req, &d);
+
4411 res = fuse_fs_lseek(f->fs, path, off, whence, fi);
+
4412 fuse_finish_interrupt(f, req, &d);
+
4413 free_path(f, ino, path);
+
4414 if (res >= 0)
+
4415 fuse_reply_lseek(req, res);
+
4416 else
+
4417 reply_err(req, res);
+
4418}
+
4419
+
4420#ifdef HAVE_STATX
+
4421static void fuse_lib_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
+
4422 struct fuse_file_info *fi)
+
4423{
+
4424 struct fuse *f = req_fuse_prepare(req);
+
4425 struct statx stxbuf;
+
4426 char *path;
+
4427 int err;
+
4428
+
4429 memset(&stxbuf, 0, sizeof(stxbuf));
+
4430
+
4431 if (fi != NULL)
+
4432 err = get_path_nullok(f, ino, &path);
+
4433 else
+
4434 err = get_path(f, ino, &path);
+
4435
+
4436 if (!err) {
+
4437 struct fuse_intr_data d;
+
4438
+
4439 if (!path)
+
4440 flags |= AT_EMPTY_PATH;
+
4441 fuse_prepare_interrupt(f, req, &d);
+
4442 err = fuse_fs_statx(f->fs, path, flags, mask, &stxbuf, fi);
+
4443 fuse_finish_interrupt(f, req, &d);
+
4444 free_path(f, ino, path);
+
4445 }
+
4446 if (!err) {
+
4447 struct node *node;
+
4448
+
4449 pthread_mutex_lock(&f->lock);
+
4450 node = get_node(f, ino);
+
4451 if (node->is_hidden && stxbuf.stx_nlink > 0)
+
4452 stxbuf.stx_nlink--;
+
4453 if (f->conf.auto_cache) {
+
4454 struct stat stbuf;
+
4455
+
4456 stbuf.st_mtime = stxbuf.stx_mtime.tv_nsec;
+
4457 ST_MTIM_NSEC(&stbuf) = stxbuf.stx_mtime.tv_nsec;
+
4458 stbuf.st_size = stxbuf.stx_size;
+
4459 update_stat(node, &stbuf);
+
4460 }
+
4461 pthread_mutex_unlock(&f->lock);
+
4462 set_statx(f, ino, &stxbuf);
+
4463 fuse_reply_statx(req, 0, &stxbuf, f->conf.attr_timeout);
+
4464 } else
+
4465 reply_err(req, err);
+
4466}
+
4467#endif
+
4468
+
4469static int clean_delay(struct fuse *f)
+
4470{
+
4471 /*
+
4472 * This is calculating the delay between clean runs. To
+
4473 * reduce the number of cleans we are doing them 10 times
+
4474 * within the remember window.
+
4475 */
+
4476 int min_sleep = 60;
+
4477 int max_sleep = 3600;
+
4478 int sleep_time = f->conf.remember / 10;
+
4479
+
4480 if (sleep_time > max_sleep)
+
4481 return max_sleep;
+
4482 if (sleep_time < min_sleep)
+
4483 return min_sleep;
+
4484 return sleep_time;
+
4485}
+
4486
+
4487int fuse_clean_cache(struct fuse *f)
+
4488{
+
4489 struct node_lru *lnode;
+
4490 struct list_head *curr, *next;
+
4491 struct node *node;
+
4492 struct timespec now;
+
4493
+
4494 pthread_mutex_lock(&f->lock);
+
4495
+
4496 curr_time(&now);
+
4497
+
4498 for (curr = f->lru_table.next; curr != &f->lru_table; curr = next) {
+
4499 double age;
+
4500
+
4501 next = curr->next;
+
4502 lnode = list_entry(curr, struct node_lru, lru);
+
4503 node = &lnode->node;
+
4504
+
4505 age = diff_timespec(&now, &lnode->forget_time);
+
4506 if (age <= f->conf.remember)
+
4507 break;
+
4508
+
4509 assert(node->nlookup == 1);
+
4510
+
4511 /* Don't forget active directories */
+
4512 if (node->refctr > 1)
+
4513 continue;
+
4514
+
4515 node->nlookup = 0;
+
4516 unhash_name(f, node);
+
4517 unref_node(f, node);
+
4518 }
+
4519 pthread_mutex_unlock(&f->lock);
+
4520
+
4521 return clean_delay(f);
+
4522}
+
4523
+
4524static struct fuse_lowlevel_ops fuse_path_ops = {
+
4525 .init = fuse_lib_init,
+
4526 .destroy = fuse_lib_destroy,
+
4527 .lookup = fuse_lib_lookup,
+
4528 .forget = fuse_lib_forget,
+
4529 .forget_multi = fuse_lib_forget_multi,
+
4530 .getattr = fuse_lib_getattr,
+
4531 .setattr = fuse_lib_setattr,
+
4532 .access = fuse_lib_access,
+
4533 .readlink = fuse_lib_readlink,
+
4534 .mknod = fuse_lib_mknod,
+
4535 .mkdir = fuse_lib_mkdir,
+
4536 .unlink = fuse_lib_unlink,
+
4537 .rmdir = fuse_lib_rmdir,
+
4538 .symlink = fuse_lib_symlink,
+
4539 .rename = fuse_lib_rename,
+
4540 .link = fuse_lib_link,
+
4541 .create = fuse_lib_create,
+
4542 .open = fuse_lib_open,
+
4543 .read = fuse_lib_read,
+
4544 .write_buf = fuse_lib_write_buf,
+
4545 .flush = fuse_lib_flush,
+
4546 .release = fuse_lib_release,
+
4547 .fsync = fuse_lib_fsync,
+
4548 .opendir = fuse_lib_opendir,
+
4549 .readdir = fuse_lib_readdir,
+
4550 .readdirplus = fuse_lib_readdirplus,
+
4551 .releasedir = fuse_lib_releasedir,
+
4552 .fsyncdir = fuse_lib_fsyncdir,
+
4553 .statfs = fuse_lib_statfs,
+
4554 .setxattr = fuse_lib_setxattr,
+
4555 .getxattr = fuse_lib_getxattr,
+
4556 .listxattr = fuse_lib_listxattr,
+
4557 .removexattr = fuse_lib_removexattr,
+
4558 .getlk = fuse_lib_getlk,
+
4559 .setlk = fuse_lib_setlk,
+
4560 .flock = fuse_lib_flock,
+
4561 .bmap = fuse_lib_bmap,
+
4562 .ioctl = fuse_lib_ioctl,
+
4563 .poll = fuse_lib_poll,
+
4564 .fallocate = fuse_lib_fallocate,
+
4565 .copy_file_range = fuse_lib_copy_file_range,
+
4566 .lseek = fuse_lib_lseek,
+
4567#ifdef HAVE_STATX
+
4568 .statx = fuse_lib_statx,
+
4569#endif
+
4570};
+
4571
+
4572int fuse_notify_poll(struct fuse_pollhandle *ph)
+
4573{
+
4574 return fuse_lowlevel_notify_poll(ph);
+
4575}
+
4576
+
4577struct fuse_session *fuse_get_session(struct fuse *f)
+
4578{
+
4579 return f->se;
+
4580}
+
4581
+
4582static int fuse_session_loop_remember(struct fuse *f)
+
4583{
+
4584 struct fuse_session *se = f->se;
+
4585 int res = 0;
+
4586 struct timespec now;
+
4587 time_t next_clean;
+
4588 struct pollfd fds = {
+
4589 .fd = se->fd,
+
4590 .events = POLLIN
+
4591 };
+
4592 struct fuse_buf fbuf = {
+
4593 .mem = NULL,
+
4594 };
+
4595
+
4596 curr_time(&now);
+
4597 next_clean = now.tv_sec;
+
4598 while (!fuse_session_exited(se)) {
+
4599 unsigned timeout;
+
4600
+
4601 curr_time(&now);
+
4602 if (now.tv_sec < next_clean)
+
4603 timeout = next_clean - now.tv_sec;
+
4604 else
+
4605 timeout = 0;
+
4606
+
4607 res = poll(&fds, 1, timeout * 1000);
+
4608 if (res == -1) {
+
4609 if (errno == EINTR)
+
4610 continue;
+
4611 else
+
4612 break;
+
4613 } else if (res > 0) {
+
4614 res = fuse_session_receive_buf_internal(se, &fbuf,
+
4615 NULL);
+
4616 if (res == -EINTR)
+
4617 continue;
+
4618 if (res <= 0)
+
4619 break;
+
4620
+
4621 fuse_session_process_buf_internal(se, &fbuf, NULL);
+
4622 } else {
+
4623 timeout = fuse_clean_cache(f);
+
4624 curr_time(&now);
+
4625 next_clean = now.tv_sec + timeout;
+
4626 }
+
4627 }
+
4628
+
4629 fuse_buf_free(&fbuf);
+
4630 return res < 0 ? -1 : 0;
+
4631}
+
4632
+
4633int fuse_loop(struct fuse *f)
+
4634{
+
4635 if (!f)
+
4636 return -1;
+
4637
+
4638 if (lru_enabled(f))
+
4639 return fuse_session_loop_remember(f);
+
4640
+
4641 return fuse_session_loop(f->se);
+
4642}
+
4643
+
4644FUSE_SYMVER("fuse_loop_mt_312", "fuse_loop_mt@@FUSE_3.12")
+
4645int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config)
+
4646{
+
4647 if (f == NULL)
+
4648 return -1;
+
4649
+
4650 int res = fuse_start_cleanup_thread(f);
+
4651 if (res)
+
4652 return -1;
+
4653
+
4654 res = fuse_session_loop_mt_312(fuse_get_session(f), config);
+ +
4656 return res;
+
4657}
+
4658
+
4659int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1);
+
4660FUSE_SYMVER("fuse_loop_mt_32", "fuse_loop_mt@FUSE_3.2")
+
4661int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1)
+
4662{
+
4663 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4664 if (config == NULL)
+
4665 return ENOMEM;
+
4666
+
4667 fuse_loop_cfg_convert(config, config_v1);
+
4668
+
4669 int res = fuse_loop_mt_312(f, config);
+
4670
+
4671 fuse_loop_cfg_destroy(config);
+
4672
+
4673 return res;
+
4674}
+
4675
+
4676int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
4677FUSE_SYMVER("fuse_loop_mt_31", "fuse_loop_mt@FUSE_3.0")
+
4678int fuse_loop_mt_31(struct fuse *f, int clone_fd)
+
4679{
+
4680 int err;
+
4681 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4682
+
4683 if (config == NULL)
+
4684 return ENOMEM;
+
4685
+
4686 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
4687
+
4688 err = fuse_loop_mt_312(f, config);
+
4689
+
4690 fuse_loop_cfg_destroy(config);
+
4691
+
4692 return err;
+
4693}
+
4694
+
4695void fuse_exit(struct fuse *f)
+
4696{
+
4697 fuse_session_exit(f->se);
+
4698}
+
4699
+
4700struct fuse_context *fuse_get_context(void)
+
4701{
+
4702 struct fuse_context_i *c = fuse_get_context_internal();
+
4703
+
4704 if (c)
+
4705 return &c->ctx;
+
4706 else
+
4707 return NULL;
+
4708}
+
4709
+
4710int fuse_getgroups(int size, gid_t list[])
+
4711{
+
4712 struct fuse_context_i *c = fuse_get_context_internal();
+
4713 if (!c)
+
4714 return -EINVAL;
+
4715
+
4716 return fuse_req_getgroups(c->req, size, list);
+
4717}
+
4718
+
4719int fuse_interrupted(void)
+
4720{
+
4721 struct fuse_context_i *c = fuse_get_context_internal();
+
4722
+
4723 if (c)
+
4724 return fuse_req_interrupted(c->req);
+
4725 else
+
4726 return 0;
+
4727}
+
4728
+
4729int fuse_invalidate_path(struct fuse *f, const char *path) {
+
4730 fuse_ino_t ino;
+
4731 int err = lookup_path_in_cache(f, path, &ino);
+
4732 if (err) {
+
4733 return err;
+
4734 }
+
4735
+
4736 return fuse_lowlevel_notify_inval_inode(f->se, ino, 0, 0);
+
4737}
+
4738
+
4739#define FUSE_LIB_OPT(t, p, v) { t, offsetof(struct fuse_config, p), v }
+
4740
+
4741static const struct fuse_opt fuse_lib_opts[] = {
+ + +
4744 FUSE_LIB_OPT("debug", debug, 1),
+
4745 FUSE_LIB_OPT("-d", debug, 1),
+
4746 FUSE_LIB_OPT("kernel_cache", kernel_cache, 1),
+
4747 FUSE_LIB_OPT("auto_cache", auto_cache, 1),
+
4748 FUSE_LIB_OPT("noauto_cache", auto_cache, 0),
+
4749 FUSE_LIB_OPT("no_rofd_flush", no_rofd_flush, 1),
+
4750 FUSE_LIB_OPT("umask=", set_mode, 1),
+
4751 FUSE_LIB_OPT("umask=%o", umask, 0),
+
4752 FUSE_LIB_OPT("fmask=", set_mode, 1),
+
4753 FUSE_LIB_OPT("fmask=%o", fmask, 0),
+
4754 FUSE_LIB_OPT("dmask=", set_mode, 1),
+
4755 FUSE_LIB_OPT("dmask=%o", dmask, 0),
+
4756 FUSE_LIB_OPT("uid=", set_uid, 1),
+
4757 FUSE_LIB_OPT("uid=%d", uid, 0),
+
4758 FUSE_LIB_OPT("gid=", set_gid, 1),
+
4759 FUSE_LIB_OPT("gid=%d", gid, 0),
+
4760 FUSE_LIB_OPT("entry_timeout=%lf", entry_timeout, 0),
+
4761 FUSE_LIB_OPT("attr_timeout=%lf", attr_timeout, 0),
+
4762 FUSE_LIB_OPT("ac_attr_timeout=%lf", ac_attr_timeout, 0),
+
4763 FUSE_LIB_OPT("ac_attr_timeout=", ac_attr_timeout_set, 1),
+
4764 FUSE_LIB_OPT("negative_timeout=%lf", negative_timeout, 0),
+
4765 FUSE_LIB_OPT("noforget", remember, -1),
+
4766 FUSE_LIB_OPT("remember=%u", remember, 0),
+
4767 FUSE_LIB_OPT("modules=%s", modules, 0),
+
4768 FUSE_LIB_OPT("parallel_direct_write=%d", parallel_direct_writes, 0),
+ +
4770};
+
4771
+
4772static int fuse_lib_opt_proc(void *data, const char *arg, int key,
+
4773 struct fuse_args *outargs)
+
4774{
+
4775 (void) arg; (void) outargs; (void) data; (void) key;
+
4776
+
4777 /* Pass through unknown options */
+
4778 return 1;
+
4779}
+
4780
+
4781
+
4782static const struct fuse_opt fuse_help_opts[] = {
+
4783 FUSE_LIB_OPT("modules=%s", modules, 1),
+
4784 FUSE_OPT_KEY("modules=%s", FUSE_OPT_KEY_KEEP),
+ +
4786};
+
4787
+
4788static void print_module_help(const char *name,
+ +
4790{
+
4791 struct fuse_args a = FUSE_ARGS_INIT(0, NULL);
+
4792 if (fuse_opt_add_arg(&a, "") == -1 ||
+
4793 fuse_opt_add_arg(&a, "-h") == -1)
+
4794 return;
+
4795 printf("\nOptions for %s module:\n", name);
+
4796 (*fac)(&a, NULL);
+ +
4798}
+
4799
+
4800void fuse_lib_help(struct fuse_args *args)
+
4801{
+
4802 /* These are not all options, but only the ones that
+
4803 may be of interest to an end-user */
+
4804 printf(
+
4805" -o kernel_cache cache files in kernel\n"
+
4806" -o [no]auto_cache enable caching based on modification times (off)\n"
+
4807" -o no_rofd_flush disable flushing of read-only fd on close (off)\n"
+
4808" -o umask=M set file permissions (octal)\n"
+
4809" -o fmask=M set file permissions (octal)\n"
+
4810" -o dmask=M set dir permissions (octal)\n"
+
4811" -o uid=N set file owner\n"
+
4812" -o gid=N set file group\n"
+
4813" -o entry_timeout=T cache timeout for names (1.0s)\n"
+
4814" -o negative_timeout=T cache timeout for deleted names (0.0s)\n"
+
4815" -o attr_timeout=T cache timeout for attributes (1.0s)\n"
+
4816" -o ac_attr_timeout=T auto cache timeout for attributes (attr_timeout)\n"
+
4817" -o noforget never forget cached inodes\n"
+
4818" -o remember=T remember cached inodes for T seconds (0s)\n"
+
4819" -o modules=M1[:M2...] names of modules to push onto filesystem stack\n");
+
4820
+
4821
+
4822 /* Print low-level help */
+ +
4824
+
4825 /* Print help for builtin modules */
+
4826 print_module_help("subdir", &fuse_module_subdir_factory);
+
4827#ifdef HAVE_ICONV
+
4828 print_module_help("iconv", &fuse_module_iconv_factory);
+
4829#endif
+
4830
+
4831 /* Parse command line options in case we need to
+
4832 activate more modules */
+
4833 struct fuse_config conf = { .modules = NULL };
+
4834 if (fuse_opt_parse(args, &conf, fuse_help_opts,
+
4835 fuse_lib_opt_proc) == -1
+
4836 || !conf.modules)
+
4837 return;
+
4838
+
4839 char *module;
+
4840 char *next;
+
4841 struct fuse_module *m;
+
4842
+
4843 // Iterate over all modules
+
4844 for (module = conf.modules; module; module = next) {
+
4845 char *p;
+
4846 for (p = module; *p && *p != ':'; p++);
+
4847 next = *p ? p + 1 : NULL;
+
4848 *p = '\0';
+
4849
+
4850 m = fuse_get_module(module);
+
4851 if (m)
+
4852 print_module_help(module, &m->factory);
+
4853 }
+
4854}
+
4855
+
4856static int fuse_init_intr_signal(int signum, int *installed)
+
4857{
+
4858 struct sigaction old_sa;
+
4859
+
4860 if (sigaction(signum, NULL, &old_sa) == -1) {
+
4861 perror("fuse: cannot get old signal handler");
+
4862 return -1;
+
4863 }
+
4864
+
4865 if (old_sa.sa_handler == SIG_DFL) {
+
4866 struct sigaction sa;
+
4867
+
4868 memset(&sa, 0, sizeof(struct sigaction));
+
4869 sa.sa_handler = fuse_intr_sighandler;
+
4870 sigemptyset(&sa.sa_mask);
+
4871
+
4872 if (sigaction(signum, &sa, NULL) == -1) {
+
4873 perror("fuse: cannot set interrupt signal handler");
+
4874 return -1;
+
4875 }
+
4876 *installed = 1;
+
4877 }
+
4878 return 0;
+
4879}
+
4880
+
4881static void fuse_restore_intr_signal(int signum)
+
4882{
+
4883 struct sigaction sa;
+
4884
+
4885 memset(&sa, 0, sizeof(struct sigaction));
+
4886 sa.sa_handler = SIG_DFL;
+
4887 sigaction(signum, &sa, NULL);
+
4888}
+
4889
+
4890
+
4891static int fuse_push_module(struct fuse *f, const char *module,
+
4892 struct fuse_args *args)
+
4893{
+
4894 struct fuse_fs *fs[2] = { f->fs, NULL };
+
4895 struct fuse_fs *newfs;
+
4896 struct fuse_module *m = fuse_get_module(module);
+
4897
+
4898 if (!m)
+
4899 return -1;
+
4900
+
4901 newfs = m->factory(args, fs);
+
4902 if (!newfs) {
+
4903 fuse_put_module(m);
+
4904 return -1;
+
4905 }
+
4906 f->fs = newfs;
+
4907 return 0;
+
4908}
+
4909
+
4910struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
4911 void *user_data)
+
4912{
+
4913 struct fuse_fs *fs;
+
4914
+
4915 if (sizeof(struct fuse_operations) < op_size) {
+
4916 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not not work\n");
+
4917 op_size = sizeof(struct fuse_operations);
+
4918 }
+
4919
+
4920 fs = (struct fuse_fs *) calloc(1, sizeof(struct fuse_fs));
+
4921 if (!fs) {
+
4922 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse_fs object\n");
+
4923 return NULL;
+
4924 }
+
4925
+
4926 fs->user_data = user_data;
+
4927 if (op)
+
4928 memcpy(&fs->op, op, op_size);
+
4929 return fs;
+
4930}
+
4931
+
4932static int node_table_init(struct node_table *t)
+
4933{
+
4934 t->size = NODE_TABLE_MIN_SIZE;
+
4935 t->array = (struct node **) calloc(1, sizeof(struct node *) * t->size);
+
4936 if (t->array == NULL) {
+
4937 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
4938 return -1;
+
4939 }
+
4940 t->use = 0;
+
4941 t->split = 0;
+
4942
+
4943 return 0;
+
4944}
+
4945
+
4946static void *fuse_prune_nodes(void *fuse)
+
4947{
+
4948 struct fuse *f = fuse;
+
4949 int sleep_time;
+
4950
+
4951 fuse_set_thread_name("fuse_prune_nodes");
+
4952
+
4953 while(1) {
+
4954 sleep_time = fuse_clean_cache(f);
+
4955 sleep(sleep_time);
+
4956 }
+
4957 return NULL;
+
4958}
+
4959
+
4960int fuse_start_cleanup_thread(struct fuse *f)
+
4961{
+
4962 if (lru_enabled(f))
+
4963 return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f);
+
4964
+
4965 return 0;
+
4966}
+
4967
+
4968void fuse_stop_cleanup_thread(struct fuse *f)
+
4969{
+
4970 if (lru_enabled(f)) {
+
4971 pthread_mutex_lock(&f->lock);
+
4972 pthread_cancel(f->prune_thread);
+
4973 pthread_mutex_unlock(&f->lock);
+
4974 pthread_join(f->prune_thread, NULL);
+
4975 }
+
4976}
+
4977
+
4978/*
+
4979 * Not supposed to be called directly, but supposed to be called
+
4980 * through the fuse_new macro
+
4981 */
+
4982struct fuse *_fuse_new_31(struct fuse_args *args,
+
4983 const struct fuse_operations *op, size_t op_size,
+
4984 struct libfuse_version *version, void *user_data)
+
4985{
+
4986 struct fuse *f;
+
4987 struct node *root;
+
4988 struct fuse_fs *fs;
+
4989 struct fuse_lowlevel_ops llop = fuse_path_ops;
+
4990
+
4991 f = (struct fuse *) calloc(1, sizeof(struct fuse));
+
4992 if (f == NULL) {
+
4993 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
4994 goto out;
+
4995 }
+
4996
+
4997 f->conf.entry_timeout = 1.0;
+
4998 f->conf.attr_timeout = 1.0;
+
4999 f->conf.negative_timeout = 0.0;
+
5000 f->conf.intr_signal = FUSE_DEFAULT_INTR_SIGNAL;
+
5001
+
5002 /* Parse options */
+
5003 if (fuse_opt_parse(args, &f->conf, fuse_lib_opts,
+
5004 fuse_lib_opt_proc) == -1)
+
5005 goto out_free;
+
5006
+
5007 pthread_mutex_lock(&fuse_context_lock);
+
5008 static int builtin_modules_registered = 0;
+
5009 /* Have the builtin modules already been registered? */
+
5010 if (builtin_modules_registered == 0) {
+
5011 /* If not, register them. */
+
5012 fuse_register_module("subdir", fuse_module_subdir_factory, NULL);
+
5013#ifdef HAVE_ICONV
+
5014 fuse_register_module("iconv", fuse_module_iconv_factory, NULL);
+
5015#endif
+
5016 builtin_modules_registered= 1;
+
5017 }
+
5018 pthread_mutex_unlock(&fuse_context_lock);
+
5019
+
5020 if (fuse_create_context_key() == -1)
+
5021 goto out_free;
+
5022
+
5023 fs = fuse_fs_new(op, op_size, user_data);
+
5024 if (!fs)
+
5025 goto out_delete_context_key;
+
5026
+
5027 f->fs = fs;
+
5028
+
5029 /* Oh f**k, this is ugly! */
+
5030 if (!fs->op.lock) {
+
5031 llop.getlk = NULL;
+
5032 llop.setlk = NULL;
+
5033 }
+
5034
+
5035 f->pagesize = getpagesize();
+
5036 init_list_head(&f->partial_slabs);
+
5037 init_list_head(&f->full_slabs);
+
5038 init_list_head(&f->lru_table);
+
5039
+
5040 if (f->conf.modules) {
+
5041 char *module;
+
5042 char *next;
+
5043
+
5044 for (module = f->conf.modules; module; module = next) {
+
5045 char *p;
+
5046 for (p = module; *p && *p != ':'; p++);
+
5047 next = *p ? p + 1 : NULL;
+
5048 *p = '\0';
+
5049 if (module[0] &&
+
5050 fuse_push_module(f, module, args) == -1)
+
5051 goto out_free_fs;
+
5052 }
+
5053 }
+
5054
+
5055 if (!f->conf.ac_attr_timeout_set)
+
5056 f->conf.ac_attr_timeout = f->conf.attr_timeout;
+
5057
+
5058#if defined(__FreeBSD__) || defined(__NetBSD__)
+
5059 /*
+
5060 * In FreeBSD, we always use these settings as inode numbers
+
5061 * are needed to make getcwd(3) work.
+
5062 */
+
5063 f->conf.readdir_ino = 1;
+
5064#endif
+
5065
+
5066 /* not declared globally, to restrict usage of this function */
+
5067 struct fuse_session *fuse_session_new_versioned(
+
5068 struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
5069 size_t op_size, struct libfuse_version *version,
+
5070 void *userdata);
+
5071 f->se = fuse_session_new_versioned(args, &llop, sizeof(llop), version,
+
5072 f);
+
5073 if (f->se == NULL)
+
5074 goto out_free_fs;
+
5075
+
5076 /* Trace topmost layer by default */
+
5077 f->fs->debug = f->conf.debug;
+
5078 f->ctr = 0;
+
5079 f->generation = 0;
+
5080 if (node_table_init(&f->name_table) == -1)
+
5081 goto out_free_session;
+
5082
+
5083 if (node_table_init(&f->id_table) == -1)
+
5084 goto out_free_name_table;
+
5085
+
5086 pthread_mutex_init(&f->lock, NULL);
+
5087
+
5088 root = alloc_node(f);
+
5089 if (root == NULL) {
+
5090 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
5091 goto out_free_id_table;
+
5092 }
+
5093 if (lru_enabled(f)) {
+
5094 struct node_lru *lnode = node_lru(root);
+
5095 init_list_head(&lnode->lru);
+
5096 }
+
5097
+
5098 strcpy(root->inline_name, "/");
+
5099 root->name = root->inline_name;
+
5100 root->parent = NULL;
+
5101 root->nodeid = FUSE_ROOT_ID;
+
5102 inc_nlookup(root);
+
5103 hash_id(f, root);
+
5104
+
5105 return f;
+
5106
+
5107out_free_id_table:
+
5108 free(f->id_table.array);
+
5109out_free_name_table:
+
5110 free(f->name_table.array);
+
5111out_free_session:
+
5112 fuse_session_destroy(f->se);
+
5113out_free_fs:
+
5114 free(f->fs);
+
5115 free(f->conf.modules);
+
5116out_delete_context_key:
+
5117 fuse_delete_context_key();
+
5118out_free:
+
5119 free(f);
+
5120out:
+
5121 return NULL;
+
5122}
+
5123
+
5124/* Emulates 3.0-style fuse_new(), which processes --help */
+
5125FUSE_SYMVER("_fuse_new_30", "_fuse_new@FUSE_3.0")
+
5126struct fuse *_fuse_new_30(struct fuse_args *args,
+
5127 const struct fuse_operations *op,
+
5128 size_t op_size,
+
5129 struct libfuse_version *version,
+
5130 void *user_data)
+
5131{
+
5132 struct fuse_config conf = {0};
+
5133
+
5134 const struct fuse_opt opts[] = {
+
5135 FUSE_LIB_OPT("-h", show_help, 1),
+
5136 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5138 };
+
5139
+
5140 if (fuse_opt_parse(args, &conf, opts,
+
5141 fuse_lib_opt_proc) == -1)
+
5142 return NULL;
+
5143
+
5144 if (conf.show_help) {
+
5145 fuse_lib_help(args);
+
5146 return NULL;
+
5147 } else
+
5148 return _fuse_new_31(args, op, op_size, version, user_data);
+
5149}
+
5150
+
5151/* ABI compat version */
+
5152struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
5153 size_t op_size, void *user_data);
+
5154FUSE_SYMVER("fuse_new_31", "fuse_new@FUSE_3.1")
+
5155struct fuse *fuse_new_31(struct fuse_args *args,
+
5156 const struct fuse_operations *op,
+
5157 size_t op_size, void *user_data)
+
5158{
+
5159 /* unknown version */
+
5160 struct libfuse_version version = { 0 };
+
5161
+
5162 return _fuse_new_31(args, op, op_size, &version, user_data);
+
5163}
+
5164
+
5165/*
+
5166 * ABI compat version
+
5167 * Emulates 3.0-style fuse_new(), which processes --help
+
5168 */
+
5169struct fuse *fuse_new_30(struct fuse_args *args, const struct fuse_operations *op,
+
5170 size_t op_size, void *user_data);
+
5171FUSE_SYMVER("fuse_new_30", "fuse_new@FUSE_3.0")
+
5172struct fuse *fuse_new_30(struct fuse_args *args,
+
5173 const struct fuse_operations *op,
+
5174 size_t op_size, void *user_data)
+
5175{
+
5176 struct fuse_config conf = {0};
+
5177
+
5178 const struct fuse_opt opts[] = {
+
5179 FUSE_LIB_OPT("-h", show_help, 1),
+
5180 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5182 };
+
5183
+
5184 if (fuse_opt_parse(args, &conf, opts,
+
5185 fuse_lib_opt_proc) == -1)
+
5186 return NULL;
+
5187
+
5188 if (conf.show_help) {
+
5189 fuse_lib_help(args);
+
5190 return NULL;
+
5191 } else
+
5192 return fuse_new_31(args, op, op_size, user_data);
+
5193}
+
5194
+
5195
+
5196void fuse_destroy(struct fuse *f)
+
5197{
+
5198 size_t i;
+
5199
+
5200 if (f->conf.intr && f->intr_installed)
+
5201 fuse_restore_intr_signal(f->conf.intr_signal);
+
5202
+
5203 if (f->fs) {
+
5204 fuse_create_context(f);
+
5205
+
5206 for (i = 0; i < f->id_table.size; i++) {
+
5207 struct node *node;
+
5208
+
5209 for (node = f->id_table.array[i]; node != NULL;
+
5210 node = node->id_next) {
+
5211 if (node->is_hidden) {
+
5212 char *path;
+
5213 if (try_get_path(f, node->nodeid, NULL, &path, NULL, false) == 0) {
+
5214 fuse_fs_unlink(f->fs, path);
+
5215 free(path);
+
5216 }
+
5217 }
+
5218 }
+
5219 }
+
5220 }
+
5221 for (i = 0; i < f->id_table.size; i++) {
+
5222 struct node *node;
+
5223 struct node *next;
+
5224
+
5225 for (node = f->id_table.array[i]; node != NULL; node = next) {
+
5226 next = node->id_next;
+
5227 free_node(f, node);
+
5228 f->id_table.use--;
+
5229 }
+
5230 }
+
5231 assert(list_empty(&f->partial_slabs));
+
5232 assert(list_empty(&f->full_slabs));
+
5233
+
5234 while (fuse_modules) {
+
5235 fuse_put_module(fuse_modules);
+
5236 }
+
5237 free(f->id_table.array);
+
5238 free(f->name_table.array);
+
5239 pthread_mutex_destroy(&f->lock);
+
5240 fuse_session_destroy(f->se);
+
5241 free(f->fs);
+
5242 free(f->conf.modules);
+
5243 free(f);
+
5244 fuse_delete_context_key();
+
5245}
+
5246
+
5247int fuse_mount(struct fuse *f, const char *mountpoint) {
+
5248 return fuse_session_mount(fuse_get_session(f), mountpoint);
+
5249}
+
5250
+
5251
+
5252void fuse_unmount(struct fuse *f) {
+ +
5254}
+
5255
+
5256int fuse_version(void)
+
5257{
+
5258 return FUSE_VERSION;
+
5259}
+
5260
+
5261const char *fuse_pkgversion(void)
+
5262{
+
5263 return PACKAGE_VERSION;
+
5264}
+
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4654
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5197
+
int fuse_interrupted(void)
Definition fuse.c:4663
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5146
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4906
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1383
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4639
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4433
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5202
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4914
+
fuse_fill_dir_flags
Definition fuse.h:58
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_SPLICE_READ
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_IS_FD
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
int fuse_version(void)
Definition fuse.c:5206
+
@ FUSE_BUF_SPLICE_MOVE
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+ + +
enum fuse_buf_flags flags
+
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + + +
int32_t show_help
Definition fuse.h:279
+ +
uint32_t no_interrupt
+ +
void * private_data
Definition fuse.h:874
+ + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:60
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t noflush
Definition fuse_common.h:93
+
uint32_t flush
Definition fuse_common.h:74
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + + +
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2fuse__i_8h_source.html b/doc/html/fuse-3_818_82_2lib_2fuse__i_8h_source.html new file mode 100644 index 0000000..b320f7e --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2fuse__i_8h_source.html @@ -0,0 +1,292 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/fuse_i.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_i.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt
+
7*/
+
8
+
9#ifndef LIB_FUSE_I_H_
+
10#define LIB_FUSE_I_H_
+
11
+
12#include "fuse.h"
+
13#include "fuse_lowlevel.h"
+
14#include "util.h"
+
15
+
16#include <pthread.h>
+
17#include <semaphore.h>
+
18#include <stdint.h>
+
19#include <stdbool.h>
+
20#include <errno.h>
+
21#include <stdatomic.h>
+
22
+
23#define MIN(a, b) \
+
24({ \
+
25 typeof(a) _a = (a); \
+
26 typeof(b) _b = (b); \
+
27 _a < _b ? _a : _b; \
+
28})
+
29
+
30struct mount_opts;
+
31struct fuse_ring_pool;
+
32
+
33struct fuse_req {
+
34 struct fuse_session *se;
+
35 uint64_t unique;
+
36 _Atomic int ref_cnt;
+
37 pthread_mutex_t lock;
+
38 struct fuse_ctx ctx;
+
39 struct fuse_chan *ch;
+
40 int interrupted;
+
41 struct {
+
42 unsigned int ioctl_64bit : 1;
+
43 unsigned int is_uring : 1;
+
44 unsigned int is_copy_file_range_64 : 1;
+
45 } flags;
+
46 union {
+
47 struct {
+
48 uint64_t unique;
+
49 } i;
+
50 struct {
+ +
52 void *data;
+
53 } ni;
+
54 } u;
+
55 struct fuse_req *next;
+
56 struct fuse_req *prev;
+
57};
+
58
+
59struct fuse_notify_req {
+
60 uint64_t unique;
+
61 void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t,
+
62 const void *, const struct fuse_buf *);
+
63 struct fuse_notify_req *next;
+
64 struct fuse_notify_req *prev;
+
65};
+
66
+
67struct fuse_session_uring {
+
68 bool enable;
+
69 unsigned int q_depth;
+
70 struct fuse_ring_pool *pool;
+
71};
+
72
+
73struct fuse_session {
+
74 _Atomic(char *)mountpoint;
+
75 int fd;
+
76 struct fuse_custom_io *io;
+
77 struct mount_opts *mo;
+
78 int debug;
+
79 int deny_others;
+
80 struct fuse_lowlevel_ops op;
+
81 int got_init;
+
82 struct cuse_data *cuse_data;
+
83 void *userdata;
+
84 uid_t owner;
+
85 struct fuse_conn_info conn;
+
86 struct fuse_req list;
+
87 struct fuse_req interrupts;
+
88 pthread_mutex_t lock;
+
89 int got_destroy;
+
90 pthread_key_t pipe_key;
+
91 int broken_splice_nonblock;
+
92 uint64_t notify_ctr;
+
93 struct fuse_notify_req notify_list;
+
94 _Atomic size_t bufsize;
+
95 int error;
+
96
+
97 /*
+
98 * This is useful if any kind of ABI incompatibility is found at
+
99 * a later version, to 'fix' it at run time.
+
100 */
+
101 struct libfuse_version version;
+
102
+
103 /* thread synchronization */
+
104 _Atomic bool mt_exited;
+
105 pthread_mutex_t mt_lock;
+
106 sem_t mt_finish;
+
107
+
108 /* true if reading requests from /dev/fuse are handled internally */
+
109 bool buf_reallocable;
+
110
+
111 /* io_uring */
+
112 struct fuse_session_uring uring;
+
113
+
114 /*
+
115 * conn->want and conn_want_ext options set by libfuse , needed
+
116 * to correctly convert want to want_ext
+
117 */
+
118 uint32_t conn_want;
+
119 uint64_t conn_want_ext;
+
120};
+
121
+
122struct fuse_chan {
+
123 pthread_mutex_t lock;
+
124 int ctr;
+
125 int fd;
+
126};
+
127
+
135struct fuse_module {
+
136 char *name;
+
137 fuse_module_factory_t factory;
+
138 struct fuse_module *next;
+
139 struct fusemod_so *so;
+
140 int ctr;
+
141};
+
142
+
151#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
152struct fuse_loop_config
+
153{
+
154 /* verififier that a correct struct was was passed. This is especially
+
155 * needed, as versions below (3, 12) were using a public struct
+
156 * (now called fuse_loop_config_v1), which was hard to extend with
+
157 * additional parameters, without risking that file system implementations
+
158 * would not have noticed and might either pass uninitialized members
+
159 * or even too small structs.
+
160 * fuse_loop_config_v1 has clone_fd at this offset, which should be either 0
+
161 * or 1. v2 or even higher version just need to set a value here
+
162 * which not conflicting and very unlikely as having been set by
+
163 * file system implementation.
+
164 */
+
165 int version_id;
+
166
+
171 int clone_fd;
+ +
184
+
190 unsigned int max_threads;
+
191};
+
192#endif
+
193
+
194/* ----------------------------------------------------------- *
+
195 * Channel interface (when using -o clone_fd) *
+
196 * ----------------------------------------------------------- */
+
197
+
204struct fuse_chan *fuse_chan_get(struct fuse_chan *ch);
+
205
+
211void fuse_chan_put(struct fuse_chan *ch);
+
212
+
213struct mount_opts *parse_mount_opts(struct fuse_args *args);
+
214void destroy_mount_opts(struct mount_opts *mo);
+
215void fuse_mount_version(void);
+
216unsigned get_max_read(struct mount_opts *o);
+
217void fuse_kern_unmount(const char *mountpoint, int fd);
+
218int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo);
+
219
+
220int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
221 int count);
+
222void fuse_free_req(fuse_req_t req);
+
223void list_init_req(struct fuse_req *req);
+
224
+
225void _cuse_lowlevel_init(fuse_req_t req, const fuse_ino_t nodeid,
+
226 const void *req_header, const void *req_payload);
+
227void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg);
+
228
+
229int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg);
+
230
+
231void fuse_buf_free(struct fuse_buf *buf);
+
232
+
233int fuse_session_receive_buf_internal(struct fuse_session *se,
+
234 struct fuse_buf *buf,
+
235 struct fuse_chan *ch);
+
236void fuse_session_process_buf_internal(struct fuse_session *se,
+
237 const struct fuse_buf *buf,
+
238 struct fuse_chan *ch);
+
239
+
240struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
241 size_t op_size, void *private_data);
+
242int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config);
+
243int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
244
+
250int fuse_loop_cfg_verify(struct fuse_loop_config *config);
+
251
+
252
+
253/*
+
254 * This can be changed dynamically on recent kernels through the
+
255 * /proc/sys/fs/fuse/max_pages_limit interface.
+
256 *
+
257 * Older kernels will always use the default value.
+
258 */
+
259#define FUSE_DEFAULT_MAX_PAGES_LIMIT 256
+
260#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32
+
261
+
262/* room needed in buffer to accommodate header */
+
263#define FUSE_BUFFER_HEADER_SIZE 0x1000
+
264
+
265
+
266#endif /* LIB_FUSE_I_H_*/
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1383
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
struct fuse_req * fuse_req_t
+
uint64_t fuse_ino_t
+ + + + + + +
unsigned int max_threads
Definition fuse_i.h:166
+
unsigned int max_idle_threads
+ + + + + +
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2fuse__log_8c_source.html b/doc/html/fuse-3_818_82_2lib_2fuse__log_8c_source.html new file mode 100644 index 0000000..effc91e --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2fuse__log_8c_source.html @@ -0,0 +1,125 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/fuse_log.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_log.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt
+
9*/
+
10
+
11#include "fuse_log.h"
+
12
+
13#include <stdio.h>
+
14#include <stdbool.h>
+
15#include <syslog.h>
+
16#include <stdarg.h>
+
17
+
18#define MAX_SYSLOG_LINE_LEN 512
+
19
+
20static bool to_syslog = false;
+
21
+
22static void default_log_func(enum fuse_log_level level, const char *fmt, va_list ap)
+
23{
+
24 if (to_syslog)
+
25 vsyslog(level, fmt, ap);
+
26 else
+
27 vfprintf(stderr, fmt, ap);
+
28}
+
29
+
30static fuse_log_func_t log_func = default_log_func;
+
31
+ +
33{
+
34 if (!func)
+
35 func = default_log_func;
+
36
+
37 log_func = func;
+
38}
+
39
+
40void fuse_log(enum fuse_log_level level, const char *fmt, ...)
+
41{
+
42 va_list ap;
+
43
+
44 va_start(ap, fmt);
+
45 log_func(level, fmt, ap);
+
46 va_end(ap);
+
47}
+
48
+
49void fuse_log_enable_syslog(const char *ident, int option, int facility)
+
50{
+
51 to_syslog = true;
+
52
+
53 openlog(ident, option, facility);
+
54}
+
55
+
56void fuse_log_close_syslog(void)
+
57{
+
58 closelog();
+
59}
+
void fuse_log_close_syslog(void)
Definition fuse_log.c:93
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:86
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:69
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2fuse__loop_8c_source.html b/doc/html/fuse-3_818_82_2lib_2fuse__loop_8c_source.html new file mode 100644 index 0000000..8580116 --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2fuse__loop_8c_source.html @@ -0,0 +1,113 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/fuse_loop.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_loop.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the single-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14#include "fuse_uring_i.h"
+
15#include <stdio.h>
+
16#include <stdlib.h>
+
17#include <errno.h>
+
18
+
19int fuse_session_loop(struct fuse_session *se)
+
20{
+
21 int res = 0;
+
22 struct fuse_buf fbuf = {
+
23 .mem = NULL,
+
24 };
+
25
+
26 while (!fuse_session_exited(se)) {
+
27 res = fuse_session_receive_buf_internal(se, &fbuf, NULL);
+
28
+
29 if (res == -EINTR)
+
30 continue;
+
31 if (res <= 0)
+
32 break;
+
33
+
34 fuse_session_process_buf(se, &fbuf);
+
35 }
+
36
+
37 fuse_buf_free(&fbuf);
+
38 if(res > 0)
+
39 /* No error, just the length of the most recently read
+
40 request */
+
41 res = 0;
+
42 if(se->error != 0)
+
43 res = se->error;
+
44
+
45 if (se->uring.pool)
+
46 fuse_uring_stop(se);
+
47 return res;
+
48}
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+ +
void * mem
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2fuse__loop__mt_8c_source.html b/doc/html/fuse-3_818_82_2lib_2fuse__loop__mt_8c_source.html new file mode 100644 index 0000000..fb6652e --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2fuse__loop__mt_8c_source.html @@ -0,0 +1,604 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/fuse_loop_mt.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_loop_mt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the multi-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt.
+
9*/
+
10
+
11#define _GNU_SOURCE
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_lowlevel.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_kernel.h"
+
17#include "fuse_i.h"
+
18#include "fuse_uring_i.h"
+
19#include "util.h"
+
20
+
21#include <stdio.h>
+
22#include <stdlib.h>
+
23#include <string.h>
+
24#include <unistd.h>
+
25#include <signal.h>
+
26#include <semaphore.h>
+
27#include <errno.h>
+
28#include <sys/time.h>
+
29#include <sys/ioctl.h>
+
30#include <assert.h>
+
31#include <limits.h>
+
32
+
33/* Environment var controlling the thread stack size */
+
34#define ENVNAME_THREAD_STACK "FUSE_THREAD_STACK"
+
35
+
36#define FUSE_LOOP_MT_V2_IDENTIFIER INT_MAX - 2
+
37#define FUSE_LOOP_MT_DEF_CLONE_FD 0
+
38#define FUSE_LOOP_MT_DEF_MAX_THREADS 10
+
39#define FUSE_LOOP_MT_DEF_IDLE_THREADS -1 /* thread destruction is disabled
+
40 * by default */
+
41
+
42/* an arbitrary large value that cannot be valid */
+
43#define FUSE_LOOP_MT_MAX_THREADS (100U * 1000)
+
44
+
45struct fuse_worker {
+
46 struct fuse_worker *prev;
+
47 struct fuse_worker *next;
+
48 pthread_t thread_id;
+
49
+
50 // We need to include fuse_buf so that we can properly free
+
51 // it when a thread is terminated by pthread_cancel().
+
52 struct fuse_buf fbuf;
+
53 struct fuse_chan *ch;
+
54 struct fuse_mt *mt;
+
55};
+
56
+
57/* synchronization via se->mt_lock */
+
58struct fuse_mt {
+
59 int numworker;
+
60 int numavail;
+
61 struct fuse_session *se;
+
62 struct fuse_worker main;
+
63 int error;
+
64 int clone_fd;
+
65 int max_idle;
+
66 int max_threads;
+
67};
+
68
+
69static struct fuse_chan *fuse_chan_new(int fd)
+
70{
+
71 struct fuse_chan *ch = (struct fuse_chan *) malloc(sizeof(*ch));
+
72 if (ch == NULL) {
+
73 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate channel\n");
+
74 return NULL;
+
75 }
+
76
+
77 memset(ch, 0, sizeof(*ch));
+
78 ch->fd = fd;
+
79 ch->ctr = 1;
+
80 pthread_mutex_init(&ch->lock, NULL);
+
81
+
82 return ch;
+
83}
+
84
+
85struct fuse_chan *fuse_chan_get(struct fuse_chan *ch)
+
86{
+
87 assert(ch->ctr > 0);
+
88 pthread_mutex_lock(&ch->lock);
+
89 ch->ctr++;
+
90 pthread_mutex_unlock(&ch->lock);
+
91
+
92 return ch;
+
93}
+
94
+
95void fuse_chan_put(struct fuse_chan *ch)
+
96{
+
97 if (ch == NULL)
+
98 return;
+
99 pthread_mutex_lock(&ch->lock);
+
100 ch->ctr--;
+
101 if (!ch->ctr) {
+
102 pthread_mutex_unlock(&ch->lock);
+
103 close(ch->fd);
+
104 pthread_mutex_destroy(&ch->lock);
+
105 free(ch);
+
106 } else
+
107 pthread_mutex_unlock(&ch->lock);
+
108}
+
109
+
110static void list_add_worker(struct fuse_worker *w, struct fuse_worker *next)
+
111{
+
112 struct fuse_worker *prev = next->prev;
+
113 w->next = next;
+
114 w->prev = prev;
+
115 prev->next = w;
+
116 next->prev = w;
+
117}
+
118
+
119static void list_del_worker(struct fuse_worker *w)
+
120{
+
121 struct fuse_worker *prev = w->prev;
+
122 struct fuse_worker *next = w->next;
+
123 prev->next = next;
+
124 next->prev = prev;
+
125}
+
126
+
127static int fuse_loop_start_thread(struct fuse_mt *mt);
+
128
+
129static void *fuse_do_work(void *data)
+
130{
+
131 struct fuse_worker *w = (struct fuse_worker *) data;
+
132 struct fuse_mt *mt = w->mt;
+
133 struct fuse_session *se = mt->se;
+
134
+
135 fuse_set_thread_name("fuse_worker");
+
136
+
137 while (!fuse_session_exited(se)) {
+
138 int isforget = 0;
+
139 int res;
+
140
+
141 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
142 res = fuse_session_receive_buf_internal(se, &w->fbuf, w->ch);
+
143 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+
144 if (res == -EINTR)
+
145 continue;
+
146 if (res <= 0) {
+
147 if (res < 0) {
+ +
149 mt->error = res;
+
150 }
+
151 break;
+
152 }
+
153
+
154 pthread_mutex_lock(&se->mt_lock);
+
155 if (fuse_session_exited(se)) {
+
156 pthread_mutex_unlock(&se->mt_lock);
+
157 return NULL;
+
158 }
+
159
+
160 /*
+
161 * This disgusting hack is needed so that zillions of threads
+
162 * are not created on a burst of FORGET messages
+
163 */
+
164 if (!(w->fbuf.flags & FUSE_BUF_IS_FD)) {
+
165 struct fuse_in_header *in = w->fbuf.mem;
+
166
+
167 if (in->opcode == FUSE_FORGET ||
+
168 in->opcode == FUSE_BATCH_FORGET)
+
169 isforget = 1;
+
170 }
+
171
+
172 if (!isforget)
+
173 mt->numavail--;
+
174 if (mt->numavail == 0 && mt->numworker < mt->max_threads &&
+
175 likely(se->got_init))
+
176 fuse_loop_start_thread(mt);
+
177 pthread_mutex_unlock(&se->mt_lock);
+
178
+
179 fuse_session_process_buf_internal(se, &w->fbuf, w->ch);
+
180
+
181 pthread_mutex_lock(&se->mt_lock);
+
182 if (!isforget)
+
183 mt->numavail++;
+
184
+
185 /* creating and destroying threads is rather expensive - and there is
+
186 * not much gain from destroying existing threads. It is therefore
+
187 * discouraged to set max_idle to anything else than -1. If there
+
188 * is indeed a good reason to destruct threads it should be done
+
189 * delayed, a moving average might be useful for that.
+
190 */
+
191 if (mt->max_idle != -1 && mt->numavail > mt->max_idle && mt->numworker > 1) {
+
192 if (fuse_session_exited(se)) {
+
193 pthread_mutex_unlock(&se->mt_lock);
+
194 return NULL;
+
195 }
+
196 list_del_worker(w);
+
197 mt->numavail--;
+
198 mt->numworker--;
+
199 pthread_mutex_unlock(&se->mt_lock);
+
200
+
201 pthread_detach(w->thread_id);
+
202 fuse_buf_free(&w->fbuf);
+
203 fuse_chan_put(w->ch);
+
204 free(w);
+
205 return NULL;
+
206 }
+
207 pthread_mutex_unlock(&se->mt_lock);
+
208 }
+
209
+
210 sem_post(&se->mt_finish);
+
211 return NULL;
+
212}
+
213
+
214int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg)
+
215{
+
216 sigset_t oldset;
+
217 sigset_t newset;
+
218 int res;
+
219 pthread_attr_t attr;
+
220 char *stack_size;
+
221
+
222 /* Override default stack size
+
223 * XXX: This should ideally be a parameter option. It is rather
+
224 * well hidden here.
+
225 */
+
226 pthread_attr_init(&attr);
+
227 stack_size = getenv(ENVNAME_THREAD_STACK);
+
228 if (stack_size) {
+
229 long size;
+
230
+
231 res = libfuse_strtol(stack_size, &size);
+
232 if (res)
+
233 fuse_log(FUSE_LOG_ERR, "fuse: invalid stack size: %s\n",
+
234 stack_size);
+
235 else if (pthread_attr_setstacksize(&attr, size))
+
236 fuse_log(FUSE_LOG_ERR, "fuse: could not set stack size: %ld\n",
+
237 size);
+
238 }
+
239
+
240 /* Disallow signal reception in worker threads */
+
241 sigemptyset(&newset);
+
242 sigaddset(&newset, SIGTERM);
+
243 sigaddset(&newset, SIGINT);
+
244 sigaddset(&newset, SIGHUP);
+
245 sigaddset(&newset, SIGQUIT);
+
246 pthread_sigmask(SIG_BLOCK, &newset, &oldset);
+
247 res = pthread_create(thread_id, &attr, func, arg);
+
248 pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+
249 pthread_attr_destroy(&attr);
+
250 if (res != 0) {
+
251 fuse_log(FUSE_LOG_ERR, "fuse: error creating thread: %s\n",
+
252 strerror(res));
+
253 return -1;
+
254 }
+
255
+
256 return 0;
+
257}
+
258
+
259static int fuse_clone_chan_fd_default(struct fuse_session *se)
+
260{
+
261 int res;
+
262 int clonefd;
+
263 uint32_t masterfd;
+
264 const char *devname = "/dev/fuse";
+
265
+
266#ifndef O_CLOEXEC
+
267#define O_CLOEXEC 0
+
268#endif
+
269 clonefd = open(devname, O_RDWR | O_CLOEXEC);
+
270 if (clonefd == -1) {
+
271 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n", devname,
+
272 strerror(errno));
+
273 return -1;
+
274 }
+
275 if (!O_CLOEXEC) {
+
276 res = fcntl(clonefd, F_SETFD, FD_CLOEXEC);
+
277 if (res == -1) {
+
278 fuse_log(FUSE_LOG_ERR, "fuse: failed to set CLOEXEC: %s\n",
+
279 strerror(errno));
+
280 close(clonefd);
+
281 return -1;
+
282 }
+
283 }
+
284
+
285 masterfd = se->fd;
+
286 res = ioctl(clonefd, FUSE_DEV_IOC_CLONE, &masterfd);
+
287 if (res == -1) {
+
288 fuse_log(FUSE_LOG_ERR, "fuse: failed to clone device fd: %s\n",
+
289 strerror(errno));
+
290 close(clonefd);
+
291 return -1;
+
292 }
+
293 return clonefd;
+
294}
+
295
+
296static struct fuse_chan *fuse_clone_chan(struct fuse_mt *mt)
+
297{
+
298 int clonefd;
+
299 struct fuse_session *se = mt->se;
+
300 struct fuse_chan *newch;
+
301
+
302 if (se->io != NULL) {
+
303 if (se->io->clone_fd != NULL)
+
304 clonefd = se->io->clone_fd(se->fd);
+
305 else
+
306 return NULL;
+
307 } else {
+
308 clonefd = fuse_clone_chan_fd_default(se);
+
309 }
+
310 if (clonefd < 0)
+
311 return NULL;
+
312
+
313 newch = fuse_chan_new(clonefd);
+
314 if (newch == NULL)
+
315 close(clonefd);
+
316
+
317 return newch;
+
318}
+
319
+
320static int fuse_loop_start_thread(struct fuse_mt *mt)
+
321{
+
322 int res;
+
323
+
324 struct fuse_worker *w = malloc(sizeof(struct fuse_worker));
+
325 if (!w) {
+
326 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate worker structure\n");
+
327 return -1;
+
328 }
+
329 memset(w, 0, sizeof(struct fuse_worker));
+
330 w->fbuf.mem = NULL;
+
331 w->mt = mt;
+
332
+
333 w->ch = NULL;
+
334 if (mt->clone_fd) {
+
335 w->ch = fuse_clone_chan(mt);
+
336 if(!w->ch) {
+
337 /* Don't attempt this again */
+
338 fuse_log(FUSE_LOG_ERR, "fuse: trying to continue "
+
339 "without -o clone_fd.\n");
+
340 mt->clone_fd = 0;
+
341 }
+
342 }
+
343
+
344 res = fuse_start_thread(&w->thread_id, fuse_do_work, w);
+
345 if (res == -1) {
+
346 fuse_chan_put(w->ch);
+
347 free(w);
+
348 return -1;
+
349 }
+
350 list_add_worker(w, &mt->main);
+
351 mt->numavail ++;
+
352 mt->numworker ++;
+
353
+
354 return 0;
+
355}
+
356
+
357static void fuse_join_worker(struct fuse_mt *mt, struct fuse_worker *w)
+
358{
+
359 pthread_join(w->thread_id, NULL);
+
360 pthread_mutex_lock(&mt->se->mt_lock);
+
361 list_del_worker(w);
+
362 pthread_mutex_unlock(&mt->se->mt_lock);
+
363 fuse_buf_free(&w->fbuf);
+
364 fuse_chan_put(w->ch);
+
365 free(w);
+
366}
+
367
+
368int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
369FUSE_SYMVER("fuse_session_loop_mt_312", "fuse_session_loop_mt@@FUSE_3.12")
+
370int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config)
+
371{
+
372int err;
+
373 struct fuse_mt mt;
+
374 struct fuse_worker *w;
+
375 int created_config = 0;
+
376
+
377 if (config) {
+
378 err = fuse_loop_cfg_verify(config);
+
379 if (err)
+
380 return err;
+
381 } else {
+
382 /* The caller does not care about parameters - use the default */
+
383 config = fuse_loop_cfg_create();
+
384 created_config = 1;
+
385 }
+
386
+
387
+
388 memset(&mt, 0, sizeof(struct fuse_mt));
+
389 mt.se = se;
+
390 mt.clone_fd = config->clone_fd;
+
391 mt.error = 0;
+
392 mt.numworker = 0;
+
393 mt.numavail = 0;
+
394 mt.max_idle = config->max_idle_threads;
+
395 mt.max_threads = config->max_threads;
+
396 mt.main.thread_id = pthread_self();
+
397 mt.main.prev = mt.main.next = &mt.main;
+
398
+
399 pthread_mutex_lock(&se->mt_lock);
+
400 err = fuse_loop_start_thread(&mt);
+
401 pthread_mutex_unlock(&se->mt_lock);
+
402 if (!err) {
+
403 while (!fuse_session_exited(se))
+
404 sem_wait(&se->mt_finish);
+
405 if (se->debug)
+
406 fuse_log(FUSE_LOG_DEBUG,
+
407 "fuse: session exited, terminating workers\n");
+
408
+
409 pthread_mutex_lock(&se->mt_lock);
+
410 for (w = mt.main.next; w != &mt.main; w = w->next)
+
411 pthread_cancel(w->thread_id);
+
412 pthread_mutex_unlock(&se->mt_lock);
+
413
+
414 while (mt.main.next != &mt.main)
+
415 fuse_join_worker(&mt, mt.main.next);
+
416
+
417 err = mt.error;
+
418
+
419 if (se->uring.pool)
+
420 fuse_uring_stop(se);
+
421 }
+
422
+
423 pthread_mutex_destroy(&se->mt_lock);
+
424 if(se->error != 0)
+
425 err = se->error;
+
426
+
427
+
428 if (created_config) {
+
429 fuse_loop_cfg_destroy(config);
+
430 config = NULL;
+
431 }
+
432
+
433 return err;
+
434}
+
435
+
436int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1);
+
437FUSE_SYMVER("fuse_session_loop_mt_32", "fuse_session_loop_mt@FUSE_3.2")
+
438int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1)
+
439{
+
440 int err;
+
441 struct fuse_loop_config *config = NULL;
+
442
+
443 if (config_v1 != NULL) {
+
444 /* convert the given v1 config */
+
445 config = fuse_loop_cfg_create();
+
446 if (config == NULL)
+
447 return ENOMEM;
+
448
+
449 fuse_loop_cfg_convert(config, config_v1);
+
450 }
+
451
+
452 err = fuse_session_loop_mt_312(se, config);
+
453
+
454 fuse_loop_cfg_destroy(config);
+
455
+
456 return err;
+
457}
+
458
+
459
+
460int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
461FUSE_SYMVER("fuse_session_loop_mt_31", "fuse_session_loop_mt@FUSE_3.0")
+
462int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd)
+
463{
+
464 int err;
+
465 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
466 if (clone_fd > 0)
+
467 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
468 err = fuse_session_loop_mt_312(se, config);
+
469
+
470 fuse_loop_cfg_destroy(config);
+
471
+
472 return err;
+
473}
+
474
+
475struct fuse_loop_config *fuse_loop_cfg_create(void)
+
476{
+
477 struct fuse_loop_config *config = calloc(1, sizeof(*config));
+
478 if (config == NULL)
+
479 return NULL;
+
480
+
481 config->version_id = FUSE_LOOP_MT_V2_IDENTIFIER;
+
482 config->max_idle_threads = FUSE_LOOP_MT_DEF_IDLE_THREADS;
+
483 config->max_threads = FUSE_LOOP_MT_DEF_MAX_THREADS;
+
484 config->clone_fd = FUSE_LOOP_MT_DEF_CLONE_FD;
+
485
+
486 return config;
+
487}
+
488
+
489void fuse_loop_cfg_destroy(struct fuse_loop_config *config)
+
490{
+
491 free(config);
+
492}
+
493
+
494int fuse_loop_cfg_verify(struct fuse_loop_config *config)
+
495{
+
496 if (config->version_id != FUSE_LOOP_MT_V2_IDENTIFIER)
+
497 return -EINVAL;
+
498
+
499 return 0;
+
500}
+
501
+
502void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
503 struct fuse_loop_config_v1 *v1_conf)
+
504{
+
505 fuse_loop_cfg_set_idle_threads(config, v1_conf->max_idle_threads);
+
506
+
507 fuse_loop_cfg_set_clone_fd(config, v1_conf->clone_fd);
+
508}
+
509
+
510void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
511 unsigned int value)
+
512{
+
513 if (value > FUSE_LOOP_MT_MAX_THREADS) {
+
514 if (value != UINT_MAX)
+
515 fuse_log(FUSE_LOG_ERR,
+
516 "Ignoring invalid max threads value "
+
517 "%u > max (%u).\n", value,
+
518 FUSE_LOOP_MT_MAX_THREADS);
+
519 return;
+
520 }
+
521 config->max_idle_threads = value;
+
522}
+
523
+
524void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
525 unsigned int value)
+
526{
+
527 config->max_threads = value;
+
528}
+
529
+
530void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
531 unsigned int value)
+
532{
+
533 config->clone_fd = value;
+
534}
+
535
+
@ FUSE_BUF_IS_FD
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_exited(struct fuse_session *se)
+ + + +
unsigned int max_threads
Definition fuse_i.h:166
+
unsigned int max_idle_threads
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2fuse__lowlevel_8c_source.html b/doc/html/fuse-3_818_82_2lib_2fuse__lowlevel_8c_source.html new file mode 100644 index 0000000..c22c461 --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2fuse__lowlevel_8c_source.html @@ -0,0 +1,4667 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/fuse_lowlevel.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_lowlevel.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of (most of) the low-level FUSE API. The session loop
+
6 functions are implemented in separate files.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file LGPL2.txt
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_kernel.h"
+
17#include "fuse_opt.h"
+
18#include "fuse_misc.h"
+
19#include "mount_util.h"
+
20#include "util.h"
+
21#include "fuse_uring_i.h"
+
22
+
23#include <pthread.h>
+
24#include <stdatomic.h>
+
25#include <stdint.h>
+
26#include <inttypes.h>
+
27#include <stdbool.h>
+
28#include <stdio.h>
+
29#include <stdlib.h>
+
30#include <stddef.h>
+
31#include <stdalign.h>
+
32#include <string.h>
+
33#include <unistd.h>
+
34#include <limits.h>
+
35#include <errno.h>
+
36#include <assert.h>
+
37#include <sys/file.h>
+
38#include <sys/ioctl.h>
+
39#include <stdalign.h>
+
40
+
41#ifdef USDT_ENABLED
+
42#include "usdt.h"
+
43#endif
+
44
+
45#ifndef F_LINUX_SPECIFIC_BASE
+
46#define F_LINUX_SPECIFIC_BASE 1024
+
47#endif
+
48#ifndef F_SETPIPE_SZ
+
49#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7)
+
50#endif
+
51
+
52#define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg)))
+
53#define OFFSET_MAX 0x7fffffffffffffffLL
+
54
+
55struct fuse_pollhandle {
+
56 uint64_t kh;
+
57 struct fuse_session *se;
+
58};
+
59
+
60static size_t pagesize;
+
61
+
62static __attribute__((constructor)) void fuse_ll_init_pagesize(void)
+
63{
+
64 pagesize = getpagesize();
+
65}
+
66
+
67#ifdef USDT_ENABLED
+
68/* tracepoints */
+
69static void trace_request_receive(int err)
+
70{
+
71 USDT(libfuse, request_receive, err);
+
72}
+
73
+
74static void trace_request_process(unsigned int opcode, unsigned int unique)
+
75{
+
76 USDT(libfuse, request_process, opcode, unique);
+
77}
+
78
+
79static void trace_request_reply(uint64_t unique, unsigned int len,
+
80 int error, int reply_err)
+
81{
+
82 USDT(libfuse, request_reply, unique, len, error, reply_err);
+
83}
+
84#else
+
85static void trace_request_receive(int err)
+
86{
+
87 (void)err;
+
88}
+
89
+
90static void trace_request_process(unsigned int opcode, unsigned int unique)
+
91{
+
92 (void)opcode;
+
93 (void)unique;
+
94}
+
95
+
96static void trace_request_reply(uint64_t unique, unsigned int len,
+
97 int error, int reply_err)
+
98{
+
99 (void)unique;
+
100 (void)len;
+
101 (void)error;
+
102 (void)reply_err;
+
103}
+
104#endif
+
105
+
106static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr)
+
107{
+
108 attr->ino = stbuf->st_ino;
+
109 attr->mode = stbuf->st_mode;
+
110 attr->nlink = stbuf->st_nlink;
+
111 attr->uid = stbuf->st_uid;
+
112 attr->gid = stbuf->st_gid;
+
113 attr->rdev = stbuf->st_rdev;
+
114 attr->size = stbuf->st_size;
+
115 attr->blksize = stbuf->st_blksize;
+
116 attr->blocks = stbuf->st_blocks;
+
117 attr->atime = stbuf->st_atime;
+
118 attr->mtime = stbuf->st_mtime;
+
119 attr->ctime = stbuf->st_ctime;
+
120 attr->atimensec = ST_ATIM_NSEC(stbuf);
+
121 attr->mtimensec = ST_MTIM_NSEC(stbuf);
+
122 attr->ctimensec = ST_CTIM_NSEC(stbuf);
+
123}
+
124
+
125static void convert_attr(const struct fuse_setattr_in *attr, struct stat *stbuf)
+
126{
+
127 stbuf->st_mode = attr->mode;
+
128 stbuf->st_uid = attr->uid;
+
129 stbuf->st_gid = attr->gid;
+
130 stbuf->st_size = attr->size;
+
131 stbuf->st_atime = attr->atime;
+
132 stbuf->st_mtime = attr->mtime;
+
133 stbuf->st_ctime = attr->ctime;
+
134 ST_ATIM_NSEC_SET(stbuf, attr->atimensec);
+
135 ST_MTIM_NSEC_SET(stbuf, attr->mtimensec);
+
136 ST_CTIM_NSEC_SET(stbuf, attr->ctimensec);
+
137}
+
138
+
139static size_t iov_length(const struct iovec *iov, size_t count)
+
140{
+
141 size_t seg;
+
142 size_t ret = 0;
+
143
+
144 for (seg = 0; seg < count; seg++)
+
145 ret += iov[seg].iov_len;
+
146 return ret;
+
147}
+
148
+
149void list_init_req(struct fuse_req *req)
+
150{
+
151 req->next = req;
+
152 req->prev = req;
+
153}
+
154
+
155static void list_del_req(struct fuse_req *req)
+
156{
+
157 struct fuse_req *prev = req->prev;
+
158 struct fuse_req *next = req->next;
+
159 prev->next = next;
+
160 next->prev = prev;
+
161}
+
162
+
163static void list_add_req(struct fuse_req *req, struct fuse_req *next)
+
164{
+
165 struct fuse_req *prev = next->prev;
+
166 req->next = next;
+
167 req->prev = prev;
+
168 prev->next = req;
+
169 next->prev = req;
+
170}
+
171
+
172static void destroy_req(fuse_req_t req)
+
173{
+
174 if (req->flags.is_uring) {
+
175 fuse_log(FUSE_LOG_ERR, "Refusing to destruct uring req\n");
+
176 return;
+
177 }
+
178 assert(req->ch == NULL);
+
179 pthread_mutex_destroy(&req->lock);
+
180 free(req);
+
181}
+
182
+
183void fuse_free_req(fuse_req_t req)
+
184{
+
185 int ctr;
+
186 struct fuse_session *se = req->se;
+
187
+
188 /* XXX: for now no support for interrupts with io-uring
+
189 * It actually might work already, though. But then would add
+
190 * a lock across ring queues.
+
191 */
+
192 if (se->conn.no_interrupt || req->flags.is_uring) {
+
193 ctr = --req->ref_cnt;
+
194 fuse_chan_put(req->ch);
+
195 req->ch = NULL;
+
196 } else {
+
197 pthread_mutex_lock(&se->lock);
+
198 req->u.ni.func = NULL;
+
199 req->u.ni.data = NULL;
+
200 list_del_req(req);
+
201 ctr = --req->ref_cnt;
+
202 fuse_chan_put(req->ch);
+
203 req->ch = NULL;
+
204 pthread_mutex_unlock(&se->lock);
+
205 }
+
206 if (!ctr)
+
207 destroy_req(req);
+
208}
+
209
+
210static struct fuse_req *fuse_ll_alloc_req(struct fuse_session *se)
+
211{
+
212 struct fuse_req *req;
+
213
+
214 req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req));
+
215 if (req == NULL) {
+
216 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate request\n");
+
217 } else {
+
218 req->se = se;
+
219 req->ref_cnt = 1;
+
220 list_init_req(req);
+
221 pthread_mutex_init(&req->lock, NULL);
+
222 }
+
223
+
224 return req;
+
225}
+
226
+
227/*
+
228 * Send data to fuse-kernel using an fd of the fuse device.
+
229 */
+
230static int fuse_write_msg_dev(struct fuse_session *se, struct fuse_chan *ch,
+
231 struct iovec *iov, int count)
+
232{
+
233 ssize_t res;
+
234 int err;
+
235
+
236 if (se->io != NULL)
+
237
+
238 /* se->io->writev is never NULL if se->io is not NULL as
+
239 * specified by fuse_session_custom_io()
+
240 */
+
241 res = se->io->writev(ch ? ch->fd : se->fd, iov, count,
+
242 se->userdata);
+
243 else
+
244 res = writev(ch ? ch->fd : se->fd, iov, count);
+
245
+
246 if (res == -1) {
+
247 /* ENOENT means the operation was interrupted */
+
248 err = errno;
+
249 if (!fuse_session_exited(se) && err != ENOENT)
+
250 perror("fuse: writing device");
+
251 return -err;
+
252 }
+
253
+
254 return 0;
+
255}
+
256
+
257static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch,
+
258 struct iovec *iov, int count, fuse_req_t req)
+
259{
+
260 struct fuse_out_header *out = iov[0].iov_base;
+
261 int err;
+
262 bool is_uring = req && req->flags.is_uring ? true : false;
+
263
+
264 if (!is_uring)
+
265 assert(se != NULL);
+
266 out->len = iov_length(iov, count);
+
267
+
268 if (se->debug) {
+
269 if (out->unique == 0) {
+
270 fuse_log(FUSE_LOG_DEBUG, "NOTIFY: code=%d length=%u\n",
+
271 out->error, out->len);
+
272 } else if (out->error) {
+
273 fuse_log(FUSE_LOG_DEBUG,
+
274 " unique: %llu, error: %i (%s), outsize: %i\n",
+
275 (unsigned long long) out->unique, out->error,
+
276 strerror(-out->error), out->len);
+
277 } else {
+
278 fuse_log(FUSE_LOG_DEBUG,
+
279 " unique: %llu, success, outsize: %i\n",
+
280 (unsigned long long) out->unique, out->len);
+
281 }
+
282 }
+
283
+
284 if (is_uring)
+
285 err = fuse_send_msg_uring(req, iov, count);
+
286 else
+
287 err = fuse_write_msg_dev(se, ch, iov, count);
+
288
+
289 trace_request_reply(out->unique, out->len, out->error, err);
+
290 return err;
+
291}
+
292
+
293int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
294 int count)
+
295{
+
296 struct fuse_out_header out;
+
297
+
298#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
+
299 const char *str = strerrordesc_np(error * -1);
+
300 if ((str == NULL && error != 0) || error > 0) {
+
301#else
+
302 if (error <= -1000 || error > 0) {
+
303#endif
+
304 fuse_log(FUSE_LOG_ERR, "fuse: bad error value: %i\n", error);
+
305 error = -ERANGE;
+
306 }
+
307
+
308 out.unique = req->unique;
+
309 out.error = error;
+
310
+
311 iov[0].iov_base = &out;
+
312 iov[0].iov_len = sizeof(struct fuse_out_header);
+
313
+
314 return fuse_send_msg(req->se, req->ch, iov, count, req);
+
315}
+
316
+
317static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov,
+
318 int count)
+
319{
+
320 int res;
+
321
+
322 res = fuse_send_reply_iov_nofree(req, error, iov, count);
+
323 fuse_free_req(req);
+
324 return res;
+
325}
+
326
+
327static int send_reply(fuse_req_t req, int error, const void *arg,
+
328 size_t argsize)
+
329{
+
330 if (req->flags.is_uring)
+
331 return send_reply_uring(req, error, arg, argsize);
+
332
+
333 struct iovec iov[2];
+
334 int count = 1;
+
335 if (argsize) {
+
336 iov[1].iov_base = (void *) arg;
+
337 iov[1].iov_len = argsize;
+
338 count++;
+
339 }
+
340 return send_reply_iov(req, error, iov, count);
+
341}
+
342
+
343int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
344{
+
345 int res;
+
346 struct iovec *padded_iov;
+
347
+
348 padded_iov = malloc((count + 1) * sizeof(struct iovec));
+
349 if (padded_iov == NULL)
+
350 return fuse_reply_err(req, ENOMEM);
+
351
+
352 memcpy(padded_iov + 1, iov, count * sizeof(struct iovec));
+
353 count++;
+
354
+
355 res = send_reply_iov(req, 0, padded_iov, count);
+
356 free(padded_iov);
+
357
+
358 return res;
+
359}
+
360
+
361
+
362/* `buf` is allowed to be empty so that the proper size may be
+
363 allocated by the caller */
+
364size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
365 const char *name, const struct stat *stbuf, off_t off)
+
366{
+
367 (void)req;
+
368 size_t namelen;
+
369 size_t entlen;
+
370 size_t entlen_padded;
+
371 struct fuse_dirent *dirent;
+
372
+
373 namelen = strlen(name);
+
374 entlen = FUSE_NAME_OFFSET + namelen;
+
375 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
376
+
377 if ((buf == NULL) || (entlen_padded > bufsize))
+
378 return entlen_padded;
+
379
+
380 dirent = (struct fuse_dirent*) buf;
+
381 dirent->ino = stbuf->st_ino;
+
382 dirent->off = off;
+
383 dirent->namelen = namelen;
+
384 dirent->type = (stbuf->st_mode & S_IFMT) >> 12;
+
385 memcpy(dirent->name, name, namelen);
+
386 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
387
+
388 return entlen_padded;
+
389}
+
390
+
391static void convert_statfs(const struct statvfs *stbuf,
+
392 struct fuse_kstatfs *kstatfs)
+
393{
+
394 kstatfs->bsize = stbuf->f_bsize;
+
395 kstatfs->frsize = stbuf->f_frsize;
+
396 kstatfs->blocks = stbuf->f_blocks;
+
397 kstatfs->bfree = stbuf->f_bfree;
+
398 kstatfs->bavail = stbuf->f_bavail;
+
399 kstatfs->files = stbuf->f_files;
+
400 kstatfs->ffree = stbuf->f_ffree;
+
401 kstatfs->namelen = stbuf->f_namemax;
+
402}
+
403
+
404static int send_reply_ok(fuse_req_t req, const void *arg, size_t argsize)
+
405{
+
406 return send_reply(req, 0, arg, argsize);
+
407}
+
408
+
409int fuse_reply_err(fuse_req_t req, int err)
+
410{
+
411 return send_reply(req, -err, NULL, 0);
+
412}
+
413
+ +
415{
+
416 fuse_free_req(req);
+
417}
+
418
+
419static unsigned long calc_timeout_sec(double t)
+
420{
+
421 if (t > (double) ULONG_MAX)
+
422 return ULONG_MAX;
+
423 else if (t < 0.0)
+
424 return 0;
+
425 else
+
426 return (unsigned long) t;
+
427}
+
428
+
429static unsigned int calc_timeout_nsec(double t)
+
430{
+
431 double f = t - (double) calc_timeout_sec(t);
+
432 if (f < 0.0)
+
433 return 0;
+
434 else if (f >= 0.999999999)
+
435 return 999999999;
+
436 else
+
437 return (unsigned int) (f * 1.0e9);
+
438}
+
439
+
440static void fill_entry(struct fuse_entry_out *arg,
+
441 const struct fuse_entry_param *e)
+
442{
+
443 arg->nodeid = e->ino;
+
444 arg->generation = e->generation;
+
445 arg->entry_valid = calc_timeout_sec(e->entry_timeout);
+
446 arg->entry_valid_nsec = calc_timeout_nsec(e->entry_timeout);
+
447 arg->attr_valid = calc_timeout_sec(e->attr_timeout);
+
448 arg->attr_valid_nsec = calc_timeout_nsec(e->attr_timeout);
+
449 convert_stat(&e->attr, &arg->attr);
+
450}
+
451
+
452/* `buf` is allowed to be empty so that the proper size may be
+
453 allocated by the caller */
+
454size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
455 const char *name,
+
456 const struct fuse_entry_param *e, off_t off)
+
457{
+
458 (void)req;
+
459 size_t namelen;
+
460 size_t entlen;
+
461 size_t entlen_padded;
+
462
+
463 namelen = strlen(name);
+
464 entlen = FUSE_NAME_OFFSET_DIRENTPLUS + namelen;
+
465 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
466 if ((buf == NULL) || (entlen_padded > bufsize))
+
467 return entlen_padded;
+
468
+
469 struct fuse_direntplus *dp = (struct fuse_direntplus *) buf;
+
470 memset(&dp->entry_out, 0, sizeof(dp->entry_out));
+
471 fill_entry(&dp->entry_out, e);
+
472
+
473 struct fuse_dirent *dirent = &dp->dirent;
+
474 dirent->ino = e->attr.st_ino;
+
475 dirent->off = off;
+
476 dirent->namelen = namelen;
+
477 dirent->type = (e->attr.st_mode & S_IFMT) >> 12;
+
478 memcpy(dirent->name, name, namelen);
+
479 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
480
+
481 return entlen_padded;
+
482}
+
483
+
484static void fill_open(struct fuse_open_out *arg,
+
485 const struct fuse_file_info *f)
+
486{
+
487 arg->fh = f->fh;
+
488 if (f->backing_id > 0) {
+
489 arg->backing_id = f->backing_id;
+
490 arg->open_flags |= FOPEN_PASSTHROUGH;
+
491 }
+
492 if (f->direct_io)
+
493 arg->open_flags |= FOPEN_DIRECT_IO;
+
494 if (f->keep_cache)
+
495 arg->open_flags |= FOPEN_KEEP_CACHE;
+
496 if (f->cache_readdir)
+
497 arg->open_flags |= FOPEN_CACHE_DIR;
+
498 if (f->nonseekable)
+
499 arg->open_flags |= FOPEN_NONSEEKABLE;
+
500 if (f->noflush)
+
501 arg->open_flags |= FOPEN_NOFLUSH;
+ +
503 arg->open_flags |= FOPEN_PARALLEL_DIRECT_WRITES;
+
504}
+
505
+
506int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
507{
+
508 struct fuse_entry_out arg;
+
509 size_t size = req->se->conn.proto_minor < 9 ?
+
510 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg);
+
511
+
512 /* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant
+
513 negative entry */
+
514 if (!e->ino && req->se->conn.proto_minor < 4)
+
515 return fuse_reply_err(req, ENOENT);
+
516
+
517 memset(&arg, 0, sizeof(arg));
+
518 fill_entry(&arg, e);
+
519 return send_reply_ok(req, &arg, size);
+
520}
+
521
+
522int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+
523 const struct fuse_file_info *f)
+
524{
+
525 alignas(uint64_t) char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)];
+
526 size_t entrysize = req->se->conn.proto_minor < 9 ?
+
527 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(struct fuse_entry_out);
+
528 struct fuse_entry_out *earg = (struct fuse_entry_out *) buf;
+
529 struct fuse_open_out *oarg = (struct fuse_open_out *) (buf + entrysize);
+
530
+
531 memset(buf, 0, sizeof(buf));
+
532 fill_entry(earg, e);
+
533 fill_open(oarg, f);
+
534 return send_reply_ok(req, buf,
+
535 entrysize + sizeof(struct fuse_open_out));
+
536}
+
537
+
538int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
539 double attr_timeout)
+
540{
+
541 struct fuse_attr_out arg;
+
542 size_t size = req->se->conn.proto_minor < 9 ?
+
543 FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg);
+
544
+
545 memset(&arg, 0, sizeof(arg));
+
546 arg.attr_valid = calc_timeout_sec(attr_timeout);
+
547 arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
+
548 convert_stat(attr, &arg.attr);
+
549
+
550 return send_reply_ok(req, &arg, size);
+
551}
+
552
+
553int fuse_reply_readlink(fuse_req_t req, const char *linkname)
+
554{
+
555 return send_reply_ok(req, linkname, strlen(linkname));
+
556}
+
557
+
558int fuse_passthrough_open(fuse_req_t req, int fd)
+
559{
+
560 struct fuse_backing_map map = { .fd = fd };
+
561 int ret;
+
562
+
563 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_OPEN, &map);
+
564 if (ret <= 0) {
+
565 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_open: %s\n", strerror(errno));
+
566 return 0;
+
567 }
+
568
+
569 return ret;
+
570}
+
571
+
572int fuse_passthrough_close(fuse_req_t req, int backing_id)
+
573{
+
574 int ret;
+
575
+
576 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_CLOSE, &backing_id);
+
577 if (ret < 0)
+
578 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_close: %s\n", strerror(errno));
+
579
+
580 return ret;
+
581}
+
582
+
583int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *f)
+
584{
+
585 struct fuse_open_out arg;
+
586
+
587 memset(&arg, 0, sizeof(arg));
+
588 fill_open(&arg, f);
+
589 return send_reply_ok(req, &arg, sizeof(arg));
+
590}
+
591
+
592static int do_fuse_reply_write(fuse_req_t req, size_t count)
+
593{
+
594 struct fuse_write_out arg;
+
595
+
596 memset(&arg, 0, sizeof(arg));
+
597 arg.size = count;
+
598
+
599 return send_reply_ok(req, &arg, sizeof(arg));
+
600}
+
601
+
602static int do_fuse_reply_copy(fuse_req_t req, size_t count)
+
603{
+
604 struct fuse_copy_file_range_out arg;
+
605
+
606 memset(&arg, 0, sizeof(arg));
+
607 arg.bytes_copied = count;
+
608
+
609 return send_reply_ok(req, &arg, sizeof(arg));
+
610}
+
611
+
612int fuse_reply_write(fuse_req_t req, size_t count)
+
613{
+
614 /*
+
615 * This function is also used by FUSE_COPY_FILE_RANGE and its 64-bit
+
616 * variant.
+
617 */
+
618 if (req->flags.is_copy_file_range_64)
+
619 return do_fuse_reply_copy(req, count);
+
620 else
+
621 return do_fuse_reply_write(req, count);
+
622}
+
623
+
624int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
625{
+
626 return send_reply_ok(req, buf, size);
+
627}
+
628
+
629static int fuse_send_data_iov_fallback(struct fuse_session *se,
+
630 struct fuse_chan *ch,
+
631 struct iovec *iov, int iov_count,
+
632 struct fuse_bufvec *buf,
+
633 size_t len, fuse_req_t req)
+
634{
+
635 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
636 void *mbuf;
+
637 int res;
+
638
+
639 /* Optimize common case */
+
640 if (buf->count == 1 && buf->idx == 0 && buf->off == 0 &&
+
641 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
642 /* FIXME: also avoid memory copy if there are multiple buffers
+
643 but none of them contain an fd */
+
644
+
645 iov[iov_count].iov_base = buf->buf[0].mem;
+
646 iov[iov_count].iov_len = len;
+
647 iov_count++;
+
648 return fuse_send_msg(se, ch, iov, iov_count, req);
+
649 }
+
650
+
651 res = posix_memalign(&mbuf, pagesize, len);
+
652 if (res != 0)
+
653 return res;
+
654
+
655 mem_buf.buf[0].mem = mbuf;
+
656 res = fuse_buf_copy(&mem_buf, buf, 0);
+
657 if (res < 0) {
+
658 free(mbuf);
+
659 return -res;
+
660 }
+
661 len = res;
+
662
+
663 iov[iov_count].iov_base = mbuf;
+
664 iov[iov_count].iov_len = len;
+
665 iov_count++;
+
666 res = fuse_send_msg(se, ch, iov, iov_count, req);
+
667 free(mbuf);
+
668
+
669 return res;
+
670}
+
671
+
672struct fuse_ll_pipe {
+
673 size_t size;
+
674 int can_grow;
+
675 int pipe[2];
+
676};
+
677
+
678static void fuse_ll_pipe_free(struct fuse_ll_pipe *llp)
+
679{
+
680 close(llp->pipe[0]);
+
681 close(llp->pipe[1]);
+
682 free(llp);
+
683}
+
684
+
685#ifdef HAVE_SPLICE
+
686#if !defined(HAVE_PIPE2) || !defined(O_CLOEXEC)
+
687static int fuse_pipe(int fds[2])
+
688{
+
689 int rv = pipe(fds);
+
690
+
691 if (rv == -1)
+
692 return rv;
+
693
+
694 if (fcntl(fds[0], F_SETFL, O_NONBLOCK) == -1 ||
+
695 fcntl(fds[1], F_SETFL, O_NONBLOCK) == -1 ||
+
696 fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 ||
+
697 fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) {
+
698 close(fds[0]);
+
699 close(fds[1]);
+
700 rv = -1;
+
701 }
+
702 return rv;
+
703}
+
704#else
+
705static int fuse_pipe(int fds[2])
+
706{
+
707 return pipe2(fds, O_CLOEXEC | O_NONBLOCK);
+
708}
+
709#endif
+
710
+
711static struct fuse_ll_pipe *fuse_ll_get_pipe(struct fuse_session *se)
+
712{
+
713 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
714 if (llp == NULL) {
+
715 int res;
+
716
+
717 llp = malloc(sizeof(struct fuse_ll_pipe));
+
718 if (llp == NULL)
+
719 return NULL;
+
720
+
721 res = fuse_pipe(llp->pipe);
+
722 if (res == -1) {
+
723 free(llp);
+
724 return NULL;
+
725 }
+
726
+
727 /*
+
728 *the default size is 16 pages on linux
+
729 */
+
730 llp->size = pagesize * 16;
+
731 llp->can_grow = 1;
+
732
+
733 pthread_setspecific(se->pipe_key, llp);
+
734 }
+
735
+
736 return llp;
+
737}
+
738#endif
+
739
+
740static void fuse_ll_clear_pipe(struct fuse_session *se)
+
741{
+
742 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
743 if (llp) {
+
744 pthread_setspecific(se->pipe_key, NULL);
+
745 fuse_ll_pipe_free(llp);
+
746 }
+
747}
+
748
+
749#if defined(HAVE_SPLICE) && defined(HAVE_VMSPLICE)
+
750static int read_back(int fd, char *buf, size_t len)
+
751{
+
752 int res;
+
753
+
754 res = read(fd, buf, len);
+
755 if (res == -1) {
+
756 fuse_log(FUSE_LOG_ERR,
+
757 "fuse: internal error: failed to read back from pipe: %s\n",
+
758 strerror(errno));
+
759 return -EIO;
+
760 }
+
761 if (res != len) {
+
762 fuse_log(FUSE_LOG_ERR,
+
763 "fuse: internal error: short read back from pipe: %i from %zd\n",
+
764 res, len);
+
765 return -EIO;
+
766 }
+
767 return 0;
+
768}
+
769
+
770static int grow_pipe_to_max(int pipefd)
+
771{
+
772 int res;
+
773 long max;
+
774 long maxfd;
+
775 char buf[32];
+
776
+
777 maxfd = open("/proc/sys/fs/pipe-max-size", O_RDONLY);
+
778 if (maxfd < 0)
+
779 return -errno;
+
780
+
781 res = read(maxfd, buf, sizeof(buf) - 1);
+
782 if (res < 0) {
+
783 int saved_errno;
+
784
+
785 saved_errno = errno;
+
786 close(maxfd);
+
787 return -saved_errno;
+
788 }
+
789 close(maxfd);
+
790 buf[res] = '\0';
+
791
+
792 res = libfuse_strtol(buf, &max);
+
793 if (res)
+
794 return res;
+
795 res = fcntl(pipefd, F_SETPIPE_SZ, max);
+
796 if (res < 0)
+
797 return -errno;
+
798 return max;
+
799}
+
800
+
801static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
802 struct iovec *iov, int iov_count,
+
803 struct fuse_bufvec *buf, unsigned int flags,
+
804 fuse_req_t req)
+
805{
+
806 int res;
+
807 size_t len = fuse_buf_size(buf);
+
808 struct fuse_out_header *out = iov[0].iov_base;
+
809 struct fuse_ll_pipe *llp;
+
810 int splice_flags;
+
811 size_t pipesize;
+
812 size_t total_buf_size;
+
813 size_t idx;
+
814 size_t headerlen;
+
815 struct fuse_bufvec pipe_buf = FUSE_BUFVEC_INIT(len);
+
816
+
817 if (se->broken_splice_nonblock)
+
818 goto fallback;
+
819
+
820 if (flags & FUSE_BUF_NO_SPLICE)
+
821 goto fallback;
+
822
+
823 total_buf_size = 0;
+
824 for (idx = buf->idx; idx < buf->count; idx++) {
+
825 total_buf_size += buf->buf[idx].size;
+
826 if (idx == buf->idx)
+
827 total_buf_size -= buf->off;
+
828 }
+
829 if (total_buf_size < 2 * pagesize)
+
830 goto fallback;
+
831
+
832 if (se->conn.proto_minor < 14 ||
+
833 !(se->conn.want_ext & FUSE_CAP_SPLICE_WRITE))
+
834 goto fallback;
+
835
+
836 llp = fuse_ll_get_pipe(se);
+
837 if (llp == NULL)
+
838 goto fallback;
+
839
+
840
+
841 headerlen = iov_length(iov, iov_count);
+
842
+
843 out->len = headerlen + len;
+
844
+
845 /*
+
846 * Heuristic for the required pipe size, does not work if the
+
847 * source contains less than page size fragments
+
848 */
+
849 pipesize = pagesize * (iov_count + buf->count + 1) + out->len;
+
850
+
851 if (llp->size < pipesize) {
+
852 if (llp->can_grow) {
+
853 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, pipesize);
+
854 if (res == -1) {
+
855 res = grow_pipe_to_max(llp->pipe[0]);
+
856 if (res > 0)
+
857 llp->size = res;
+
858 llp->can_grow = 0;
+
859 goto fallback;
+
860 }
+
861 llp->size = res;
+
862 }
+
863 if (llp->size < pipesize)
+
864 goto fallback;
+
865 }
+
866
+
867
+
868 res = vmsplice(llp->pipe[1], iov, iov_count, SPLICE_F_NONBLOCK);
+
869 if (res == -1)
+
870 goto fallback;
+
871
+
872 if (res != headerlen) {
+
873 res = -EIO;
+
874 fuse_log(FUSE_LOG_ERR, "fuse: short vmsplice to pipe: %u/%zu\n", res,
+
875 headerlen);
+
876 goto clear_pipe;
+
877 }
+
878
+
879 pipe_buf.buf[0].flags = FUSE_BUF_IS_FD;
+
880 pipe_buf.buf[0].fd = llp->pipe[1];
+
881
+
882 res = fuse_buf_copy(&pipe_buf, buf,
+ +
884 if (res < 0) {
+
885 if (res == -EAGAIN || res == -EINVAL) {
+
886 /*
+
887 * Should only get EAGAIN on kernels with
+
888 * broken SPLICE_F_NONBLOCK support (<=
+
889 * 2.6.35) where this error or a short read is
+
890 * returned even if the pipe itself is not
+
891 * full
+
892 *
+
893 * EINVAL might mean that splice can't handle
+
894 * this combination of input and output.
+
895 */
+
896 if (res == -EAGAIN)
+
897 se->broken_splice_nonblock = 1;
+
898
+
899 pthread_setspecific(se->pipe_key, NULL);
+
900 fuse_ll_pipe_free(llp);
+
901 goto fallback;
+
902 }
+
903 res = -res;
+
904 goto clear_pipe;
+
905 }
+
906
+
907 if (res != 0 && res < len) {
+
908 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
909 void *mbuf;
+
910 size_t now_len = res;
+
911 /*
+
912 * For regular files a short count is either
+
913 * 1) due to EOF, or
+
914 * 2) because of broken SPLICE_F_NONBLOCK (see above)
+
915 *
+
916 * For other inputs it's possible that we overflowed
+
917 * the pipe because of small buffer fragments.
+
918 */
+
919
+
920 res = posix_memalign(&mbuf, pagesize, len);
+
921 if (res != 0)
+
922 goto clear_pipe;
+
923
+
924 mem_buf.buf[0].mem = mbuf;
+
925 mem_buf.off = now_len;
+
926 res = fuse_buf_copy(&mem_buf, buf, 0);
+
927 if (res > 0) {
+
928 char *tmpbuf;
+
929 size_t extra_len = res;
+
930 /*
+
931 * Trickiest case: got more data. Need to get
+
932 * back the data from the pipe and then fall
+
933 * back to regular write.
+
934 */
+
935 tmpbuf = malloc(headerlen);
+
936 if (tmpbuf == NULL) {
+
937 free(mbuf);
+
938 res = ENOMEM;
+
939 goto clear_pipe;
+
940 }
+
941 res = read_back(llp->pipe[0], tmpbuf, headerlen);
+
942 free(tmpbuf);
+
943 if (res != 0) {
+
944 free(mbuf);
+
945 goto clear_pipe;
+
946 }
+
947 res = read_back(llp->pipe[0], mbuf, now_len);
+
948 if (res != 0) {
+
949 free(mbuf);
+
950 goto clear_pipe;
+
951 }
+
952 len = now_len + extra_len;
+
953 iov[iov_count].iov_base = mbuf;
+
954 iov[iov_count].iov_len = len;
+
955 iov_count++;
+
956 res = fuse_send_msg(se, ch, iov, iov_count, req);
+
957 free(mbuf);
+
958 return res;
+
959 }
+
960 free(mbuf);
+
961 res = now_len;
+
962 }
+
963 len = res;
+
964 out->len = headerlen + len;
+
965
+
966 if (se->debug) {
+
967 fuse_log(FUSE_LOG_DEBUG,
+
968 " unique: %llu, success, outsize: %i (splice)\n",
+
969 (unsigned long long) out->unique, out->len);
+
970 }
+
971
+
972 splice_flags = 0;
+
973 if ((flags & FUSE_BUF_SPLICE_MOVE) &&
+
974 (se->conn.want_ext & FUSE_CAP_SPLICE_MOVE))
+
975 splice_flags |= SPLICE_F_MOVE;
+
976
+
977 if (se->io != NULL && se->io->splice_send != NULL) {
+
978 res = se->io->splice_send(llp->pipe[0], NULL,
+
979 ch ? ch->fd : se->fd, NULL, out->len,
+
980 splice_flags, se->userdata);
+
981 } else {
+
982 res = splice(llp->pipe[0], NULL, ch ? ch->fd : se->fd, NULL,
+
983 out->len, splice_flags);
+
984 }
+
985 if (res == -1) {
+
986 res = -errno;
+
987 perror("fuse: splice from pipe");
+
988 goto clear_pipe;
+
989 }
+
990 if (res != out->len) {
+
991 res = -EIO;
+
992 fuse_log(FUSE_LOG_ERR, "fuse: short splice from pipe: %u/%u\n",
+
993 res, out->len);
+
994 goto clear_pipe;
+
995 }
+
996 return 0;
+
997
+
998clear_pipe:
+
999 fuse_ll_clear_pipe(se);
+
1000 return res;
+
1001
+
1002fallback:
+
1003 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len, req);
+
1004}
+
1005#else
+
1006static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
1007 struct iovec *iov, int iov_count,
+
1008 struct fuse_bufvec *req_data, unsigned int flags,
+
1009 fuse_req_t req)
+
1010{
+
1011 size_t len = fuse_buf_size(req_data);
+
1012 (void) flags;
+
1013
+
1014 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, req_data, len, req);
+
1015}
+
1016#endif
+
1017
+
1018int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+
1019 enum fuse_buf_copy_flags flags)
+
1020{
+
1021 struct iovec iov[2];
+
1022 struct fuse_out_header out;
+
1023 int res;
+
1024
+
1025 if (req->flags.is_uring)
+
1026 return fuse_reply_data_uring(req, bufv, flags);
+
1027
+
1028 iov[0].iov_base = &out;
+
1029 iov[0].iov_len = sizeof(struct fuse_out_header);
+
1030
+
1031 out.unique = req->unique;
+
1032 out.error = 0;
+
1033
+
1034 res = fuse_send_data_iov(req->se, req->ch, iov, 1, bufv, flags, req);
+
1035 if (res <= 0) {
+
1036 fuse_free_req(req);
+
1037 return res;
+
1038 } else {
+
1039 return fuse_reply_err(req, res);
+
1040 }
+
1041}
+
1042
+
1043int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
1044{
+
1045 struct fuse_statfs_out arg;
+
1046 size_t size = req->se->conn.proto_minor < 4 ?
+
1047 FUSE_COMPAT_STATFS_SIZE : sizeof(arg);
+
1048
+
1049 memset(&arg, 0, sizeof(arg));
+
1050 convert_statfs(stbuf, &arg.st);
+
1051
+
1052 return send_reply_ok(req, &arg, size);
+
1053}
+
1054
+
1055int fuse_reply_xattr(fuse_req_t req, size_t count)
+
1056{
+
1057 struct fuse_getxattr_out arg;
+
1058
+
1059 memset(&arg, 0, sizeof(arg));
+
1060 arg.size = count;
+
1061
+
1062 return send_reply_ok(req, &arg, sizeof(arg));
+
1063}
+
1064
+
1065int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
1066{
+
1067 struct fuse_lk_out arg;
+
1068
+
1069 memset(&arg, 0, sizeof(arg));
+
1070 arg.lk.type = lock->l_type;
+
1071 if (lock->l_type != F_UNLCK) {
+
1072 arg.lk.start = lock->l_start;
+
1073 if (lock->l_len == 0)
+
1074 arg.lk.end = OFFSET_MAX;
+
1075 else
+
1076 arg.lk.end = lock->l_start + lock->l_len - 1;
+
1077 }
+
1078 arg.lk.pid = lock->l_pid;
+
1079 return send_reply_ok(req, &arg, sizeof(arg));
+
1080}
+
1081
+
1082int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
1083{
+
1084 struct fuse_bmap_out arg;
+
1085
+
1086 memset(&arg, 0, sizeof(arg));
+
1087 arg.block = idx;
+
1088
+
1089 return send_reply_ok(req, &arg, sizeof(arg));
+
1090}
+
1091
+
1092static struct fuse_ioctl_iovec *fuse_ioctl_iovec_copy(const struct iovec *iov,
+
1093 size_t count)
+
1094{
+
1095 struct fuse_ioctl_iovec *fiov;
+
1096 size_t i;
+
1097
+
1098 fiov = malloc(sizeof(fiov[0]) * count);
+
1099 if (!fiov)
+
1100 return NULL;
+
1101
+
1102 for (i = 0; i < count; i++) {
+
1103 fiov[i].base = (uintptr_t) iov[i].iov_base;
+
1104 fiov[i].len = iov[i].iov_len;
+
1105 }
+
1106
+
1107 return fiov;
+
1108}
+
1109
+ +
1111 const struct iovec *in_iov, size_t in_count,
+
1112 const struct iovec *out_iov, size_t out_count)
+
1113{
+
1114 struct fuse_ioctl_out arg;
+
1115 struct fuse_ioctl_iovec *in_fiov = NULL;
+
1116 struct fuse_ioctl_iovec *out_fiov = NULL;
+
1117 struct iovec iov[4];
+
1118 size_t count = 1;
+
1119 int res;
+
1120
+
1121 memset(&arg, 0, sizeof(arg));
+
1122 arg.flags |= FUSE_IOCTL_RETRY;
+
1123 arg.in_iovs = in_count;
+
1124 arg.out_iovs = out_count;
+
1125 iov[count].iov_base = &arg;
+
1126 iov[count].iov_len = sizeof(arg);
+
1127 count++;
+
1128
+
1129 if (req->se->conn.proto_minor < 16) {
+
1130 if (in_count) {
+
1131 iov[count].iov_base = (void *)in_iov;
+
1132 iov[count].iov_len = sizeof(in_iov[0]) * in_count;
+
1133 count++;
+
1134 }
+
1135
+
1136 if (out_count) {
+
1137 iov[count].iov_base = (void *)out_iov;
+
1138 iov[count].iov_len = sizeof(out_iov[0]) * out_count;
+
1139 count++;
+
1140 }
+
1141 } else {
+
1142 /* Can't handle non-compat 64bit ioctls on 32bit */
+
1143 if (sizeof(void *) == 4 && req->flags.ioctl_64bit) {
+
1144 res = fuse_reply_err(req, EINVAL);
+
1145 goto out;
+
1146 }
+
1147
+
1148 if (in_count) {
+
1149 in_fiov = fuse_ioctl_iovec_copy(in_iov, in_count);
+
1150 if (!in_fiov)
+
1151 goto enomem;
+
1152
+
1153 iov[count].iov_base = (void *)in_fiov;
+
1154 iov[count].iov_len = sizeof(in_fiov[0]) * in_count;
+
1155 count++;
+
1156 }
+
1157 if (out_count) {
+
1158 out_fiov = fuse_ioctl_iovec_copy(out_iov, out_count);
+
1159 if (!out_fiov)
+
1160 goto enomem;
+
1161
+
1162 iov[count].iov_base = (void *)out_fiov;
+
1163 iov[count].iov_len = sizeof(out_fiov[0]) * out_count;
+
1164 count++;
+
1165 }
+
1166 }
+
1167
+
1168 res = send_reply_iov(req, 0, iov, count);
+
1169out:
+
1170 free(in_fiov);
+
1171 free(out_fiov);
+
1172
+
1173 return res;
+
1174
+
1175enomem:
+
1176 res = fuse_reply_err(req, ENOMEM);
+
1177 goto out;
+
1178}
+
1179
+
1180int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
1181{
+
1182 struct fuse_ioctl_out arg;
+
1183 struct iovec iov[3];
+
1184 size_t count = 1;
+
1185
+
1186 memset(&arg, 0, sizeof(arg));
+
1187 arg.result = result;
+
1188 iov[count].iov_base = &arg;
+
1189 iov[count].iov_len = sizeof(arg);
+
1190 count++;
+
1191
+
1192 if (size) {
+
1193 iov[count].iov_base = (char *) buf;
+
1194 iov[count].iov_len = size;
+
1195 count++;
+
1196 }
+
1197
+
1198 return send_reply_iov(req, 0, iov, count);
+
1199}
+
1200
+
1201int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1202 int count)
+
1203{
+
1204 struct iovec *padded_iov;
+
1205 struct fuse_ioctl_out arg;
+
1206 int res;
+
1207
+
1208 padded_iov = malloc((count + 2) * sizeof(struct iovec));
+
1209 if (padded_iov == NULL)
+
1210 return fuse_reply_err(req, ENOMEM);
+
1211
+
1212 memset(&arg, 0, sizeof(arg));
+
1213 arg.result = result;
+
1214 padded_iov[1].iov_base = &arg;
+
1215 padded_iov[1].iov_len = sizeof(arg);
+
1216
+
1217 memcpy(&padded_iov[2], iov, count * sizeof(struct iovec));
+
1218
+
1219 res = send_reply_iov(req, 0, padded_iov, count + 2);
+
1220 free(padded_iov);
+
1221
+
1222 return res;
+
1223}
+
1224
+
1225int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
1226{
+
1227 struct fuse_poll_out arg;
+
1228
+
1229 memset(&arg, 0, sizeof(arg));
+
1230 arg.revents = revents;
+
1231
+
1232 return send_reply_ok(req, &arg, sizeof(arg));
+
1233}
+
1234
+
1235int fuse_reply_lseek(fuse_req_t req, off_t off)
+
1236{
+
1237 struct fuse_lseek_out arg;
+
1238
+
1239 memset(&arg, 0, sizeof(arg));
+
1240 arg.offset = off;
+
1241
+
1242 return send_reply_ok(req, &arg, sizeof(arg));
+
1243}
+
1244
+
1245#ifdef HAVE_STATX
+
1246int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx,
+
1247 double attr_timeout)
+
1248{
+
1249 struct fuse_statx_out arg;
+
1250
+
1251 memset(&arg, 0, sizeof(arg));
+
1252 arg.flags = flags;
+
1253 arg.attr_valid = calc_timeout_sec(attr_timeout);
+
1254 arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
+
1255 memcpy(&arg.stat, statx, sizeof(arg.stat));
+
1256
+
1257 return send_reply_ok(req, &arg, sizeof(arg));
+
1258}
+
1259#else
+
1260int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx,
+
1261 double attr_timeout)
+
1262{
+
1263 (void)req;
+
1264 (void)flags;
+
1265 (void)statx;
+
1266 (void)attr_timeout;
+
1267
+
1268 return -ENOSYS;
+
1269}
+
1270#endif
+
1271
+
1272static void _do_lookup(fuse_req_t req, const fuse_ino_t nodeid,
+
1273 const void *op_in, const void *in_payload)
+
1274{
+
1275 (void)op_in;
+
1276
+
1277 char *name = (char *)in_payload;
+
1278
+
1279 if (req->se->op.lookup)
+
1280 req->se->op.lookup(req, nodeid, name);
+
1281 else
+
1282 fuse_reply_err(req, ENOSYS);
+
1283}
+
1284
+
1285static void do_lookup(fuse_req_t req, const fuse_ino_t nodeid,
+
1286 const void *inarg)
+
1287{
+
1288 _do_lookup(req, nodeid, NULL, inarg);
+
1289}
+
1290
+
1291static void _do_forget(fuse_req_t req, const fuse_ino_t nodeid,
+
1292 const void *op_in, const void *in_payload)
+
1293{
+
1294 (void)in_payload;
+
1295
+
1296 struct fuse_forget_in *arg = (struct fuse_forget_in *)op_in;
+
1297
+
1298 if (req->se->op.forget)
+
1299 req->se->op.forget(req, nodeid, arg->nlookup);
+
1300 else
+
1301 fuse_reply_none(req);
+
1302}
+
1303
+
1304static void do_forget(fuse_req_t req, const fuse_ino_t nodeid,
+
1305 const void *inarg)
+
1306{
+
1307 _do_forget(req, nodeid, inarg, NULL);
+
1308}
+
1309
+
1310static void _do_batch_forget(fuse_req_t req, const fuse_ino_t nodeid,
+
1311 const void *op_in, const void *in_payload)
+
1312{
+
1313 (void)nodeid;
+
1314 unsigned int i;
+
1315
+
1316 const struct fuse_batch_forget_in *arg = op_in;
+
1317 const struct fuse_forget_one *forgets = in_payload;
+
1318
+
1319 if (req->se->op.forget_multi) {
+
1320 req->se->op.forget_multi(req, arg->count,
+
1321 (struct fuse_forget_data *)in_payload);
+
1322 } else if (req->se->op.forget) {
+
1323 for (i = 0; i < arg->count; i++) {
+
1324 const struct fuse_forget_one *forget = &forgets[i];
+
1325 struct fuse_req *dummy_req;
+
1326
+
1327 dummy_req = fuse_ll_alloc_req(req->se);
+
1328 if (dummy_req == NULL)
+
1329 break;
+
1330
+
1331 dummy_req->unique = req->unique;
+
1332 dummy_req->ctx = req->ctx;
+
1333 dummy_req->ch = NULL;
+
1334
+
1335 req->se->op.forget(dummy_req, forget->nodeid,
+
1336 forget->nlookup);
+
1337 }
+
1338 fuse_reply_none(req);
+
1339 } else {
+
1340 fuse_reply_none(req);
+
1341 }
+
1342}
+
1343
+
1344static void do_batch_forget(fuse_req_t req, const fuse_ino_t nodeid,
+
1345 const void *inarg)
+
1346{
+
1347 struct fuse_batch_forget_in *arg = (void *)inarg;
+
1348 struct fuse_forget_one *param = (void *)PARAM(arg);
+
1349
+
1350 _do_batch_forget(req, nodeid, inarg, param);
+
1351}
+
1352
+
1353static void _do_getattr(fuse_req_t req, const fuse_ino_t nodeid,
+
1354 const void *op_in, const void *in_payload)
+
1355{
+
1356 struct fuse_getattr_in *arg = (struct fuse_getattr_in *)op_in;
+
1357 (void)in_payload;
+
1358
+
1359 struct fuse_file_info *fip = NULL;
+
1360 struct fuse_file_info fi;
+
1361
+
1362 if (req->se->conn.proto_minor >= 9) {
+
1363 if (arg->getattr_flags & FUSE_GETATTR_FH) {
+
1364 memset(&fi, 0, sizeof(fi));
+
1365 fi.fh = arg->fh;
+
1366 fip = &fi;
+
1367 }
+
1368 }
+
1369
+
1370 if (req->se->op.getattr)
+
1371 req->se->op.getattr(req, nodeid, fip);
+
1372 else
+
1373 fuse_reply_err(req, ENOSYS);
+
1374}
+
1375
+
1376static void do_getattr(fuse_req_t req, const fuse_ino_t nodeid,
+
1377 const void *inarg)
+
1378{
+
1379 _do_getattr(req, nodeid, inarg, NULL);
+
1380}
+
1381
+
1382static void _do_setattr(fuse_req_t req, const fuse_ino_t nodeid,
+
1383 const void *op_in, const void *in_payload)
+
1384{
+
1385 (void)in_payload;
+
1386 const struct fuse_setattr_in *arg = op_in;
+
1387 uint32_t valid = arg->valid;
+
1388
+
1389 if (req->se->op.setattr) {
+
1390 struct fuse_file_info *fi = NULL;
+
1391 struct fuse_file_info fi_store;
+
1392 struct stat stbuf;
+
1393 memset(&stbuf, 0, sizeof(stbuf));
+
1394 convert_attr(arg, &stbuf);
+
1395 if (arg->valid & FATTR_FH) {
+
1396 valid &= ~FATTR_FH;
+
1397 memset(&fi_store, 0, sizeof(fi_store));
+
1398 fi = &fi_store;
+
1399 fi->fh = arg->fh;
+
1400 }
+
1401 valid &= FUSE_SET_ATTR_MODE | FUSE_SET_ATTR_UID |
+
1402 FUSE_SET_ATTR_GID | FUSE_SET_ATTR_SIZE |
+
1403 FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME |
+
1404 FUSE_SET_ATTR_KILL_SUID | FUSE_SET_ATTR_KILL_SGID |
+
1405 FUSE_SET_ATTR_ATIME_NOW | FUSE_SET_ATTR_MTIME_NOW |
+
1406 FUSE_SET_ATTR_CTIME;
+
1407
+
1408 req->se->op.setattr(req, nodeid, &stbuf, valid, fi);
+
1409 } else
+
1410 fuse_reply_err(req, ENOSYS);
+
1411}
+
1412
+
1413static void do_setattr(fuse_req_t req, const fuse_ino_t nodeid,
+
1414 const void *inarg)
+
1415{
+
1416 _do_setattr(req, nodeid, inarg, NULL);
+
1417}
+
1418
+
1419static void _do_access(fuse_req_t req, const fuse_ino_t nodeid,
+
1420 const void *op_in, const void *in_payload)
+
1421{
+
1422 (void)in_payload;
+
1423 const struct fuse_access_in *arg = op_in;
+
1424
+
1425 if (req->se->op.access)
+
1426 req->se->op.access(req, nodeid, arg->mask);
+
1427 else
+
1428 fuse_reply_err(req, ENOSYS);
+
1429}
+
1430
+
1431static void do_access(fuse_req_t req, const fuse_ino_t nodeid,
+
1432 const void *inarg)
+
1433{
+
1434 _do_access(req, nodeid, inarg, NULL);
+
1435}
+
1436
+
1437static void _do_readlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1438 const void *op_in, const void *in_payload)
+
1439{
+
1440 (void)op_in;
+
1441 (void)in_payload;
+
1442
+
1443 if (req->se->op.readlink)
+
1444 req->se->op.readlink(req, nodeid);
+
1445 else
+
1446 fuse_reply_err(req, ENOSYS);
+
1447}
+
1448
+
1449static void do_readlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1450 const void *inarg)
+
1451{
+
1452 _do_readlink(req, nodeid, inarg, NULL);
+
1453}
+
1454
+
1455static void _do_mknod(fuse_req_t req, const fuse_ino_t nodeid,
+
1456 const void *op_in, const void *in_payload)
+
1457{
+
1458 const struct fuse_mknod_in *arg = (struct fuse_mknod_in *)op_in;
+
1459 const char *name = in_payload;
+
1460
+
1461 if (req->se->conn.proto_minor >= 12)
+
1462 req->ctx.umask = arg->umask;
+
1463
+
1464 if (req->se->op.mknod)
+
1465 req->se->op.mknod(req, nodeid, name, arg->mode, arg->rdev);
+
1466 else
+
1467 fuse_reply_err(req, ENOSYS);
+
1468}
+
1469
+
1470static void do_mknod(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1471{
+
1472 struct fuse_mknod_in *arg = (struct fuse_mknod_in *)inarg;
+
1473 char *name = PARAM(arg);
+
1474
+
1475 if (req->se->conn.proto_minor < 12)
+
1476 name = (char *)inarg + FUSE_COMPAT_MKNOD_IN_SIZE;
+
1477
+
1478 _do_mknod(req, nodeid, inarg, name);
+
1479}
+
1480
+
1481static void _do_mkdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1482 const void *op_in, const void *in_payload)
+
1483{
+
1484 const char *name = in_payload;
+
1485 const struct fuse_mkdir_in *arg = op_in;
+
1486
+
1487 if (req->se->conn.proto_minor >= 12)
+
1488 req->ctx.umask = arg->umask;
+
1489
+
1490 if (req->se->op.mkdir)
+
1491 req->se->op.mkdir(req, nodeid, name, arg->mode);
+
1492 else
+
1493 fuse_reply_err(req, ENOSYS);
+
1494}
+
1495
+
1496static void do_mkdir(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1497{
+
1498 const struct fuse_mkdir_in *arg = inarg;
+
1499 const char *name = PARAM(arg);
+
1500
+
1501 _do_mkdir(req, nodeid, inarg, name);
+
1502}
+
1503
+
1504static void _do_unlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1505 const void *op_in, const void *in_payload)
+
1506{
+
1507 (void)op_in;
+
1508 const char *name = in_payload;
+
1509
+
1510 if (req->se->op.unlink)
+
1511 req->se->op.unlink(req, nodeid, name);
+
1512 else
+
1513 fuse_reply_err(req, ENOSYS);
+
1514}
+
1515
+
1516static void do_unlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1517 const void *inarg)
+
1518{
+
1519 _do_unlink(req, nodeid, NULL, inarg);
+
1520}
+
1521
+
1522static void _do_rmdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1523 const void *op_in, const void *in_payload)
+
1524{
+
1525 (void)op_in;
+
1526 const char *name = in_payload;
+
1527
+
1528 if (req->se->op.rmdir)
+
1529 req->se->op.rmdir(req, nodeid, name);
+
1530 else
+
1531 fuse_reply_err(req, ENOSYS);
+
1532}
+
1533
+
1534static void do_rmdir(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1535{
+
1536 _do_rmdir(req, nodeid, NULL, inarg);
+
1537}
+
1538
+
1539static void _do_symlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1540 const void *op_in, const void *in_payload)
+
1541{
+
1542 (void)op_in;
+
1543 const char *name = (char *)in_payload;
+
1544 const char *linkname = name + strlen(name) + 1;
+
1545
+
1546 if (req->se->op.symlink)
+
1547 req->se->op.symlink(req, linkname, nodeid, name);
+
1548 else
+
1549 fuse_reply_err(req, ENOSYS);
+
1550}
+
1551
+
1552static void do_symlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1553 const void *inarg)
+
1554{
+
1555 _do_symlink(req, nodeid, NULL, inarg);
+
1556}
+
1557
+
1558static void _do_rename(fuse_req_t req, const fuse_ino_t nodeid,
+
1559 const void *op_in, const void *in_payload)
+
1560{
+
1561 const struct fuse_rename_in *arg = (struct fuse_rename_in *)op_in;
+
1562 const char *oldname = in_payload;
+
1563 const char *newname = oldname + strlen(oldname) + 1;
+
1564
+
1565 if (req->se->op.rename)
+
1566 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1567 0);
+
1568 else
+
1569 fuse_reply_err(req, ENOSYS);
+
1570}
+
1571
+
1572static void do_rename(fuse_req_t req, const fuse_ino_t nodeid,
+
1573 const void *inarg)
+
1574{
+
1575 const struct fuse_rename_in *arg = inarg;
+
1576 const void *payload = PARAM(arg);
+
1577
+
1578 _do_rename(req, nodeid, arg, payload);
+
1579}
+
1580
+
1581static void _do_rename2(fuse_req_t req, const fuse_ino_t nodeid,
+
1582 const void *op_in, const void *in_payload)
+
1583{
+
1584 const struct fuse_rename2_in *arg = op_in;
+
1585 const char *oldname = in_payload;
+
1586 const char *newname = oldname + strlen(oldname) + 1;
+
1587
+
1588 if (req->se->op.rename)
+
1589 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1590 arg->flags);
+
1591 else
+
1592 fuse_reply_err(req, ENOSYS);
+
1593}
+
1594
+
1595static void do_rename2(fuse_req_t req, const fuse_ino_t nodeid,
+
1596 const void *inarg)
+
1597{
+
1598 const struct fuse_rename2_in *arg = inarg;
+
1599 const void *payload = PARAM(arg);
+
1600
+
1601 _do_rename2(req, nodeid, arg, payload);
+
1602}
+
1603
+
1604static void _do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *op_in,
+
1605 const void *in_payload)
+
1606{
+
1607 (void)in_payload;
+
1608 const struct fuse_create_in *arg = op_in;
+
1609
+
1610 if (req->se->op.tmpfile) {
+
1611 struct fuse_file_info fi;
+
1612
+
1613 memset(&fi, 0, sizeof(fi));
+
1614 fi.flags = arg->flags;
+
1615
+
1616 if (req->se->conn.proto_minor >= 12)
+
1617 req->ctx.umask = arg->umask;
+
1618
+
1619 req->se->op.tmpfile(req, nodeid, arg->mode, &fi);
+
1620 } else
+
1621 fuse_reply_err(req, ENOSYS);
+
1622}
+
1623
+
1624static void do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1625{
+
1626 struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
+
1627
+
1628 _do_tmpfile(req, nodeid, arg, NULL);
+
1629}
+
1630
+
1631static void _do_link(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
1632 const void *in_payload)
+
1633{
+
1634 struct fuse_link_in *arg = (struct fuse_link_in *)op_in;
+
1635
+
1636 if (req->se->op.link)
+
1637 req->se->op.link(req, arg->oldnodeid, nodeid, in_payload);
+
1638 else
+
1639 fuse_reply_err(req, ENOSYS);
+
1640}
+
1641
+
1642static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1643{
+
1644 const struct fuse_link_in *arg = inarg;
+
1645 const void *name = PARAM(arg);
+
1646
+
1647 _do_link(req, nodeid, inarg, name);
+
1648}
+
1649
+
1650static void _do_create(fuse_req_t req, const fuse_ino_t nodeid,
+
1651 const void *op_in, const void *in_payload)
+
1652{
+
1653 const struct fuse_create_in *arg = op_in;
+
1654 const char *name = in_payload;
+
1655
+
1656 if (req->se->op.create) {
+
1657 struct fuse_file_info fi;
+
1658
+
1659 memset(&fi, 0, sizeof(fi));
+
1660 fi.flags = arg->flags;
+
1661
+
1662 if (req->se->conn.proto_minor >= 12)
+
1663 req->ctx.umask = arg->umask;
+
1664
+
1665 /* XXX: fuse_create_in::open_flags */
+
1666
+
1667 req->se->op.create(req, nodeid, name, arg->mode, &fi);
+
1668 } else {
+
1669 fuse_reply_err(req, ENOSYS);
+
1670 }
+
1671}
+
1672
+
1673static void do_create(fuse_req_t req, const fuse_ino_t nodeid,
+
1674 const void *inarg)
+
1675{
+
1676 const struct fuse_create_in *arg = (struct fuse_create_in *)inarg;
+
1677 void *payload = PARAM(arg);
+
1678
+
1679 if (req->se->conn.proto_minor < 12)
+
1680 payload = (char *)inarg + sizeof(struct fuse_open_in);
+
1681
+
1682 _do_create(req, nodeid, arg, payload);
+
1683}
+
1684
+
1685static void _do_open(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
1686 const void *in_payload)
+
1687{
+
1688 (void)in_payload;
+
1689 struct fuse_open_in *arg = (struct fuse_open_in *)op_in;
+
1690 struct fuse_file_info fi;
+
1691
+
1692 memset(&fi, 0, sizeof(fi));
+
1693 fi.flags = arg->flags;
+
1694
+
1695 /* XXX: fuse_open_in::open_flags */
+
1696
+
1697 if (req->se->op.open)
+
1698 req->se->op.open(req, nodeid, &fi);
+
1699 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPEN_SUPPORT)
+
1700 fuse_reply_err(req, ENOSYS);
+
1701 else
+
1702 fuse_reply_open(req, &fi);
+
1703}
+
1704
+
1705static void do_open(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1706{
+
1707 _do_open(req, nodeid, inarg, NULL);
+
1708}
+
1709
+
1710static void _do_read(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
1711 const void *in_payload)
+
1712{
+
1713 (void)in_payload;
+
1714 struct fuse_read_in *arg = (struct fuse_read_in *)op_in;
+
1715
+
1716 if (req->se->op.read) {
+
1717 struct fuse_file_info fi;
+
1718
+
1719 memset(&fi, 0, sizeof(fi));
+
1720 fi.fh = arg->fh;
+
1721 if (req->se->conn.proto_minor >= 9) {
+
1722 fi.lock_owner = arg->lock_owner;
+
1723 fi.flags = arg->flags;
+
1724 }
+
1725 req->se->op.read(req, nodeid, arg->size, arg->offset, &fi);
+
1726 } else
+
1727 fuse_reply_err(req, ENOSYS);
+
1728}
+
1729
+
1730static void do_read(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1731{
+
1732 _do_read(req, nodeid, inarg, NULL);
+
1733}
+
1734
+
1735static void _do_write(fuse_req_t req, const fuse_ino_t nodeid,
+
1736 const void *op_in, const void *in_payload)
+
1737{
+
1738 struct fuse_write_in *arg = (struct fuse_write_in *)op_in;
+
1739 const char *buf = in_payload;
+
1740 struct fuse_file_info fi;
+
1741
+
1742 memset(&fi, 0, sizeof(fi));
+
1743 fi.fh = arg->fh;
+
1744 fi.writepage = (arg->write_flags & FUSE_WRITE_CACHE) != 0;
+
1745
+
1746 if (req->se->conn.proto_minor >= 9) {
+
1747 fi.lock_owner = arg->lock_owner;
+
1748 fi.flags = arg->flags;
+
1749 }
+
1750
+
1751 if (req->se->op.write)
+
1752 req->se->op.write(req, nodeid, buf, arg->size, arg->offset,
+
1753 &fi);
+
1754 else
+
1755 fuse_reply_err(req, ENOSYS);
+
1756}
+
1757
+
1758static void do_write(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1759{
+
1760 struct fuse_write_in *arg = (struct fuse_write_in *)inarg;
+
1761 const void *payload;
+
1762
+
1763 if (req->se->conn.proto_minor < 9)
+
1764 payload = ((char *)arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1765 else
+
1766 payload = PARAM(arg);
+
1767
+
1768 _do_write(req, nodeid, arg, payload);
+
1769}
+
1770
+
1771static void _do_write_buf(fuse_req_t req, const fuse_ino_t nodeid,
+
1772 const void *op_in, struct fuse_bufvec *bufv)
+
1773{
+
1774 struct fuse_session *se = req->se;
+
1775 struct fuse_write_in *arg = (struct fuse_write_in *)op_in;
+
1776 struct fuse_file_info fi;
+
1777
+
1778 memset(&fi, 0, sizeof(fi));
+
1779 fi.fh = arg->fh;
+
1780 fi.writepage = arg->write_flags & FUSE_WRITE_CACHE;
+
1781
+
1782 if (se->conn.proto_minor >= 9) {
+
1783 fi.lock_owner = arg->lock_owner;
+
1784 fi.flags = arg->flags;
+
1785 }
+
1786
+
1787 se->op.write_buf(req, nodeid, bufv, arg->offset, &fi);
+
1788}
+
1789
+
1790static void do_write_buf(fuse_req_t req, const fuse_ino_t nodeid,
+
1791 const void *inarg, const struct fuse_buf *ibuf)
+
1792{
+
1793 struct fuse_session *se = req->se;
+
1794 struct fuse_bufvec bufv = {
+
1795 .buf[0] = *ibuf,
+
1796 .count = 1,
+
1797 };
+
1798 struct fuse_write_in *arg = (struct fuse_write_in *)inarg;
+
1799
+
1800 if (se->conn.proto_minor < 9) {
+
1801 bufv.buf[0].mem = ((char *)arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1802 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1803 FUSE_COMPAT_WRITE_IN_SIZE;
+
1804 assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD));
+
1805 } else {
+
1806 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
1807 bufv.buf[0].mem = PARAM(arg);
+
1808
+
1809 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1810 sizeof(struct fuse_write_in);
+
1811 }
+
1812 if (bufv.buf[0].size < arg->size) {
+
1813 fuse_log(FUSE_LOG_ERR,
+
1814 "fuse: %s: buffer size too small\n", __func__);
+
1815 fuse_reply_err(req, EIO);
+
1816 goto out;
+
1817 }
+
1818 bufv.buf[0].size = arg->size;
+
1819
+
1820 _do_write_buf(req, nodeid, inarg, &bufv);
+
1821
+
1822out:
+
1823 /* Need to reset the pipe if ->write_buf() didn't consume all data */
+
1824 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
1825 fuse_ll_clear_pipe(se);
+
1826}
+
1827
+
1828static void _do_flush(fuse_req_t req, const fuse_ino_t nodeid,
+
1829 const void *op_in, const void *in_payload)
+
1830{
+
1831 (void)in_payload;
+
1832 struct fuse_flush_in *arg = (struct fuse_flush_in *)op_in;
+
1833 struct fuse_file_info fi;
+
1834
+
1835 memset(&fi, 0, sizeof(fi));
+
1836 fi.fh = arg->fh;
+
1837 fi.flush = 1;
+
1838 if (req->se->conn.proto_minor >= 7)
+
1839 fi.lock_owner = arg->lock_owner;
+
1840
+
1841 if (req->se->op.flush)
+
1842 req->se->op.flush(req, nodeid, &fi);
+
1843 else
+
1844 fuse_reply_err(req, ENOSYS);
+
1845}
+
1846
+
1847static void do_flush(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1848{
+
1849 _do_flush(req, nodeid, inarg, NULL);
+
1850}
+
1851
+
1852static void _do_release(fuse_req_t req, const fuse_ino_t nodeid,
+
1853 const void *op_in, const void *in_payload)
+
1854{
+
1855 (void)in_payload;
+
1856 const struct fuse_release_in *arg = op_in;
+
1857 struct fuse_file_info fi;
+
1858
+
1859 memset(&fi, 0, sizeof(fi));
+
1860 fi.flags = arg->flags;
+
1861 fi.fh = arg->fh;
+
1862 if (req->se->conn.proto_minor >= 8) {
+
1863 fi.flush = (arg->release_flags & FUSE_RELEASE_FLUSH) ? 1 : 0;
+
1864 fi.lock_owner = arg->lock_owner;
+
1865 }
+
1866 if (arg->release_flags & FUSE_RELEASE_FLOCK_UNLOCK) {
+
1867 fi.flock_release = 1;
+
1868 fi.lock_owner = arg->lock_owner;
+
1869 }
+
1870
+
1871 if (req->se->op.release)
+
1872 req->se->op.release(req, nodeid, &fi);
+
1873 else
+
1874 fuse_reply_err(req, 0);
+
1875}
+
1876
+
1877static void do_release(fuse_req_t req, const fuse_ino_t nodeid,
+
1878 const void *inarg)
+
1879{
+
1880 _do_release(req, nodeid, inarg, NULL);
+
1881}
+
1882
+
1883static void _do_fsync(fuse_req_t req, const fuse_ino_t nodeid,
+
1884 const void *op_in, const void *in_payload)
+
1885{
+
1886 (void)in_payload;
+
1887 const struct fuse_fsync_in *arg = op_in;
+
1888 struct fuse_file_info fi;
+
1889 int datasync = arg->fsync_flags & 1;
+
1890
+
1891 memset(&fi, 0, sizeof(fi));
+
1892 fi.fh = arg->fh;
+
1893
+
1894 if (req->se->op.fsync)
+
1895 req->se->op.fsync(req, nodeid, datasync, &fi);
+
1896 else
+
1897 fuse_reply_err(req, ENOSYS);
+
1898}
+
1899
+
1900static void do_fsync(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1901{
+
1902 _do_fsync(req, nodeid, inarg, NULL);
+
1903}
+
1904
+
1905static void _do_opendir(fuse_req_t req, const fuse_ino_t nodeid,
+
1906 const void *op_in, const void *in_payload)
+
1907{
+
1908 (void)in_payload;
+
1909 const struct fuse_open_in *arg = op_in;
+
1910 struct fuse_file_info fi;
+
1911
+
1912 memset(&fi, 0, sizeof(fi));
+
1913 fi.flags = arg->flags;
+
1914 /* XXX: fuse_open_in::open_flags */
+
1915
+
1916 if (req->se->op.opendir)
+
1917 req->se->op.opendir(req, nodeid, &fi);
+
1918 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPENDIR_SUPPORT)
+
1919 fuse_reply_err(req, ENOSYS);
+
1920 else
+
1921 fuse_reply_open(req, &fi);
+
1922}
+
1923
+
1924static void do_opendir(fuse_req_t req, const fuse_ino_t nodeid,
+
1925 const void *inarg)
+
1926{
+
1927 _do_opendir(req, nodeid, inarg, NULL);
+
1928}
+
1929
+
1930static void _do_readdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1931 const void *op_in, const void *in_payload)
+
1932{
+
1933 (void)in_payload;
+
1934 struct fuse_read_in *arg = (struct fuse_read_in *)op_in;
+
1935 struct fuse_file_info fi;
+
1936
+
1937 memset(&fi, 0, sizeof(fi));
+
1938 fi.fh = arg->fh;
+
1939
+
1940 if (req->se->op.readdir)
+
1941 req->se->op.readdir(req, nodeid, arg->size, arg->offset, &fi);
+
1942 else
+
1943 fuse_reply_err(req, ENOSYS);
+
1944}
+
1945
+
1946static void do_readdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1947 const void *inarg)
+
1948{
+
1949 _do_readdir(req, nodeid, inarg, NULL);
+
1950}
+
1951
+
1952static void _do_readdirplus(fuse_req_t req, const fuse_ino_t nodeid,
+
1953 const void *op_in, const void *in_payload)
+
1954{
+
1955 (void)in_payload;
+
1956 struct fuse_read_in *arg = (struct fuse_read_in *)op_in;
+
1957 struct fuse_file_info fi;
+
1958
+
1959 memset(&fi, 0, sizeof(fi));
+
1960 fi.fh = arg->fh;
+
1961
+
1962 if (req->se->op.readdirplus)
+
1963 req->se->op.readdirplus(req, nodeid, arg->size, arg->offset, &fi);
+
1964 else
+
1965 fuse_reply_err(req, ENOSYS);
+
1966}
+
1967
+
1968static void do_readdirplus(fuse_req_t req, const fuse_ino_t nodeid,
+
1969 const void *inarg)
+
1970{
+
1971 _do_readdirplus(req, nodeid, inarg, NULL);
+
1972}
+
1973
+
1974static void _do_releasedir(fuse_req_t req, const fuse_ino_t nodeid,
+
1975 const void *op_in, const void *in_payload)
+
1976{
+
1977 (void)in_payload;
+
1978 struct fuse_release_in *arg = (struct fuse_release_in *)op_in;
+
1979 struct fuse_file_info fi;
+
1980
+
1981 memset(&fi, 0, sizeof(fi));
+
1982 fi.flags = arg->flags;
+
1983 fi.fh = arg->fh;
+
1984
+
1985 if (req->se->op.releasedir)
+
1986 req->se->op.releasedir(req, nodeid, &fi);
+
1987 else
+
1988 fuse_reply_err(req, 0);
+
1989}
+
1990
+
1991static void do_releasedir(fuse_req_t req, const fuse_ino_t nodeid,
+
1992 const void *inarg)
+
1993{
+
1994 _do_releasedir(req, nodeid, inarg, NULL);
+
1995}
+
1996
+
1997static void _do_fsyncdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1998 const void *op_in, const void *in_payload)
+
1999{
+
2000 (void)in_payload;
+
2001 struct fuse_fsync_in *arg = (struct fuse_fsync_in *)op_in;
+
2002 struct fuse_file_info fi;
+
2003 int datasync = arg->fsync_flags & 1;
+
2004
+
2005 memset(&fi, 0, sizeof(fi));
+
2006 fi.fh = arg->fh;
+
2007
+
2008 if (req->se->op.fsyncdir)
+
2009 req->se->op.fsyncdir(req, nodeid, datasync, &fi);
+
2010 else
+
2011 fuse_reply_err(req, ENOSYS);
+
2012}
+
2013static void do_fsyncdir(fuse_req_t req, const fuse_ino_t nodeid,
+
2014 const void *inarg)
+
2015{
+
2016 _do_fsyncdir(req, nodeid, inarg, NULL);
+
2017}
+
2018
+
2019static void _do_statfs(fuse_req_t req, const fuse_ino_t nodeid,
+
2020 const void *op_in, const void *in_payload)
+
2021{
+
2022 (void) nodeid;
+
2023 (void)op_in;
+
2024 (void)in_payload;
+
2025
+
2026 if (req->se->op.statfs)
+
2027 req->se->op.statfs(req, nodeid);
+
2028 else {
+
2029 struct statvfs buf = {
+
2030 .f_namemax = 255,
+
2031 .f_bsize = 512,
+
2032 };
+
2033 fuse_reply_statfs(req, &buf);
+
2034 }
+
2035}
+
2036static void do_statfs(fuse_req_t req, const fuse_ino_t nodeid,
+
2037 const void *inarg)
+
2038{
+
2039 _do_statfs(req, nodeid, inarg, NULL);
+
2040}
+
2041
+
2042static void _do_setxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2043 const void *op_in, const void *in_payload)
+
2044{
+
2045 struct fuse_setxattr_in *arg = (struct fuse_setxattr_in *)op_in;
+
2046 const char *name = in_payload;
+
2047 const char *value = name + strlen(name) + 1;
+
2048
+
2049 /* XXX:The API should be extended to support extra_flags/setxattr_flags */
+
2050
+
2051 if (req->se->op.setxattr)
+
2052 req->se->op.setxattr(req, nodeid, name, value, arg->size,
+
2053 arg->flags);
+
2054 else
+
2055 fuse_reply_err(req, ENOSYS);
+
2056}
+
2057static void do_setxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2058 const void *inarg)
+
2059{
+
2060 struct fuse_session *se = req->se;
+
2061 unsigned int xattr_ext = !!(se->conn.want & FUSE_CAP_SETXATTR_EXT);
+
2062 const struct fuse_setxattr_in *arg = inarg;
+
2063 char *payload = xattr_ext ? PARAM(arg) :
+
2064 (char *)arg + FUSE_COMPAT_SETXATTR_IN_SIZE;
+
2065
+
2066 _do_setxattr(req, nodeid, arg, payload);
+
2067}
+
2068
+
2069static void _do_getxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2070 const void *op_in, const void *in_payload)
+
2071{
+
2072 const struct fuse_getxattr_in *arg = op_in;
+
2073
+
2074 if (req->se->op.getxattr)
+
2075 req->se->op.getxattr(req, nodeid, in_payload, arg->size);
+
2076 else
+
2077 fuse_reply_err(req, ENOSYS);
+
2078}
+
2079
+
2080static void do_getxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2081 const void *inarg)
+
2082{
+
2083 const struct fuse_getxattr_in *arg = inarg;
+
2084 const void *payload = PARAM(arg);
+
2085
+
2086 _do_getxattr(req, nodeid, arg, payload);
+
2087}
+
2088
+
2089static void _do_listxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2090 const void *inarg, const void *in_payload)
+
2091{
+
2092 (void)in_payload;
+
2093 const struct fuse_getxattr_in *arg = inarg;
+
2094
+
2095 if (req->se->op.listxattr)
+
2096 req->se->op.listxattr(req, nodeid, arg->size);
+
2097 else
+
2098 fuse_reply_err(req, ENOSYS);
+
2099}
+
2100static void do_listxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2101 const void *inarg)
+
2102{
+
2103 _do_listxattr(req, nodeid, inarg, NULL);
+
2104}
+
2105
+
2106static void _do_removexattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2107 const void *inarg, const void *in_payload)
+
2108{
+
2109 (void)inarg;
+
2110 const char *name = in_payload;
+
2111
+
2112 if (req->se->op.removexattr)
+
2113 req->se->op.removexattr(req, nodeid, name);
+
2114 else
+
2115 fuse_reply_err(req, ENOSYS);
+
2116}
+
2117static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2118{
+
2119 _do_removexattr(req, nodeid, NULL, inarg);
+
2120}
+
2121
+
2122static void convert_fuse_file_lock(const struct fuse_file_lock *fl,
+
2123 struct flock *flock)
+
2124{
+
2125 memset(flock, 0, sizeof(struct flock));
+
2126 flock->l_type = fl->type;
+
2127 flock->l_whence = SEEK_SET;
+
2128 flock->l_start = fl->start;
+
2129 if (fl->end == OFFSET_MAX)
+
2130 flock->l_len = 0;
+
2131 else
+
2132 flock->l_len = fl->end - fl->start + 1;
+
2133 flock->l_pid = fl->pid;
+
2134}
+
2135
+
2136static void _do_getlk(fuse_req_t req, const fuse_ino_t nodeid,
+
2137 const void *op_in, const void *in_payload)
+
2138{
+
2139 (void)in_payload;
+
2140 const struct fuse_lk_in *arg = op_in;
+
2141 struct fuse_file_info fi;
+
2142 struct flock flock;
+
2143
+
2144 memset(&fi, 0, sizeof(fi));
+
2145 fi.fh = arg->fh;
+
2146 fi.lock_owner = arg->owner;
+
2147
+
2148 convert_fuse_file_lock(&arg->lk, &flock);
+
2149 if (req->se->op.getlk)
+
2150 req->se->op.getlk(req, nodeid, &fi, &flock);
+
2151 else
+
2152 fuse_reply_err(req, ENOSYS);
+
2153}
+
2154static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2155{
+
2156 _do_getlk(req, nodeid, inarg, NULL);
+
2157}
+
2158
+
2159static void do_setlk_common(fuse_req_t req, const fuse_ino_t nodeid,
+
2160 const void *op_in, int sleep)
+
2161{
+
2162 const struct fuse_lk_in *arg = op_in;
+
2163 struct fuse_file_info fi;
+
2164 struct flock flock;
+
2165
+
2166 memset(&fi, 0, sizeof(fi));
+
2167 fi.fh = arg->fh;
+
2168 fi.lock_owner = arg->owner;
+
2169
+
2170 if (arg->lk_flags & FUSE_LK_FLOCK) {
+
2171 int op = 0;
+
2172
+
2173 switch (arg->lk.type) {
+
2174 case F_RDLCK:
+
2175 op = LOCK_SH;
+
2176 break;
+
2177 case F_WRLCK:
+
2178 op = LOCK_EX;
+
2179 break;
+
2180 case F_UNLCK:
+
2181 op = LOCK_UN;
+
2182 break;
+
2183 }
+
2184 if (!sleep)
+
2185 op |= LOCK_NB;
+
2186
+
2187 if (req->se->op.flock)
+
2188 req->se->op.flock(req, nodeid, &fi, op);
+
2189 else
+
2190 fuse_reply_err(req, ENOSYS);
+
2191 } else {
+
2192 convert_fuse_file_lock(&arg->lk, &flock);
+
2193 if (req->se->op.setlk)
+
2194 req->se->op.setlk(req, nodeid, &fi, &flock, sleep);
+
2195 else
+
2196 fuse_reply_err(req, ENOSYS);
+
2197 }
+
2198}
+
2199
+
2200static void _do_setlk(fuse_req_t req, const fuse_ino_t nodeid,
+
2201 const void *op_in, const void *in_payload)
+
2202{
+
2203 (void)in_payload;
+
2204 do_setlk_common(req, nodeid, op_in, 0);
+
2205}
+
2206
+
2207static void do_setlk(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
2208{
+
2209 _do_setlk(req, nodeid, inarg, NULL);
+
2210}
+
2211
+
2212static void _do_setlkw(fuse_req_t req, const fuse_ino_t nodeid,
+
2213 const void *op_in, const void *in_payload)
+
2214{
+
2215 (void)in_payload;
+
2216 do_setlk_common(req, nodeid, op_in, 1);
+
2217}
+
2218static void do_setlkw(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2219{
+
2220 _do_setlkw(req, nodeid, inarg, NULL);
+
2221}
+
2222
+
2223static int find_interrupted(struct fuse_session *se, struct fuse_req *req)
+
2224{
+
2225 struct fuse_req *curr;
+
2226
+
2227 for (curr = se->list.next; curr != &se->list; curr = curr->next) {
+
2228 if (curr->unique == req->u.i.unique) {
+ +
2230 void *data;
+
2231
+
2232 curr->ref_cnt++;
+
2233 pthread_mutex_unlock(&se->lock);
+
2234
+
2235 /* Ugh, ugly locking */
+
2236 pthread_mutex_lock(&curr->lock);
+
2237 pthread_mutex_lock(&se->lock);
+
2238 curr->interrupted = 1;
+
2239 func = curr->u.ni.func;
+
2240 data = curr->u.ni.data;
+
2241 pthread_mutex_unlock(&se->lock);
+
2242 if (func)
+
2243 func(curr, data);
+
2244 pthread_mutex_unlock(&curr->lock);
+
2245
+
2246 pthread_mutex_lock(&se->lock);
+
2247 curr->ref_cnt--;
+
2248 if (!curr->ref_cnt) {
+
2249 destroy_req(curr);
+
2250 }
+
2251
+
2252 return 1;
+
2253 }
+
2254 }
+
2255 for (curr = se->interrupts.next; curr != &se->interrupts;
+
2256 curr = curr->next) {
+
2257 if (curr->u.i.unique == req->u.i.unique)
+
2258 return 1;
+
2259 }
+
2260 return 0;
+
2261}
+
2262
+
2263static void _do_interrupt(fuse_req_t req, const fuse_ino_t nodeid,
+
2264 const void *op_in, const void *in_payload)
+
2265{
+
2266 (void)in_payload;
+
2267 const struct fuse_interrupt_in *arg = op_in;
+
2268 struct fuse_session *se = req->se;
+
2269
+
2270 (void) nodeid;
+
2271 if (se->debug)
+
2272 fuse_log(FUSE_LOG_DEBUG, "INTERRUPT: %llu\n",
+
2273 (unsigned long long) arg->unique);
+
2274
+
2275 req->u.i.unique = arg->unique;
+
2276
+
2277 pthread_mutex_lock(&se->lock);
+
2278 if (find_interrupted(se, req)) {
+
2279 fuse_chan_put(req->ch);
+
2280 req->ch = NULL;
+
2281 destroy_req(req);
+
2282 } else
+
2283 list_add_req(req, &se->interrupts);
+
2284 pthread_mutex_unlock(&se->lock);
+
2285}
+
2286static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2287{
+
2288 _do_interrupt(req, nodeid, inarg, NULL);
+
2289}
+
2290
+
2291static struct fuse_req *check_interrupt(struct fuse_session *se,
+
2292 struct fuse_req *req)
+
2293{
+
2294 struct fuse_req *curr;
+
2295
+
2296 for (curr = se->interrupts.next; curr != &se->interrupts;
+
2297 curr = curr->next) {
+
2298 if (curr->u.i.unique == req->unique) {
+
2299 req->interrupted = 1;
+
2300 list_del_req(curr);
+
2301 fuse_chan_put(curr->ch);
+
2302 curr->ch = NULL;
+
2303 destroy_req(curr);
+
2304 return NULL;
+
2305 }
+
2306 }
+
2307 curr = se->interrupts.next;
+
2308 if (curr != &se->interrupts) {
+
2309 list_del_req(curr);
+
2310 list_init_req(curr);
+
2311 return curr;
+
2312 } else
+
2313 return NULL;
+
2314}
+
2315
+
2316static void _do_bmap(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
2317 const void *in_payload)
+
2318{
+
2319 (void)in_payload;
+
2320 const struct fuse_bmap_in *arg = op_in;
+
2321
+
2322 if (req->se->op.bmap)
+
2323 req->se->op.bmap(req, nodeid, arg->blocksize, arg->block);
+
2324 else
+
2325 fuse_reply_err(req, ENOSYS);
+
2326}
+
2327static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2328{
+
2329 _do_bmap(req, nodeid, inarg, NULL);
+
2330}
+
2331
+
2332static void _do_ioctl(fuse_req_t req, const fuse_ino_t nodeid,
+
2333 const void *op_in, const void *in_payload)
+
2334{
+
2335 struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *)op_in;
+
2336 unsigned int flags = arg->flags;
+
2337 const void *in_buf = in_payload;
+
2338 struct fuse_file_info fi;
+
2339
+
2340 if (flags & FUSE_IOCTL_DIR &&
+
2341 !(req->se->conn.want_ext & FUSE_CAP_IOCTL_DIR)) {
+
2342 fuse_reply_err(req, ENOTTY);
+
2343 return;
+
2344 }
+
2345
+
2346 memset(&fi, 0, sizeof(fi));
+
2347 fi.fh = arg->fh;
+
2348
+
2349 if (sizeof(void *) == 4 && req->se->conn.proto_minor >= 16 &&
+
2350 !(flags & FUSE_IOCTL_32BIT)) {
+
2351 req->flags.ioctl_64bit = 1;
+
2352 }
+
2353
+
2354 if (req->se->op.ioctl)
+
2355 req->se->op.ioctl(req, nodeid, arg->cmd,
+
2356 (void *)(uintptr_t)arg->arg, &fi, flags,
+
2357 in_buf, arg->in_size, arg->out_size);
+
2358 else
+
2359 fuse_reply_err(req, ENOSYS);
+
2360}
+
2361static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2362{
+
2363 const struct fuse_ioctl_in *arg = inarg;
+
2364 void *in_buf = arg->in_size ? PARAM(arg) : NULL;
+
2365
+
2366 _do_ioctl(req, nodeid, arg, in_buf);
+
2367}
+
2368
+
2369void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
2370{
+
2371 free(ph);
+
2372}
+
2373
+
2374static void _do_poll(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
2375 const void *in_payload)
+
2376{
+
2377 (void)in_payload;
+
2378 struct fuse_poll_in *arg = (struct fuse_poll_in *)op_in;
+
2379 struct fuse_file_info fi;
+
2380
+
2381 memset(&fi, 0, sizeof(fi));
+
2382 fi.fh = arg->fh;
+
2383 fi.poll_events = arg->events;
+
2384
+
2385 if (req->se->op.poll) {
+
2386 struct fuse_pollhandle *ph = NULL;
+
2387
+
2388 if (arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) {
+
2389 ph = malloc(sizeof(struct fuse_pollhandle));
+
2390 if (ph == NULL) {
+
2391 fuse_reply_err(req, ENOMEM);
+
2392 return;
+
2393 }
+
2394 ph->kh = arg->kh;
+
2395 ph->se = req->se;
+
2396 }
+
2397
+
2398 req->se->op.poll(req, nodeid, &fi, ph);
+
2399 } else {
+
2400 fuse_reply_err(req, ENOSYS);
+
2401 }
+
2402}
+
2403
+
2404static void do_poll(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
2405{
+
2406 _do_poll(req, nodeid, inarg, NULL);
+
2407}
+
2408
+
2409static void _do_fallocate(fuse_req_t req, const fuse_ino_t nodeid,
+
2410 const void *op_in, const void *in_payload)
+
2411{
+
2412 (void)in_payload;
+
2413 const struct fuse_fallocate_in *arg = op_in;
+
2414 struct fuse_file_info fi;
+
2415
+
2416 memset(&fi, 0, sizeof(fi));
+
2417 fi.fh = arg->fh;
+
2418
+
2419 if (req->se->op.fallocate)
+
2420 req->se->op.fallocate(req, nodeid, arg->mode, arg->offset,
+
2421 arg->length, &fi);
+
2422 else
+
2423 fuse_reply_err(req, ENOSYS);
+
2424}
+
2425
+
2426static void do_fallocate(fuse_req_t req, const fuse_ino_t nodeid,
+
2427 const void *inarg)
+
2428{
+
2429 _do_fallocate(req, nodeid, inarg, NULL);
+
2430}
+
2431
+
2432static void copy_file_range_common(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2433 const struct fuse_copy_file_range_in *arg)
+
2434{
+
2435 struct fuse_file_info fi_in, fi_out;
+
2436
+
2437 memset(&fi_in, 0, sizeof(fi_in));
+
2438 fi_in.fh = arg->fh_in;
+
2439
+
2440 memset(&fi_out, 0, sizeof(fi_out));
+
2441 fi_out.fh = arg->fh_out;
+
2442
+
2443 if (req->se->op.copy_file_range)
+
2444 req->se->op.copy_file_range(req, nodeid_in, arg->off_in, &fi_in,
+
2445 arg->nodeid_out, arg->off_out,
+
2446 &fi_out, arg->len, arg->flags);
+
2447 else
+
2448 fuse_reply_err(req, ENOSYS);
+
2449}
+
2450
+
2451static void _do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2452 const void *op_in, const void *in_payload)
+
2453{
+
2454 const struct fuse_copy_file_range_in *arg = op_in;
+
2455 struct fuse_copy_file_range_in arg_tmp;
+
2456
+
2457 (void) in_payload;
+
2458 /* fuse_write_out can only handle 32bit copy size */
+
2459 if (arg->len > 0xfffff000) {
+
2460 arg_tmp = *arg;
+
2461 arg_tmp.len = 0xfffff000;
+
2462 arg = &arg_tmp;
+
2463 }
+
2464 copy_file_range_common(req, nodeid_in, arg);
+
2465}
+
2466
+
2467static void do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2468 const void *inarg)
+
2469{
+
2470 _do_copy_file_range(req, nodeid_in, inarg, NULL);
+
2471}
+
2472
+
2473static void _do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2474 const void *op_in, const void *in_payload)
+
2475{
+
2476 (void) in_payload;
+
2477 req->flags.is_copy_file_range_64 = 1;
+
2478 /* Limit size on 32bit userspace to avoid conversion overflow */
+
2479 if (sizeof(size_t) == 4)
+
2480 _do_copy_file_range(req, nodeid_in, op_in, NULL);
+
2481 else
+
2482 copy_file_range_common(req, nodeid_in, op_in);
+
2483}
+
2484
+
2485static void do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2486 const void *inarg)
+
2487{
+
2488 _do_copy_file_range_64(req, nodeid_in, inarg, NULL);
+
2489}
+
2490
+
2491/*
+
2492 * Note that the uint64_t offset in struct fuse_lseek_in is derived from
+
2493 * linux kernel loff_t and is therefore signed.
+
2494 */
+
2495static void _do_lseek(fuse_req_t req, const fuse_ino_t nodeid,
+
2496 const void *op_in, const void *in_payload)
+
2497{
+
2498 (void)in_payload;
+
2499 const struct fuse_lseek_in *arg = op_in;
+
2500 struct fuse_file_info fi;
+
2501
+
2502 memset(&fi, 0, sizeof(fi));
+
2503 fi.fh = arg->fh;
+
2504
+
2505 if (req->se->op.lseek)
+
2506 req->se->op.lseek(req, nodeid, arg->offset, arg->whence, &fi);
+
2507 else
+
2508 fuse_reply_err(req, ENOSYS);
+
2509}
+
2510
+
2511static void do_lseek(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
2512{
+
2513 _do_lseek(req, nodeid, inarg, NULL);
+
2514}
+
2515
+
2516#ifdef HAVE_STATX
+
2517static void _do_statx(fuse_req_t req, const fuse_ino_t nodeid,
+
2518 const void *op_in, const void *in_payload)
+
2519{
+
2520 (void)in_payload;
+
2521 const struct fuse_statx_in *arg = op_in;
+
2522 struct fuse_file_info *fip = NULL;
+
2523 struct fuse_file_info fi;
+
2524
+
2525 if (arg->getattr_flags & FUSE_GETATTR_FH) {
+
2526 memset(&fi, 0, sizeof(fi));
+
2527 fi.fh = arg->fh;
+
2528 fip = &fi;
+
2529 }
+
2530
+
2531 if (req->se->op.statx)
+
2532 req->se->op.statx(req, nodeid, arg->sx_flags, arg->sx_mask, fip);
+
2533 else
+
2534 fuse_reply_err(req, ENOSYS);
+
2535}
+
2536#else
+
2537static void _do_statx(fuse_req_t req, const fuse_ino_t nodeid,
+
2538 const void *op_in, const void *in_payload)
+
2539{
+
2540 (void)in_payload;
+
2541 (void)req;
+
2542 (void)nodeid;
+
2543 (void)op_in;
+
2544}
+
2545#endif
+
2546
+
2547static void do_statx(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2548{
+
2549 _do_statx(req, nodeid, inarg, NULL);
+
2550}
+
2551
+
2552static bool want_flags_valid(uint64_t capable, uint64_t want)
+
2553{
+
2554 uint64_t unknown_flags = want & (~capable);
+
2555 if (unknown_flags != 0) {
+
2556 fuse_log(FUSE_LOG_ERR,
+
2557 "fuse: unknown connection 'want' flags: 0x%08llx\n",
+
2558 (unsigned long long)unknown_flags);
+
2559 return false;
+
2560 }
+
2561 return true;
+
2562}
+
2563
+ +
2568{
+
2569 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
2570
+
2571 /*
+
2572 * Convert want to want_ext if necessary.
+
2573 * For the high level interface this function might be called
+
2574 * twice, once from the high level interface and once from the
+
2575 * low level interface. Both, with different want_ext_default and
+
2576 * want_default values. In order to suppress a failure for the
+
2577 * second call, we check if the lower 32 bits of want_ext are
+
2578 * already set to the value of want.
+
2579 */
+
2580 if (conn->want != se->conn_want &&
+
2581 fuse_lower_32_bits(conn->want_ext) != conn->want) {
+
2582 if (conn->want_ext != se->conn_want_ext) {
+
2583 fuse_log(FUSE_LOG_ERR,
+
2584 "%s: Both conn->want_ext and conn->want are set.\n"
+
2585 "want=%x want_ext=%llx, se->want=%x se->want_ext=%llx\n",
+
2586 __func__, conn->want,
+
2587 (unsigned long long)conn->want_ext,
+
2588 se->conn_want,
+
2589 (unsigned long long)se->conn_want_ext);
+
2590 return -EINVAL;
+
2591 }
+
2592
+
2593 /* high bits from want_ext, low bits from want */
+
2594 conn->want_ext = fuse_higher_32_bits(conn->want_ext) |
+
2595 conn->want;
+
2596 }
+
2597
+
2598 /* ensure there won't be a second conversion */
+
2599 conn->want = fuse_lower_32_bits(conn->want_ext);
+
2600
+
2601 return 0;
+
2602}
+
2603
+
2604bool fuse_set_feature_flag(struct fuse_conn_info *conn,
+
2605 uint64_t flag)
+
2606{
+
2607 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
2608
+
2609 if (conn->capable_ext & flag) {
+
2610 conn->want_ext |= flag;
+
2611 se->conn_want_ext |= flag;
+
2612 conn->want |= flag;
+
2613 se->conn_want |= flag;
+
2614 return true;
+
2615 }
+
2616 return false;
+
2617}
+
2618
+
2619void fuse_unset_feature_flag(struct fuse_conn_info *conn,
+
2620 uint64_t flag)
+
2621{
+
2622 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
2623
+
2624 conn->want_ext &= ~flag;
+
2625 se->conn_want_ext &= ~flag;
+
2626 conn->want &= ~flag;
+
2627 se->conn_want &= ~flag;
+
2628}
+
2629
+
2630bool fuse_get_feature_flag(struct fuse_conn_info *conn,
+
2631 uint64_t flag)
+
2632{
+
2633 return conn->capable_ext & flag ? true : false;
+
2634}
+
2635
+
2636/* Prevent bogus data races (bogus since "init" is called before
+
2637 * multi-threading becomes relevant */
+
2638static __attribute__((no_sanitize("thread"))) void
+
2639_do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
2640 const void *in_payload)
+
2641{
+
2642 (void)in_payload;
+
2643 const struct fuse_init_in *arg = op_in;
+
2644 struct fuse_init_out outarg;
+
2645 struct fuse_session *se = req->se;
+
2646 size_t bufsize = se->bufsize;
+
2647 size_t outargsize = sizeof(outarg);
+
2648 uint64_t inargflags = 0;
+
2649 uint64_t outargflags = 0;
+
2650 bool buf_reallocable = se->buf_reallocable;
+
2651 (void) nodeid;
+
2652 bool enable_io_uring = false;
+
2653
+
2654 if (se->debug) {
+
2655 fuse_log(FUSE_LOG_DEBUG, "INIT: %u.%u\n", arg->major, arg->minor);
+
2656 if (arg->major == 7 && arg->minor >= 6) {
+
2657 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
2658 fuse_log(FUSE_LOG_DEBUG, "max_readahead=0x%08x\n",
+
2659 arg->max_readahead);
+
2660 }
+
2661 }
+
2662 se->conn.proto_major = arg->major;
+
2663 se->conn.proto_minor = arg->minor;
+
2664 se->conn.capable_ext = 0;
+
2665 se->conn.want_ext = 0;
+
2666
+
2667 memset(&outarg, 0, sizeof(outarg));
+
2668 outarg.major = FUSE_KERNEL_VERSION;
+
2669 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
2670
+
2671 if (arg->major < 7) {
+
2672 fuse_log(FUSE_LOG_ERR, "fuse: unsupported protocol version: %u.%u\n",
+
2673 arg->major, arg->minor);
+
2674 fuse_reply_err(req, EPROTO);
+
2675 return;
+
2676 }
+
2677
+
2678 if (arg->major > 7) {
+
2679 /* Wait for a second INIT request with a 7.X version */
+
2680 send_reply_ok(req, &outarg, sizeof(outarg));
+
2681 return;
+
2682 }
+
2683
+
2684 if (arg->minor >= 6) {
+
2685 if (arg->max_readahead < se->conn.max_readahead)
+
2686 se->conn.max_readahead = arg->max_readahead;
+
2687 inargflags = arg->flags;
+
2688 if (inargflags & FUSE_INIT_EXT)
+
2689 inargflags = inargflags | (uint64_t) arg->flags2 << 32;
+
2690 if (inargflags & FUSE_ASYNC_READ)
+
2691 se->conn.capable_ext |= FUSE_CAP_ASYNC_READ;
+
2692 if (inargflags & FUSE_POSIX_LOCKS)
+
2693 se->conn.capable_ext |= FUSE_CAP_POSIX_LOCKS;
+
2694 if (inargflags & FUSE_ATOMIC_O_TRUNC)
+
2695 se->conn.capable_ext |= FUSE_CAP_ATOMIC_O_TRUNC;
+
2696 if (inargflags & FUSE_EXPORT_SUPPORT)
+
2697 se->conn.capable_ext |= FUSE_CAP_EXPORT_SUPPORT;
+
2698 if (inargflags & FUSE_DONT_MASK)
+
2699 se->conn.capable_ext |= FUSE_CAP_DONT_MASK;
+
2700 if (inargflags & FUSE_FLOCK_LOCKS)
+
2701 se->conn.capable_ext |= FUSE_CAP_FLOCK_LOCKS;
+
2702 if (inargflags & FUSE_AUTO_INVAL_DATA)
+
2703 se->conn.capable_ext |= FUSE_CAP_AUTO_INVAL_DATA;
+
2704 if (inargflags & FUSE_DO_READDIRPLUS)
+
2705 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS;
+
2706 if (inargflags & FUSE_READDIRPLUS_AUTO)
+
2707 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS_AUTO;
+
2708 if (inargflags & FUSE_ASYNC_DIO)
+
2709 se->conn.capable_ext |= FUSE_CAP_ASYNC_DIO;
+
2710 if (inargflags & FUSE_WRITEBACK_CACHE)
+
2711 se->conn.capable_ext |= FUSE_CAP_WRITEBACK_CACHE;
+
2712 if (inargflags & FUSE_NO_OPEN_SUPPORT)
+
2713 se->conn.capable_ext |= FUSE_CAP_NO_OPEN_SUPPORT;
+
2714 if (inargflags & FUSE_PARALLEL_DIROPS)
+
2715 se->conn.capable_ext |= FUSE_CAP_PARALLEL_DIROPS;
+
2716 if (inargflags & FUSE_POSIX_ACL)
+
2717 se->conn.capable_ext |= FUSE_CAP_POSIX_ACL;
+
2718 if (inargflags & FUSE_HANDLE_KILLPRIV)
+
2719 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV;
+
2720 if (inargflags & FUSE_HANDLE_KILLPRIV_V2)
+
2721 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV_V2;
+
2722 if (inargflags & FUSE_CACHE_SYMLINKS)
+
2723 se->conn.capable_ext |= FUSE_CAP_CACHE_SYMLINKS;
+
2724 if (inargflags & FUSE_NO_OPENDIR_SUPPORT)
+
2725 se->conn.capable_ext |= FUSE_CAP_NO_OPENDIR_SUPPORT;
+
2726 if (inargflags & FUSE_EXPLICIT_INVAL_DATA)
+
2727 se->conn.capable_ext |= FUSE_CAP_EXPLICIT_INVAL_DATA;
+
2728 if (inargflags & FUSE_SETXATTR_EXT)
+
2729 se->conn.capable_ext |= FUSE_CAP_SETXATTR_EXT;
+
2730 if (!(inargflags & FUSE_MAX_PAGES)) {
+
2731 size_t max_bufsize =
+
2732 FUSE_DEFAULT_MAX_PAGES_PER_REQ * getpagesize()
+
2733 + FUSE_BUFFER_HEADER_SIZE;
+
2734 if (bufsize > max_bufsize) {
+
2735 bufsize = max_bufsize;
+
2736 }
+
2737 buf_reallocable = false;
+
2738 }
+
2739 if (inargflags & FUSE_DIRECT_IO_ALLOW_MMAP)
+
2740 se->conn.capable_ext |= FUSE_CAP_DIRECT_IO_ALLOW_MMAP;
+
2741 if (arg->minor >= 38 || (inargflags & FUSE_HAS_EXPIRE_ONLY))
+
2742 se->conn.capable_ext |= FUSE_CAP_EXPIRE_ONLY;
+
2743 if (inargflags & FUSE_PASSTHROUGH)
+
2744 se->conn.capable_ext |= FUSE_CAP_PASSTHROUGH;
+
2745 if (inargflags & FUSE_NO_EXPORT_SUPPORT)
+
2746 se->conn.capable_ext |= FUSE_CAP_NO_EXPORT_SUPPORT;
+
2747 if (inargflags & FUSE_OVER_IO_URING)
+
2748 se->conn.capable_ext |= FUSE_CAP_OVER_IO_URING;
+
2749
+
2750 } else {
+
2751 se->conn.max_readahead = 0;
+
2752 }
+
2753
+
2754 if (se->conn.proto_minor >= 14) {
+
2755#ifdef HAVE_SPLICE
+
2756#ifdef HAVE_VMSPLICE
+
2757 if ((se->io == NULL) || (se->io->splice_send != NULL)) {
+
2758 se->conn.capable_ext |= FUSE_CAP_SPLICE_WRITE |
+ +
2760 }
+
2761#endif
+
2762 if ((se->io == NULL) || (se->io->splice_receive != NULL)) {
+
2763 se->conn.capable_ext |= FUSE_CAP_SPLICE_READ;
+
2764 }
+
2765#endif
+
2766 }
+
2767 if (se->conn.proto_minor >= 18)
+
2768 se->conn.capable_ext |= FUSE_CAP_IOCTL_DIR;
+
2769
+
2770 /* Default settings for modern filesystems.
+
2771 *
+
2772 * Most of these capabilities were disabled by default in
+
2773 * libfuse2 for backwards compatibility reasons. In libfuse3,
+
2774 * we can finally enable them by default (as long as they're
+
2775 * supported by the kernel).
+
2776 */
+
2777#define LL_SET_DEFAULT(cond, cap) \
+
2778 if ((cond)) \
+
2779 fuse_set_feature_flag(&se->conn, cap)
+
2780
+
2781 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_READ);
+
2782 LL_SET_DEFAULT(1, FUSE_CAP_AUTO_INVAL_DATA);
+
2783 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_DIO);
+
2784 LL_SET_DEFAULT(1, FUSE_CAP_IOCTL_DIR);
+
2785 LL_SET_DEFAULT(1, FUSE_CAP_ATOMIC_O_TRUNC);
+
2786 LL_SET_DEFAULT(se->op.write_buf, FUSE_CAP_SPLICE_READ);
+
2787 LL_SET_DEFAULT(se->op.getlk && se->op.setlk,
+ +
2789 LL_SET_DEFAULT(se->op.flock, FUSE_CAP_FLOCK_LOCKS);
+
2790 LL_SET_DEFAULT(se->op.readdirplus, FUSE_CAP_READDIRPLUS);
+
2791 LL_SET_DEFAULT(se->op.readdirplus && se->op.readdir,
+ +
2793 LL_SET_DEFAULT(1, FUSE_CAP_OVER_IO_URING);
+
2794
+
2795 /* This could safely become default, but libfuse needs an API extension
+
2796 * to support it
+
2797 * LL_SET_DEFAULT(1, FUSE_CAP_SETXATTR_EXT);
+
2798 */
+
2799
+
2800 se->conn.time_gran = 1;
+
2801
+
2802 if (se->op.init) {
+
2803 // Apply the first 32 bits of capable_ext to capable
+
2804 se->conn.capable = fuse_lower_32_bits(se->conn.capable_ext);
+
2805
+
2806 se->op.init(se->userdata, &se->conn);
+
2807
+
2808 /*
+
2809 * se->conn.want is 32-bit value and deprecated in favour of
+
2810 * se->conn.want_ext
+
2811 * Userspace might still use conn.want - we need to convert it
+
2812 */
+ +
2814 }
+
2815
+
2816 if (!want_flags_valid(se->conn.capable_ext, se->conn.want_ext)) {
+
2817 fuse_reply_err(req, EPROTO);
+
2818 se->error = -EPROTO;
+ +
2820 return;
+
2821 }
+
2822
+
2823 unsigned max_read_mo = get_max_read(se->mo);
+
2824 if (se->conn.max_read != max_read_mo) {
+
2825 fuse_log(FUSE_LOG_ERR, "fuse: error: init() and fuse_session_new() "
+
2826 "requested different maximum read size (%u vs %u)\n",
+
2827 se->conn.max_read, max_read_mo);
+
2828 fuse_reply_err(req, EPROTO);
+
2829 se->error = -EPROTO;
+ +
2831 return;
+
2832 }
+
2833
+
2834 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
2835 fuse_log(FUSE_LOG_ERR,
+
2836 "fuse: warning: buffer size too small: %zu\n",
+
2837 bufsize);
+
2838 bufsize = FUSE_MIN_READ_BUFFER;
+
2839 }
+
2840
+
2841 if (buf_reallocable)
+
2842 bufsize = UINT_MAX;
+
2843 se->conn.max_write = MIN(se->conn.max_write, bufsize - FUSE_BUFFER_HEADER_SIZE);
+
2844 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
2845
+
2846 if (arg->flags & FUSE_MAX_PAGES) {
+
2847 outarg.flags |= FUSE_MAX_PAGES;
+
2848 outarg.max_pages = (se->conn.max_write - 1) / getpagesize() + 1;
+
2849 }
+
2850 outargflags = outarg.flags;
+
2851 /* Always enable big writes, this is superseded
+
2852 by the max_write option */
+
2853 outargflags |= FUSE_BIG_WRITES;
+
2854
+
2855 if (se->conn.want_ext & FUSE_CAP_ASYNC_READ)
+
2856 outargflags |= FUSE_ASYNC_READ;
+
2857 if (se->conn.want_ext & FUSE_CAP_POSIX_LOCKS)
+
2858 outargflags |= FUSE_POSIX_LOCKS;
+
2859 if (se->conn.want_ext & FUSE_CAP_ATOMIC_O_TRUNC)
+
2860 outargflags |= FUSE_ATOMIC_O_TRUNC;
+
2861 if (se->conn.want_ext & FUSE_CAP_EXPORT_SUPPORT)
+
2862 outargflags |= FUSE_EXPORT_SUPPORT;
+
2863 if (se->conn.want_ext & FUSE_CAP_DONT_MASK)
+
2864 outargflags |= FUSE_DONT_MASK;
+
2865 if (se->conn.want_ext & FUSE_CAP_FLOCK_LOCKS)
+
2866 outargflags |= FUSE_FLOCK_LOCKS;
+
2867 if (se->conn.want_ext & FUSE_CAP_AUTO_INVAL_DATA)
+
2868 outargflags |= FUSE_AUTO_INVAL_DATA;
+
2869 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS)
+
2870 outargflags |= FUSE_DO_READDIRPLUS;
+
2871 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS_AUTO)
+
2872 outargflags |= FUSE_READDIRPLUS_AUTO;
+
2873 if (se->conn.want_ext & FUSE_CAP_ASYNC_DIO)
+
2874 outargflags |= FUSE_ASYNC_DIO;
+
2875 if (se->conn.want_ext & FUSE_CAP_WRITEBACK_CACHE)
+
2876 outargflags |= FUSE_WRITEBACK_CACHE;
+
2877 if (se->conn.want_ext & FUSE_CAP_PARALLEL_DIROPS)
+
2878 outargflags |= FUSE_PARALLEL_DIROPS;
+
2879 if (se->conn.want_ext & FUSE_CAP_POSIX_ACL)
+
2880 outargflags |= FUSE_POSIX_ACL;
+
2881 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV)
+
2882 outargflags |= FUSE_HANDLE_KILLPRIV;
+
2883 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2)
+
2884 outargflags |= FUSE_HANDLE_KILLPRIV_V2;
+
2885 if (se->conn.want_ext & FUSE_CAP_CACHE_SYMLINKS)
+
2886 outargflags |= FUSE_CACHE_SYMLINKS;
+
2887 if (se->conn.want_ext & FUSE_CAP_EXPLICIT_INVAL_DATA)
+
2888 outargflags |= FUSE_EXPLICIT_INVAL_DATA;
+
2889 if (se->conn.want_ext & FUSE_CAP_SETXATTR_EXT)
+
2890 outargflags |= FUSE_SETXATTR_EXT;
+
2891 if (se->conn.want_ext & FUSE_CAP_DIRECT_IO_ALLOW_MMAP)
+
2892 outargflags |= FUSE_DIRECT_IO_ALLOW_MMAP;
+
2893 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH) {
+
2894 outargflags |= FUSE_PASSTHROUGH;
+
2895 /*
+
2896 * outarg.max_stack_depth includes the fuse stack layer,
+
2897 * so it is one more than max_backing_stack_depth.
+
2898 */
+
2899 outarg.max_stack_depth = se->conn.max_backing_stack_depth + 1;
+
2900 }
+
2901 if (se->conn.want_ext & FUSE_CAP_NO_EXPORT_SUPPORT)
+
2902 outargflags |= FUSE_NO_EXPORT_SUPPORT;
+
2903 if (se->uring.enable && se->conn.want_ext & FUSE_CAP_OVER_IO_URING) {
+
2904 outargflags |= FUSE_OVER_IO_URING;
+
2905 enable_io_uring = true;
+
2906 }
+
2907
+
2908 if ((inargflags & FUSE_REQUEST_TIMEOUT) && se->conn.request_timeout) {
+
2909 outargflags |= FUSE_REQUEST_TIMEOUT;
+
2910 outarg.request_timeout = se->conn.request_timeout;
+
2911 }
+
2912
+
2913 outarg.max_readahead = se->conn.max_readahead;
+
2914 outarg.max_write = se->conn.max_write;
+
2915 if (se->conn.proto_minor >= 13) {
+
2916 if (se->conn.max_background >= (1 << 16))
+
2917 se->conn.max_background = (1 << 16) - 1;
+
2918 if (se->conn.congestion_threshold > se->conn.max_background)
+
2919 se->conn.congestion_threshold = se->conn.max_background;
+
2920 if (!se->conn.congestion_threshold) {
+
2921 se->conn.congestion_threshold =
+
2922 se->conn.max_background * 3 / 4;
+
2923 }
+
2924
+
2925 outarg.max_background = se->conn.max_background;
+
2926 outarg.congestion_threshold = se->conn.congestion_threshold;
+
2927 }
+
2928 if (se->conn.proto_minor >= 23)
+
2929 outarg.time_gran = se->conn.time_gran;
+
2930
+
2931 if (se->debug) {
+
2932 fuse_log(FUSE_LOG_DEBUG, " INIT: %u.%u\n", outarg.major, outarg.minor);
+
2933 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
2934 fuse_log(FUSE_LOG_DEBUG, " max_readahead=0x%08x\n",
+
2935 outarg.max_readahead);
+
2936 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
2937 fuse_log(FUSE_LOG_DEBUG, " max_background=%i\n",
+
2938 outarg.max_background);
+
2939 fuse_log(FUSE_LOG_DEBUG, " congestion_threshold=%i\n",
+
2940 outarg.congestion_threshold);
+
2941 fuse_log(FUSE_LOG_DEBUG, " time_gran=%u\n",
+
2942 outarg.time_gran);
+
2943 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH)
+
2944 fuse_log(FUSE_LOG_DEBUG, " max_stack_depth=%u\n",
+
2945 outarg.max_stack_depth);
+
2946 }
+
2947 if (arg->minor < 5)
+
2948 outargsize = FUSE_COMPAT_INIT_OUT_SIZE;
+
2949 else if (arg->minor < 23)
+
2950 outargsize = FUSE_COMPAT_22_INIT_OUT_SIZE;
+
2951
+
2952 /* XXX: Add an option to make non-available io-uring fatal */
+
2953 if (enable_io_uring) {
+
2954 int ring_rc = fuse_uring_start(se);
+
2955
+
2956 if (ring_rc != 0) {
+
2957 fuse_log(FUSE_LOG_INFO,
+
2958 "fuse: failed to start io-uring: %s\n",
+
2959 strerror(ring_rc));
+
2960 outargflags &= ~FUSE_OVER_IO_URING;
+
2961 enable_io_uring = false;
+
2962 }
+
2963 }
+
2964
+
2965 if (inargflags & FUSE_INIT_EXT) {
+
2966 outargflags |= FUSE_INIT_EXT;
+
2967 outarg.flags2 = outargflags >> 32;
+
2968 }
+
2969 outarg.flags = outargflags;
+
2970
+
2971 /*
+
2972 * Has to be set before replying, as new kernel requests might
+
2973 * immediately arrive and got_init is used for op-code sanity.
+
2974 * Especially with external handlers, where we have no control
+
2975 * over the thread scheduling.
+
2976 */
+
2977 se->got_init = 1;
+
2978 send_reply_ok(req, &outarg, outargsize);
+
2979 if (enable_io_uring)
+
2980 fuse_uring_wake_ring_threads(se);
+
2981}
+
2982
+
2983static __attribute__((no_sanitize("thread"))) void
+
2984do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2985{
+
2986 _do_init(req, nodeid, inarg, NULL);
+
2987}
+
2988
+
2989static void _do_destroy(fuse_req_t req, const fuse_ino_t nodeid,
+
2990 const void *op_in, const void *in_payload)
+
2991{
+
2992 struct fuse_session *se = req->se;
+
2993 char *mountpoint;
+
2994
+
2995 (void) nodeid;
+
2996 (void)op_in;
+
2997 (void)in_payload;
+
2998
+
2999 mountpoint = atomic_exchange(&se->mountpoint, NULL);
+
3000 free(mountpoint);
+
3001
+
3002 se->got_destroy = 1;
+
3003 se->got_init = 0;
+
3004 if (se->op.destroy)
+
3005 se->op.destroy(se->userdata);
+
3006
+
3007 send_reply_ok(req, NULL, 0);
+
3008}
+
3009
+
3010static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
3011{
+
3012 _do_destroy(req, nodeid, inarg, NULL);
+
3013}
+
3014
+
3015static void list_del_nreq(struct fuse_notify_req *nreq)
+
3016{
+
3017 struct fuse_notify_req *prev = nreq->prev;
+
3018 struct fuse_notify_req *next = nreq->next;
+
3019 prev->next = next;
+
3020 next->prev = prev;
+
3021}
+
3022
+
3023static void list_add_nreq(struct fuse_notify_req *nreq,
+
3024 struct fuse_notify_req *next)
+
3025{
+
3026 struct fuse_notify_req *prev = next->prev;
+
3027 nreq->next = next;
+
3028 nreq->prev = prev;
+
3029 prev->next = nreq;
+
3030 next->prev = nreq;
+
3031}
+
3032
+
3033static void list_init_nreq(struct fuse_notify_req *nreq)
+
3034{
+
3035 nreq->next = nreq;
+
3036 nreq->prev = nreq;
+
3037}
+
3038
+
3039static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid,
+
3040 const void *inarg, const struct fuse_buf *buf)
+
3041{
+
3042 struct fuse_session *se = req->se;
+
3043 struct fuse_notify_req *nreq;
+
3044 struct fuse_notify_req *head;
+
3045
+
3046 pthread_mutex_lock(&se->lock);
+
3047 head = &se->notify_list;
+
3048 for (nreq = head->next; nreq != head; nreq = nreq->next) {
+
3049 if (nreq->unique == req->unique) {
+
3050 list_del_nreq(nreq);
+
3051 break;
+
3052 }
+
3053 }
+
3054 pthread_mutex_unlock(&se->lock);
+
3055
+
3056 if (nreq != head)
+
3057 nreq->reply(nreq, req, nodeid, inarg, buf);
+
3058}
+
3059
+
3060static int send_notify_iov(struct fuse_session *se, int notify_code,
+
3061 struct iovec *iov, int count)
+
3062{
+
3063 struct fuse_out_header out;
+
3064 struct fuse_req *req = NULL;
+
3065
+
3066 if (!se->got_init)
+
3067 return -ENOTCONN;
+
3068
+
3069 out.unique = 0;
+
3070 out.error = notify_code;
+
3071 iov[0].iov_base = &out;
+
3072 iov[0].iov_len = sizeof(struct fuse_out_header);
+
3073
+
3074 return fuse_send_msg(se, NULL, iov, count, req);
+
3075}
+
3076
+
3077int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
3078{
+
3079 if (ph != NULL) {
+
3080 struct fuse_notify_poll_wakeup_out outarg;
+
3081 struct iovec iov[2];
+
3082
+
3083 outarg.kh = ph->kh;
+
3084
+
3085 iov[1].iov_base = &outarg;
+
3086 iov[1].iov_len = sizeof(outarg);
+
3087
+
3088 return send_notify_iov(ph->se, FUSE_NOTIFY_POLL, iov, 2);
+
3089 } else {
+
3090 return 0;
+
3091 }
+
3092}
+
3093
+
3094int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
3095 off_t off, off_t len)
+
3096{
+
3097 struct fuse_notify_inval_inode_out outarg;
+
3098 struct iovec iov[2];
+
3099
+
3100 if (!se)
+
3101 return -EINVAL;
+
3102
+
3103 if (se->conn.proto_minor < 12)
+
3104 return -ENOSYS;
+
3105
+
3106 outarg.ino = ino;
+
3107 outarg.off = off;
+
3108 outarg.len = len;
+
3109
+
3110 iov[1].iov_base = &outarg;
+
3111 iov[1].iov_len = sizeof(outarg);
+
3112
+
3113 return send_notify_iov(se, FUSE_NOTIFY_INVAL_INODE, iov, 2);
+
3114}
+
3115
+
3116int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
+
3117{
+
3118 struct iovec iov[1];
+
3119
+
3120 if (!se)
+
3121 return -EINVAL;
+
3122
+
3123 if (se->conn.proto_minor < 44)
+
3124 return -ENOSYS;
+
3125
+
3126 return send_notify_iov(se, FUSE_NOTIFY_INC_EPOCH, iov, 1);
+
3127}
+
3128
+
3148static int fuse_lowlevel_notify_entry(struct fuse_session *se, fuse_ino_t parent,
+
3149 const char *name, size_t namelen,
+
3150 enum fuse_notify_entry_flags flags)
+
3151{
+
3152 struct fuse_notify_inval_entry_out outarg;
+
3153 struct iovec iov[3];
+
3154
+
3155 if (!se)
+
3156 return -EINVAL;
+
3157
+
3158 if (se->conn.proto_minor < 12)
+
3159 return -ENOSYS;
+
3160
+
3161 outarg.parent = parent;
+
3162 outarg.namelen = namelen;
+
3163 outarg.flags = 0;
+
3164 if (flags & FUSE_LL_EXPIRE_ONLY)
+
3165 outarg.flags |= FUSE_EXPIRE_ONLY;
+
3166
+
3167 iov[1].iov_base = &outarg;
+
3168 iov[1].iov_len = sizeof(outarg);
+
3169 iov[2].iov_base = (void *)name;
+
3170 iov[2].iov_len = namelen + 1;
+
3171
+
3172 return send_notify_iov(se, FUSE_NOTIFY_INVAL_ENTRY, iov, 3);
+
3173}
+
3174
+
3175int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
3176 const char *name, size_t namelen)
+
3177{
+
3178 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_INVALIDATE);
+
3179}
+
3180
+
3181int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
3182 const char *name, size_t namelen)
+
3183{
+
3184 if (!se)
+
3185 return -EINVAL;
+
3186
+
3187 if (!(se->conn.capable_ext & FUSE_CAP_EXPIRE_ONLY))
+
3188 return -ENOSYS;
+
3189
+
3190 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_EXPIRE_ONLY);
+
3191}
+
3192
+
3193
+
3194int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
3195 fuse_ino_t parent, fuse_ino_t child,
+
3196 const char *name, size_t namelen)
+
3197{
+
3198 struct fuse_notify_delete_out outarg;
+
3199 struct iovec iov[3];
+
3200
+
3201 if (!se)
+
3202 return -EINVAL;
+
3203
+
3204 if (se->conn.proto_minor < 18)
+
3205 return -ENOSYS;
+
3206
+
3207 outarg.parent = parent;
+
3208 outarg.child = child;
+
3209 outarg.namelen = namelen;
+
3210 outarg.padding = 0;
+
3211
+
3212 iov[1].iov_base = &outarg;
+
3213 iov[1].iov_len = sizeof(outarg);
+
3214 iov[2].iov_base = (void *)name;
+
3215 iov[2].iov_len = namelen + 1;
+
3216
+
3217 return send_notify_iov(se, FUSE_NOTIFY_DELETE, iov, 3);
+
3218}
+
3219
+
3220int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
3221 off_t offset, struct fuse_bufvec *bufv,
+
3222 enum fuse_buf_copy_flags flags)
+
3223{
+
3224 struct fuse_out_header out;
+
3225 struct fuse_notify_store_out outarg;
+
3226 struct iovec iov[3];
+
3227 size_t size = fuse_buf_size(bufv);
+
3228 int res;
+
3229 struct fuse_req *req = NULL;
+
3230
+
3231 if (!se)
+
3232 return -EINVAL;
+
3233
+
3234 if (se->conn.proto_minor < 15)
+
3235 return -ENOSYS;
+
3236
+
3237 out.unique = 0;
+
3238 out.error = FUSE_NOTIFY_STORE;
+
3239
+
3240 outarg.nodeid = ino;
+
3241 outarg.offset = offset;
+
3242 outarg.size = size;
+
3243 outarg.padding = 0;
+
3244
+
3245 iov[0].iov_base = &out;
+
3246 iov[0].iov_len = sizeof(out);
+
3247 iov[1].iov_base = &outarg;
+
3248 iov[1].iov_len = sizeof(outarg);
+
3249
+
3250 res = fuse_send_data_iov(se, NULL, iov, 2, bufv, flags, req);
+
3251 if (res > 0)
+
3252 res = -res;
+
3253
+
3254 return res;
+
3255}
+
3256
+
3257struct fuse_retrieve_req {
+
3258 struct fuse_notify_req nreq;
+
3259 void *cookie;
+
3260};
+
3261
+
3262static void fuse_ll_retrieve_reply(struct fuse_notify_req *nreq,
+
3263 fuse_req_t req, fuse_ino_t ino,
+
3264 const void *inarg,
+
3265 const struct fuse_buf *ibuf)
+
3266{
+
3267 struct fuse_session *se = req->se;
+
3268 struct fuse_retrieve_req *rreq =
+
3269 container_of(nreq, struct fuse_retrieve_req, nreq);
+
3270 const struct fuse_notify_retrieve_in *arg = inarg;
+
3271 struct fuse_bufvec bufv = {
+
3272 .buf[0] = *ibuf,
+
3273 .count = 1,
+
3274 };
+
3275
+
3276 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
3277 bufv.buf[0].mem = PARAM(arg);
+
3278
+
3279 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
3280 sizeof(struct fuse_notify_retrieve_in);
+
3281
+
3282 if (bufv.buf[0].size < arg->size) {
+
3283 fuse_log(FUSE_LOG_ERR, "fuse: retrieve reply: buffer size too small\n");
+
3284 fuse_reply_none(req);
+
3285 goto out;
+
3286 }
+
3287 bufv.buf[0].size = arg->size;
+
3288
+
3289 if (se->op.retrieve_reply) {
+
3290 se->op.retrieve_reply(req, rreq->cookie, ino,
+
3291 arg->offset, &bufv);
+
3292 } else {
+
3293 fuse_reply_none(req);
+
3294 }
+
3295out:
+
3296 free(rreq);
+
3297 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
3298 fuse_ll_clear_pipe(se);
+
3299}
+
3300
+
3301int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
3302 size_t size, off_t offset, void *cookie)
+
3303{
+
3304 struct fuse_notify_retrieve_out outarg;
+
3305 struct iovec iov[2];
+
3306 struct fuse_retrieve_req *rreq;
+
3307 int err;
+
3308
+
3309 if (!se)
+
3310 return -EINVAL;
+
3311
+
3312 if (se->conn.proto_minor < 15)
+
3313 return -ENOSYS;
+
3314
+
3315 rreq = malloc(sizeof(*rreq));
+
3316 if (rreq == NULL)
+
3317 return -ENOMEM;
+
3318
+
3319 pthread_mutex_lock(&se->lock);
+
3320 rreq->cookie = cookie;
+
3321 rreq->nreq.unique = se->notify_ctr++;
+
3322 rreq->nreq.reply = fuse_ll_retrieve_reply;
+
3323 list_add_nreq(&rreq->nreq, &se->notify_list);
+
3324 pthread_mutex_unlock(&se->lock);
+
3325
+
3326 outarg.notify_unique = rreq->nreq.unique;
+
3327 outarg.nodeid = ino;
+
3328 outarg.offset = offset;
+
3329 outarg.size = size;
+
3330 outarg.padding = 0;
+
3331
+
3332 iov[1].iov_base = &outarg;
+
3333 iov[1].iov_len = sizeof(outarg);
+
3334
+
3335 err = send_notify_iov(se, FUSE_NOTIFY_RETRIEVE, iov, 2);
+
3336 if (err) {
+
3337 pthread_mutex_lock(&se->lock);
+
3338 list_del_nreq(&rreq->nreq);
+
3339 pthread_mutex_unlock(&se->lock);
+
3340 free(rreq);
+
3341 }
+
3342
+
3343 return err;
+
3344}
+
3345
+ +
3347{
+
3348 return req->se->userdata;
+
3349}
+
3350
+
3351const struct fuse_ctx *fuse_req_ctx(fuse_req_t req)
+
3352{
+
3353 return &req->ctx;
+
3354}
+
3355
+ +
3357 void *data)
+
3358{
+
3359 pthread_mutex_lock(&req->lock);
+
3360 pthread_mutex_lock(&req->se->lock);
+
3361 req->u.ni.func = func;
+
3362 req->u.ni.data = data;
+
3363 pthread_mutex_unlock(&req->se->lock);
+
3364 if (req->interrupted && func)
+
3365 func(req, data);
+
3366 pthread_mutex_unlock(&req->lock);
+
3367}
+
3368
+ +
3370{
+
3371 int interrupted;
+
3372
+
3373 pthread_mutex_lock(&req->se->lock);
+
3374 interrupted = req->interrupted;
+
3375 pthread_mutex_unlock(&req->se->lock);
+
3376
+
3377 return interrupted;
+
3378}
+
3379
+ +
3381{
+
3382 return req->flags.is_uring;
+
3383}
+
3384
+
3385#ifndef HAVE_URING
+
3386int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz,
+
3387 void **mr)
+
3388{
+
3389 (void)req;
+
3390 (void)payload;
+
3391 (void)payload_sz;
+
3392 (void)mr;
+
3393 return -ENOTSUP;
+
3394}
+
3395#endif
+
3396
+
3397static struct {
+
3398 void (*func)(fuse_req_t req, const fuse_ino_t node, const void *arg);
+
3399 const char *name;
+
3400} fuse_ll_ops[] = {
+
3401 [FUSE_LOOKUP] = { do_lookup, "LOOKUP" },
+
3402 [FUSE_FORGET] = { do_forget, "FORGET" },
+
3403 [FUSE_GETATTR] = { do_getattr, "GETATTR" },
+
3404 [FUSE_SETATTR] = { do_setattr, "SETATTR" },
+
3405 [FUSE_READLINK] = { do_readlink, "READLINK" },
+
3406 [FUSE_SYMLINK] = { do_symlink, "SYMLINK" },
+
3407 [FUSE_MKNOD] = { do_mknod, "MKNOD" },
+
3408 [FUSE_MKDIR] = { do_mkdir, "MKDIR" },
+
3409 [FUSE_UNLINK] = { do_unlink, "UNLINK" },
+
3410 [FUSE_RMDIR] = { do_rmdir, "RMDIR" },
+
3411 [FUSE_RENAME] = { do_rename, "RENAME" },
+
3412 [FUSE_LINK] = { do_link, "LINK" },
+
3413 [FUSE_OPEN] = { do_open, "OPEN" },
+
3414 [FUSE_READ] = { do_read, "READ" },
+
3415 [FUSE_WRITE] = { do_write, "WRITE" },
+
3416 [FUSE_STATFS] = { do_statfs, "STATFS" },
+
3417 [FUSE_RELEASE] = { do_release, "RELEASE" },
+
3418 [FUSE_FSYNC] = { do_fsync, "FSYNC" },
+
3419 [FUSE_SETXATTR] = { do_setxattr, "SETXATTR" },
+
3420 [FUSE_GETXATTR] = { do_getxattr, "GETXATTR" },
+
3421 [FUSE_LISTXATTR] = { do_listxattr, "LISTXATTR" },
+
3422 [FUSE_REMOVEXATTR] = { do_removexattr, "REMOVEXATTR" },
+
3423 [FUSE_FLUSH] = { do_flush, "FLUSH" },
+
3424 [FUSE_INIT] = { do_init, "INIT" },
+
3425 [FUSE_OPENDIR] = { do_opendir, "OPENDIR" },
+
3426 [FUSE_READDIR] = { do_readdir, "READDIR" },
+
3427 [FUSE_RELEASEDIR] = { do_releasedir, "RELEASEDIR" },
+
3428 [FUSE_FSYNCDIR] = { do_fsyncdir, "FSYNCDIR" },
+
3429 [FUSE_GETLK] = { do_getlk, "GETLK" },
+
3430 [FUSE_SETLK] = { do_setlk, "SETLK" },
+
3431 [FUSE_SETLKW] = { do_setlkw, "SETLKW" },
+
3432 [FUSE_ACCESS] = { do_access, "ACCESS" },
+
3433 [FUSE_CREATE] = { do_create, "CREATE" },
+
3434 [FUSE_TMPFILE] = { do_tmpfile, "TMPFILE" },
+
3435 [FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" },
+
3436 [FUSE_BMAP] = { do_bmap, "BMAP" },
+
3437 [FUSE_IOCTL] = { do_ioctl, "IOCTL" },
+
3438 [FUSE_POLL] = { do_poll, "POLL" },
+
3439 [FUSE_FALLOCATE] = { do_fallocate, "FALLOCATE" },
+
3440 [FUSE_DESTROY] = { do_destroy, "DESTROY" },
+
3441 [FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" },
+
3442 [FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" },
+
3443 [FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"},
+
3444 [FUSE_RENAME2] = { do_rename2, "RENAME2" },
+
3445 [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" },
+
3446 [FUSE_COPY_FILE_RANGE_64] = { do_copy_file_range_64, "COPY_FILE_RANGE_64" },
+
3447 [FUSE_LSEEK] = { do_lseek, "LSEEK" },
+
3448 [FUSE_STATX] = { do_statx, "STATX" },
+
3449 [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" },
+
3450};
+
3451
+
3452static struct {
+
3453 void (*func)(fuse_req_t req, const fuse_ino_t ino, const void *op_in,
+
3454 const void *op_payload);
+
3455 const char *name;
+
3456} fuse_ll_ops2[] __attribute__((unused)) = {
+
3457 [FUSE_LOOKUP] = { _do_lookup, "LOOKUP" },
+
3458 [FUSE_FORGET] = { _do_forget, "FORGET" },
+
3459 [FUSE_GETATTR] = { _do_getattr, "GETATTR" },
+
3460 [FUSE_SETATTR] = { _do_setattr, "SETATTR" },
+
3461 [FUSE_READLINK] = { _do_readlink, "READLINK" },
+
3462 [FUSE_SYMLINK] = { _do_symlink, "SYMLINK" },
+
3463 [FUSE_MKNOD] = { _do_mknod, "MKNOD" },
+
3464 [FUSE_MKDIR] = { _do_mkdir, "MKDIR" },
+
3465 [FUSE_UNLINK] = { _do_unlink, "UNLINK" },
+
3466 [FUSE_RMDIR] = { _do_rmdir, "RMDIR" },
+
3467 [FUSE_RENAME] = { _do_rename, "RENAME" },
+
3468 [FUSE_LINK] = { _do_link, "LINK" },
+
3469 [FUSE_OPEN] = { _do_open, "OPEN" },
+
3470 [FUSE_READ] = { _do_read, "READ" },
+
3471 [FUSE_WRITE] = { _do_write, "WRITE" },
+
3472 [FUSE_STATFS] = { _do_statfs, "STATFS" },
+
3473 [FUSE_RELEASE] = { _do_release, "RELEASE" },
+
3474 [FUSE_FSYNC] = { _do_fsync, "FSYNC" },
+
3475 [FUSE_SETXATTR] = { _do_setxattr, "SETXATTR" },
+
3476 [FUSE_GETXATTR] = { _do_getxattr, "GETXATTR" },
+
3477 [FUSE_LISTXATTR] = { _do_listxattr, "LISTXATTR" },
+
3478 [FUSE_REMOVEXATTR] = { _do_removexattr, "REMOVEXATTR" },
+
3479 [FUSE_FLUSH] = { _do_flush, "FLUSH" },
+
3480 [FUSE_INIT] = { _do_init, "INIT" },
+
3481 [FUSE_OPENDIR] = { _do_opendir, "OPENDIR" },
+
3482 [FUSE_READDIR] = { _do_readdir, "READDIR" },
+
3483 [FUSE_RELEASEDIR] = { _do_releasedir, "RELEASEDIR" },
+
3484 [FUSE_FSYNCDIR] = { _do_fsyncdir, "FSYNCDIR" },
+
3485 [FUSE_GETLK] = { _do_getlk, "GETLK" },
+
3486 [FUSE_SETLK] = { _do_setlk, "SETLK" },
+
3487 [FUSE_SETLKW] = { _do_setlkw, "SETLKW" },
+
3488 [FUSE_ACCESS] = { _do_access, "ACCESS" },
+
3489 [FUSE_CREATE] = { _do_create, "CREATE" },
+
3490 [FUSE_TMPFILE] = { _do_tmpfile, "TMPFILE" },
+
3491 [FUSE_INTERRUPT] = { _do_interrupt, "INTERRUPT" },
+
3492 [FUSE_BMAP] = { _do_bmap, "BMAP" },
+
3493 [FUSE_IOCTL] = { _do_ioctl, "IOCTL" },
+
3494 [FUSE_POLL] = { _do_poll, "POLL" },
+
3495 [FUSE_FALLOCATE] = { _do_fallocate, "FALLOCATE" },
+
3496 [FUSE_DESTROY] = { _do_destroy, "DESTROY" },
+
3497 [FUSE_NOTIFY_REPLY] = { (void *)1, "NOTIFY_REPLY" },
+
3498 [FUSE_BATCH_FORGET] = { _do_batch_forget, "BATCH_FORGET" },
+
3499 [FUSE_READDIRPLUS] = { _do_readdirplus, "READDIRPLUS" },
+
3500 [FUSE_RENAME2] = { _do_rename2, "RENAME2" },
+
3501 [FUSE_COPY_FILE_RANGE] = { _do_copy_file_range, "COPY_FILE_RANGE" },
+
3502 [FUSE_COPY_FILE_RANGE_64] = { _do_copy_file_range_64, "COPY_FILE_RANGE_64" },
+
3503 [FUSE_LSEEK] = { _do_lseek, "LSEEK" },
+
3504 [FUSE_STATX] = { _do_statx, "STATX" },
+
3505 [CUSE_INIT] = { _cuse_lowlevel_init, "CUSE_INIT" },
+
3506};
+
3507
+
3508/*
+
3509 * For ABI compatibility we cannot allow higher values than CUSE_INIT.
+
3510 * Without ABI compatibility we could use the size of the array.
+
3511 * #define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
+
3512 */
+
3513#define FUSE_MAXOP (CUSE_INIT + 1)
+
3514
+
3515
+
3520static inline int
+
3521fuse_req_opcode_sanity_ok(struct fuse_session *se, enum fuse_opcode in_op)
+
3522{
+
3523 int err = EIO;
+
3524
+
3525 if (!se->got_init) {
+
3526 enum fuse_opcode expected;
+
3527
+
3528 expected = se->cuse_data ? CUSE_INIT : FUSE_INIT;
+
3529 if (in_op != expected)
+
3530 return err;
+
3531 } else if (in_op == FUSE_INIT || in_op == CUSE_INIT)
+
3532 return err;
+
3533
+
3534 return 0;
+
3535}
+
3536
+
3537static inline void
+
3538fuse_session_in2req(struct fuse_req *req, struct fuse_in_header *in)
+
3539{
+
3540 req->unique = in->unique;
+
3541 req->ctx.uid = in->uid;
+
3542 req->ctx.gid = in->gid;
+
3543 req->ctx.pid = in->pid;
+
3544}
+
3545
+
3549static inline int
+
3550fuse_req_check_allow_root(struct fuse_session *se, enum fuse_opcode in_op,
+
3551 uid_t in_uid)
+
3552{
+
3553 int err = EACCES;
+
3554
+
3555 if (se->deny_others && in_uid != se->owner && in_uid != 0 &&
+
3556 in_op != FUSE_INIT && in_op != FUSE_READ &&
+
3557 in_op != FUSE_WRITE && in_op != FUSE_FSYNC &&
+
3558 in_op != FUSE_RELEASE && in_op != FUSE_READDIR &&
+
3559 in_op != FUSE_FSYNCDIR && in_op != FUSE_RELEASEDIR &&
+
3560 in_op != FUSE_NOTIFY_REPLY &&
+
3561 in_op != FUSE_READDIRPLUS)
+
3562 return err;
+
3563
+
3564 return 0;
+
3565}
+
3566
+
3567static const char *opname(enum fuse_opcode opcode)
+
3568{
+
3569 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
+
3570 return "???";
+
3571 else
+
3572 return fuse_ll_ops[opcode].name;
+
3573}
+
3574
+
3575static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst,
+
3576 struct fuse_bufvec *src)
+
3577{
+
3578 ssize_t res = fuse_buf_copy(dst, src, 0);
+
3579 if (res < 0) {
+
3580 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n", strerror(-res));
+
3581 return res;
+
3582 }
+
3583 if ((size_t)res < fuse_buf_size(dst)) {
+
3584 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: short read\n");
+
3585 return -1;
+
3586 }
+
3587 return 0;
+
3588}
+
3589
+
3590void fuse_session_process_buf(struct fuse_session *se,
+
3591 const struct fuse_buf *buf)
+
3592{
+
3593 fuse_session_process_buf_internal(se, buf, NULL);
+
3594}
+
3595
+
3596/* libfuse internal handler */
+
3597void fuse_session_process_buf_internal(struct fuse_session *se,
+
3598 const struct fuse_buf *buf, struct fuse_chan *ch)
+
3599{
+
3600 const size_t write_header_size = sizeof(struct fuse_in_header) +
+
3601 sizeof(struct fuse_write_in);
+
3602 struct fuse_bufvec bufv = { .buf[0] = *buf, .count = 1 };
+
3603 struct fuse_bufvec tmpbuf = FUSE_BUFVEC_INIT(write_header_size);
+
3604 struct fuse_in_header *in;
+
3605 const void *inarg;
+
3606 struct fuse_req *req;
+
3607 void *mbuf = NULL;
+
3608 int err;
+
3609 int res;
+
3610
+
3611 if (buf->flags & FUSE_BUF_IS_FD) {
+
3612 if (buf->size < tmpbuf.buf[0].size)
+
3613 tmpbuf.buf[0].size = buf->size;
+
3614
+
3615 mbuf = malloc(tmpbuf.buf[0].size);
+
3616 if (mbuf == NULL) {
+
3617 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate header\n");
+
3618 goto clear_pipe;
+
3619 }
+
3620 tmpbuf.buf[0].mem = mbuf;
+
3621
+
3622 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
3623 if (res < 0)
+
3624 goto clear_pipe;
+
3625
+
3626 in = mbuf;
+
3627 } else {
+
3628 in = buf->mem;
+
3629 }
+
3630
+
3631 trace_request_process(in->opcode, in->unique);
+
3632
+
3633 if (se->debug) {
+
3634 fuse_log(FUSE_LOG_DEBUG,
+
3635 "dev unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n",
+
3636 (unsigned long long) in->unique,
+
3637 opname((enum fuse_opcode) in->opcode), in->opcode,
+
3638 (unsigned long long) in->nodeid, buf->size, in->pid);
+
3639 }
+
3640
+
3641 req = fuse_ll_alloc_req(se);
+
3642 if (req == NULL) {
+
3643 struct fuse_out_header out = {
+
3644 .unique = in->unique,
+
3645 .error = -ENOMEM,
+
3646 };
+
3647 struct iovec iov = {
+
3648 .iov_base = &out,
+
3649 .iov_len = sizeof(struct fuse_out_header),
+
3650 };
+
3651
+
3652 fuse_send_msg(se, ch, &iov, 1, NULL);
+
3653 goto clear_pipe;
+
3654 }
+
3655
+
3656 fuse_session_in2req(req, in);
+
3657 req->ch = ch ? fuse_chan_get(ch) : NULL;
+
3658
+
3659 err = fuse_req_opcode_sanity_ok(se, in->opcode);
+
3660 if (err)
+
3661 goto reply_err;
+
3662
+
3663 err = fuse_req_check_allow_root(se, in->opcode, in->uid);
+
3664 if (err)
+
3665 goto reply_err;
+
3666
+
3667 err = ENOSYS;
+
3668 if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func)
+
3669 goto reply_err;
+
3670 /* Do not process interrupt request */
+
3671 if (se->conn.no_interrupt && in->opcode == FUSE_INTERRUPT) {
+
3672 if (se->debug)
+
3673 fuse_log(FUSE_LOG_DEBUG, "FUSE_INTERRUPT: reply to kernel to disable interrupt\n");
+
3674 goto reply_err;
+
3675 }
+
3676 if (!se->conn.no_interrupt && in->opcode != FUSE_INTERRUPT) {
+
3677 struct fuse_req *intr;
+
3678 pthread_mutex_lock(&se->lock);
+
3679 intr = check_interrupt(se, req);
+
3680 list_add_req(req, &se->list);
+
3681 pthread_mutex_unlock(&se->lock);
+
3682 if (intr)
+
3683 fuse_reply_err(intr, EAGAIN);
+
3684 }
+
3685
+
3686 if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size &&
+
3687 (in->opcode != FUSE_WRITE || !se->op.write_buf) &&
+
3688 in->opcode != FUSE_NOTIFY_REPLY) {
+
3689 void *newmbuf;
+
3690
+
3691 err = ENOMEM;
+
3692 newmbuf = realloc(mbuf, buf->size);
+
3693 if (newmbuf == NULL)
+
3694 goto reply_err;
+
3695 mbuf = newmbuf;
+
3696
+
3697 tmpbuf = FUSE_BUFVEC_INIT(buf->size - write_header_size);
+
3698 tmpbuf.buf[0].mem = (char *)mbuf + write_header_size;
+
3699
+
3700 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
3701 err = -res;
+
3702 if (res < 0)
+
3703 goto reply_err;
+
3704
+
3705 in = mbuf;
+
3706 }
+
3707
+
3708 inarg = (void *) &in[1];
+
3709 if (in->opcode == FUSE_WRITE && se->op.write_buf)
+
3710 do_write_buf(req, in->nodeid, inarg, buf);
+
3711 else if (in->opcode == FUSE_NOTIFY_REPLY)
+
3712 do_notify_reply(req, in->nodeid, inarg, buf);
+
3713 else
+
3714 fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg);
+
3715
+
3716out_free:
+
3717 free(mbuf);
+
3718 return;
+
3719
+
3720reply_err:
+
3721 fuse_reply_err(req, err);
+
3722clear_pipe:
+
3723 if (buf->flags & FUSE_BUF_IS_FD)
+
3724 fuse_ll_clear_pipe(se);
+
3725 goto out_free;
+
3726}
+
3727
+
3728void fuse_session_process_uring_cqe(struct fuse_session *se,
+
3729 struct fuse_req *req,
+
3730 struct fuse_in_header *in, void *op_in,
+
3731 void *op_payload, size_t payload_len)
+
3732{
+
3733 int err;
+
3734
+
3735 fuse_session_in2req(req, in);
+
3736
+
3737 err = fuse_req_opcode_sanity_ok(se, in->opcode);
+
3738 if (err)
+
3739 goto reply_err;
+
3740
+
3741 err = fuse_req_check_allow_root(se, in->opcode, in->uid);
+
3742 if (err)
+
3743 goto reply_err;
+
3744
+
3745 err = ENOSYS;
+
3746 if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func)
+
3747 goto reply_err;
+
3748
+
3749 if (se->debug) {
+
3750 fuse_log(
+
3751 FUSE_LOG_DEBUG,
+
3752 "cqe unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n",
+
3753 (unsigned long long)in->unique,
+
3754 opname((enum fuse_opcode)in->opcode), in->opcode,
+
3755 (unsigned long long)in->nodeid, payload_len, in->pid);
+
3756 }
+
3757
+
3758 if (in->opcode == FUSE_WRITE && se->op.write_buf) {
+
3759 struct fuse_bufvec bufv = {
+
3760 .buf[0] = { .size = payload_len,
+
3761 .flags = 0,
+
3762 .mem = op_payload },
+
3763 .count = 1,
+
3764 };
+
3765 _do_write_buf(req, in->nodeid, op_in, &bufv);
+
3766 } else if (in->opcode == FUSE_NOTIFY_REPLY) {
+
3767 struct fuse_buf buf = { .size = payload_len,
+
3768 .mem = op_payload };
+
3769 do_notify_reply(req, in->nodeid, op_in, &buf);
+
3770 } else {
+
3771 fuse_ll_ops2[in->opcode].func(req, in->nodeid, op_in,
+
3772 op_payload);
+
3773 }
+
3774
+
3775 return;
+
3776
+
3777reply_err:
+
3778 fuse_reply_err(req, err);
+
3779}
+
3780
+
3781#define LL_OPTION(n,o,v) \
+
3782 { n, offsetof(struct fuse_session, o), v }
+
3783
+
3784static const struct fuse_opt fuse_ll_opts[] = {
+
3785 LL_OPTION("debug", debug, 1),
+
3786 LL_OPTION("-d", debug, 1),
+
3787 LL_OPTION("--debug", debug, 1),
+
3788 LL_OPTION("allow_root", deny_others, 1),
+
3789 LL_OPTION("io_uring", uring.enable, 1),
+
3790 LL_OPTION("io_uring_q_depth=%u", uring.q_depth, -1),
+ +
3792};
+
3793
+
3794void fuse_lowlevel_version(void)
+
3795{
+
3796 printf("using FUSE kernel interface version %i.%i\n",
+
3797 FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION);
+
3798 fuse_mount_version();
+
3799}
+
3800
+
3801void fuse_lowlevel_help(void)
+
3802{
+
3803 /* These are not all options, but the ones that are
+
3804 potentially of interest to an end-user */
+
3805 printf(
+
3806" -o allow_other allow access by all users\n"
+
3807" -o allow_root allow access by root\n"
+
3808" -o auto_unmount auto unmount on process termination\n"
+
3809" -o io_uring enable io-uring\n"
+
3810" -o io_uring_q_depth=<n> io-uring queue depth\n"
+
3811);
+
3812}
+
3813
+
3814void fuse_session_destroy(struct fuse_session *se)
+
3815{
+
3816 struct fuse_ll_pipe *llp;
+
3817
+
3818 if (se->got_init && !se->got_destroy) {
+
3819 if (se->op.destroy)
+
3820 se->op.destroy(se->userdata);
+
3821 }
+
3822 llp = pthread_getspecific(se->pipe_key);
+
3823 if (llp != NULL)
+
3824 fuse_ll_pipe_free(llp);
+
3825 pthread_key_delete(se->pipe_key);
+
3826 sem_destroy(&se->mt_finish);
+
3827 pthread_mutex_destroy(&se->mt_lock);
+
3828 pthread_mutex_destroy(&se->lock);
+
3829 free(se->cuse_data);
+
3830 if (se->fd != -1)
+
3831 close(se->fd);
+
3832 if (se->io != NULL)
+
3833 free(se->io);
+
3834 destroy_mount_opts(se->mo);
+
3835 free(se);
+
3836}
+
3837
+
3838
+
3839static void fuse_ll_pipe_destructor(void *data)
+
3840{
+
3841 struct fuse_ll_pipe *llp = data;
+
3842 fuse_ll_pipe_free(llp);
+
3843}
+
3844
+
3845void fuse_buf_free(struct fuse_buf *buf)
+
3846{
+
3847 if (buf->mem == NULL)
+
3848 return;
+
3849
+
3850 size_t write_header_sz =
+
3851 sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in);
+
3852
+
3853 char *ptr = (char *)buf->mem - pagesize + write_header_sz;
+
3854 free(ptr);
+
3855 buf->mem = NULL;
+
3856}
+
3857
+
3858/*
+
3859 * This is used to allocate buffers that hold fuse requests
+
3860 */
+
3861static void *buf_alloc(size_t size, bool internal)
+
3862{
+
3863 /*
+
3864 * For libfuse internal caller add in alignment. That cannot be done
+
3865 * for an external caller, as it is not guaranteed that the external
+
3866 * caller frees the raw pointer.
+
3867 */
+
3868 if (internal) {
+
3869 size_t write_header_sz = sizeof(struct fuse_in_header) +
+
3870 sizeof(struct fuse_write_in);
+
3871 size_t new_size = ROUND_UP(size + write_header_sz, pagesize);
+
3872
+
3873 char *buf = aligned_alloc(pagesize, new_size);
+
3874 if (buf == NULL)
+
3875 return NULL;
+
3876
+
3877 buf += pagesize - write_header_sz;
+
3878
+
3879 return buf;
+
3880 } else {
+
3881 return malloc(size);
+
3882 }
+
3883}
+
3884
+
3885/*
+
3886 *@param internal true if called from libfuse internal code
+
3887 */
+
3888static int _fuse_session_receive_buf(struct fuse_session *se,
+
3889 struct fuse_buf *buf, struct fuse_chan *ch,
+
3890 bool internal)
+
3891{
+
3892 int err;
+
3893 ssize_t res;
+
3894 size_t bufsize;
+
3895#ifdef HAVE_SPLICE
+
3896 struct fuse_ll_pipe *llp;
+
3897 struct fuse_buf tmpbuf;
+
3898
+
3899pipe_retry:
+
3900 bufsize = se->bufsize;
+
3901
+
3902 if (se->conn.proto_minor < 14 ||
+
3903 !(se->conn.want_ext & FUSE_CAP_SPLICE_READ))
+
3904 goto fallback;
+
3905
+
3906 llp = fuse_ll_get_pipe(se);
+
3907 if (llp == NULL)
+
3908 goto fallback;
+
3909
+
3910 if (llp->size < bufsize) {
+
3911 if (llp->can_grow) {
+
3912 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, bufsize);
+
3913 if (res == -1) {
+
3914 llp->can_grow = 0;
+
3915 res = grow_pipe_to_max(llp->pipe[0]);
+
3916 if (res > 0)
+
3917 llp->size = res;
+
3918 goto fallback;
+
3919 }
+
3920 llp->size = res;
+
3921 }
+
3922 if (llp->size < bufsize)
+
3923 goto fallback;
+
3924 }
+
3925
+
3926 if (se->io != NULL && se->io->splice_receive != NULL) {
+
3927 res = se->io->splice_receive(ch ? ch->fd : se->fd, NULL,
+
3928 llp->pipe[1], NULL, bufsize, 0,
+
3929 se->userdata);
+
3930 } else {
+
3931 res = splice(ch ? ch->fd : se->fd, NULL, llp->pipe[1], NULL,
+
3932 bufsize, 0);
+
3933 }
+
3934 err = errno;
+
3935 trace_request_receive(err);
+
3936
+
3937 if (fuse_session_exited(se))
+
3938 return 0;
+
3939
+
3940 if (res == -1) {
+
3941 if (err == ENODEV) {
+
3942 /* Filesystem was unmounted, or connection was aborted
+
3943 via /sys/fs/fuse/connections */
+ +
3945 return 0;
+
3946 }
+
3947
+
3948 /* FUSE_INIT might have increased the required bufsize */
+
3949 if (err == EINVAL && bufsize < se->bufsize) {
+
3950 fuse_ll_clear_pipe(se);
+
3951 goto pipe_retry;
+
3952 }
+
3953
+
3954 if (err != EINTR && err != EAGAIN)
+
3955 perror("fuse: splice from device");
+
3956 return -err;
+
3957 }
+
3958
+
3959 if (res < sizeof(struct fuse_in_header)) {
+
3960 fuse_log(FUSE_LOG_ERR, "short splice from fuse device\n");
+
3961 return -EIO;
+
3962 }
+
3963
+
3964 tmpbuf = (struct fuse_buf){
+
3965 .size = res,
+
3966 .flags = FUSE_BUF_IS_FD,
+
3967 .fd = llp->pipe[0],
+
3968 };
+
3969
+
3970 /*
+
3971 * Don't bother with zero copy for small requests.
+
3972 * fuse_loop_mt() needs to check for FORGET so this more than
+
3973 * just an optimization.
+
3974 */
+
3975 if (res < sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) +
+
3976 pagesize) {
+
3977 struct fuse_bufvec src = { .buf[0] = tmpbuf, .count = 1 };
+
3978 struct fuse_bufvec dst = { .count = 1 };
+
3979
+
3980 if (!buf->mem) {
+
3981 buf->mem = buf_alloc(bufsize, internal);
+
3982 if (!buf->mem) {
+
3983 fuse_log(
+
3984 FUSE_LOG_ERR,
+
3985 "fuse: failed to allocate read buffer\n");
+
3986 return -ENOMEM;
+
3987 }
+
3988 buf->mem_size = bufsize;
+
3989 }
+
3990 buf->size = bufsize;
+
3991 buf->flags = 0;
+
3992 dst.buf[0] = *buf;
+
3993
+
3994 res = fuse_buf_copy(&dst, &src, 0);
+
3995 if (res < 0) {
+
3996 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n",
+
3997 strerror(-res));
+
3998 fuse_ll_clear_pipe(se);
+
3999 return res;
+
4000 }
+
4001 if (res < tmpbuf.size) {
+
4002 fuse_log(FUSE_LOG_ERR,
+
4003 "fuse: copy from pipe: short read\n");
+
4004 fuse_ll_clear_pipe(se);
+
4005 return -EIO;
+
4006 }
+
4007 assert(res == tmpbuf.size);
+
4008
+
4009 } else {
+
4010 /* Don't overwrite buf->mem, as that would cause a leak */
+
4011 buf->fd = tmpbuf.fd;
+
4012 buf->flags = tmpbuf.flags;
+
4013 }
+
4014 buf->size = tmpbuf.size;
+
4015
+
4016 return res;
+
4017
+
4018fallback:
+
4019#endif
+
4020 bufsize = internal ? buf->mem_size : se->bufsize;
+
4021 if (!buf->mem) {
+
4022 bufsize = se->bufsize; /* might have changed */
+
4023 buf->mem = buf_alloc(bufsize, internal);
+
4024 if (!buf->mem) {
+
4025 fuse_log(FUSE_LOG_ERR,
+
4026 "fuse: failed to allocate read buffer\n");
+
4027 return -ENOMEM;
+
4028 }
+
4029
+
4030 if (internal)
+
4031 buf->mem_size = bufsize;
+
4032 }
+
4033
+
4034restart:
+
4035 if (se->io != NULL) {
+
4036 /* se->io->read is never NULL if se->io is not NULL as
+
4037 specified by fuse_session_custom_io()*/
+
4038 res = se->io->read(ch ? ch->fd : se->fd, buf->mem, bufsize,
+
4039 se->userdata);
+
4040 } else {
+
4041 res = read(ch ? ch->fd : se->fd, buf->mem, bufsize);
+
4042 }
+
4043 err = errno;
+
4044 trace_request_receive(err);
+
4045
+
4046 if (fuse_session_exited(se))
+
4047 return 0;
+
4048 if (res == -1) {
+
4049 if (err == EINVAL && internal && se->bufsize > bufsize) {
+
4050 /* FUSE_INIT might have increased the required bufsize */
+
4051 bufsize = se->bufsize;
+
4052 void *newbuf = buf_alloc(bufsize, internal);
+
4053 if (!newbuf) {
+
4054 fuse_log(
+
4055 FUSE_LOG_ERR,
+
4056 "fuse: failed to (re)allocate read buffer\n");
+
4057 return -ENOMEM;
+
4058 }
+
4059 fuse_buf_free(buf);
+
4060 buf->mem = newbuf;
+
4061 buf->mem_size = bufsize;
+
4062 goto restart;
+
4063 }
+
4064
+
4065 /* ENOENT means the operation was interrupted, it's safe
+
4066 to restart */
+
4067 if (err == ENOENT)
+
4068 goto restart;
+
4069
+
4070 if (err == ENODEV) {
+
4071 /* Filesystem was unmounted, or connection was aborted
+
4072 via /sys/fs/fuse/connections */
+ +
4074 return 0;
+
4075 }
+
4076 /* Errors occurring during normal operation: EINTR (read
+
4077 interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem
+
4078 umounted) */
+
4079 if (err != EINTR && err != EAGAIN)
+
4080 perror("fuse: reading device");
+
4081 return -err;
+
4082 }
+
4083 if ((size_t)res < sizeof(struct fuse_in_header)) {
+
4084 fuse_log(FUSE_LOG_ERR, "short read on fuse device\n");
+
4085 return -EIO;
+
4086 }
+
4087
+
4088 buf->size = res;
+
4089
+
4090 return res;
+
4091}
+
4092
+
4093int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
4094{
+
4095 return _fuse_session_receive_buf(se, buf, NULL, false);
+
4096}
+
4097
+
4098/* libfuse internal handler */
+
4099int fuse_session_receive_buf_internal(struct fuse_session *se,
+
4100 struct fuse_buf *buf,
+
4101 struct fuse_chan *ch)
+
4102{
+
4103 /*
+
4104 * if run internally thread buffers are from libfuse - we can
+
4105 * reallocate them
+
4106 */
+
4107 if (unlikely(!se->got_init) && !se->buf_reallocable)
+
4108 se->buf_reallocable = true;
+
4109
+
4110 return _fuse_session_receive_buf(se, buf, ch, true);
+
4111}
+
4112
+
4113struct fuse_session *
+
4114fuse_session_new_versioned(struct fuse_args *args,
+
4115 const struct fuse_lowlevel_ops *op, size_t op_size,
+
4116 struct libfuse_version *version, void *userdata)
+
4117{
+
4118 int err;
+
4119 struct fuse_session *se;
+
4120 struct mount_opts *mo;
+
4121
+
4122 if (op == NULL || op_size == 0) {
+
4123 fuse_log(FUSE_LOG_ERR,
+
4124 "fuse: warning: empty op list passed to fuse_session_new()\n");
+
4125 return NULL;
+
4126 }
+
4127
+
4128 if (version == NULL) {
+
4129 fuse_log(FUSE_LOG_ERR, "fuse: warning: version not passed to fuse_session_new()\n");
+
4130 return NULL;
+
4131 }
+
4132
+
4133 if (sizeof(struct fuse_lowlevel_ops) < op_size) {
+
4134 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
4135 op_size = sizeof(struct fuse_lowlevel_ops);
+
4136 }
+
4137
+
4138 if (args == NULL || args->argc == 0) {
+
4139 fuse_log(FUSE_LOG_ERR, "fuse: empty argv passed to fuse_session_new().\n");
+
4140 return NULL;
+
4141 }
+
4142
+
4143 se = (struct fuse_session *) calloc(1, sizeof(struct fuse_session));
+
4144 if (se == NULL) {
+
4145 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
4146 goto out1;
+
4147 }
+
4148 se->fd = -1;
+
4149 se->conn.max_write = FUSE_DEFAULT_MAX_PAGES_LIMIT * getpagesize();
+
4150 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
4151 se->conn.max_readahead = UINT_MAX;
+
4152
+
4153 /*
+
4154 * Allow overriding with env, mostly to avoid the need to modify
+
4155 * all tests. I.e. to test with and without io-uring being enabled.
+
4156 */
+
4157 se->uring.enable = getenv("FUSE_URING_ENABLE") ?
+
4158 atoi(getenv("FUSE_URING_ENABLE")) :
+
4159 SESSION_DEF_URING_ENABLE;
+
4160 se->uring.q_depth = getenv("FUSE_URING_QUEUE_DEPTH") ?
+
4161 atoi(getenv("FUSE_URING_QUEUE_DEPTH")) :
+
4162 SESSION_DEF_URING_Q_DEPTH;
+
4163
+
4164 /* Parse options */
+
4165 if(fuse_opt_parse(args, se, fuse_ll_opts, NULL) == -1)
+
4166 goto out2;
+
4167 if(se->deny_others) {
+
4168 /* Allowing access only by root is done by instructing
+
4169 * kernel to allow access by everyone, and then restricting
+
4170 * access to root and mountpoint owner in libfuse.
+
4171 */
+
4172 // We may be adding the option a second time, but
+
4173 // that doesn't hurt.
+
4174 if(fuse_opt_add_arg(args, "-oallow_other") == -1)
+
4175 goto out2;
+
4176 }
+
4177 mo = parse_mount_opts(args);
+
4178 if (mo == NULL)
+
4179 goto out3;
+
4180
+
4181 if(args->argc == 1 &&
+
4182 args->argv[0][0] == '-') {
+
4183 fuse_log(FUSE_LOG_ERR, "fuse: warning: argv[0] looks like an option, but "
+
4184 "will be ignored\n");
+
4185 } else if (args->argc != 1) {
+
4186 int i;
+
4187 fuse_log(FUSE_LOG_ERR, "fuse: unknown option(s): `");
+
4188 for(i = 1; i < args->argc-1; i++)
+
4189 fuse_log(FUSE_LOG_ERR, "%s ", args->argv[i]);
+
4190 fuse_log(FUSE_LOG_ERR, "%s'\n", args->argv[i]);
+
4191 goto out4;
+
4192 }
+
4193
+
4194 if (se->debug)
+
4195 fuse_log(FUSE_LOG_DEBUG, "FUSE library version: %s\n", PACKAGE_VERSION);
+
4196
+
4197 list_init_req(&se->list);
+
4198 list_init_req(&se->interrupts);
+
4199 list_init_nreq(&se->notify_list);
+
4200 se->notify_ctr = 1;
+
4201 pthread_mutex_init(&se->lock, NULL);
+
4202 sem_init(&se->mt_finish, 0, 0);
+
4203 pthread_mutex_init(&se->mt_lock, NULL);
+
4204
+
4205 err = pthread_key_create(&se->pipe_key, fuse_ll_pipe_destructor);
+
4206 if (err) {
+
4207 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
4208 strerror(err));
+
4209 goto out5;
+
4210 }
+
4211
+
4212 memcpy(&se->op, op, op_size);
+
4213 se->owner = getuid();
+
4214 se->userdata = userdata;
+
4215
+
4216 se->mo = mo;
+
4217
+
4218 /* Fuse server application should pass the version it was compiled
+
4219 * against and pass it. If a libfuse version accidentally introduces an
+
4220 * ABI incompatibility, it might be possible to 'fix' that at run time,
+
4221 * by checking the version numbers.
+
4222 */
+
4223 se->version = *version;
+
4224
+
4225 return se;
+
4226
+
4227out5:
+
4228 sem_destroy(&se->mt_finish);
+
4229 pthread_mutex_destroy(&se->mt_lock);
+
4230 pthread_mutex_destroy(&se->lock);
+
4231out4:
+
4232 fuse_opt_free_args(args);
+
4233out3:
+
4234 if (mo != NULL)
+
4235 destroy_mount_opts(mo);
+
4236out2:
+
4237 free(se);
+
4238out1:
+
4239 return NULL;
+
4240}
+
4241
+
4242struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
4243 const struct fuse_lowlevel_ops *op,
+
4244 size_t op_size, void *userdata);
+
4245struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
4246 const struct fuse_lowlevel_ops *op,
+
4247 size_t op_size,
+
4248 void *userdata)
+
4249{
+
4250 struct fuse_lowlevel_ops null_ops = { 0 };
+
4251
+
4252 /* unknown version */
+
4253 struct libfuse_version version = { 0 };
+
4254
+
4255 /*
+
4256 * This function is the ABI interface function from fuse_session_new in
+
4257 * compat.c. External libraries like "fuser" might call fuse_session_new()
+
4258 * with NULL ops and then pass that session to fuse_session_mount().
+
4259 * The actual FUSE operations are handled in their own library.
+
4260 */
+
4261 if (op == NULL) {
+
4262 op = &null_ops;
+
4263 op_size = sizeof(null_ops);
+
4264 }
+
4265
+
4266 return fuse_session_new_versioned(args, op, op_size, &version,
+
4267 userdata);
+
4268}
+
4269
+
4270FUSE_SYMVER("fuse_session_custom_io_317", "fuse_session_custom_io@@FUSE_3.17")
+
4271int fuse_session_custom_io_317(struct fuse_session *se,
+
4272 const struct fuse_custom_io *io, size_t op_size, int fd)
+
4273{
+
4274 if (sizeof(struct fuse_custom_io) < op_size) {
+
4275 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
4276 op_size = sizeof(struct fuse_custom_io);
+
4277 }
+
4278
+
4279 if (fd < 0) {
+
4280 fuse_log(FUSE_LOG_ERR, "Invalid file descriptor value %d passed to "
+
4281 "fuse_session_custom_io()\n", fd);
+
4282 return -EBADF;
+
4283 }
+
4284 if (io == NULL) {
+
4285 fuse_log(FUSE_LOG_ERR, "No custom IO passed to "
+
4286 "fuse_session_custom_io()\n");
+
4287 return -EINVAL;
+
4288 } else if (io->read == NULL || io->writev == NULL) {
+
4289 /* If the user provides their own file descriptor, we can't
+
4290 guarantee that the default behavior of the io operations made
+
4291 in libfuse will function properly. Therefore, we enforce the
+
4292 user to implement these io operations when using custom io. */
+
4293 fuse_log(FUSE_LOG_ERR, "io passed to fuse_session_custom_io() must "
+
4294 "implement both io->read() and io->writev\n");
+
4295 return -EINVAL;
+
4296 }
+
4297
+
4298 se->io = calloc(1, sizeof(struct fuse_custom_io));
+
4299 if (se->io == NULL) {
+
4300 fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for custom io. "
+
4301 "Error: %s\n", strerror(errno));
+
4302 return -errno;
+
4303 }
+
4304
+
4305 se->fd = fd;
+
4306 memcpy(se->io, io, op_size);
+
4307 return 0;
+
4308}
+
4309
+
4310int fuse_session_custom_io_30(struct fuse_session *se,
+
4311 const struct fuse_custom_io *io, int fd);
+
4312FUSE_SYMVER("fuse_session_custom_io_30", "fuse_session_custom_io@FUSE_3.0")
+
4313int fuse_session_custom_io_30(struct fuse_session *se,
+
4314 const struct fuse_custom_io *io, int fd)
+
4315{
+
4316 return fuse_session_custom_io_317(se, io,
+
4317 offsetof(struct fuse_custom_io, clone_fd), fd);
+
4318}
+
4319
+
4320int fuse_session_mount(struct fuse_session *se, const char *_mountpoint)
+
4321{
+
4322 int fd;
+
4323 char *mountpoint;
+
4324
+
4325 if (_mountpoint == NULL) {
+
4326 fuse_log(FUSE_LOG_ERR, "Invalid null-ptr mountpoint!\n");
+
4327 return -1;
+
4328 }
+
4329
+
4330 mountpoint = strdup(_mountpoint);
+
4331 if (mountpoint == NULL) {
+
4332 fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for mountpoint. Error: %s\n",
+
4333 strerror(errno));
+
4334 return -1;
+
4335 }
+
4336
+
4337 /*
+
4338 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
4339 * would ensue.
+
4340 */
+
4341 do {
+
4342 fd = open("/dev/null", O_RDWR);
+
4343 if (fd > 2)
+
4344 close(fd);
+
4345 } while (fd >= 0 && fd <= 2);
+
4346
+
4347 /*
+
4348 * To allow FUSE daemons to run without privileges, the caller may open
+
4349 * /dev/fuse before launching the file system and pass on the file
+
4350 * descriptor by specifying /dev/fd/N as the mount point. Note that the
+
4351 * parent process takes care of performing the mount in this case.
+
4352 */
+
4353 fd = fuse_mnt_parse_fuse_fd(mountpoint);
+
4354 if (fd != -1) {
+
4355 if (fcntl(fd, F_GETFD) == -1) {
+
4356 fuse_log(FUSE_LOG_ERR,
+
4357 "fuse: Invalid file descriptor /dev/fd/%u\n",
+
4358 fd);
+
4359 goto error_out;
+
4360 }
+
4361 se->fd = fd;
+
4362 return 0;
+
4363 }
+
4364
+
4365 /* Open channel */
+
4366 fd = fuse_kern_mount(mountpoint, se->mo);
+
4367 if (fd == -1)
+
4368 goto error_out;
+
4369 se->fd = fd;
+
4370
+
4371 /* Save mountpoint */
+
4372 se->mountpoint = mountpoint;
+
4373
+
4374 return 0;
+
4375
+
4376error_out:
+
4377 free(mountpoint);
+
4378 return -1;
+
4379}
+
4380
+
4381int fuse_session_fd(struct fuse_session *se)
+
4382{
+
4383 return se->fd;
+
4384}
+
4385
+
4386void fuse_session_unmount(struct fuse_session *se)
+
4387{
+
4388 if (se->mountpoint != NULL) {
+
4389 char *mountpoint = atomic_exchange(&se->mountpoint, NULL);
+
4390
+
4391 fuse_kern_unmount(mountpoint, se->fd);
+
4392 se->fd = -1;
+
4393 free(mountpoint);
+
4394 }
+
4395}
+
4396
+
4397#ifdef linux
+
4398int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
4399{
+
4400 char *buf;
+
4401 size_t bufsize = 1024;
+
4402 char path[128];
+
4403 int ret;
+
4404 int fd;
+
4405 unsigned long pid = req->ctx.pid;
+
4406 char *s;
+
4407
+
4408 sprintf(path, "/proc/%lu/task/%lu/status", pid, pid);
+
4409
+
4410retry:
+
4411 buf = malloc(bufsize);
+
4412 if (buf == NULL)
+
4413 return -ENOMEM;
+
4414
+
4415 ret = -EIO;
+
4416 fd = open(path, O_RDONLY);
+
4417 if (fd == -1)
+
4418 goto out_free;
+
4419
+
4420 ret = read(fd, buf, bufsize);
+
4421 close(fd);
+
4422 if (ret < 0) {
+
4423 ret = -EIO;
+
4424 goto out_free;
+
4425 }
+
4426
+
4427 if ((size_t)ret == bufsize) {
+
4428 free(buf);
+
4429 bufsize *= 4;
+
4430 goto retry;
+
4431 }
+
4432
+
4433 buf[ret] = '\0';
+
4434 ret = -EIO;
+
4435 s = strstr(buf, "\nGroups:");
+
4436 if (s == NULL)
+
4437 goto out_free;
+
4438
+
4439 s += 8;
+
4440 ret = 0;
+
4441 while (1) {
+
4442 char *end;
+
4443 unsigned long val = strtoul(s, &end, 0);
+
4444 if (end == s)
+
4445 break;
+
4446
+
4447 s = end;
+
4448 if (ret < size)
+
4449 list[ret] = val;
+
4450 ret++;
+
4451 }
+
4452
+
4453out_free:
+
4454 free(buf);
+
4455 return ret;
+
4456}
+
4457#else /* linux */
+
4458/*
+
4459 * This is currently not implemented on other than Linux...
+
4460 */
+
4461int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
4462{
+
4463 (void) req; (void) size; (void) list;
+
4464 return -ENOSYS;
+
4465}
+
4466#endif
+
4467
+
4468/* Prevent spurious data race warning - we don't care
+
4469 * about races for this flag */
+
4470__attribute__((no_sanitize_thread))
+
4471void fuse_session_exit(struct fuse_session *se)
+
4472{
+
4473 atomic_store_explicit(&se->mt_exited, 1, memory_order_relaxed);
+
4474 sem_post(&se->mt_finish);
+
4475}
+
4476
+
4477__attribute__((no_sanitize_thread))
+
4478void fuse_session_reset(struct fuse_session *se)
+
4479{
+
4480 se->mt_exited = false;
+
4481 se->error = 0;
+
4482}
+
4483
+
4484__attribute__((no_sanitize_thread))
+
4485int fuse_session_exited(struct fuse_session *se)
+
4486{
+
4487 bool exited =
+
4488 atomic_load_explicit(&se->mt_exited, memory_order_relaxed);
+
4489
+
4490 return exited ? 1 : 0;
+
4491}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
@ FUSE_BUF_IS_FD
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_CAP_OVER_IO_URING
+
int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+
int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
+
@ FUSE_BUF_IS_FD
+
bool fuse_req_is_uring(fuse_req_t req)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, void **mr)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
enum fuse_buf_flags flags
+
size_t mem_size
+ +
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + + + +
uint64_t capable_ext
+
uint64_t want_ext
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:60
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t nonseekable
Definition fuse_common.h:78
+
int32_t backing_id
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t noflush
Definition fuse_common.h:93
+
uint32_t flush
Definition fuse_common.h:74
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2fuse__misc_8h_source.html b/doc/html/fuse-3_818_82_2lib_2fuse__misc_8h_source.html new file mode 100644 index 0000000..644e6c7 --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2fuse__misc_8h_source.html @@ -0,0 +1,111 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/fuse_misc.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_misc.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt
+
7*/
+
8
+
9#include <pthread.h>
+
10
+
11/*
+
12 Versioned symbols cannot be used in some cases because it
+
13 - not supported on MacOSX (in MachO binary format)
+
14
+
15 Note: "@@" denotes the default symbol, "@" is binary a compat version.
+
16
+
17*/
+
18#ifdef LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS
+
19# if HAVE_SYMVER_ATTRIBUTE
+
20# define FUSE_SYMVER(sym1, sym2) __attribute__ ((symver (sym2)))
+
21# else
+
22# define FUSE_SYMVER(sym1, sym2) __asm__("\t.symver " sym1 "," sym2);
+
23# endif
+
24#else
+
25#define FUSE_SYMVER(sym1, sym2)
+
26#endif
+
27
+
28#ifdef HAVE_STRUCT_STAT_ST_ATIM
+
29/* Linux */
+
30#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec)
+
31#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec)
+
32#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec)
+
33#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atim.tv_nsec = (val)
+
34#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctim.tv_nsec = (val)
+
35#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtim.tv_nsec = (val)
+
36#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC)
+
37/* FreeBSD */
+
38#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimespec.tv_nsec)
+
39#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimespec.tv_nsec)
+
40#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimespec.tv_nsec)
+
41#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atimespec.tv_nsec = (val)
+
42#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctimespec.tv_nsec = (val)
+
43#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtimespec.tv_nsec = (val)
+
44#else
+
45#define ST_ATIM_NSEC(stbuf) 0
+
46#define ST_CTIM_NSEC(stbuf) 0
+
47#define ST_MTIM_NSEC(stbuf) 0
+
48#define ST_ATIM_NSEC_SET(stbuf, val) do { } while (0)
+
49#define ST_CTIM_NSEC_SET(stbuf, val) do { } while (0)
+
50#define ST_MTIM_NSEC_SET(stbuf, val) do { } while (0)
+
51#endif
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2fuse__opt_8c_source.html b/doc/html/fuse-3_818_82_2lib_2fuse__opt_8c_source.html new file mode 100644 index 0000000..5d89e34 --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2fuse__opt_8c_source.html @@ -0,0 +1,504 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/fuse_opt.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_opt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of option parsing routines (dealing with `struct
+
6 fuse_args`).
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file LGPL2.txt
+
10*/
+
11
+
12#include "fuse_config.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15#include "fuse_misc.h"
+
16
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <assert.h>
+
21
+
22struct fuse_opt_context {
+
23 void *data;
+
24 const struct fuse_opt *opt;
+
25 fuse_opt_proc_t proc;
+
26 int argctr;
+
27 int argc;
+
28 char **argv;
+
29 struct fuse_args outargs;
+
30 char *opts;
+
31 int nonopt;
+
32};
+
33
+
34void fuse_opt_free_args(struct fuse_args *args)
+
35{
+
36 if (args) {
+
37 if (args->argv && args->allocated) {
+
38 int i;
+
39 for (i = 0; i < args->argc; i++)
+
40 free(args->argv[i]);
+
41 free(args->argv);
+
42 }
+
43 args->argc = 0;
+
44 args->argv = NULL;
+
45 args->allocated = 0;
+
46 }
+
47}
+
48
+
49static int alloc_failed(void)
+
50{
+
51 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
52 return -1;
+
53}
+
54
+
55int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
+
56{
+
57 char **newargv;
+
58 char *newarg;
+
59
+
60 assert(!args->argv || args->allocated);
+
61
+
62 newarg = strdup(arg);
+
63 if (!newarg)
+
64 return alloc_failed();
+
65
+
66 newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *));
+
67 if (!newargv) {
+
68 free(newarg);
+
69 return alloc_failed();
+
70 }
+
71
+
72 args->argv = newargv;
+
73 args->allocated = 1;
+
74 args->argv[args->argc++] = newarg;
+
75 args->argv[args->argc] = NULL;
+
76 return 0;
+
77}
+
78
+
79static int fuse_opt_insert_arg_common(struct fuse_args *args, int pos,
+
80 const char *arg)
+
81{
+
82 assert(pos <= args->argc);
+
83 if (fuse_opt_add_arg(args, arg) == -1)
+
84 return -1;
+
85
+
86 if (pos != args->argc - 1) {
+
87 char *newarg = args->argv[args->argc - 1];
+
88 memmove(&args->argv[pos + 1], &args->argv[pos],
+
89 sizeof(char *) * (args->argc - pos - 1));
+
90 args->argv[pos] = newarg;
+
91 }
+
92 return 0;
+
93}
+
94
+
95int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
+
96{
+
97 return fuse_opt_insert_arg_common(args, pos, arg);
+
98}
+
99
+
100static int next_arg(struct fuse_opt_context *ctx, const char *opt)
+
101{
+
102 if (ctx->argctr + 1 >= ctx->argc) {
+
103 fuse_log(FUSE_LOG_ERR, "fuse: missing argument after `%s'\n", opt);
+
104 return -1;
+
105 }
+
106 ctx->argctr++;
+
107 return 0;
+
108}
+
109
+
110static int add_arg(struct fuse_opt_context *ctx, const char *arg)
+
111{
+
112 return fuse_opt_add_arg(&ctx->outargs, arg);
+
113}
+
114
+
115static int add_opt_common(char **opts, const char *opt, int esc)
+
116{
+
117 unsigned oldlen = *opts ? strlen(*opts) : 0;
+
118 char *d = realloc(*opts, oldlen + 1 + strlen(opt) * 2 + 1);
+
119
+
120 if (!d)
+
121 return alloc_failed();
+
122
+
123 *opts = d;
+
124 if (oldlen) {
+
125 d += oldlen;
+
126 *d++ = ',';
+
127 }
+
128
+
129 for (; *opt; opt++) {
+
130 if (esc && (*opt == ',' || *opt == '\\'))
+
131 *d++ = '\\';
+
132 *d++ = *opt;
+
133 }
+
134 *d = '\0';
+
135
+
136 return 0;
+
137}
+
138
+
139int fuse_opt_add_opt(char **opts, const char *opt)
+
140{
+
141 return add_opt_common(opts, opt, 0);
+
142}
+
143
+
144int fuse_opt_add_opt_escaped(char **opts, const char *opt)
+
145{
+
146 return add_opt_common(opts, opt, 1);
+
147}
+
148
+
149static int add_opt(struct fuse_opt_context *ctx, const char *opt)
+
150{
+
151 return add_opt_common(&ctx->opts, opt, 1);
+
152}
+
153
+
154static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key,
+
155 int iso)
+
156{
+
157 if (key == FUSE_OPT_KEY_DISCARD)
+
158 return 0;
+
159
+
160 if (key != FUSE_OPT_KEY_KEEP && ctx->proc) {
+
161 int res = ctx->proc(ctx->data, arg, key, &ctx->outargs);
+
162 if (res == -1 || !res)
+
163 return res;
+
164 }
+
165 if (iso)
+
166 return add_opt(ctx, arg);
+
167 else
+
168 return add_arg(ctx, arg);
+
169}
+
170
+
171static int match_template(const char *t, const char *arg, unsigned *sepp)
+
172{
+
173 int arglen = strlen(arg);
+
174 const char *sep = strchr(t, '=');
+
175 sep = sep ? sep : strchr(t, ' ');
+
176 if (sep && (!sep[1] || sep[1] == '%')) {
+
177 int tlen = sep - t;
+
178 if (sep[0] == '=')
+
179 tlen ++;
+
180 if (arglen >= tlen && strncmp(arg, t, tlen) == 0) {
+
181 *sepp = sep - t;
+
182 return 1;
+
183 }
+
184 }
+
185 if (strcmp(t, arg) == 0) {
+
186 *sepp = 0;
+
187 return 1;
+
188 }
+
189 return 0;
+
190}
+
191
+
192static const struct fuse_opt *find_opt(const struct fuse_opt *opt,
+
193 const char *arg, unsigned *sepp)
+
194{
+
195 for (; opt && opt->templ; opt++)
+
196 if (match_template(opt->templ, arg, sepp))
+
197 return opt;
+
198 return NULL;
+
199}
+
200
+
201int fuse_opt_match(const struct fuse_opt *opts, const char *opt)
+
202{
+
203 unsigned dummy;
+
204 return find_opt(opts, opt, &dummy) ? 1 : 0;
+
205}
+
206
+
207static int process_opt_param(void *var, const char *format, const char *param,
+
208 const char *arg)
+
209{
+
210 assert(format[0] == '%');
+
211 if (format[1] == 's') {
+
212 char **s = var;
+
213 char *copy = strdup(param);
+
214 if (!copy)
+
215 return alloc_failed();
+
216
+
217 free(*s);
+
218 *s = copy;
+
219 } else {
+
220 if (sscanf(param, format, var) != 1) {
+
221 fuse_log(FUSE_LOG_ERR, "fuse: invalid parameter in option `%s'\n", arg);
+
222 return -1;
+
223 }
+
224 }
+
225 return 0;
+
226}
+
227
+
228static int process_opt(struct fuse_opt_context *ctx,
+
229 const struct fuse_opt *opt, unsigned sep,
+
230 const char *arg, int iso)
+
231{
+
232 if (opt->offset == -1U) {
+
233 if (call_proc(ctx, arg, opt->value, iso) == -1)
+
234 return -1;
+
235 } else {
+
236 void *var = (char *)ctx->data + opt->offset;
+
237 if (sep && opt->templ[sep + 1]) {
+
238 const char *param = arg + sep;
+
239 if (opt->templ[sep] == '=')
+
240 param ++;
+
241 if (process_opt_param(var, opt->templ + sep + 1,
+
242 param, arg) == -1)
+
243 return -1;
+
244 } else
+
245 *(int *)var = opt->value;
+
246 }
+
247 return 0;
+
248}
+
249
+
250static int process_opt_sep_arg(struct fuse_opt_context *ctx,
+
251 const struct fuse_opt *opt, unsigned sep,
+
252 const char *arg, int iso)
+
253{
+
254 int res;
+
255 char *newarg;
+
256 char *param;
+
257
+
258 if (next_arg(ctx, arg) == -1)
+
259 return -1;
+
260
+
261 param = ctx->argv[ctx->argctr];
+
262 newarg = malloc(sep + strlen(param) + 1);
+
263 if (!newarg)
+
264 return alloc_failed();
+
265
+
266 memcpy(newarg, arg, sep);
+
267 strcpy(newarg + sep, param);
+
268 res = process_opt(ctx, opt, sep, newarg, iso);
+
269 free(newarg);
+
270
+
271 return res;
+
272}
+
273
+
274static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso)
+
275{
+
276 unsigned sep;
+
277 const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep);
+
278 if (opt) {
+
279 for (; opt; opt = find_opt(opt + 1, arg, &sep)) {
+
280 int res;
+
281 if (sep && opt->templ[sep] == ' ' && !arg[sep])
+
282 res = process_opt_sep_arg(ctx, opt, sep, arg,
+
283 iso);
+
284 else
+
285 res = process_opt(ctx, opt, sep, arg, iso);
+
286 if (res == -1)
+
287 return -1;
+
288 }
+
289 return 0;
+
290 } else
+
291 return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso);
+
292}
+
293
+
294static int process_real_option_group(struct fuse_opt_context *ctx, char *opts)
+
295{
+
296 char *s = opts;
+
297 char *d = s;
+
298 int end = 0;
+
299
+
300 while (!end) {
+
301 if (*s == '\0')
+
302 end = 1;
+
303 if (*s == ',' || end) {
+
304 int res;
+
305
+
306 *d = '\0';
+
307 res = process_gopt(ctx, opts, 1);
+
308 if (res == -1)
+
309 return -1;
+
310 d = opts;
+
311 } else {
+
312 if (s[0] == '\\' && s[1] != '\0') {
+
313 s++;
+
314 if (s[0] >= '0' && s[0] <= '3' &&
+
315 s[1] >= '0' && s[1] <= '7' &&
+
316 s[2] >= '0' && s[2] <= '7') {
+
317 *d++ = (s[0] - '0') * 0100 +
+
318 (s[1] - '0') * 0010 +
+
319 (s[2] - '0');
+
320 s += 2;
+
321 } else {
+
322 *d++ = *s;
+
323 }
+
324 } else {
+
325 *d++ = *s;
+
326 }
+
327 }
+
328 s++;
+
329 }
+
330
+
331 return 0;
+
332}
+
333
+
334static int process_option_group(struct fuse_opt_context *ctx, const char *opts)
+
335{
+
336 int res;
+
337 char *copy = strdup(opts);
+
338
+
339 if (!copy) {
+
340 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
341 return -1;
+
342 }
+
343 res = process_real_option_group(ctx, copy);
+
344 free(copy);
+
345 return res;
+
346}
+
347
+
348static int process_one(struct fuse_opt_context *ctx, const char *arg)
+
349{
+
350 if (ctx->nonopt || arg[0] != '-')
+
351 return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0);
+
352 else if (arg[1] == 'o') {
+
353 if (arg[2])
+
354 return process_option_group(ctx, arg + 2);
+
355 else {
+
356 if (next_arg(ctx, arg) == -1)
+
357 return -1;
+
358
+
359 return process_option_group(ctx,
+
360 ctx->argv[ctx->argctr]);
+
361 }
+
362 } else if (arg[1] == '-' && !arg[2]) {
+
363 if (add_arg(ctx, arg) == -1)
+
364 return -1;
+
365 ctx->nonopt = ctx->outargs.argc;
+
366 return 0;
+
367 } else
+
368 return process_gopt(ctx, arg, 0);
+
369}
+
370
+
371static int opt_parse(struct fuse_opt_context *ctx)
+
372{
+
373 if (ctx->argc) {
+
374 if (add_arg(ctx, ctx->argv[0]) == -1)
+
375 return -1;
+
376 }
+
377
+
378 for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++)
+
379 if (process_one(ctx, ctx->argv[ctx->argctr]) == -1)
+
380 return -1;
+
381
+
382 if (ctx->opts) {
+
383 if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 ||
+
384 fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1)
+
385 return -1;
+
386 }
+
387
+
388 /* If option separator ("--") is the last argument, remove it */
+
389 if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc &&
+
390 strcmp(ctx->outargs.argv[ctx->outargs.argc - 1], "--") == 0) {
+
391 free(ctx->outargs.argv[ctx->outargs.argc - 1]);
+
392 ctx->outargs.argv[--ctx->outargs.argc] = NULL;
+
393 }
+
394
+
395 return 0;
+
396}
+
397
+
398int fuse_opt_parse(struct fuse_args *args, void *data,
+
399 const struct fuse_opt opts[], fuse_opt_proc_t proc)
+
400{
+
401 int res;
+
402 struct fuse_opt_context ctx = {
+
403 .data = data,
+
404 .opt = opts,
+
405 .proc = proc,
+
406 };
+
407
+
408 if (!args || !args->argv || !args->argc)
+
409 return 0;
+
410
+
411 ctx.argc = args->argc;
+
412 ctx.argv = args->argv;
+
413
+
414 res = opt_parse(&ctx);
+
415 if (res != -1) {
+
416 struct fuse_args tmp = *args;
+
417 *args = ctx.outargs;
+
418 ctx.outargs = tmp;
+
419 }
+
420 free(ctx.opts);
+
421 fuse_opt_free_args(&ctx.outargs);
+
422 return res;
+
423}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
#define FUSE_OPT_KEY_OPT
Definition fuse_opt.h:129
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
unsigned long offset
Definition fuse_opt.h:85
+
const char * templ
Definition fuse_opt.h:79
+
int value
Definition fuse_opt.h:91
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2fuse__signals_8c_source.html b/doc/html/fuse-3_818_82_2lib_2fuse__signals_8c_source.html new file mode 100644 index 0000000..771cdca --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2fuse__signals_8c_source.html @@ -0,0 +1,281 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/fuse_signals.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_signals.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Utility functions for setting signal handlers.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14
+
15#include <stdio.h>
+
16#include <string.h>
+
17#include <signal.h>
+
18#include <stdlib.h>
+
19#include <errno.h>
+
20
+
21#ifdef HAVE_BACKTRACE
+
22#include <execinfo.h>
+
23#endif
+
24
+
25/*
+
26 * Must not handle SIGCANCEL, as that is used to wake up threads from
+
27 * syscalls reading requests from /dev/fuse
+
28 */
+
29static int teardown_sigs[] = { SIGHUP, SIGINT, SIGTERM };
+
30
+
31static int ignore_sigs[] = { SIGPIPE};
+
32static int fail_sigs[] = { SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGSEGV };
+
33static struct fuse_session *fuse_instance;
+
34
+
35#ifdef HAVE_BACKTRACE
+
36#define BT_STACK_SZ (1024 * 1024)
+
37static void *backtrace_buffer[BT_STACK_SZ];
+
38#endif
+
39
+
40static void dump_stack(void)
+
41{
+
42#ifdef HAVE_BACKTRACE
+
43 char **strings;
+
44
+
45 int nptrs = backtrace(backtrace_buffer, BT_STACK_SZ);
+
46 strings = backtrace_symbols(backtrace_buffer, nptrs);
+
47
+
48 if (strings == NULL) {
+
49 fuse_log(FUSE_LOG_ERR, "Failed to get backtrace symbols: %s\n",
+
50 strerror(errno));
+
51 return;
+
52 }
+
53
+
54 for (int idx = 0; idx < nptrs; idx++)
+
55 fuse_log(FUSE_LOG_ERR, "%s\n", strings[idx]);
+
56
+
57 free(strings);
+
58#endif
+
59}
+
60
+
61static void exit_handler(int sig)
+
62{
+
63 if (fuse_instance == NULL) {
+
64 fuse_log(FUSE_LOG_ERR, "fuse_instance is NULL\n");
+
65 return;
+
66 }
+
67
+
68 if (fuse_instance->debug)
+
69 fuse_log(FUSE_LOG_ERR, "exit_handler called with sig %d\n",
+
70 sig);
+
71
+
72 fuse_session_exit(fuse_instance);
+
73
+
74 if (sig < 0) {
+
75 fuse_log(FUSE_LOG_ERR,
+
76 "assertion error: signal value <= 0\n");
+
77 dump_stack();
+
78 abort();
+
79 fuse_instance->error = sig;
+
80 }
+
81
+
82 fuse_instance->error = sig;
+
83}
+
84
+
85static void exit_backtrace(int sig)
+
86{
+
87 if (fuse_instance == NULL)
+
88 return;
+
89
+
90 fuse_session_exit(fuse_instance);
+
91
+
92 fuse_remove_signal_handlers(fuse_instance);
+
93 fuse_log(FUSE_LOG_ERR, "Got signal: %d\n", sig);
+
94 dump_stack();
+
95 abort();
+
96}
+
97
+
98
+
99static void do_nothing(int sig)
+
100{
+
101 (void) sig;
+
102}
+
103
+
104static int set_one_signal_handler(int sig, void (*handler)(int), int remove)
+
105{
+
106 struct sigaction sa;
+
107 struct sigaction old_sa;
+
108
+
109 memset(&sa, 0, sizeof(struct sigaction));
+
110 sa.sa_handler = remove ? SIG_DFL : handler;
+
111 sigemptyset(&(sa.sa_mask));
+
112 sa.sa_flags = 0;
+
113
+
114 if (sigaction(sig, NULL, &old_sa) == -1) {
+
115 perror("fuse: cannot get old signal handler");
+
116 return -1;
+
117 }
+
118
+
119 if (old_sa.sa_handler == (remove ? handler : SIG_DFL) &&
+
120 sigaction(sig, &sa, NULL) == -1) {
+
121 perror("fuse: cannot set signal handler");
+
122 return -1;
+
123 }
+
124 return 0;
+
125}
+
126
+
127static int _fuse_set_signal_handlers(int signals[], int nr_signals,
+
128 void (*handler)(int))
+
129{
+
130 for (int idx = 0; idx < nr_signals; idx++) {
+
131 int signal = signals[idx];
+
132
+
133 /*
+
134 * If we used SIG_IGN instead of the do_nothing function,
+
135 * then we would be unable to tell if we set SIG_IGN (and
+
136 * thus should reset to SIG_DFL in fuse_remove_signal_handlers)
+
137 * or if it was already set to SIG_IGN (and should be left
+
138 * untouched.
+
139 */
+
140 if (set_one_signal_handler(signal, handler, 0) == -1) {
+
141 fuse_log(FUSE_LOG_ERR,
+
142 "Failed to install signal handler for sig %d\n",
+
143 signal);
+
144 return -1;
+
145 }
+
146 }
+
147
+
148 return 0;
+
149}
+
150
+
151int fuse_set_signal_handlers(struct fuse_session *se)
+
152{
+
153 size_t nr_signals;
+
154 int rc;
+
155
+
156 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
157 rc = _fuse_set_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
158 if (rc < 0)
+
159 return rc;
+
160
+
161 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
162 rc = _fuse_set_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
163 if (rc < 0)
+
164 return rc;
+
165
+
166 /*
+
167 * needs to be set independently if already set, as some applications
+
168 * may have multiple sessions and might rely on traditional behavior
+
169 * that the last session is used.
+
170 */
+
171 fuse_instance = se;
+
172
+
173 return 0;
+
174}
+
175
+
176int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
177{
+
178 size_t nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
179
+
180 int rc = _fuse_set_signal_handlers(fail_sigs, nr_signals,
+
181 exit_backtrace);
+
182 if (rc < 0)
+
183 return rc;
+
184
+
185 /* See fuse_set_signal_handlers, why set unconditionally */
+
186 fuse_instance = se;
+
187
+
188 return 0;
+
189}
+
190
+
191static void _fuse_remove_signal_handlers(int signals[], int nr_signals,
+
192 void (*handler)(int))
+
193{
+
194 for (int idx = 0; idx < nr_signals; idx++)
+
195 set_one_signal_handler(signals[idx], handler, 1);
+
196}
+
197
+
198void fuse_remove_signal_handlers(struct fuse_session *se)
+
199{
+
200 size_t nr_signals;
+
201
+
202 if (fuse_instance != se)
+
203 fuse_log(FUSE_LOG_ERR,
+
204 "fuse: fuse_remove_signal_handlers: unknown session\n");
+
205 else
+
206 fuse_instance = NULL;
+
207
+
208 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
209 _fuse_remove_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
210
+
211 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
212 _fuse_remove_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
213
+
214 nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
215 _fuse_remove_signal_handlers(fail_sigs, nr_signals, exit_backtrace);
+
216}
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2fuse__uring_8c_source.html b/doc/html/fuse-3_818_82_2lib_2fuse__uring_8c_source.html new file mode 100644 index 0000000..6b05a13 --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2fuse__uring_8c_source.html @@ -0,0 +1,1015 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/fuse_uring.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_uring.c
+
+
+
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 * Copyright (C) 2025 Bernd Schubert <bschubert@ddn.com>
+
4 *
+
5 * Implementation of (most of) FUSE-over-io-uring.
+
6 *
+
7 * This program can be distributed under the terms of the GNU LGPLv2.
+
8 * See the file LGPL2.txt
+
9 */
+
10
+
11#define _GNU_SOURCE
+
12
+
13#include "fuse_i.h"
+
14#include "fuse_kernel.h"
+
15#include "fuse_uring_i.h"
+
16
+
17#include <stdlib.h>
+
18#include <liburing.h>
+
19#include <sys/sysinfo.h>
+
20#include <stdint.h>
+
21#include <inttypes.h>
+
22#include <stdbool.h>
+
23#include <string.h>
+
24#include <unistd.h>
+
25#include <numa.h>
+
26#include <pthread.h>
+
27#include <stdio.h>
+
28#include <linux/sched.h>
+
29#include <poll.h>
+
30#include <sys/eventfd.h>
+
31
+
32/* Size of command data area in SQE when IORING_SETUP_SQE128 is used */
+
33#define FUSE_URING_MAX_SQE128_CMD_DATA 80
+
34
+
35struct fuse_ring_ent {
+
36 struct fuse_ring_queue *ring_queue; /* back pointer */
+
37 struct fuse_req req;
+
38
+
39 struct fuse_uring_req_header *req_header;
+
40 void *op_payload;
+
41 size_t req_payload_sz;
+
42
+
43 /* commit id of a fuse request */
+
44 uint64_t req_commit_id;
+
45
+
46 enum fuse_uring_cmd last_cmd;
+
47
+
48 /* header and payload */
+
49 struct iovec iov[2];
+
50};
+
51
+
52struct fuse_ring_queue {
+
53 /* back pointer */
+
54 struct fuse_ring_pool *ring_pool;
+
55 int qid;
+
56 int numa_node;
+
57 pthread_t tid;
+
58 int eventfd;
+
59 size_t req_header_sz;
+
60 struct io_uring ring;
+
61
+
62 pthread_mutex_t ring_lock;
+
63 bool cqe_processing;
+
64
+
65 /* size depends on queue depth */
+
66 struct fuse_ring_ent ent[];
+
67};
+
68
+
72struct fuse_ring_pool {
+
73 struct fuse_session *se;
+
74
+
75 /* number of queues */
+
76 size_t nr_queues;
+
77
+
78 /* number of per queue entries */
+
79 size_t queue_depth;
+
80
+
81 /* max payload size for fuse requests*/
+
82 size_t max_req_payload_sz;
+
83
+
84 /* size of a single queue */
+
85 size_t queue_mem_size;
+
86
+
87 unsigned int started_threads;
+
88 unsigned int failed_threads;
+
89
+
90 /* Avoid sending queue entries before FUSE_INIT reply*/
+
91 sem_t init_sem;
+
92
+
93 pthread_cond_t thread_start_cond;
+
94 pthread_mutex_t thread_start_mutex;
+
95
+
96 /* pointer to the first queue */
+
97 struct fuse_ring_queue *queues;
+
98};
+
99
+
100static size_t
+
101fuse_ring_queue_size(const size_t q_depth)
+
102{
+
103 const size_t req_size = sizeof(struct fuse_ring_ent) * q_depth;
+
104
+
105 return sizeof(struct fuse_ring_queue) + req_size;
+
106}
+
107
+
108static struct fuse_ring_queue *
+
109fuse_uring_get_queue(struct fuse_ring_pool *fuse_ring, int qid)
+
110{
+
111 void *ptr =
+
112 ((char *)fuse_ring->queues) + (qid * fuse_ring->queue_mem_size);
+
113
+
114 return ptr;
+
115}
+
116
+
120static void *fuse_uring_get_sqe_cmd(struct io_uring_sqe *sqe)
+
121{
+
122 return (void *)&sqe->cmd[0];
+
123}
+
124
+
125static void fuse_uring_sqe_set_req_data(struct fuse_uring_cmd_req *req,
+
126 const unsigned int qid,
+
127 const uint64_t commit_id)
+
128{
+
129 req->qid = qid;
+
130 req->commit_id = commit_id;
+
131 req->flags = 0;
+
132}
+
133
+
134static void
+
135fuse_uring_sqe_prepare(struct io_uring_sqe *sqe, struct fuse_ring_ent *req,
+
136 __u32 cmd_op)
+
137{
+
138 /* These fields should be written once, never change */
+
139 sqe->opcode = IORING_OP_URING_CMD;
+
140
+
141 /*
+
142 * IOSQE_FIXED_FILE: fd is the index to the fd *array*
+
143 * given to io_uring_register_files()
+
144 */
+
145 sqe->flags = IOSQE_FIXED_FILE;
+
146 sqe->fd = 0;
+
147
+
148 sqe->rw_flags = 0;
+
149 sqe->ioprio = 0;
+
150 sqe->off = 0;
+
151
+
152 io_uring_sqe_set_data(sqe, req);
+
153
+
154 sqe->cmd_op = cmd_op;
+
155 sqe->__pad1 = 0;
+
156}
+
157
+
158static int fuse_uring_commit_sqe(struct fuse_ring_pool *ring_pool,
+
159 struct fuse_ring_queue *queue,
+
160 struct fuse_ring_ent *ring_ent)
+
161{
+
162 bool locked = false;
+
163 struct fuse_session *se = ring_pool->se;
+
164 struct fuse_uring_req_header *rrh = ring_ent->req_header;
+
165 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
+
166 struct fuse_uring_ent_in_out *ent_in_out =
+
167 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
+
168 struct io_uring_sqe *sqe;
+
169
+
170 if (pthread_self() != queue->tid) {
+
171 pthread_mutex_lock(&queue->ring_lock);
+
172 locked = true;
+
173 }
+
174
+
175 sqe = io_uring_get_sqe(&queue->ring);
+
176
+
177 if (sqe == NULL) {
+
178 /* This is an impossible condition, unless there is a bug.
+
179 * The kernel sent back an SQEs, which is assigned to a request.
+
180 * There is no way to get out of SQEs, as the number of
+
181 * SQEs matches the number tof requests.
+
182 */
+
183
+
184 se->error = -EIO;
+
185 fuse_log(FUSE_LOG_ERR, "Failed to get a ring SQEs\n");
+
186
+
187 return -EIO;
+
188 }
+
189
+
190 ring_ent->last_cmd = FUSE_IO_URING_CMD_COMMIT_AND_FETCH;
+
191 fuse_uring_sqe_prepare(sqe, ring_ent, ring_ent->last_cmd);
+
192 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), queue->qid,
+
193 ring_ent->req_commit_id);
+
194
+
195 if (se->debug) {
+
196 fuse_log(FUSE_LOG_DEBUG, " unique: %" PRIu64 ", result=%d\n",
+
197 out->unique, ent_in_out->payload_sz);
+
198 }
+
199
+
200 if (!queue->cqe_processing)
+
201 io_uring_submit(&queue->ring);
+
202
+
203 if (locked)
+
204 pthread_mutex_unlock(&queue->ring_lock);
+
205
+
206 return 0;
+
207}
+
208
+
209int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz,
+
210 void **mr)
+
211{
+
212 struct fuse_ring_ent *ring_ent;
+
213
+
214 /* Not possible without io-uring interface */
+
215 if (!req->flags.is_uring)
+
216 return -EINVAL;
+
217
+
218 ring_ent = container_of(req, struct fuse_ring_ent, req);
+
219
+
220 *payload = ring_ent->op_payload;
+
221 *payload_sz = ring_ent->req_payload_sz;
+
222
+
223 /*
+
224 * For now unused, but will be used later when the application can
+
225 * allocate the buffers itself and register them for rdma.
+
226 */
+
227 if (mr)
+
228 *mr = NULL;
+
229
+
230 return 0;
+
231}
+
232
+
233int send_reply_uring(fuse_req_t req, int error, const void *arg, size_t argsize)
+
234{
+
235 int res;
+
236 struct fuse_ring_ent *ring_ent =
+
237 container_of(req, struct fuse_ring_ent, req);
+
238 struct fuse_uring_req_header *rrh = ring_ent->req_header;
+
239 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
+
240 struct fuse_uring_ent_in_out *ent_in_out =
+
241 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
+
242
+
243 struct fuse_ring_queue *queue = ring_ent->ring_queue;
+
244 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
245 size_t max_payload_sz = ring_pool->max_req_payload_sz;
+
246
+
247 if (argsize > max_payload_sz) {
+
248 fuse_log(FUSE_LOG_ERR, "argsize %zu exceeds buffer size %zu",
+
249 argsize, max_payload_sz);
+
250 error = -EINVAL;
+
251 } else if (argsize) {
+
252 if (arg != ring_ent->op_payload)
+
253 memcpy(ring_ent->op_payload, arg, argsize);
+
254 }
+
255 ent_in_out->payload_sz = argsize;
+
256
+
257 out->error = error;
+
258 out->unique = req->unique;
+
259
+
260 res = fuse_uring_commit_sqe(ring_pool, queue, ring_ent);
+
261
+
262 fuse_free_req(req);
+
263
+
264 return res;
+
265}
+
266
+
267int fuse_reply_data_uring(fuse_req_t req, struct fuse_bufvec *bufv,
+
268 enum fuse_buf_copy_flags flags)
+
269{
+
270 struct fuse_ring_ent *ring_ent =
+
271 container_of(req, struct fuse_ring_ent, req);
+
272
+
273 struct fuse_ring_queue *queue = ring_ent->ring_queue;
+
274 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
275 struct fuse_uring_req_header *rrh = ring_ent->req_header;
+
276 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
+
277 struct fuse_uring_ent_in_out *ent_in_out =
+
278 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
+
279 size_t max_payload_sz = ring_ent->req_payload_sz;
+
280 struct fuse_bufvec dest_vec = FUSE_BUFVEC_INIT(max_payload_sz);
+
281 int res;
+
282
+
283 dest_vec.buf[0].mem = ring_ent->op_payload;
+
284 dest_vec.buf[0].size = max_payload_sz;
+
285
+
286 res = fuse_buf_copy(&dest_vec, bufv, flags);
+
287
+
288 out->error = res < 0 ? res : 0;
+
289 out->unique = req->unique;
+
290
+
291 ent_in_out->payload_sz = res > 0 ? res : 0;
+
292
+
293 res = fuse_uring_commit_sqe(ring_pool, queue, ring_ent);
+
294
+
295 fuse_free_req(req);
+
296
+
297 return res;
+
298}
+
299
+
303int fuse_send_msg_uring(fuse_req_t req, struct iovec *iov, int count)
+
304{
+
305 struct fuse_ring_ent *ring_ent =
+
306 container_of(req, struct fuse_ring_ent, req);
+
307
+
308 struct fuse_ring_queue *queue = ring_ent->ring_queue;
+
309 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
310 struct fuse_uring_req_header *rrh = ring_ent->req_header;
+
311 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
+
312 struct fuse_uring_ent_in_out *ent_in_out =
+
313 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
+
314 size_t max_buf = ring_pool->max_req_payload_sz;
+
315 size_t len = 0;
+
316 int res = 0;
+
317
+
318 /* copy iov into the payload, idx=0 is the header section */
+
319 for (int idx = 1; idx < count; idx++) {
+
320 struct iovec *cur = &iov[idx];
+
321
+
322 if (len + cur->iov_len > max_buf) {
+
323 fuse_log(FUSE_LOG_ERR,
+
324 "iov[%d] exceeds buffer size %zu",
+
325 idx, max_buf);
+
326 res = -EINVAL; /* Gracefully handle this? */
+
327 break;
+
328 }
+
329
+
330 memcpy(ring_ent->op_payload + len, cur->iov_base, cur->iov_len);
+
331 len += cur->iov_len;
+
332 }
+
333
+
334 ent_in_out->payload_sz = len;
+
335
+
336 out->error = res;
+
337 out->unique = req->unique;
+
338 out->len = len;
+
339
+
340 return fuse_uring_commit_sqe(ring_pool, queue, ring_ent);
+
341}
+
342
+
343static int fuse_queue_setup_io_uring(struct io_uring *ring, size_t qid,
+
344 size_t depth, int fd, int evfd)
+
345{
+
346 int rc;
+
347 struct io_uring_params params = {0};
+
348 int files[2] = { fd, evfd };
+
349
+
350 depth += 1; /* for the eventfd poll SQE */
+
351
+
352 params.flags = IORING_SETUP_SQE128;
+
353
+
354 /* Avoid cq overflow */
+
355 params.flags |= IORING_SETUP_CQSIZE;
+
356 params.cq_entries = depth * 2;
+
357
+
358 /* These flags should help to increase performance, but actually
+
359 * make it a bit slower - reason should get investigated.
+
360 */
+
361 if (0) {
+
362 /* Has the main slow down effect */
+
363 params.flags |= IORING_SETUP_SINGLE_ISSUER;
+
364
+
365 // params.flags |= IORING_SETUP_DEFER_TASKRUN;
+
366 params.flags |= IORING_SETUP_TASKRUN_FLAG;
+
367
+
368 /* Second main effect to make it slower */
+
369 params.flags |= IORING_SETUP_COOP_TASKRUN;
+
370 }
+
371
+
372 rc = io_uring_queue_init_params(depth, ring, &params);
+
373 if (rc != 0) {
+
374 fuse_log(FUSE_LOG_ERR, "Failed to setup qid %zu: %d (%s)\n",
+
375 qid, rc, strerror(-rc));
+
376 return rc;
+
377 }
+
378
+
379 rc = io_uring_register_files(ring, files, 1);
+
380 if (rc != 0) {
+
381 rc = -errno;
+
382 fuse_log(FUSE_LOG_ERR,
+
383 "Failed to register files for ring idx %zu: %s",
+
384 qid, strerror(errno));
+
385 return rc;
+
386 }
+
387
+
388 return 0;
+
389}
+
390
+
391static void fuse_session_destruct_uring(struct fuse_ring_pool *fuse_ring)
+
392{
+
393 for (size_t qid = 0; qid < fuse_ring->nr_queues; qid++) {
+
394 struct fuse_ring_queue *queue =
+
395 fuse_uring_get_queue(fuse_ring, qid);
+
396
+
397 if (queue->tid != 0) {
+
398 uint64_t value = 1ULL;
+
399 int rc;
+
400
+
401 rc = write(queue->eventfd, &value, sizeof(value));
+
402 if (rc != sizeof(value))
+
403 fprintf(stderr,
+
404 "Wrote to eventfd=%d err=%s: rc=%d\n",
+
405 queue->eventfd, strerror(errno), rc);
+
406 pthread_cancel(queue->tid);
+
407 pthread_join(queue->tid, NULL);
+
408 queue->tid = 0;
+
409 }
+
410
+
411 if (queue->eventfd >= 0) {
+
412 close(queue->eventfd);
+
413 queue->eventfd = -1;
+
414 }
+
415
+
416 if (queue->ring.ring_fd != -1)
+
417 io_uring_queue_exit(&queue->ring);
+
418
+
419 for (size_t idx = 0; idx < fuse_ring->queue_depth; idx++) {
+
420 struct fuse_ring_ent *ent = &queue->ent[idx];
+
421
+
422 numa_free(ent->op_payload, ent->req_payload_sz);
+
423 numa_free(ent->req_header, queue->req_header_sz);
+
424 }
+
425
+
426 pthread_mutex_destroy(&queue->ring_lock);
+
427 }
+
428
+
429 free(fuse_ring->queues);
+
430 pthread_cond_destroy(&fuse_ring->thread_start_cond);
+
431 pthread_mutex_destroy(&fuse_ring->thread_start_mutex);
+
432 free(fuse_ring);
+
433}
+
434
+
435static int fuse_uring_register_ent(struct fuse_ring_queue *queue,
+
436 struct fuse_ring_ent *ent)
+
437{
+
438 struct io_uring_sqe *sqe;
+
439
+
440 sqe = io_uring_get_sqe(&queue->ring);
+
441 if (sqe == NULL) {
+
442 /*
+
443 * All SQEs are idle here - no good reason this
+
444 * could fail
+
445 */
+
446 fuse_log(FUSE_LOG_ERR, "Failed to get all ring SQEs");
+
447 return -EIO;
+
448 }
+
449
+
450 ent->last_cmd = FUSE_IO_URING_CMD_REGISTER;
+
451 fuse_uring_sqe_prepare(sqe, ent, ent->last_cmd);
+
452
+
453 /* only needed for fetch */
+
454 ent->iov[0].iov_base = ent->req_header;
+
455 ent->iov[0].iov_len = queue->req_header_sz;
+
456
+
457 ent->iov[1].iov_base = ent->op_payload;
+
458 ent->iov[1].iov_len = ent->req_payload_sz;
+
459
+
460 sqe->addr = (uint64_t)(ent->iov);
+
461 sqe->len = 2;
+
462
+
463 /* this is a fetch, kernel does not read commit id */
+
464 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), queue->qid, 0);
+
465
+
466 return 0;
+
467
+
468}
+
469
+
470static int fuse_uring_register_queue(struct fuse_ring_queue *queue)
+
471{
+
472 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
473 unsigned int sq_ready;
+
474 struct io_uring_sqe *sqe;
+
475 int res;
+
476
+
477 for (size_t idx = 0; idx < ring_pool->queue_depth; idx++) {
+
478 struct fuse_ring_ent *ent = &queue->ent[idx];
+
479
+
480 res = fuse_uring_register_ent(queue, ent);
+
481 if (res != 0)
+
482 return res;
+
483 }
+
484
+
485 sq_ready = io_uring_sq_ready(&queue->ring);
+
486 if (sq_ready != ring_pool->queue_depth) {
+
487 fuse_log(FUSE_LOG_ERR,
+
488 "SQE ready mismatch, expected %zu got %u\n",
+
489 ring_pool->queue_depth, sq_ready);
+
490 return -EINVAL;
+
491 }
+
492
+
493 /* Poll SQE for the eventfd to wake up on teardown */
+
494 sqe = io_uring_get_sqe(&queue->ring);
+
495 if (sqe == NULL) {
+
496 fuse_log(FUSE_LOG_ERR, "Failed to get eventfd SQE");
+
497 return -EIO;
+
498 }
+
499
+
500 io_uring_prep_poll_add(sqe, queue->eventfd, POLLIN);
+
501 io_uring_sqe_set_data(sqe, (void *)(uintptr_t)queue->eventfd);
+
502
+
503 /* Only preparation until here, no submission yet */
+
504
+
505 return 0;
+
506}
+
507
+
508static struct fuse_ring_pool *fuse_create_ring(struct fuse_session *se)
+
509{
+
510 struct fuse_ring_pool *fuse_ring = NULL;
+
511 const size_t nr_queues = get_nprocs_conf();
+
512 size_t payload_sz = se->bufsize - FUSE_BUFFER_HEADER_SIZE;
+
513 size_t queue_sz;
+
514
+
515 if (se->debug)
+
516 fuse_log(FUSE_LOG_DEBUG, "starting io-uring q-depth=%d\n",
+
517 se->uring.q_depth);
+
518
+
519 fuse_ring = calloc(1, sizeof(*fuse_ring));
+
520 if (fuse_ring == NULL) {
+
521 fuse_log(FUSE_LOG_ERR, "Allocating the ring failed\n");
+
522 goto err;
+
523 }
+
524
+
525 queue_sz = fuse_ring_queue_size(se->uring.q_depth);
+
526 fuse_ring->queues = calloc(1, queue_sz * nr_queues);
+
527 if (fuse_ring->queues == NULL) {
+
528 fuse_log(FUSE_LOG_ERR, "Allocating the queues failed\n");
+
529 goto err;
+
530 }
+
531
+
532 fuse_ring->se = se;
+
533 fuse_ring->nr_queues = nr_queues;
+
534 fuse_ring->queue_depth = se->uring.q_depth;
+
535 fuse_ring->max_req_payload_sz = payload_sz;
+
536 fuse_ring->queue_mem_size = queue_sz;
+
537
+
538 /*
+
539 * very basic queue initialization, that cannot fail and will
+
540 * allow easy cleanup if something (like mmap) fails in the middle
+
541 * below
+
542 */
+
543 for (size_t qid = 0; qid < nr_queues; qid++) {
+
544 struct fuse_ring_queue *queue =
+
545 fuse_uring_get_queue(fuse_ring, qid);
+
546
+
547 queue->ring.ring_fd = -1;
+
548 queue->numa_node = numa_node_of_cpu(qid);
+
549 queue->qid = qid;
+
550 queue->ring_pool = fuse_ring;
+
551 queue->eventfd = -1;
+
552 pthread_mutex_init(&queue->ring_lock, NULL);
+
553 }
+
554
+
555 pthread_cond_init(&fuse_ring->thread_start_cond, NULL);
+
556 pthread_mutex_init(&fuse_ring->thread_start_mutex, NULL);
+
557 sem_init(&fuse_ring->init_sem, 0, 0);
+
558
+
559 return fuse_ring;
+
560
+
561err:
+
562 if (fuse_ring)
+
563 fuse_session_destruct_uring(fuse_ring);
+
564
+
565 return NULL;
+
566}
+
567
+
568static void fuse_uring_resubmit(struct fuse_ring_queue *queue,
+
569 struct fuse_ring_ent *ent)
+
570{
+
571 struct io_uring_sqe *sqe;
+
572
+
573 sqe = io_uring_get_sqe(&queue->ring);
+
574 if (sqe == NULL) {
+
575 /* This is an impossible condition, unless there is a bug.
+
576 * The kernel sent back an SQEs, which is assigned to a request.
+
577 * There is no way to get out of SQEs, as the number of
+
578 * SQEs matches the number tof requests.
+
579 */
+
580
+
581 queue->ring_pool->se->error = -EIO;
+
582 fuse_log(FUSE_LOG_ERR, "Failed to get a ring SQEs\n");
+
583
+
584 return;
+
585 }
+
586
+
587 fuse_uring_sqe_prepare(sqe, ent, ent->last_cmd);
+
588
+
589 switch (ent->last_cmd) {
+
590 case FUSE_IO_URING_CMD_REGISTER:
+
591 sqe->addr = (uint64_t)(ent->iov);
+
592 sqe->len = 2;
+
593 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe),
+
594 queue->qid, 0);
+
595 break;
+
596 case FUSE_IO_URING_CMD_COMMIT_AND_FETCH:
+
597 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe),
+
598 queue->qid, ent->req_commit_id);
+
599 break;
+
600 default:
+
601 fuse_log(FUSE_LOG_ERR, "Unknown command type: %d\n",
+
602 ent->last_cmd);
+
603 queue->ring_pool->se->error = -EINVAL;
+
604 break;
+
605 }
+
606
+
607 /* caller submits */
+
608}
+
609
+
610static void fuse_uring_handle_cqe(struct fuse_ring_queue *queue,
+
611 struct io_uring_cqe *cqe)
+
612{
+
613 struct fuse_ring_ent *ent = io_uring_cqe_get_data(cqe);
+
614
+
615 if (!ent) {
+
616 fuse_log(FUSE_LOG_ERR,
+
617 "cqe=%p io_uring_cqe_get_data returned NULL\n", cqe);
+
618 return;
+
619 }
+
620
+
621 struct fuse_req *req = &ent->req;
+
622 struct fuse_ring_pool *fuse_ring = queue->ring_pool;
+
623 struct fuse_uring_req_header *rrh = ent->req_header;
+
624
+
625 struct fuse_in_header *in = (struct fuse_in_header *)&rrh->in_out;
+
626 struct fuse_uring_ent_in_out *ent_in_out = &rrh->ring_ent_in_out;
+
627
+
628 ent->req_commit_id = ent_in_out->commit_id;
+
629 if (unlikely(ent->req_commit_id == 0)) {
+
630 /*
+
631 * If this happens kernel will not find the response - it will
+
632 * be stuck forever - better to abort immediately.
+
633 */
+
634 fuse_log(FUSE_LOG_ERR, "Received invalid commit_id=0\n");
+
635 abort();
+
636 }
+
637
+
638 memset(&req->flags, 0, sizeof(req->flags));
+
639 memset(&req->u, 0, sizeof(req->u));
+
640 req->flags.is_uring = 1;
+
641 req->ref_cnt++;
+
642 req->ch = NULL; /* not needed for uring */
+
643 req->interrupted = 0;
+
644 list_init_req(req);
+
645
+
646 fuse_session_process_uring_cqe(fuse_ring->se, req, in, &rrh->op_in,
+
647 ent->op_payload, ent_in_out->payload_sz);
+
648}
+
649
+
650static int fuse_uring_queue_handle_cqes(struct fuse_ring_queue *queue)
+
651{
+
652 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
653 struct fuse_session *se = ring_pool->se;
+
654 size_t num_completed = 0;
+
655 struct io_uring_cqe *cqe;
+
656 unsigned int head;
+
657 struct fuse_ring_ent *ent;
+
658 int ret = 0;
+
659
+
660 io_uring_for_each_cqe(&queue->ring, head, cqe) {
+
661 int err = 0;
+
662
+
663 num_completed++;
+
664
+
665 err = cqe->res;
+
666 if (unlikely(err != 0)) {
+
667 if (err > 0 && ((uintptr_t)io_uring_cqe_get_data(cqe) ==
+
668 (unsigned int)queue->eventfd)) {
+
669 /* teardown from eventfd */
+
670 return -ENOTCONN;
+
671 }
+
672
+
673
+
674 switch (err) {
+
675 case -EAGAIN:
+
676 fallthrough;
+
677 case -EINTR:
+
678 ent = io_uring_cqe_get_data(cqe);
+
679 fuse_uring_resubmit(queue, ent);
+
680 continue;
+
681 default:
+
682 break;
+
683 }
+
684
+
685 /* -ENOTCONN is ok on umount */
+
686 if (err != -ENOTCONN) {
+
687 se->error = cqe->res;
+
688
+
689 /* return first error */
+
690 if (ret == 0)
+
691 ret = err;
+
692 }
+
693
+
694 } else {
+
695 fuse_uring_handle_cqe(queue, cqe);
+
696 }
+
697 }
+
698
+
699 if (num_completed)
+
700 io_uring_cq_advance(&queue->ring, num_completed);
+
701
+
702 return ret == 0 ? 0 : num_completed;
+
703}
+
704
+
709static void fuse_uring_set_thread_core(int qid)
+
710{
+
711 cpu_set_t mask;
+
712 int rc;
+
713
+
714 CPU_ZERO(&mask);
+
715 CPU_SET(qid, &mask);
+
716 rc = sched_setaffinity(0, sizeof(cpu_set_t), &mask);
+
717 if (rc != 0)
+
718 fuse_log(FUSE_LOG_ERR, "Failed to bind qid=%d to its core: %s\n",
+
719 qid, strerror(errno));
+
720
+
721 if (0) {
+
722 const int policy = SCHED_IDLE;
+
723 const struct sched_param param = {
+
724 .sched_priority = sched_get_priority_min(policy),
+
725 };
+
726
+
727 /* Set the lowest possible priority, so that the application
+
728 * submitting requests is not moved away from the current core.
+
729 */
+
730 rc = sched_setscheduler(0, policy, &param);
+
731 if (rc != 0)
+
732 fuse_log(FUSE_LOG_ERR, "Failed to set scheduler: %s\n",
+
733 strerror(errno));
+
734 }
+
735}
+
736
+
737/*
+
738 * @return negative error code or io-uring file descriptor
+
739 */
+
740static int fuse_uring_init_queue(struct fuse_ring_queue *queue)
+
741{
+
742 struct fuse_ring_pool *ring = queue->ring_pool;
+
743 struct fuse_session *se = ring->se;
+
744 int res;
+
745 size_t page_sz = sysconf(_SC_PAGESIZE);
+
746
+
747 queue->eventfd = eventfd(0, EFD_CLOEXEC);
+
748 if (queue->eventfd < 0) {
+
749 res = -errno;
+
750 fuse_log(FUSE_LOG_ERR,
+
751 "Failed to create eventfd for qid %d: %s\n",
+
752 queue->qid, strerror(errno));
+
753 return res;
+
754 }
+
755
+
756 res = fuse_queue_setup_io_uring(&queue->ring, queue->qid,
+
757 ring->queue_depth, se->fd,
+
758 queue->eventfd);
+
759 if (res != 0) {
+
760 fuse_log(FUSE_LOG_ERR, "qid=%d io_uring init failed\n",
+
761 queue->qid);
+
762 return res;
+
763 }
+
764
+
765 queue->req_header_sz = ROUND_UP(sizeof(struct fuse_ring_ent),
+
766 page_sz);
+
767
+
768 for (size_t idx = 0; idx < ring->queue_depth; idx++) {
+
769 struct fuse_ring_ent *ring_ent = &queue->ent[idx];
+
770 struct fuse_req *req = &ring_ent->req;
+
771
+
772 ring_ent->ring_queue = queue;
+
773
+
774 /*
+
775 * Also allocate the header to have it page aligned, which
+
776 * is a requirement for page pinning
+
777 */
+
778 ring_ent->req_header =
+
779 numa_alloc_local(queue->req_header_sz);
+
780 if (!ring_ent->req_header)
+
781 return -ENOMEM;
+
782 ring_ent->req_payload_sz = ring->max_req_payload_sz;
+
783
+
784 ring_ent->op_payload =
+
785 numa_alloc_local(ring_ent->req_payload_sz);
+
786 if (!ring_ent->op_payload)
+
787 return -ENOMEM;
+
788
+
789 req->se = se;
+
790 pthread_mutex_init(&req->lock, NULL);
+
791 req->flags.is_uring = 1;
+
792 req->ref_cnt = 1; /* extra ref to avoid destruction */
+
793 list_init_req(req);
+
794 }
+
795
+
796 res = fuse_uring_register_queue(queue);
+
797 if (res != 0) {
+
798 fuse_log(
+
799 FUSE_LOG_ERR,
+
800 "Grave fuse-uring error on preparing SQEs, aborting\n");
+
801 se->error = -EIO;
+ +
803 return res;
+
804 }
+
805
+
806 return queue->ring.ring_fd;
+
807}
+
808
+
809static void *fuse_uring_thread(void *arg)
+
810{
+
811 struct fuse_ring_queue *queue = arg;
+
812 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
813 struct fuse_session *se = ring_pool->se;
+
814 int err;
+
815 char thread_name[16] = { 0 };
+
816
+
817 snprintf(thread_name, 16, "fuse-ring-%d", queue->qid);
+
818 thread_name[15] = '\0';
+
819 fuse_set_thread_name(thread_name);
+
820
+
821 fuse_uring_set_thread_core(queue->qid);
+
822
+
823 err = fuse_uring_init_queue(queue);
+
824 pthread_mutex_lock(&ring_pool->thread_start_mutex);
+
825 if (err < 0)
+
826 ring_pool->failed_threads++;
+
827 ring_pool->started_threads++;
+
828 pthread_cond_broadcast(&ring_pool->thread_start_cond);
+
829 pthread_mutex_unlock(&ring_pool->thread_start_mutex);
+
830
+
831 if (err < 0) {
+
832 fuse_log(FUSE_LOG_ERR, "qid=%d queue setup failed\n",
+
833 queue->qid);
+
834 goto err_non_fatal;
+
835 }
+
836
+
837 sem_wait(&ring_pool->init_sem);
+
838
+
839 /* Not using fuse_session_exited(se), as that cannot be inlined */
+
840 while (!atomic_load_explicit(&se->mt_exited, memory_order_relaxed)) {
+
841 io_uring_submit_and_wait(&queue->ring, 1);
+
842
+
843 pthread_mutex_lock(&queue->ring_lock);
+
844 queue->cqe_processing = true;
+
845 err = fuse_uring_queue_handle_cqes(queue);
+
846 queue->cqe_processing = false;
+
847 pthread_mutex_unlock(&queue->ring_lock);
+
848 if (err < 0)
+
849 goto err;
+
850 }
+
851
+
852 return NULL;
+
853
+
854err:
+ +
856err_non_fatal:
+
857 return NULL;
+
858}
+
859
+
860static int fuse_uring_start_ring_threads(struct fuse_ring_pool *ring)
+
861{
+
862 int rc = 0;
+
863
+
864 for (size_t qid = 0; qid < ring->nr_queues; qid++) {
+
865 struct fuse_ring_queue *queue = fuse_uring_get_queue(ring, qid);
+
866
+
867 rc = pthread_create(&queue->tid, NULL, fuse_uring_thread, queue);
+
868 if (rc != 0)
+
869 break;
+
870 }
+
871
+
872 return rc;
+
873}
+
874
+
875static int fuse_uring_sanity_check(struct fuse_session *se)
+
876{
+
877 if (se->uring.q_depth == 0) {
+
878 fuse_log(FUSE_LOG_ERR, "io-uring queue depth must be > 0\n");
+
879 return -EINVAL;
+
880 }
+
881
+
882 _Static_assert(sizeof(struct fuse_uring_cmd_req) <=
+
883 FUSE_URING_MAX_SQE128_CMD_DATA,
+
884 "SQE128_CMD_DATA has 80B cmd data");
+
885
+
886 return 0;
+
887}
+
888
+
889int fuse_uring_start(struct fuse_session *se)
+
890{
+
891 int err = 0;
+
892 struct fuse_ring_pool *fuse_ring;
+
893
+
894 fuse_uring_sanity_check(se);
+
895
+
896 fuse_ring = fuse_create_ring(se);
+
897 if (fuse_ring == NULL) {
+
898 err = -EADDRNOTAVAIL;
+
899 goto err;
+
900 }
+
901
+
902 se->uring.pool = fuse_ring;
+
903
+
904 /* Hold off threads from send fuse ring entries (SQEs) */
+
905 sem_init(&fuse_ring->init_sem, 0, 0);
+
906 pthread_cond_init(&fuse_ring->thread_start_cond, NULL);
+
907 pthread_mutex_init(&fuse_ring->thread_start_mutex, NULL);
+
908
+
909 err = fuse_uring_start_ring_threads(fuse_ring);
+
910 if (err)
+
911 goto err;
+
912
+
913 /*
+
914 * Wait for all threads to start or to fail
+
915 */
+
916 pthread_mutex_lock(&fuse_ring->thread_start_mutex);
+
917 while (fuse_ring->started_threads < fuse_ring->nr_queues)
+
918 pthread_cond_wait(&fuse_ring->thread_start_cond,
+
919 &fuse_ring->thread_start_mutex);
+
920
+
921 if (fuse_ring->failed_threads != 0)
+
922 err = -EADDRNOTAVAIL;
+
923 pthread_mutex_unlock(&fuse_ring->thread_start_mutex);
+
924
+
925err:
+
926 if (err) {
+
927 /* Note all threads need to have been started */
+
928 if (fuse_ring)
+
929 fuse_session_destruct_uring(fuse_ring);
+
930 se->uring.pool = NULL;
+
931 }
+
932 return err;
+
933}
+
934
+
935int fuse_uring_stop(struct fuse_session *se)
+
936{
+
937 struct fuse_ring_pool *ring = se->uring.pool;
+
938
+
939 if (ring == NULL)
+
940 return 0;
+
941
+
942 fuse_session_destruct_uring(ring);
+
943
+
944 return 0;
+
945}
+
946
+
947void fuse_uring_wake_ring_threads(struct fuse_session *se)
+
948{
+
949 struct fuse_ring_pool *ring = se->uring.pool;
+
950
+
951 /* Wake up the threads to let them send SQEs */
+
952 for (size_t qid = 0; qid < ring->nr_queues; qid++)
+
953 sem_post(&ring->init_sem);
+
954}
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
fuse_buf_copy_flags
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
struct fuse_req * fuse_req_t
+
int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, void **mr)
+
void * mem
+
size_t size
+ +
struct fuse_buf buf[1]
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2fuse__uring__i_8h_source.html b/doc/html/fuse-3_818_82_2lib_2fuse__uring__i_8h_source.html new file mode 100644 index 0000000..60c27e1 --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2fuse__uring__i_8h_source.html @@ -0,0 +1,149 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/fuse_uring_i.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_uring_i.h
+
+
+
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 * Copyright (C) 2025 Bernd Schubert <bschubert@ddn.com>
+
4 * This program can be distributed under the terms of the GNU LGPLv2.
+
5 * See the file LGPL2.txt
+
6 */
+
7
+
8#ifndef FUSE_URING_I_H_
+
9#define FUSE_URING_I_H_
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_kernel.h"
+
14
+
15#ifndef HAVE_URING
+
16#include "util.h"
+
17#endif
+
18
+
19#include <errno.h> // IWYU pragma: keep
+
20
+
21/* io-uring defaults */
+
22#define SESSION_DEF_URING_ENABLE (0)
+
23#define SESSION_DEF_URING_Q_DEPTH (8)
+
24
+
25void fuse_session_process_uring_cqe(struct fuse_session *se,
+
26 struct fuse_req *req,
+
27 struct fuse_in_header *in, void *in_header,
+
28 void *in_payload, size_t payload_len);
+
29
+
30#ifdef HAVE_URING
+
31
+
32struct fuse_in_header;
+
33
+
34int fuse_uring_start(struct fuse_session *se);
+
35void fuse_uring_wake_ring_threads(struct fuse_session *se);
+
36int fuse_uring_stop(struct fuse_session *se);
+
37int send_reply_uring(fuse_req_t req, int error, const void *arg,
+
38 size_t argsize);
+
39
+
40int fuse_reply_data_uring(fuse_req_t req, struct fuse_bufvec *bufv,
+
41 enum fuse_buf_copy_flags flags);
+
42int fuse_send_msg_uring(fuse_req_t req, struct iovec *iov, int count);
+
43
+
44#else // HAVE_URING
+
45
+
46static inline int fuse_uring_start(struct fuse_session *se FUSE_VAR_UNUSED)
+
47{
+
48 return -ENOTSUP;
+
49}
+
50
+
51static inline void
+
52fuse_uring_wake_ring_threads(struct fuse_session *se FUSE_VAR_UNUSED)
+
53{
+
54}
+
55
+
56static inline int fuse_uring_stop(struct fuse_session *se FUSE_VAR_UNUSED)
+
57{
+
58 return -ENOTSUP;
+
59}
+
60
+
61static inline int send_reply_uring(fuse_req_t req FUSE_VAR_UNUSED,
+
62 int error FUSE_VAR_UNUSED,
+
63 const void *arg FUSE_VAR_UNUSED,
+
64 size_t argsize FUSE_VAR_UNUSED)
+
65{
+
66 return -ENOTSUP;
+
67}
+
68
+
69static inline int
+
70fuse_reply_data_uring(fuse_req_t req FUSE_VAR_UNUSED,
+
71 struct fuse_bufvec *bufv FUSE_VAR_UNUSED,
+
72 enum fuse_buf_copy_flags flags FUSE_VAR_UNUSED)
+
73{
+
74 return -ENOTSUP;
+
75}
+
76
+
77static inline int fuse_send_msg_uring(fuse_req_t req FUSE_VAR_UNUSED,
+
78 struct iovec *iov FUSE_VAR_UNUSED,
+
79 int count FUSE_VAR_UNUSED)
+
80{
+
81 return -ENOTSUP;
+
82}
+
83
+
84#endif // HAVE_URING
+
85
+
86#endif // FUSE_URING_I_H_
+
fuse_buf_copy_flags
+
struct fuse_req * fuse_req_t
+ +
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2helper_8c_source.html b/doc/html/fuse-3_818_82_2lib_2helper_8c_source.html new file mode 100644 index 0000000..aa006db --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2helper_8c_source.html @@ -0,0 +1,606 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/helper.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
helper.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file LGPL2.txt.
+
11*/
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_i.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_opt.h"
+
17#include "fuse_lowlevel.h"
+
18#include "mount_util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <stddef.h>
+
23#include <unistd.h>
+
24#include <string.h>
+
25#include <limits.h>
+
26#include <errno.h>
+
27#include <sys/param.h>
+
28
+
29#define FUSE_HELPER_OPT(t, p) \
+
30 { t, offsetof(struct fuse_cmdline_opts, p), 1 }
+
31
+
32static const struct fuse_opt fuse_helper_opts[] = {
+
33 FUSE_HELPER_OPT("-h", show_help),
+
34 FUSE_HELPER_OPT("--help", show_help),
+
35 FUSE_HELPER_OPT("-V", show_version),
+
36 FUSE_HELPER_OPT("--version", show_version),
+
37 FUSE_HELPER_OPT("-d", debug),
+
38 FUSE_HELPER_OPT("debug", debug),
+
39 FUSE_HELPER_OPT("-d", foreground),
+
40 FUSE_HELPER_OPT("debug", foreground),
+ + +
43 FUSE_HELPER_OPT("-f", foreground),
+
44 FUSE_HELPER_OPT("-s", singlethread),
+
45 FUSE_HELPER_OPT("fsname=", nodefault_subtype),
+ +
47#ifndef __FreeBSD__
+
48 FUSE_HELPER_OPT("subtype=", nodefault_subtype),
+ +
50#endif
+
51 FUSE_HELPER_OPT("clone_fd", clone_fd),
+
52 FUSE_HELPER_OPT("max_idle_threads=%u", max_idle_threads),
+
53 FUSE_HELPER_OPT("max_threads=%u", max_threads),
+ +
55};
+
56
+
57struct fuse_conn_info_opts {
+
58 int atomic_o_trunc;
+
59 int no_remote_posix_lock;
+
60 int no_remote_flock;
+
61 int splice_write;
+
62 int splice_move;
+
63 int splice_read;
+
64 int no_splice_write;
+
65 int no_splice_move;
+
66 int no_splice_read;
+
67 int auto_inval_data;
+
68 int no_auto_inval_data;
+
69 int no_readdirplus;
+
70 int no_readdirplus_auto;
+
71 int async_dio;
+
72 int no_async_dio;
+
73 int writeback_cache;
+
74 int no_writeback_cache;
+
75 int async_read;
+
76 int sync_read;
+
77 unsigned max_write;
+
78 unsigned max_readahead;
+
79 unsigned max_background;
+
80 unsigned congestion_threshold;
+
81 unsigned time_gran;
+
82 int set_max_write;
+
83 int set_max_readahead;
+
84 int set_max_background;
+
85 int set_congestion_threshold;
+
86 int set_time_gran;
+
87};
+
88
+
89#define CONN_OPTION(t, p, v) \
+
90 { t, offsetof(struct fuse_conn_info_opts, p), v }
+
91static const struct fuse_opt conn_info_opt_spec[] = {
+
92 CONN_OPTION("max_write=%u", max_write, 0),
+
93 CONN_OPTION("max_write=", set_max_write, 1),
+
94 CONN_OPTION("max_readahead=%u", max_readahead, 0),
+
95 CONN_OPTION("max_readahead=", set_max_readahead, 1),
+
96 CONN_OPTION("max_background=%u", max_background, 0),
+
97 CONN_OPTION("max_background=", set_max_background, 1),
+
98 CONN_OPTION("congestion_threshold=%u", congestion_threshold, 0),
+
99 CONN_OPTION("congestion_threshold=", set_congestion_threshold, 1),
+
100 CONN_OPTION("sync_read", sync_read, 1),
+
101 CONN_OPTION("async_read", async_read, 1),
+
102 CONN_OPTION("atomic_o_trunc", atomic_o_trunc, 1),
+
103 CONN_OPTION("no_remote_lock", no_remote_posix_lock, 1),
+
104 CONN_OPTION("no_remote_lock", no_remote_flock, 1),
+
105 CONN_OPTION("no_remote_flock", no_remote_flock, 1),
+
106 CONN_OPTION("no_remote_posix_lock", no_remote_posix_lock, 1),
+
107 CONN_OPTION("splice_write", splice_write, 1),
+
108 CONN_OPTION("no_splice_write", no_splice_write, 1),
+
109 CONN_OPTION("splice_move", splice_move, 1),
+
110 CONN_OPTION("no_splice_move", no_splice_move, 1),
+
111 CONN_OPTION("splice_read", splice_read, 1),
+
112 CONN_OPTION("no_splice_read", no_splice_read, 1),
+
113 CONN_OPTION("auto_inval_data", auto_inval_data, 1),
+
114 CONN_OPTION("no_auto_inval_data", no_auto_inval_data, 1),
+
115 CONN_OPTION("readdirplus=no", no_readdirplus, 1),
+
116 CONN_OPTION("readdirplus=yes", no_readdirplus, 0),
+
117 CONN_OPTION("readdirplus=yes", no_readdirplus_auto, 1),
+
118 CONN_OPTION("readdirplus=auto", no_readdirplus, 0),
+
119 CONN_OPTION("readdirplus=auto", no_readdirplus_auto, 0),
+
120 CONN_OPTION("async_dio", async_dio, 1),
+
121 CONN_OPTION("no_async_dio", no_async_dio, 1),
+
122 CONN_OPTION("writeback_cache", writeback_cache, 1),
+
123 CONN_OPTION("no_writeback_cache", no_writeback_cache, 1),
+
124 CONN_OPTION("time_gran=%u", time_gran, 0),
+
125 CONN_OPTION("time_gran=", set_time_gran, 1),
+ +
127};
+
128
+
129
+
130void fuse_cmdline_help(void)
+
131{
+
132 printf(" -h --help print help\n"
+
133 " -V --version print version\n"
+
134 " -d -o debug enable debug output (implies -f)\n"
+
135 " -f foreground operation\n"
+
136 " -s disable multi-threaded operation\n"
+
137 " -o clone_fd use separate fuse device fd for each thread\n"
+
138 " (may improve performance)\n"
+
139 " -o max_idle_threads the maximum number of idle worker threads\n"
+
140 " allowed (default: -1)\n"
+
141 " -o max_threads the maximum number of worker threads\n"
+
142 " allowed (default: 10)\n");
+
143}
+
144
+
145static int fuse_helper_opt_proc(void *data, const char *arg, int key,
+
146 struct fuse_args *outargs)
+
147{
+
148 (void) outargs;
+
149 struct fuse_cmdline_opts *opts = data;
+
150
+
151 switch (key) {
+ +
153 if (!opts->mountpoint) {
+
154 if (fuse_mnt_parse_fuse_fd(arg) != -1) {
+
155 return fuse_opt_add_opt(&opts->mountpoint, arg);
+
156 }
+
157
+
158 char mountpoint[PATH_MAX] = "";
+
159 if (realpath(arg, mountpoint) == NULL) {
+
160 fuse_log(FUSE_LOG_ERR,
+
161 "fuse: bad mount point `%s': %s\n",
+
162 arg, strerror(errno));
+
163 return -1;
+
164 }
+
165 return fuse_opt_add_opt(&opts->mountpoint, mountpoint);
+
166 } else {
+
167 fuse_log(FUSE_LOG_ERR, "fuse: invalid argument `%s'\n", arg);
+
168 return -1;
+
169 }
+
170
+
171 default:
+
172 /* Pass through unknown options */
+
173 return 1;
+
174 }
+
175}
+
176
+
177/* Under FreeBSD, there is no subtype option so this
+
178 function actually sets the fsname */
+
179static int add_default_subtype(const char *progname, struct fuse_args *args)
+
180{
+
181 int res;
+
182 char *subtype_opt;
+
183
+
184 const char *basename = strrchr(progname, '/');
+
185 if (basename == NULL)
+
186 basename = progname;
+
187 else if (basename[1] != '\0')
+
188 basename++;
+
189
+
190 subtype_opt = (char *) malloc(strlen(basename) + 64);
+
191 if (subtype_opt == NULL) {
+
192 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
193 return -1;
+
194 }
+
195#ifdef __FreeBSD__
+
196 sprintf(subtype_opt, "-ofsname=%s", basename);
+
197#else
+
198 sprintf(subtype_opt, "-osubtype=%s", basename);
+
199#endif
+
200 res = fuse_opt_add_arg(args, subtype_opt);
+
201 free(subtype_opt);
+
202 return res;
+
203}
+
204
+
205int fuse_parse_cmdline_312(struct fuse_args *args,
+
206 struct fuse_cmdline_opts *opts);
+
207FUSE_SYMVER("fuse_parse_cmdline_312", "fuse_parse_cmdline@@FUSE_3.12")
+
208int fuse_parse_cmdline_312(struct fuse_args *args,
+
209 struct fuse_cmdline_opts *opts)
+
210{
+
211 memset(opts, 0, sizeof(struct fuse_cmdline_opts));
+
212
+
213 opts->max_idle_threads = UINT_MAX; /* new default in fuse version 3.12 */
+
214 opts->max_threads = 10;
+
215
+
216 if (fuse_opt_parse(args, opts, fuse_helper_opts,
+
217 fuse_helper_opt_proc) == -1)
+
218 return -1;
+
219
+
220 /* *Linux*: if neither -o subtype nor -o fsname are specified,
+
221 set subtype to program's basename.
+
222 *FreeBSD*: if fsname is not specified, set to program's
+
223 basename. */
+
224 if (!opts->nodefault_subtype)
+
225 if (add_default_subtype(args->argv[0], args) == -1)
+
226 return -1;
+
227
+
228 return 0;
+
229}
+
230
+
234int fuse_parse_cmdline_30(struct fuse_args *args,
+
235 struct fuse_cmdline_opts *opts);
+
236FUSE_SYMVER("fuse_parse_cmdline_30", "fuse_parse_cmdline@FUSE_3.0")
+
237int fuse_parse_cmdline_30(struct fuse_args *args,
+
238 struct fuse_cmdline_opts *out_opts)
+
239{
+
240 struct fuse_cmdline_opts opts;
+
241
+
242 int rc = fuse_parse_cmdline_312(args, &opts);
+
243 if (rc == 0) {
+
244 /* copy up to the size of the old pre 3.12 struct */
+
245 memcpy(out_opts, &opts,
+
246 offsetof(struct fuse_cmdline_opts, max_idle_threads) +
+
247 sizeof(opts.max_idle_threads));
+
248 }
+
249
+
250 return rc;
+
251}
+
252
+
253int fuse_daemonize(int foreground)
+
254{
+
255 if (!foreground) {
+
256 int nullfd;
+
257 int waiter[2];
+
258 char completed;
+
259
+
260 if (pipe(waiter)) {
+
261 perror("fuse_daemonize: pipe");
+
262 return -1;
+
263 }
+
264
+
265 /*
+
266 * demonize current process by forking it and killing the
+
267 * parent. This makes current process as a child of 'init'.
+
268 */
+
269 switch(fork()) {
+
270 case -1:
+
271 perror("fuse_daemonize: fork");
+
272 return -1;
+
273 case 0:
+
274 break;
+
275 default:
+
276 (void) read(waiter[0], &completed, sizeof(completed));
+
277 _exit(0);
+
278 }
+
279
+
280 if (setsid() == -1) {
+
281 perror("fuse_daemonize: setsid");
+
282 return -1;
+
283 }
+
284
+
285 (void) chdir("/");
+
286
+
287 nullfd = open("/dev/null", O_RDWR, 0);
+
288 if (nullfd != -1) {
+
289 (void) dup2(nullfd, 0);
+
290 (void) dup2(nullfd, 1);
+
291 (void) dup2(nullfd, 2);
+
292 if (nullfd > 2)
+
293 close(nullfd);
+
294 }
+
295
+
296 /* Propagate completion of daemon initialization */
+
297 completed = 1;
+
298 (void) write(waiter[1], &completed, sizeof(completed));
+
299 close(waiter[0]);
+
300 close(waiter[1]);
+
301 } else {
+
302 (void) chdir("/");
+
303 }
+
304 return 0;
+
305}
+
306
+
307int fuse_main_real_versioned(int argc, char *argv[],
+
308 const struct fuse_operations *op, size_t op_size,
+
309 struct libfuse_version *version, void *user_data)
+
310{
+
311 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
312 struct fuse *fuse;
+
313 struct fuse_cmdline_opts opts;
+
314 int res;
+
315 struct fuse_loop_config *loop_config = NULL;
+
316
+
317 if (fuse_parse_cmdline(&args, &opts) != 0)
+
318 return 1;
+
319
+
320 if (opts.show_version) {
+
321 printf("FUSE library version %s\n", PACKAGE_VERSION);
+ +
323 res = 0;
+
324 goto out1;
+
325 }
+
326
+
327 if (opts.show_help) {
+
328 if(args.argv[0][0] != '\0')
+
329 printf("usage: %s [options] <mountpoint>\n\n",
+
330 args.argv[0]);
+
331 printf("FUSE options:\n");
+ +
333 fuse_lib_help(&args);
+
334 res = 0;
+
335 goto out1;
+
336 }
+
337
+
338 if (!opts.show_help &&
+
339 !opts.mountpoint) {
+
340 fuse_log(FUSE_LOG_ERR, "error: no mountpoint specified\n");
+
341 res = 2;
+
342 goto out1;
+
343 }
+
344
+
345 struct fuse *_fuse_new_31(struct fuse_args *args,
+
346 const struct fuse_operations *op, size_t op_size,
+
347 struct libfuse_version *version,
+
348 void *user_data);
+
349 fuse = _fuse_new_31(&args, op, op_size, version, user_data);
+
350 if (fuse == NULL) {
+
351 res = 3;
+
352 goto out1;
+
353 }
+
354
+
355 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
356 res = 4;
+
357 goto out2;
+
358 }
+
359
+
360 if (fuse_daemonize(opts.foreground) != 0) {
+
361 res = 5;
+
362 goto out3;
+
363 }
+
364
+
365 struct fuse_session *se = fuse_get_session(fuse);
+
366 if (fuse_set_signal_handlers(se) != 0) {
+
367 res = 6;
+
368 goto out3;
+
369 }
+
370
+
371 if (opts.singlethread)
+
372 res = fuse_loop(fuse);
+
373 else {
+
374 loop_config = fuse_loop_cfg_create();
+
375 if (loop_config == NULL) {
+
376 res = 7;
+
377 goto out3;
+
378 }
+
379
+
380 fuse_loop_cfg_set_clone_fd(loop_config, opts.clone_fd);
+
381
+
382 fuse_loop_cfg_set_idle_threads(loop_config, opts.max_idle_threads);
+
383 fuse_loop_cfg_set_max_threads(loop_config, opts.max_threads);
+
384 res = fuse_loop_mt(fuse, loop_config);
+
385 }
+
386 if (res)
+
387 res = 8;
+
388
+ +
390out3:
+
391 fuse_unmount(fuse);
+
392out2:
+
393 fuse_destroy(fuse);
+
394out1:
+
395 fuse_loop_cfg_destroy(loop_config);
+
396 free(opts.mountpoint);
+
397 fuse_opt_free_args(&args);
+
398 return res;
+
399}
+
400
+
401/* Not symboled, as not part of the official API */
+
402int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
403 size_t op_size, void *user_data);
+
404int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
405 size_t op_size, void *user_data)
+
406{
+
407 struct libfuse_version version = { 0 };
+
408 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
409 user_data);
+
410}
+
411
+
412void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
413 struct fuse_conn_info *conn)
+
414{
+
415 if(opts->set_max_write)
+
416 conn->max_write = opts->max_write;
+
417 if(opts->set_max_background)
+
418 conn->max_background = opts->max_background;
+
419 if(opts->set_congestion_threshold)
+
420 conn->congestion_threshold = opts->congestion_threshold;
+
421 if(opts->set_time_gran)
+
422 conn->time_gran = opts->time_gran;
+
423 if(opts->set_max_readahead)
+
424 conn->max_readahead = opts->max_readahead;
+
425
+
426#define LL_ENABLE(cond, cap) \
+
427 do { \
+
428 if (cond) \
+
429 fuse_set_feature_flag(conn, cap); \
+
430 } while (0)
+
431
+
432#define LL_DISABLE(cond, cap) \
+
433 do { \
+
434 if (cond) \
+
435 fuse_unset_feature_flag(conn, cap); \
+
436 } while (0)
+
437
+
438 LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ);
+
439 LL_DISABLE(opts->no_splice_read, FUSE_CAP_SPLICE_READ);
+
440
+
441 LL_ENABLE(opts->splice_write, FUSE_CAP_SPLICE_WRITE);
+
442 LL_DISABLE(opts->no_splice_write, FUSE_CAP_SPLICE_WRITE);
+
443
+
444 LL_ENABLE(opts->splice_move, FUSE_CAP_SPLICE_MOVE);
+
445 LL_DISABLE(opts->no_splice_move, FUSE_CAP_SPLICE_MOVE);
+
446
+
447 LL_ENABLE(opts->auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
448 LL_DISABLE(opts->no_auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
449
+
450 LL_DISABLE(opts->no_readdirplus, FUSE_CAP_READDIRPLUS);
+
451 LL_DISABLE(opts->no_readdirplus_auto, FUSE_CAP_READDIRPLUS_AUTO);
+
452
+
453 LL_ENABLE(opts->async_dio, FUSE_CAP_ASYNC_DIO);
+
454 LL_DISABLE(opts->no_async_dio, FUSE_CAP_ASYNC_DIO);
+
455
+
456 LL_ENABLE(opts->writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
457 LL_DISABLE(opts->no_writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
458
+
459 LL_ENABLE(opts->async_read, FUSE_CAP_ASYNC_READ);
+
460 LL_DISABLE(opts->sync_read, FUSE_CAP_ASYNC_READ);
+
461
+
462 LL_DISABLE(opts->no_remote_posix_lock, FUSE_CAP_POSIX_LOCKS);
+
463 LL_DISABLE(opts->no_remote_flock, FUSE_CAP_FLOCK_LOCKS);
+
464}
+
465
+
466struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args)
+
467{
+
468 struct fuse_conn_info_opts *opts;
+
469
+
470 opts = calloc(1, sizeof(struct fuse_conn_info_opts));
+
471 if(opts == NULL) {
+
472 fuse_log(FUSE_LOG_ERR, "calloc failed\n");
+
473 return NULL;
+
474 }
+
475 if(fuse_opt_parse(args, opts, conn_info_opt_spec, NULL) == -1) {
+
476 free(opts);
+
477 return NULL;
+
478 }
+
479 return opts;
+
480}
+
481
+
482int fuse_open_channel(const char *mountpoint, const char* options)
+
483{
+
484 struct mount_opts *opts = NULL;
+
485 int fd = -1;
+
486 const char *argv[] = { "", "-o", options };
+
487 int argc = sizeof(argv) / sizeof(argv[0]);
+
488 struct fuse_args args = FUSE_ARGS_INIT(argc, (char**) argv);
+
489
+
490 opts = parse_mount_opts(&args);
+
491 if (opts == NULL)
+
492 return -1;
+
493
+
494 fd = fuse_kern_mount(mountpoint, opts);
+
495 destroy_mount_opts(opts);
+
496
+
497 return fd;
+
498}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5197
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5146
+
int fuse_main_real_versioned(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
Definition helper.c:307
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:482
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5202
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:466
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_READDIRPLUS
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:412
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t time_gran
+
uint32_t congestion_threshold
+
uint32_t max_write
+
uint32_t max_readahead
+
uint32_t max_background
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2modules_2iconv_8c_source.html b/doc/html/fuse-3_818_82_2lib_2modules_2iconv_8c_source.html new file mode 100644 index 0000000..f028692 --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2modules_2iconv_8c_source.html @@ -0,0 +1,835 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/modules/iconv.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
iconv.c
+
+
+
1/*
+
2 fuse iconv module: file name charset conversion
+
3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt
+
7*/
+
8
+
9#include <fuse_config.h>
+
10
+
11#include <fuse.h>
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <stddef.h>
+
15#include <string.h>
+
16#include <errno.h>
+
17#include <iconv.h>
+
18#include <pthread.h>
+
19#include <locale.h>
+
20#include <langinfo.h>
+
21
+
22struct iconv {
+
23 struct fuse_fs *next;
+
24 pthread_mutex_t lock;
+
25 char *from_code;
+
26 char *to_code;
+
27 iconv_t tofs;
+
28 iconv_t fromfs;
+
29};
+
30
+
31struct iconv_dh {
+
32 struct iconv *ic;
+
33 void *prev_buf;
+
34 fuse_fill_dir_t prev_filler;
+
35};
+
36
+
37static struct iconv *iconv_get(void)
+
38{
+ +
40}
+
41
+
42static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp,
+
43 int fromfs)
+
44{
+
45 size_t pathlen;
+
46 size_t newpathlen;
+
47 char *newpath;
+
48 size_t plen;
+
49 char *p;
+
50 size_t res;
+
51 int err;
+
52
+
53 if (path == NULL) {
+
54 *newpathp = NULL;
+
55 return 0;
+
56 }
+
57
+
58 pathlen = strlen(path);
+
59 newpathlen = pathlen * 4;
+
60 newpath = malloc(newpathlen + 1);
+
61 if (!newpath)
+
62 return -ENOMEM;
+
63
+
64 plen = newpathlen;
+
65 p = newpath;
+
66 pthread_mutex_lock(&ic->lock);
+
67 do {
+
68 res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path,
+
69 &pathlen, &p, &plen);
+
70 if (res == (size_t) -1) {
+
71 char *tmp;
+
72 size_t inc;
+
73
+
74 err = -EILSEQ;
+
75 if (errno != E2BIG)
+
76 goto err;
+
77
+
78 inc = (pathlen + 1) * 4;
+
79 newpathlen += inc;
+
80 int dp = p - newpath;
+
81 tmp = realloc(newpath, newpathlen + 1);
+
82 err = -ENOMEM;
+
83 if (!tmp)
+
84 goto err;
+
85
+
86 p = tmp + dp;
+
87 plen += inc;
+
88 newpath = tmp;
+
89 }
+
90 } while (res == (size_t) -1);
+
91 pthread_mutex_unlock(&ic->lock);
+
92 *p = '\0';
+
93 *newpathp = newpath;
+
94 return 0;
+
95
+
96err:
+
97 iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL);
+
98 pthread_mutex_unlock(&ic->lock);
+
99 free(newpath);
+
100 return err;
+
101}
+
102
+
103static int iconv_getattr(const char *path, struct stat *stbuf,
+
104 struct fuse_file_info *fi)
+
105{
+
106 struct iconv *ic = iconv_get();
+
107 char *newpath;
+
108 int err = iconv_convpath(ic, path, &newpath, 0);
+
109 if (!err) {
+
110 err = fuse_fs_getattr(ic->next, newpath, stbuf, fi);
+
111 free(newpath);
+
112 }
+
113 return err;
+
114}
+
115
+
116static int iconv_access(const char *path, int mask)
+
117{
+
118 struct iconv *ic = iconv_get();
+
119 char *newpath;
+
120 int err = iconv_convpath(ic, path, &newpath, 0);
+
121 if (!err) {
+
122 err = fuse_fs_access(ic->next, newpath, mask);
+
123 free(newpath);
+
124 }
+
125 return err;
+
126}
+
127
+
128static int iconv_readlink(const char *path, char *buf, size_t size)
+
129{
+
130 struct iconv *ic = iconv_get();
+
131 char *newpath;
+
132 int err = iconv_convpath(ic, path, &newpath, 0);
+
133 if (!err) {
+
134 err = fuse_fs_readlink(ic->next, newpath, buf, size);
+
135 if (!err) {
+
136 char *newlink;
+
137 err = iconv_convpath(ic, buf, &newlink, 1);
+
138 if (!err) {
+
139 strncpy(buf, newlink, size - 1);
+
140 buf[size - 1] = '\0';
+
141 free(newlink);
+
142 }
+
143 }
+
144 free(newpath);
+
145 }
+
146 return err;
+
147}
+
148
+
149static int iconv_opendir(const char *path, struct fuse_file_info *fi)
+
150{
+
151 struct iconv *ic = iconv_get();
+
152 char *newpath;
+
153 int err = iconv_convpath(ic, path, &newpath, 0);
+
154 if (!err) {
+
155 err = fuse_fs_opendir(ic->next, newpath, fi);
+
156 free(newpath);
+
157 }
+
158 return err;
+
159}
+
160
+
161static int iconv_dir_fill(void *buf, const char *name,
+
162 const struct stat *stbuf, off_t off,
+
163 enum fuse_fill_dir_flags flags)
+
164{
+
165 struct iconv_dh *dh = buf;
+
166 char *newname;
+
167 int res = 0;
+
168 if (iconv_convpath(dh->ic, name, &newname, 1) == 0) {
+
169 res = dh->prev_filler(dh->prev_buf, newname, stbuf, off, flags);
+
170 free(newname);
+
171 }
+
172 return res;
+
173}
+
174
+
175static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
176 off_t offset, struct fuse_file_info *fi,
+
177 enum fuse_readdir_flags flags)
+
178{
+
179 struct iconv *ic = iconv_get();
+
180 char *newpath;
+
181 int err = iconv_convpath(ic, path, &newpath, 0);
+
182 if (!err) {
+
183 struct iconv_dh dh;
+
184 dh.ic = ic;
+
185 dh.prev_buf = buf;
+
186 dh.prev_filler = filler;
+
187 err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill,
+
188 offset, fi, flags);
+
189 free(newpath);
+
190 }
+
191 return err;
+
192}
+
193
+
194static int iconv_releasedir(const char *path, struct fuse_file_info *fi)
+
195{
+
196 struct iconv *ic = iconv_get();
+
197 char *newpath;
+
198 int err = iconv_convpath(ic, path, &newpath, 0);
+
199 if (!err) {
+
200 err = fuse_fs_releasedir(ic->next, newpath, fi);
+
201 free(newpath);
+
202 }
+
203 return err;
+
204}
+
205
+
206static int iconv_mknod(const char *path, mode_t mode, dev_t rdev)
+
207{
+
208 struct iconv *ic = iconv_get();
+
209 char *newpath;
+
210 int err = iconv_convpath(ic, path, &newpath, 0);
+
211 if (!err) {
+
212 err = fuse_fs_mknod(ic->next, newpath, mode, rdev);
+
213 free(newpath);
+
214 }
+
215 return err;
+
216}
+
217
+
218static int iconv_mkdir(const char *path, mode_t mode)
+
219{
+
220 struct iconv *ic = iconv_get();
+
221 char *newpath;
+
222 int err = iconv_convpath(ic, path, &newpath, 0);
+
223 if (!err) {
+
224 err = fuse_fs_mkdir(ic->next, newpath, mode);
+
225 free(newpath);
+
226 }
+
227 return err;
+
228}
+
229
+
230static int iconv_unlink(const char *path)
+
231{
+
232 struct iconv *ic = iconv_get();
+
233 char *newpath;
+
234 int err = iconv_convpath(ic, path, &newpath, 0);
+
235 if (!err) {
+
236 err = fuse_fs_unlink(ic->next, newpath);
+
237 free(newpath);
+
238 }
+
239 return err;
+
240}
+
241
+
242static int iconv_rmdir(const char *path)
+
243{
+
244 struct iconv *ic = iconv_get();
+
245 char *newpath;
+
246 int err = iconv_convpath(ic, path, &newpath, 0);
+
247 if (!err) {
+
248 err = fuse_fs_rmdir(ic->next, newpath);
+
249 free(newpath);
+
250 }
+
251 return err;
+
252}
+
253
+
254static int iconv_symlink(const char *from, const char *to)
+
255{
+
256 struct iconv *ic = iconv_get();
+
257 char *newfrom;
+
258 char *newto;
+
259 int err = iconv_convpath(ic, from, &newfrom, 0);
+
260 if (!err) {
+
261 err = iconv_convpath(ic, to, &newto, 0);
+
262 if (!err) {
+
263 err = fuse_fs_symlink(ic->next, newfrom, newto);
+
264 free(newto);
+
265 }
+
266 free(newfrom);
+
267 }
+
268 return err;
+
269}
+
270
+
271static int iconv_rename(const char *from, const char *to, unsigned int flags)
+
272{
+
273 struct iconv *ic = iconv_get();
+
274 char *newfrom;
+
275 char *newto;
+
276 int err = iconv_convpath(ic, from, &newfrom, 0);
+
277 if (!err) {
+
278 err = iconv_convpath(ic, to, &newto, 0);
+
279 if (!err) {
+
280 err = fuse_fs_rename(ic->next, newfrom, newto, flags);
+
281 free(newto);
+
282 }
+
283 free(newfrom);
+
284 }
+
285 return err;
+
286}
+
287
+
288static int iconv_link(const char *from, const char *to)
+
289{
+
290 struct iconv *ic = iconv_get();
+
291 char *newfrom;
+
292 char *newto;
+
293 int err = iconv_convpath(ic, from, &newfrom, 0);
+
294 if (!err) {
+
295 err = iconv_convpath(ic, to, &newto, 0);
+
296 if (!err) {
+
297 err = fuse_fs_link(ic->next, newfrom, newto);
+
298 free(newto);
+
299 }
+
300 free(newfrom);
+
301 }
+
302 return err;
+
303}
+
304
+
305static int iconv_chmod(const char *path, mode_t mode,
+
306 struct fuse_file_info *fi)
+
307{
+
308 struct iconv *ic = iconv_get();
+
309 char *newpath;
+
310 int err = iconv_convpath(ic, path, &newpath, 0);
+
311 if (!err) {
+
312 err = fuse_fs_chmod(ic->next, newpath, mode, fi);
+
313 free(newpath);
+
314 }
+
315 return err;
+
316}
+
317
+
318static int iconv_chown(const char *path, uid_t uid, gid_t gid,
+
319 struct fuse_file_info *fi)
+
320{
+
321 struct iconv *ic = iconv_get();
+
322 char *newpath;
+
323 int err = iconv_convpath(ic, path, &newpath, 0);
+
324 if (!err) {
+
325 err = fuse_fs_chown(ic->next, newpath, uid, gid, fi);
+
326 free(newpath);
+
327 }
+
328 return err;
+
329}
+
330
+
331static int iconv_truncate(const char *path, off_t size,
+
332 struct fuse_file_info *fi)
+
333{
+
334 struct iconv *ic = iconv_get();
+
335 char *newpath;
+
336 int err = iconv_convpath(ic, path, &newpath, 0);
+
337 if (!err) {
+
338 err = fuse_fs_truncate(ic->next, newpath, size, fi);
+
339 free(newpath);
+
340 }
+
341 return err;
+
342}
+
343
+
344static int iconv_utimens(const char *path, const struct timespec ts[2],
+
345 struct fuse_file_info *fi)
+
346{
+
347 struct iconv *ic = iconv_get();
+
348 char *newpath;
+
349 int err = iconv_convpath(ic, path, &newpath, 0);
+
350 if (!err) {
+
351 err = fuse_fs_utimens(ic->next, newpath, ts, fi);
+
352 free(newpath);
+
353 }
+
354 return err;
+
355}
+
356
+
357static int iconv_create(const char *path, mode_t mode,
+
358 struct fuse_file_info *fi)
+
359{
+
360 struct iconv *ic = iconv_get();
+
361 char *newpath;
+
362 int err = iconv_convpath(ic, path, &newpath, 0);
+
363 if (!err) {
+
364 err = fuse_fs_create(ic->next, newpath, mode, fi);
+
365 free(newpath);
+
366 }
+
367 return err;
+
368}
+
369
+
370static int iconv_open_file(const char *path, struct fuse_file_info *fi)
+
371{
+
372 struct iconv *ic = iconv_get();
+
373 char *newpath;
+
374 int err = iconv_convpath(ic, path, &newpath, 0);
+
375 if (!err) {
+
376 err = fuse_fs_open(ic->next, newpath, fi);
+
377 free(newpath);
+
378 }
+
379 return err;
+
380}
+
381
+
382static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp,
+
383 size_t size, off_t offset, struct fuse_file_info *fi)
+
384{
+
385 struct iconv *ic = iconv_get();
+
386 char *newpath;
+
387 int err = iconv_convpath(ic, path, &newpath, 0);
+
388 if (!err) {
+
389 err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi);
+
390 free(newpath);
+
391 }
+
392 return err;
+
393}
+
394
+
395static int iconv_write_buf(const char *path, struct fuse_bufvec *buf,
+
396 off_t offset, struct fuse_file_info *fi)
+
397{
+
398 struct iconv *ic = iconv_get();
+
399 char *newpath;
+
400 int err = iconv_convpath(ic, path, &newpath, 0);
+
401 if (!err) {
+
402 err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi);
+
403 free(newpath);
+
404 }
+
405 return err;
+
406}
+
407
+
408static int iconv_statfs(const char *path, struct statvfs *stbuf)
+
409{
+
410 struct iconv *ic = iconv_get();
+
411 char *newpath;
+
412 int err = iconv_convpath(ic, path, &newpath, 0);
+
413 if (!err) {
+
414 err = fuse_fs_statfs(ic->next, newpath, stbuf);
+
415 free(newpath);
+
416 }
+
417 return err;
+
418}
+
419
+
420static int iconv_flush(const char *path, struct fuse_file_info *fi)
+
421{
+
422 struct iconv *ic = iconv_get();
+
423 char *newpath;
+
424 int err = iconv_convpath(ic, path, &newpath, 0);
+
425 if (!err) {
+
426 err = fuse_fs_flush(ic->next, newpath, fi);
+
427 free(newpath);
+
428 }
+
429 return err;
+
430}
+
431
+
432static int iconv_release(const char *path, struct fuse_file_info *fi)
+
433{
+
434 struct iconv *ic = iconv_get();
+
435 char *newpath;
+
436 int err = iconv_convpath(ic, path, &newpath, 0);
+
437 if (!err) {
+
438 err = fuse_fs_release(ic->next, newpath, fi);
+
439 free(newpath);
+
440 }
+
441 return err;
+
442}
+
443
+
444static int iconv_fsync(const char *path, int isdatasync,
+
445 struct fuse_file_info *fi)
+
446{
+
447 struct iconv *ic = iconv_get();
+
448 char *newpath;
+
449 int err = iconv_convpath(ic, path, &newpath, 0);
+
450 if (!err) {
+
451 err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi);
+
452 free(newpath);
+
453 }
+
454 return err;
+
455}
+
456
+
457static int iconv_fsyncdir(const char *path, int isdatasync,
+
458 struct fuse_file_info *fi)
+
459{
+
460 struct iconv *ic = iconv_get();
+
461 char *newpath;
+
462 int err = iconv_convpath(ic, path, &newpath, 0);
+
463 if (!err) {
+
464 err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi);
+
465 free(newpath);
+
466 }
+
467 return err;
+
468}
+
469
+
470static int iconv_setxattr(const char *path, const char *name,
+
471 const char *value, size_t size, int flags)
+
472{
+
473 struct iconv *ic = iconv_get();
+
474 char *newpath;
+
475 int err = iconv_convpath(ic, path, &newpath, 0);
+
476 if (!err) {
+
477 err = fuse_fs_setxattr(ic->next, newpath, name, value, size,
+
478 flags);
+
479 free(newpath);
+
480 }
+
481 return err;
+
482}
+
483
+
484static int iconv_getxattr(const char *path, const char *name, char *value,
+
485 size_t size)
+
486{
+
487 struct iconv *ic = iconv_get();
+
488 char *newpath;
+
489 int err = iconv_convpath(ic, path, &newpath, 0);
+
490 if (!err) {
+
491 err = fuse_fs_getxattr(ic->next, newpath, name, value, size);
+
492 free(newpath);
+
493 }
+
494 return err;
+
495}
+
496
+
497static int iconv_listxattr(const char *path, char *list, size_t size)
+
498{
+
499 struct iconv *ic = iconv_get();
+
500 char *newpath;
+
501 int err = iconv_convpath(ic, path, &newpath, 0);
+
502 if (!err) {
+
503 err = fuse_fs_listxattr(ic->next, newpath, list, size);
+
504 free(newpath);
+
505 }
+
506 return err;
+
507}
+
508
+
509static int iconv_removexattr(const char *path, const char *name)
+
510{
+
511 struct iconv *ic = iconv_get();
+
512 char *newpath;
+
513 int err = iconv_convpath(ic, path, &newpath, 0);
+
514 if (!err) {
+
515 err = fuse_fs_removexattr(ic->next, newpath, name);
+
516 free(newpath);
+
517 }
+
518 return err;
+
519}
+
520
+
521static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
522 struct flock *lock)
+
523{
+
524 struct iconv *ic = iconv_get();
+
525 char *newpath;
+
526 int err = iconv_convpath(ic, path, &newpath, 0);
+
527 if (!err) {
+
528 err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock);
+
529 free(newpath);
+
530 }
+
531 return err;
+
532}
+
533
+
534static int iconv_flock(const char *path, struct fuse_file_info *fi, int op)
+
535{
+
536 struct iconv *ic = iconv_get();
+
537 char *newpath;
+
538 int err = iconv_convpath(ic, path, &newpath, 0);
+
539 if (!err) {
+
540 err = fuse_fs_flock(ic->next, newpath, fi, op);
+
541 free(newpath);
+
542 }
+
543 return err;
+
544}
+
545
+
546static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx)
+
547{
+
548 struct iconv *ic = iconv_get();
+
549 char *newpath;
+
550 int err = iconv_convpath(ic, path, &newpath, 0);
+
551 if (!err) {
+
552 err = fuse_fs_bmap(ic->next, newpath, blocksize, idx);
+
553 free(newpath);
+
554 }
+
555 return err;
+
556}
+
557
+
558static off_t iconv_lseek(const char *path, off_t off, int whence,
+
559 struct fuse_file_info *fi)
+
560{
+
561 struct iconv *ic = iconv_get();
+
562 char *newpath;
+
563 int res = iconv_convpath(ic, path, &newpath, 0);
+
564 if (!res) {
+
565 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
+
566 free(newpath);
+
567 }
+
568 return res;
+
569}
+
570
+
571#ifdef HAVE_STATX
+
572static int iconv_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
573 struct fuse_file_info *fi)
+
574{
+
575 struct iconv *ic = iconv_get();
+
576 char *newpath;
+
577 int res = iconv_convpath(ic, path, &newpath, 0);
+
578
+
579 if (!res) {
+
580 res = fuse_fs_statx(ic->next, newpath, flags, mask, stxbuf, fi);
+
581 free(newpath);
+
582 }
+
583 return res;
+
584}
+
585#endif
+
586
+
587static void *iconv_init(struct fuse_conn_info *conn,
+
588 struct fuse_config *cfg)
+
589{
+
590 struct iconv *ic = iconv_get();
+
591 fuse_fs_init(ic->next, conn, cfg);
+
592 /* Don't touch cfg->nullpath_ok, we can work with
+
593 either */
+
594 return ic;
+
595}
+
596
+
597static void iconv_destroy(void *data)
+
598{
+
599 struct iconv *ic = data;
+
600 fuse_fs_destroy(ic->next);
+
601 iconv_close(ic->tofs);
+
602 iconv_close(ic->fromfs);
+
603 pthread_mutex_destroy(&ic->lock);
+
604 free(ic->from_code);
+
605 free(ic->to_code);
+
606 free(ic);
+
607}
+
608
+
609static const struct fuse_operations iconv_oper = {
+
610 .destroy = iconv_destroy,
+
611 .init = iconv_init,
+
612 .getattr = iconv_getattr,
+
613 .access = iconv_access,
+
614 .readlink = iconv_readlink,
+
615 .opendir = iconv_opendir,
+
616 .readdir = iconv_readdir,
+
617 .releasedir = iconv_releasedir,
+
618 .mknod = iconv_mknod,
+
619 .mkdir = iconv_mkdir,
+
620 .symlink = iconv_symlink,
+
621 .unlink = iconv_unlink,
+
622 .rmdir = iconv_rmdir,
+
623 .rename = iconv_rename,
+
624 .link = iconv_link,
+
625 .chmod = iconv_chmod,
+
626 .chown = iconv_chown,
+
627 .truncate = iconv_truncate,
+
628 .utimens = iconv_utimens,
+
629 .create = iconv_create,
+
630 .open = iconv_open_file,
+
631 .read_buf = iconv_read_buf,
+
632 .write_buf = iconv_write_buf,
+
633 .statfs = iconv_statfs,
+
634 .flush = iconv_flush,
+
635 .release = iconv_release,
+
636 .fsync = iconv_fsync,
+
637 .fsyncdir = iconv_fsyncdir,
+
638 .setxattr = iconv_setxattr,
+
639 .getxattr = iconv_getxattr,
+
640 .listxattr = iconv_listxattr,
+
641 .removexattr = iconv_removexattr,
+
642 .lock = iconv_lock,
+
643 .flock = iconv_flock,
+
644 .bmap = iconv_bmap,
+
645 .lseek = iconv_lseek,
+
646#ifdef HAVE_STATX
+
647 .statx = iconv_statx,
+
648#endif
+
649};
+
650
+
651static const struct fuse_opt iconv_opts[] = {
+
652 FUSE_OPT_KEY("-h", 0),
+
653 FUSE_OPT_KEY("--help", 0),
+
654 { "from_code=%s", offsetof(struct iconv, from_code), 0 },
+
655 { "to_code=%s", offsetof(struct iconv, to_code), 1 },
+ +
657};
+
658
+
659static void iconv_help(void)
+
660{
+
661 char *charmap;
+
662 const char *old = setlocale(LC_CTYPE, "");
+
663
+
664 charmap = strdup(nl_langinfo(CODESET));
+
665 if (old)
+
666 setlocale(LC_CTYPE, old);
+
667 else
+
668 perror("setlocale");
+
669
+
670 printf(
+
671" -o from_code=CHARSET original encoding of file names (default: UTF-8)\n"
+
672" -o to_code=CHARSET new encoding of the file names (default: %s)\n",
+
673 charmap);
+
674 free(charmap);
+
675}
+
676
+
677static int iconv_opt_proc(void *data, const char *arg, int key,
+
678 struct fuse_args *outargs)
+
679{
+
680 (void) data; (void) arg; (void) outargs;
+
681
+
682 if (!key) {
+
683 iconv_help();
+
684 return -1;
+
685 }
+
686
+
687 return 1;
+
688}
+
689
+
690static struct fuse_fs *iconv_new(struct fuse_args *args,
+
691 struct fuse_fs *next[])
+
692{
+
693 struct fuse_fs *fs;
+
694 struct iconv *ic;
+
695 const char *old = NULL;
+
696 const char *from;
+
697 const char *to;
+
698
+
699 ic = calloc(1, sizeof(struct iconv));
+
700 if (ic == NULL) {
+
701 fuse_log(FUSE_LOG_ERR, "fuse-iconv: memory allocation failed\n");
+
702 return NULL;
+
703 }
+
704
+
705 if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1)
+
706 goto out_free;
+
707
+
708 if (!next[0] || next[1]) {
+
709 fuse_log(FUSE_LOG_ERR, "fuse-iconv: exactly one next filesystem required\n");
+
710 goto out_free;
+
711 }
+
712
+
713 from = ic->from_code ? ic->from_code : "UTF-8";
+
714 to = ic->to_code ? ic->to_code : "";
+
715 /* FIXME: detect charset equivalence? */
+
716 if (!to[0])
+
717 old = setlocale(LC_CTYPE, "");
+
718 ic->tofs = iconv_open(from, to);
+
719 if (ic->tofs == (iconv_t) -1) {
+
720 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
721 to, from);
+
722 goto out_free;
+
723 }
+
724 ic->fromfs = iconv_open(to, from);
+
725 if (ic->tofs == (iconv_t) -1) {
+
726 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
727 from, to);
+
728 goto out_iconv_close_to;
+
729 }
+
730 if (old) {
+
731 setlocale(LC_CTYPE, old);
+
732 old = NULL;
+
733 }
+
734
+
735 ic->next = next[0];
+
736 fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic);
+
737 if (!fs)
+
738 goto out_iconv_close_from;
+
739
+
740 return fs;
+
741
+
742out_iconv_close_from:
+
743 iconv_close(ic->fromfs);
+
744out_iconv_close_to:
+
745 iconv_close(ic->tofs);
+
746out_free:
+
747 free(ic->from_code);
+
748 free(ic->to_code);
+
749 free(ic);
+
750 if (old) {
+
751 setlocale(LC_CTYPE, old);
+
752 }
+
753 return NULL;
+
754}
+
755
+
756FUSE_REGISTER_MODULE(iconv, iconv_new);
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
fuse_fill_dir_flags
Definition fuse.h:58
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_REGISTER_MODULE(name_, factory_)
Definition fuse.h:1395
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + + +
void * private_data
Definition fuse.h:874
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+ +
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2modules_2subdir_8c_source.html b/doc/html/fuse-3_818_82_2lib_2modules_2subdir_8c_source.html new file mode 100644 index 0000000..69b0950 --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2modules_2subdir_8c_source.html @@ -0,0 +1,786 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/modules/subdir.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
subdir.c
+
+
+
1/*
+
2 fuse subdir module: offset paths with a base directory
+
3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt
+
7*/
+
8
+
9#include <fuse_config.h>
+
10
+
11#include <fuse.h>
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <stddef.h>
+
15#include <string.h>
+
16#include <errno.h>
+
17
+
18struct subdir {
+
19 char *base;
+
20 size_t baselen;
+
21 int rellinks;
+
22 struct fuse_fs *next;
+
23};
+
24
+
25static struct subdir *subdir_get(void)
+
26{
+ +
28}
+
29
+
30static int subdir_addpath(struct subdir *d, const char *path, char **newpathp)
+
31{
+
32 char *newpath = NULL;
+
33
+
34 if (path != NULL) {
+
35 unsigned newlen = d->baselen + strlen(path);
+
36
+
37 newpath = malloc(newlen + 2);
+
38 if (!newpath)
+
39 return -ENOMEM;
+
40
+
41 if (path[0] == '/')
+
42 path++;
+
43 strcpy(newpath, d->base);
+
44 strcpy(newpath + d->baselen, path);
+
45 if (!newpath[0])
+
46 strcpy(newpath, ".");
+
47 }
+
48 *newpathp = newpath;
+
49
+
50 return 0;
+
51}
+
52
+
53static int subdir_getattr(const char *path, struct stat *stbuf,
+
54 struct fuse_file_info *fi)
+
55{
+
56 struct subdir *d = subdir_get();
+
57 char *newpath;
+
58 int err = subdir_addpath(d, path, &newpath);
+
59 if (!err) {
+
60 err = fuse_fs_getattr(d->next, newpath, stbuf, fi);
+
61 free(newpath);
+
62 }
+
63 return err;
+
64}
+
65
+
66static int subdir_access(const char *path, int mask)
+
67{
+
68 struct subdir *d = subdir_get();
+
69 char *newpath;
+
70 int err = subdir_addpath(d, path, &newpath);
+
71 if (!err) {
+
72 err = fuse_fs_access(d->next, newpath, mask);
+
73 free(newpath);
+
74 }
+
75 return err;
+
76}
+
77
+
78
+
79static int count_components(const char *p)
+
80{
+
81 int ctr;
+
82
+
83 for (; *p == '/'; p++);
+
84 for (ctr = 0; *p; ctr++) {
+
85 for (; *p && *p != '/'; p++);
+
86 for (; *p == '/'; p++);
+
87 }
+
88 return ctr;
+
89}
+
90
+
91static void strip_common(const char **sp, const char **tp)
+
92{
+
93 const char *s = *sp;
+
94 const char *t = *tp;
+
95 do {
+
96 for (; *s == '/'; s++);
+
97 for (; *t == '/'; t++);
+
98 *tp = t;
+
99 *sp = s;
+
100 for (; *s == *t && *s && *s != '/'; s++, t++);
+
101 } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t));
+
102}
+
103
+
104static void transform_symlink(struct subdir *d, const char *path,
+
105 char *buf, size_t size)
+
106{
+
107 const char *l = buf;
+
108 size_t llen;
+
109 char *s;
+
110 int dotdots;
+
111 int i;
+
112
+
113 if (l[0] != '/' || d->base[0] != '/')
+
114 return;
+
115
+
116 strip_common(&l, &path);
+
117 if (l - buf < (long) d->baselen)
+
118 return;
+
119
+
120 dotdots = count_components(path);
+
121 if (!dotdots)
+
122 return;
+
123 dotdots--;
+
124
+
125 llen = strlen(l);
+
126 if (dotdots * 3 + llen + 2 > size)
+
127 return;
+
128
+
129 s = buf + dotdots * 3;
+
130 if (llen)
+
131 memmove(s, l, llen + 1);
+
132 else if (!dotdots)
+
133 strcpy(s, ".");
+
134 else
+
135 *s = '\0';
+
136
+
137 for (s = buf, i = 0; i < dotdots; i++, s += 3)
+
138 memcpy(s, "../", 3);
+
139}
+
140
+
141
+
142static int subdir_readlink(const char *path, char *buf, size_t size)
+
143{
+
144 struct subdir *d = subdir_get();
+
145 char *newpath;
+
146 int err = subdir_addpath(d, path, &newpath);
+
147 if (!err) {
+
148 err = fuse_fs_readlink(d->next, newpath, buf, size);
+
149 if (!err && d->rellinks)
+
150 transform_symlink(d, newpath, buf, size);
+
151 free(newpath);
+
152 }
+
153 return err;
+
154}
+
155
+
156static int subdir_opendir(const char *path, struct fuse_file_info *fi)
+
157{
+
158 struct subdir *d = subdir_get();
+
159 char *newpath;
+
160 int err = subdir_addpath(d, path, &newpath);
+
161 if (!err) {
+
162 err = fuse_fs_opendir(d->next, newpath, fi);
+
163 free(newpath);
+
164 }
+
165 return err;
+
166}
+
167
+
168static int subdir_readdir(const char *path, void *buf,
+
169 fuse_fill_dir_t filler, off_t offset,
+
170 struct fuse_file_info *fi,
+
171 enum fuse_readdir_flags flags)
+
172{
+
173 struct subdir *d = subdir_get();
+
174 char *newpath;
+
175 int err = subdir_addpath(d, path, &newpath);
+
176 if (!err) {
+
177 err = fuse_fs_readdir(d->next, newpath, buf, filler, offset,
+
178 fi, flags);
+
179 free(newpath);
+
180 }
+
181 return err;
+
182}
+
183
+
184static int subdir_releasedir(const char *path, struct fuse_file_info *fi)
+
185{
+
186 struct subdir *d = subdir_get();
+
187 char *newpath;
+
188 int err = subdir_addpath(d, path, &newpath);
+
189 if (!err) {
+
190 err = fuse_fs_releasedir(d->next, newpath, fi);
+
191 free(newpath);
+
192 }
+
193 return err;
+
194}
+
195
+
196static int subdir_mknod(const char *path, mode_t mode, dev_t rdev)
+
197{
+
198 struct subdir *d = subdir_get();
+
199 char *newpath;
+
200 int err = subdir_addpath(d, path, &newpath);
+
201 if (!err) {
+
202 err = fuse_fs_mknod(d->next, newpath, mode, rdev);
+
203 free(newpath);
+
204 }
+
205 return err;
+
206}
+
207
+
208static int subdir_mkdir(const char *path, mode_t mode)
+
209{
+
210 struct subdir *d = subdir_get();
+
211 char *newpath;
+
212 int err = subdir_addpath(d, path, &newpath);
+
213 if (!err) {
+
214 err = fuse_fs_mkdir(d->next, newpath, mode);
+
215 free(newpath);
+
216 }
+
217 return err;
+
218}
+
219
+
220static int subdir_unlink(const char *path)
+
221{
+
222 struct subdir *d = subdir_get();
+
223 char *newpath;
+
224 int err = subdir_addpath(d, path, &newpath);
+
225 if (!err) {
+
226 err = fuse_fs_unlink(d->next, newpath);
+
227 free(newpath);
+
228 }
+
229 return err;
+
230}
+
231
+
232static int subdir_rmdir(const char *path)
+
233{
+
234 struct subdir *d = subdir_get();
+
235 char *newpath;
+
236 int err = subdir_addpath(d, path, &newpath);
+
237 if (!err) {
+
238 err = fuse_fs_rmdir(d->next, newpath);
+
239 free(newpath);
+
240 }
+
241 return err;
+
242}
+
243
+
244static int subdir_symlink(const char *from, const char *path)
+
245{
+
246 struct subdir *d = subdir_get();
+
247 char *newpath;
+
248 int err = subdir_addpath(d, path, &newpath);
+
249 if (!err) {
+
250 err = fuse_fs_symlink(d->next, from, newpath);
+
251 free(newpath);
+
252 }
+
253 return err;
+
254}
+
255
+
256static int subdir_rename(const char *from, const char *to, unsigned int flags)
+
257{
+
258 struct subdir *d = subdir_get();
+
259 char *newfrom;
+
260 char *newto;
+
261 int err = subdir_addpath(d, from, &newfrom);
+
262 if (!err) {
+
263 err = subdir_addpath(d, to, &newto);
+
264 if (!err) {
+
265 err = fuse_fs_rename(d->next, newfrom, newto, flags);
+
266 free(newto);
+
267 }
+
268 free(newfrom);
+
269 }
+
270 return err;
+
271}
+
272
+
273static int subdir_link(const char *from, const char *to)
+
274{
+
275 struct subdir *d = subdir_get();
+
276 char *newfrom;
+
277 char *newto;
+
278 int err = subdir_addpath(d, from, &newfrom);
+
279 if (!err) {
+
280 err = subdir_addpath(d, to, &newto);
+
281 if (!err) {
+
282 err = fuse_fs_link(d->next, newfrom, newto);
+
283 free(newto);
+
284 }
+
285 free(newfrom);
+
286 }
+
287 return err;
+
288}
+
289
+
290static int subdir_chmod(const char *path, mode_t mode,
+
291 struct fuse_file_info *fi)
+
292{
+
293 struct subdir *d = subdir_get();
+
294 char *newpath;
+
295 int err = subdir_addpath(d, path, &newpath);
+
296 if (!err) {
+
297 err = fuse_fs_chmod(d->next, newpath, mode, fi);
+
298 free(newpath);
+
299 }
+
300 return err;
+
301}
+
302
+
303static int subdir_chown(const char *path, uid_t uid, gid_t gid,
+
304 struct fuse_file_info *fi)
+
305{
+
306 struct subdir *d = subdir_get();
+
307 char *newpath;
+
308 int err = subdir_addpath(d, path, &newpath);
+
309 if (!err) {
+
310 err = fuse_fs_chown(d->next, newpath, uid, gid, fi);
+
311 free(newpath);
+
312 }
+
313 return err;
+
314}
+
315
+
316static int subdir_truncate(const char *path, off_t size,
+
317 struct fuse_file_info *fi)
+
318{
+
319 struct subdir *d = subdir_get();
+
320 char *newpath;
+
321 int err = subdir_addpath(d, path, &newpath);
+
322 if (!err) {
+
323 err = fuse_fs_truncate(d->next, newpath, size, fi);
+
324 free(newpath);
+
325 }
+
326 return err;
+
327}
+
328
+
329static int subdir_utimens(const char *path, const struct timespec ts[2],
+
330 struct fuse_file_info *fi)
+
331{
+
332 struct subdir *d = subdir_get();
+
333 char *newpath;
+
334 int err = subdir_addpath(d, path, &newpath);
+
335 if (!err) {
+
336 err = fuse_fs_utimens(d->next, newpath, ts, fi);
+
337 free(newpath);
+
338 }
+
339 return err;
+
340}
+
341
+
342static int subdir_create(const char *path, mode_t mode,
+
343 struct fuse_file_info *fi)
+
344{
+
345 struct subdir *d = subdir_get();
+
346 char *newpath;
+
347 int err = subdir_addpath(d, path, &newpath);
+
348 if (!err) {
+
349 err = fuse_fs_create(d->next, newpath, mode, fi);
+
350 free(newpath);
+
351 }
+
352 return err;
+
353}
+
354
+
355static int subdir_open(const char *path, struct fuse_file_info *fi)
+
356{
+
357 struct subdir *d = subdir_get();
+
358 char *newpath;
+
359 int err = subdir_addpath(d, path, &newpath);
+
360 if (!err) {
+
361 err = fuse_fs_open(d->next, newpath, fi);
+
362 free(newpath);
+
363 }
+
364 return err;
+
365}
+
366
+
367static int subdir_read_buf(const char *path, struct fuse_bufvec **bufp,
+
368 size_t size, off_t offset, struct fuse_file_info *fi)
+
369{
+
370 struct subdir *d = subdir_get();
+
371 char *newpath;
+
372 int err = subdir_addpath(d, path, &newpath);
+
373 if (!err) {
+
374 err = fuse_fs_read_buf(d->next, newpath, bufp, size, offset, fi);
+
375 free(newpath);
+
376 }
+
377 return err;
+
378}
+
379
+
380static int subdir_write_buf(const char *path, struct fuse_bufvec *buf,
+
381 off_t offset, struct fuse_file_info *fi)
+
382{
+
383 struct subdir *d = subdir_get();
+
384 char *newpath;
+
385 int err = subdir_addpath(d, path, &newpath);
+
386 if (!err) {
+
387 err = fuse_fs_write_buf(d->next, newpath, buf, offset, fi);
+
388 free(newpath);
+
389 }
+
390 return err;
+
391}
+
392
+
393static int subdir_statfs(const char *path, struct statvfs *stbuf)
+
394{
+
395 struct subdir *d = subdir_get();
+
396 char *newpath;
+
397 int err = subdir_addpath(d, path, &newpath);
+
398 if (!err) {
+
399 err = fuse_fs_statfs(d->next, newpath, stbuf);
+
400 free(newpath);
+
401 }
+
402 return err;
+
403}
+
404
+
405static int subdir_flush(const char *path, struct fuse_file_info *fi)
+
406{
+
407 struct subdir *d = subdir_get();
+
408 char *newpath;
+
409 int err = subdir_addpath(d, path, &newpath);
+
410 if (!err) {
+
411 err = fuse_fs_flush(d->next, newpath, fi);
+
412 free(newpath);
+
413 }
+
414 return err;
+
415}
+
416
+
417static int subdir_release(const char *path, struct fuse_file_info *fi)
+
418{
+
419 struct subdir *d = subdir_get();
+
420 char *newpath;
+
421 int err = subdir_addpath(d, path, &newpath);
+
422 if (!err) {
+
423 err = fuse_fs_release(d->next, newpath, fi);
+
424 free(newpath);
+
425 }
+
426 return err;
+
427}
+
428
+
429static int subdir_fsync(const char *path, int isdatasync,
+
430 struct fuse_file_info *fi)
+
431{
+
432 struct subdir *d = subdir_get();
+
433 char *newpath;
+
434 int err = subdir_addpath(d, path, &newpath);
+
435 if (!err) {
+
436 err = fuse_fs_fsync(d->next, newpath, isdatasync, fi);
+
437 free(newpath);
+
438 }
+
439 return err;
+
440}
+
441
+
442static int subdir_fsyncdir(const char *path, int isdatasync,
+
443 struct fuse_file_info *fi)
+
444{
+
445 struct subdir *d = subdir_get();
+
446 char *newpath;
+
447 int err = subdir_addpath(d, path, &newpath);
+
448 if (!err) {
+
449 err = fuse_fs_fsyncdir(d->next, newpath, isdatasync, fi);
+
450 free(newpath);
+
451 }
+
452 return err;
+
453}
+
454
+
455static int subdir_setxattr(const char *path, const char *name,
+
456 const char *value, size_t size, int flags)
+
457{
+
458 struct subdir *d = subdir_get();
+
459 char *newpath;
+
460 int err = subdir_addpath(d, path, &newpath);
+
461 if (!err) {
+
462 err = fuse_fs_setxattr(d->next, newpath, name, value, size,
+
463 flags);
+
464 free(newpath);
+
465 }
+
466 return err;
+
467}
+
468
+
469static int subdir_getxattr(const char *path, const char *name, char *value,
+
470 size_t size)
+
471{
+
472 struct subdir *d = subdir_get();
+
473 char *newpath;
+
474 int err = subdir_addpath(d, path, &newpath);
+
475 if (!err) {
+
476 err = fuse_fs_getxattr(d->next, newpath, name, value, size);
+
477 free(newpath);
+
478 }
+
479 return err;
+
480}
+
481
+
482static int subdir_listxattr(const char *path, char *list, size_t size)
+
483{
+
484 struct subdir *d = subdir_get();
+
485 char *newpath;
+
486 int err = subdir_addpath(d, path, &newpath);
+
487 if (!err) {
+
488 err = fuse_fs_listxattr(d->next, newpath, list, size);
+
489 free(newpath);
+
490 }
+
491 return err;
+
492}
+
493
+
494static int subdir_removexattr(const char *path, const char *name)
+
495{
+
496 struct subdir *d = subdir_get();
+
497 char *newpath;
+
498 int err = subdir_addpath(d, path, &newpath);
+
499 if (!err) {
+
500 err = fuse_fs_removexattr(d->next, newpath, name);
+
501 free(newpath);
+
502 }
+
503 return err;
+
504}
+
505
+
506static int subdir_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
507 struct flock *lock)
+
508{
+
509 struct subdir *d = subdir_get();
+
510 char *newpath;
+
511 int err = subdir_addpath(d, path, &newpath);
+
512 if (!err) {
+
513 err = fuse_fs_lock(d->next, newpath, fi, cmd, lock);
+
514 free(newpath);
+
515 }
+
516 return err;
+
517}
+
518
+
519static int subdir_flock(const char *path, struct fuse_file_info *fi, int op)
+
520{
+
521 struct subdir *d = subdir_get();
+
522 char *newpath;
+
523 int err = subdir_addpath(d, path, &newpath);
+
524 if (!err) {
+
525 err = fuse_fs_flock(d->next, newpath, fi, op);
+
526 free(newpath);
+
527 }
+
528 return err;
+
529}
+
530
+
531static int subdir_bmap(const char *path, size_t blocksize, uint64_t *idx)
+
532{
+
533 struct subdir *d = subdir_get();
+
534 char *newpath;
+
535 int err = subdir_addpath(d, path, &newpath);
+
536 if (!err) {
+
537 err = fuse_fs_bmap(d->next, newpath, blocksize, idx);
+
538 free(newpath);
+
539 }
+
540 return err;
+
541}
+
542
+
543static off_t subdir_lseek(const char *path, off_t off, int whence,
+
544 struct fuse_file_info *fi)
+
545{
+
546 struct subdir *ic = subdir_get();
+
547 char *newpath;
+
548 int res = subdir_addpath(ic, path, &newpath);
+
549 if (!res) {
+
550 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
+
551 free(newpath);
+
552 }
+
553 return res;
+
554}
+
555
+
556#ifdef HAVE_STATX
+
557static int subdir_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
558 struct fuse_file_info *fi)
+
559{
+
560 struct subdir *ic = subdir_get();
+
561 char *newpath;
+
562 int res = subdir_addpath(ic, path, &newpath);
+
563
+
564 if (!res) {
+
565 res = fuse_fs_statx(ic->next, newpath, flags, mask, stxbuf, fi);
+
566 free(newpath);
+
567 }
+
568 return res;
+
569}
+
570#endif
+
571
+
572static void *subdir_init(struct fuse_conn_info *conn,
+
573 struct fuse_config *cfg)
+
574{
+
575 struct subdir *d = subdir_get();
+
576 fuse_fs_init(d->next, conn, cfg);
+
577 /* Don't touch cfg->nullpath_ok, we can work with
+
578 either */
+
579 return d;
+
580}
+
581
+
582static void subdir_destroy(void *data)
+
583{
+
584 struct subdir *d = data;
+
585 fuse_fs_destroy(d->next);
+
586 free(d->base);
+
587 free(d);
+
588}
+
589
+
590static const struct fuse_operations subdir_oper = {
+
591 .destroy = subdir_destroy,
+
592 .init = subdir_init,
+
593 .getattr = subdir_getattr,
+
594 .access = subdir_access,
+
595 .readlink = subdir_readlink,
+
596 .opendir = subdir_opendir,
+
597 .readdir = subdir_readdir,
+
598 .releasedir = subdir_releasedir,
+
599 .mknod = subdir_mknod,
+
600 .mkdir = subdir_mkdir,
+
601 .symlink = subdir_symlink,
+
602 .unlink = subdir_unlink,
+
603 .rmdir = subdir_rmdir,
+
604 .rename = subdir_rename,
+
605 .link = subdir_link,
+
606 .chmod = subdir_chmod,
+
607 .chown = subdir_chown,
+
608 .truncate = subdir_truncate,
+
609 .utimens = subdir_utimens,
+
610 .create = subdir_create,
+
611 .open = subdir_open,
+
612 .read_buf = subdir_read_buf,
+
613 .write_buf = subdir_write_buf,
+
614 .statfs = subdir_statfs,
+
615 .flush = subdir_flush,
+
616 .release = subdir_release,
+
617 .fsync = subdir_fsync,
+
618 .fsyncdir = subdir_fsyncdir,
+
619 .setxattr = subdir_setxattr,
+
620 .getxattr = subdir_getxattr,
+
621 .listxattr = subdir_listxattr,
+
622 .removexattr = subdir_removexattr,
+
623 .lock = subdir_lock,
+
624 .flock = subdir_flock,
+
625 .bmap = subdir_bmap,
+
626 .lseek = subdir_lseek,
+
627#ifdef HAVE_STATX
+
628 .statx = subdir_statx,
+
629#endif
+
630};
+
631
+
632static const struct fuse_opt subdir_opts[] = {
+
633 FUSE_OPT_KEY("-h", 0),
+
634 FUSE_OPT_KEY("--help", 0),
+
635 { "subdir=%s", offsetof(struct subdir, base), 0 },
+
636 { "rellinks", offsetof(struct subdir, rellinks), 1 },
+
637 { "norellinks", offsetof(struct subdir, rellinks), 0 },
+ +
639};
+
640
+
641static void subdir_help(void)
+
642{
+
643 printf(
+
644" -o subdir=DIR prepend this directory to all paths (mandatory)\n"
+
645" -o [no]rellinks transform absolute symlinks to relative\n");
+
646}
+
647
+
648static int subdir_opt_proc(void *data, const char *arg, int key,
+
649 struct fuse_args *outargs)
+
650{
+
651 (void) data; (void) arg; (void) outargs;
+
652
+
653 if (!key) {
+
654 subdir_help();
+
655 return -1;
+
656 }
+
657
+
658 return 1;
+
659}
+
660
+
661static struct fuse_fs *subdir_new(struct fuse_args *args,
+
662 struct fuse_fs *next[])
+
663{
+
664 struct fuse_fs *fs;
+
665 struct subdir *d;
+
666
+
667 d = calloc(1, sizeof(struct subdir));
+
668 if (d == NULL) {
+
669 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
+
670 return NULL;
+
671 }
+
672
+
673 if (fuse_opt_parse(args, d, subdir_opts, subdir_opt_proc) == -1)
+
674 goto out_free;
+
675
+
676 if (!next[0] || next[1]) {
+
677 fuse_log(FUSE_LOG_ERR, "fuse-subdir: exactly one next filesystem required\n");
+
678 goto out_free;
+
679 }
+
680
+
681 if (!d->base) {
+
682 fuse_log(FUSE_LOG_ERR, "fuse-subdir: missing 'subdir' option\n");
+
683 goto out_free;
+
684 }
+
685
+
686 if (d->base[0] && d->base[strlen(d->base)-1] != '/') {
+
687 char *tmp = realloc(d->base, strlen(d->base) + 2);
+
688 if (!tmp) {
+
689 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
+
690 goto out_free;
+
691 }
+
692 d->base = tmp;
+
693 strcat(d->base, "/");
+
694 }
+
695 d->baselen = strlen(d->base);
+
696 d->next = next[0];
+
697 fs = fuse_fs_new(&subdir_oper, sizeof(subdir_oper), d);
+
698 if (!fs)
+
699 goto out_free;
+
700 return fs;
+
701
+
702out_free:
+
703 free(d->base);
+
704 free(d);
+
705 return NULL;
+
706}
+
707
+
708FUSE_REGISTER_MODULE(subdir, subdir_new);
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_REGISTER_MODULE(name_, factory_)
Definition fuse.h:1395
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + + +
void * private_data
Definition fuse.h:874
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+ +
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2mount_8c_source.html b/doc/html/fuse-3_818_82_2lib_2mount_8c_source.html new file mode 100644 index 0000000..94406dc --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2mount_8c_source.html @@ -0,0 +1,794 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/mount.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Architecture specific file system mounting (Linux).
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt.
+
9*/
+
10
+
11/* For environ */
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_misc.h"
+
17#include "fuse_opt.h"
+
18#include "mount_util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <unistd.h>
+
23#include <stddef.h>
+
24#include <string.h>
+
25#include <fcntl.h>
+
26#include <errno.h>
+
27#include <poll.h>
+
28#include <spawn.h>
+
29#include <sys/socket.h>
+
30#include <sys/un.h>
+
31#include <sys/wait.h>
+
32
+
33#include "fuse_mount_compat.h"
+
34
+
35#ifdef __NetBSD__
+
36#include <perfuse.h>
+
37
+
38#define MS_RDONLY MNT_RDONLY
+
39#define MS_NOSUID MNT_NOSUID
+
40#define MS_NODEV MNT_NODEV
+
41#define MS_NOEXEC MNT_NOEXEC
+
42#define MS_SYNCHRONOUS MNT_SYNCHRONOUS
+
43#define MS_NOATIME MNT_NOATIME
+
44#define MS_NOSYMFOLLOW MNT_NOSYMFOLLOW
+
45
+
46#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
+
47#endif
+
48
+
49#define FUSERMOUNT_PROG "fusermount3"
+
50#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
+
51#define FUSE_COMMFD2_ENV "_FUSE_COMMFD2"
+
52#define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE"
+
53
+
54#ifndef MS_DIRSYNC
+
55#define MS_DIRSYNC 128
+
56#endif
+
57
+
58enum {
+
59 KEY_KERN_FLAG,
+
60 KEY_KERN_OPT,
+
61 KEY_FUSERMOUNT_OPT,
+
62 KEY_SUBTYPE_OPT,
+
63 KEY_MTAB_OPT,
+
64 KEY_ALLOW_OTHER,
+
65 KEY_RO,
+
66};
+
67
+
68struct mount_opts {
+
69 int allow_other;
+
70 int flags;
+
71 int auto_unmount;
+
72 int blkdev;
+
73 char *fsname;
+
74 char *subtype;
+
75 char *subtype_opt;
+
76 char *mtab_opts;
+
77 char *fusermount_opts;
+
78 char *kernel_opts;
+
79 unsigned max_read;
+
80};
+
81
+
82#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 }
+
83
+
84static const struct fuse_opt fuse_mount_opts[] = {
+
85 FUSE_MOUNT_OPT("allow_other", allow_other),
+
86 FUSE_MOUNT_OPT("blkdev", blkdev),
+
87 FUSE_MOUNT_OPT("auto_unmount", auto_unmount),
+
88 FUSE_MOUNT_OPT("fsname=%s", fsname),
+
89 FUSE_MOUNT_OPT("max_read=%u", max_read),
+
90 FUSE_MOUNT_OPT("subtype=%s", subtype),
+
91 FUSE_OPT_KEY("allow_other", KEY_KERN_OPT),
+
92 FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT),
+
93 FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT),
+
94 FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT),
+
95 FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT),
+
96 FUSE_OPT_KEY("blksize=", KEY_KERN_OPT),
+
97 FUSE_OPT_KEY("default_permissions", KEY_KERN_OPT),
+
98 FUSE_OPT_KEY("context=", KEY_KERN_OPT),
+
99 FUSE_OPT_KEY("fscontext=", KEY_KERN_OPT),
+
100 FUSE_OPT_KEY("defcontext=", KEY_KERN_OPT),
+
101 FUSE_OPT_KEY("rootcontext=", KEY_KERN_OPT),
+
102 FUSE_OPT_KEY("max_read=", KEY_KERN_OPT),
+
103 FUSE_OPT_KEY("user=", KEY_MTAB_OPT),
+
104 FUSE_OPT_KEY("-n", KEY_MTAB_OPT),
+
105 FUSE_OPT_KEY("-r", KEY_RO),
+
106 FUSE_OPT_KEY("ro", KEY_KERN_FLAG),
+
107 FUSE_OPT_KEY("rw", KEY_KERN_FLAG),
+
108 FUSE_OPT_KEY("suid", KEY_KERN_FLAG),
+
109 FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG),
+
110 FUSE_OPT_KEY("dev", KEY_KERN_FLAG),
+
111 FUSE_OPT_KEY("nodev", KEY_KERN_FLAG),
+
112 FUSE_OPT_KEY("exec", KEY_KERN_FLAG),
+
113 FUSE_OPT_KEY("noexec", KEY_KERN_FLAG),
+
114 FUSE_OPT_KEY("async", KEY_KERN_FLAG),
+
115 FUSE_OPT_KEY("sync", KEY_KERN_FLAG),
+
116 FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG),
+
117 FUSE_OPT_KEY("noatime", KEY_KERN_FLAG),
+
118 FUSE_OPT_KEY("nodiratime", KEY_KERN_FLAG),
+
119 FUSE_OPT_KEY("nostrictatime", KEY_KERN_FLAG),
+
120 FUSE_OPT_KEY("symfollow", KEY_KERN_FLAG),
+
121 FUSE_OPT_KEY("nosymfollow", KEY_KERN_FLAG),
+ +
123};
+
124
+
125/*
+
126 * Running fusermount by calling 'posix_spawn'
+
127 *
+
128 * @param out_pid might be NULL
+
129 */
+
130static int fusermount_posix_spawn(posix_spawn_file_actions_t *action,
+
131 char const * const argv[], pid_t *out_pid)
+
132{
+
133 const char *full_path = FUSERMOUNT_DIR "/" FUSERMOUNT_PROG;
+
134 pid_t pid;
+
135
+
136 /* See man 7 environ for the global environ pointer */
+
137
+
138 /* first try the install path */
+
139 int status = posix_spawn(&pid, full_path, action, NULL,
+
140 (char * const *) argv, environ);
+
141 if (status != 0) {
+
142 /* if that fails, try a system install */
+
143 status = posix_spawnp(&pid, FUSERMOUNT_PROG, action, NULL,
+
144 (char * const *) argv, environ);
+
145 }
+
146
+
147 if (status != 0) {
+
148 fuse_log(FUSE_LOG_ERR, "Failed to call '%s': %s\n",
+
149 FUSERMOUNT_PROG, strerror(status));
+
150 return -status;
+
151 }
+
152
+
153 if (out_pid)
+
154 *out_pid = pid;
+
155 else
+
156 waitpid(pid, NULL, 0); /* FIXME: check exit code and return error if any */
+
157
+
158 return 0;
+
159}
+
160
+
161void fuse_mount_version(void)
+
162{
+
163 char const *const argv[] = {FUSERMOUNT_PROG, "--version", NULL};
+
164 int status = fusermount_posix_spawn(NULL, argv, NULL);
+
165
+
166 if(status != 0)
+
167 fuse_log(FUSE_LOG_ERR, "Running '%s --version' failed",
+
168 FUSERMOUNT_PROG);
+
169}
+
170
+
171struct mount_flags {
+
172 const char *opt;
+
173 unsigned long flag;
+
174 int on;
+
175};
+
176
+
177static const struct mount_flags mount_flags[] = {
+
178 {"rw", MS_RDONLY, 0},
+
179 {"ro", MS_RDONLY, 1},
+
180 {"suid", MS_NOSUID, 0},
+
181 {"nosuid", MS_NOSUID, 1},
+
182 {"dev", MS_NODEV, 0},
+
183 {"nodev", MS_NODEV, 1},
+
184 {"exec", MS_NOEXEC, 0},
+
185 {"noexec", MS_NOEXEC, 1},
+
186 {"async", MS_SYNCHRONOUS, 0},
+
187 {"sync", MS_SYNCHRONOUS, 1},
+
188 {"noatime", MS_NOATIME, 1},
+
189 {"nodiratime", MS_NODIRATIME, 1},
+
190 {"norelatime", MS_RELATIME, 0},
+
191 {"nostrictatime", MS_STRICTATIME, 0},
+
192 {"symfollow", MS_NOSYMFOLLOW, 0},
+
193 {"nosymfollow", MS_NOSYMFOLLOW, 1},
+
194#ifndef __NetBSD__
+
195 {"dirsync", MS_DIRSYNC, 1},
+
196#endif
+
197 {NULL, 0, 0}
+
198};
+
199
+
200unsigned get_max_read(struct mount_opts *o)
+
201{
+
202 return o->max_read;
+
203}
+
204
+
205static void set_mount_flag(const char *s, int *flags)
+
206{
+
207 int i;
+
208
+
209 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
210 const char *opt = mount_flags[i].opt;
+
211 if (strcmp(opt, s) == 0) {
+
212 if (mount_flags[i].on)
+
213 *flags |= mount_flags[i].flag;
+
214 else
+
215 *flags &= ~mount_flags[i].flag;
+
216 return;
+
217 }
+
218 }
+
219 fuse_log(FUSE_LOG_ERR, "fuse: internal error, can't find mount flag\n");
+
220 abort();
+
221}
+
222
+
223static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+
224 struct fuse_args *outargs)
+
225{
+
226 (void) outargs;
+
227 struct mount_opts *mo = data;
+
228
+
229 switch (key) {
+
230 case KEY_RO:
+
231 arg = "ro";
+
232 /* fall through */
+
233 case KEY_KERN_FLAG:
+
234 set_mount_flag(arg, &mo->flags);
+
235 return 0;
+
236
+
237 case KEY_KERN_OPT:
+
238 return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
239
+
240 case KEY_FUSERMOUNT_OPT:
+
241 return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg);
+
242
+
243 case KEY_SUBTYPE_OPT:
+
244 return fuse_opt_add_opt(&mo->subtype_opt, arg);
+
245
+
246 case KEY_MTAB_OPT:
+
247 return fuse_opt_add_opt(&mo->mtab_opts, arg);
+
248
+
249 /* Third party options like 'x-gvfs-notrash' */
+
250 case FUSE_OPT_KEY_OPT:
+
251 return (strncmp("x-", arg, 2) == 0) ?
+
252 fuse_opt_add_opt(&mo->mtab_opts, arg) :
+
253 1;
+
254 }
+
255
+
256 /* Pass through unknown options */
+
257 return 1;
+
258}
+
259
+
260/* return value:
+
261 * >= 0 => fd
+
262 * -1 => error
+
263 */
+
264static int receive_fd(int fd)
+
265{
+
266 struct msghdr msg;
+
267 struct iovec iov;
+
268 char buf[1];
+
269 int rv;
+
270 size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)];
+
271 struct cmsghdr *cmsg;
+
272
+
273 iov.iov_base = buf;
+
274 iov.iov_len = 1;
+
275
+
276 memset(&msg, 0, sizeof(msg));
+
277 msg.msg_name = 0;
+
278 msg.msg_namelen = 0;
+
279 msg.msg_iov = &iov;
+
280 msg.msg_iovlen = 1;
+
281 /* old BSD implementations should use msg_accrights instead of
+
282 * msg_control; the interface is different. */
+
283 msg.msg_control = ccmsg;
+
284 msg.msg_controllen = sizeof(ccmsg);
+
285
+
286 while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR);
+
287 if (rv == -1) {
+
288 fuse_log(FUSE_LOG_ERR, "recvmsg failed: %s", strerror(errno));
+
289 return -1;
+
290 }
+
291 if(!rv) {
+
292 /* EOF */
+
293 return -1;
+
294 }
+
295
+
296 cmsg = CMSG_FIRSTHDR(&msg);
+
297 if (cmsg->cmsg_type != SCM_RIGHTS) {
+
298 fuse_log(FUSE_LOG_ERR, "got control message of unknown type %d\n",
+
299 cmsg->cmsg_type);
+
300 return -1;
+
301 }
+
302 return *(int*)CMSG_DATA(cmsg);
+
303}
+
304
+
305void fuse_kern_unmount(const char *mountpoint, int fd)
+
306{
+
307 int res;
+
308
+
309 if (fd != -1) {
+
310 struct pollfd pfd;
+
311
+
312 pfd.fd = fd;
+
313 pfd.events = 0;
+
314 res = poll(&pfd, 1, 0);
+
315
+
316 /* Need to close file descriptor, otherwise synchronous umount
+
317 would recurse into filesystem, and deadlock.
+
318
+
319 Caller expects fuse_kern_unmount to close the fd, so close it
+
320 anyway. */
+
321 close(fd);
+
322
+
323 /* If file poll returns POLLERR on the device file descriptor,
+
324 then the filesystem is already unmounted or the connection
+
325 was severed via /sys/fs/fuse/connections/NNN/abort */
+
326 if (res == 1 && (pfd.revents & POLLERR))
+
327 return;
+
328 }
+
329
+
330 if (geteuid() == 0) {
+
331 fuse_mnt_umount("fuse", mountpoint, mountpoint, 1);
+
332 return;
+
333 }
+
334
+
335 res = umount2(mountpoint, 2);
+
336 if (res == 0)
+
337 return;
+
338
+
339 char const * const argv[] =
+
340 { FUSERMOUNT_PROG, "--unmount", "--quiet", "--lazy",
+
341 "--", mountpoint, NULL };
+
342 int status = fusermount_posix_spawn(NULL, argv, NULL);
+
343 if(status != 0) {
+
344 fuse_log(FUSE_LOG_ERR, "Spawning %s to unmount failed: %s",
+
345 FUSERMOUNT_PROG, strerror(-status));
+
346 return;
+
347 }
+
348}
+
349
+
350static int setup_auto_unmount(const char *mountpoint, int quiet)
+
351{
+
352 int fds[2];
+
353 pid_t pid;
+
354 int res;
+
355
+
356 if (!mountpoint) {
+
357 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
358 return -1;
+
359 }
+
360
+
361 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+
362 if(res == -1) {
+
363 fuse_log(FUSE_LOG_ERR, "Setting up auto-unmount socketpair() failed: %s\n",
+
364 strerror(errno));
+
365 return -1;
+
366 }
+
367
+
368 char arg_fd_entry[30];
+
369 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
+
370 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
+
371 /*
+
372 * This helps to identify the FD hold by parent process.
+
373 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
+
374 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
+
375 * One potential use case is to satisfy FD-Leak checks.
+
376 */
+
377 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
+
378 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
+
379
+
380 char const *const argv[] = {
+
381 FUSERMOUNT_PROG,
+
382 "--auto-unmount",
+
383 "--",
+
384 mountpoint,
+
385 NULL,
+
386 };
+
387
+
388 // TODO: add error handling for all manipulations of action.
+
389 posix_spawn_file_actions_t action;
+
390 posix_spawn_file_actions_init(&action);
+
391
+
392 if (quiet) {
+
393 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
+
394 posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
+
395 }
+
396 posix_spawn_file_actions_addclose(&action, fds[1]);
+
397
+
398 /*
+
399 * auto-umount runs in the background - it is not waiting for the
+
400 * process
+
401 */
+
402 int status = fusermount_posix_spawn(&action, argv, &pid);
+
403
+
404 posix_spawn_file_actions_destroy(&action);
+
405
+
406 if(status != 0) {
+
407 close(fds[0]);
+
408 close(fds[1]);
+
409 fuse_log(FUSE_LOG_ERR, "fuse: Setting up auto-unmount failed (spawn): %s",
+
410 strerror(-status));
+
411 return -1;
+
412 }
+
413 // passed to child now, so can close here.
+
414 close(fds[0]);
+
415
+
416 // Now fusermount3 will only exit when fds[1] closes automatically when our
+
417 // process exits.
+
418 return 0;
+
419 // Note: fds[1] is leakend and doesn't get FD_CLOEXEC
+
420}
+
421
+
422static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
+
423 const char *opts, int quiet)
+
424{
+
425 int fds[2];
+
426 pid_t pid;
+
427 int res;
+
428
+
429 if (!mountpoint) {
+
430 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
431 return -1;
+
432 }
+
433
+
434 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+
435 if(res == -1) {
+
436 fuse_log(FUSE_LOG_ERR, "Running %s: socketpair() failed: %s\n",
+
437 FUSERMOUNT_PROG, strerror(errno));
+
438 return -1;
+
439 }
+
440
+
441 char arg_fd_entry[30];
+
442 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
+
443 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
+
444 /*
+
445 * This helps to identify the FD hold by parent process.
+
446 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
+
447 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
+
448 * One potential use case is to satisfy FD-Leak checks.
+
449 */
+
450 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
+
451 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
+
452
+
453 char const *const argv[] = {
+
454 FUSERMOUNT_PROG,
+
455 "-o", opts ? opts : "",
+
456 "--",
+
457 mountpoint,
+
458 NULL,
+
459 };
+
460
+
461
+
462 posix_spawn_file_actions_t action;
+
463 posix_spawn_file_actions_init(&action);
+
464
+
465 if (quiet) {
+
466 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
+
467 posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
+
468 }
+
469 posix_spawn_file_actions_addclose(&action, fds[1]);
+
470
+
471 int status = fusermount_posix_spawn(&action, argv, &pid);
+
472
+
473 posix_spawn_file_actions_destroy(&action);
+
474
+
475 if(status != 0) {
+
476 close(fds[0]);
+
477 close(fds[1]);
+
478 fuse_log(FUSE_LOG_ERR, "posix_spawn(p)() for %s failed: %s",
+
479 FUSERMOUNT_PROG, strerror(-status));
+
480 return -1;
+
481 }
+
482
+
483 // passed to child now, so can close here.
+
484 close(fds[0]);
+
485
+
486 int fd = receive_fd(fds[1]);
+
487
+
488 if (!mo->auto_unmount) {
+
489 /* with auto_unmount option fusermount3 will not exit until
+
490 this socket is closed */
+
491 close(fds[1]);
+
492 waitpid(pid, NULL, 0); /* bury zombie */
+
493 }
+
494
+
495 if (fd >= 0)
+
496 fcntl(fd, F_SETFD, FD_CLOEXEC);
+
497
+
498 return fd;
+
499}
+
500
+
501#ifndef O_CLOEXEC
+
502#define O_CLOEXEC 0
+
503#endif
+
504
+
505static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
+
506 const char *mnt_opts)
+
507{
+
508 char tmp[128];
+
509 const char *devname = getenv(FUSE_KERN_DEVICE_ENV) ?: "/dev/fuse";
+
510 char *source = NULL;
+
511 char *type = NULL;
+
512 struct stat stbuf;
+
513 int fd;
+
514 int res;
+
515
+
516 if (!mnt) {
+
517 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
518 return -1;
+
519 }
+
520
+
521 res = stat(mnt, &stbuf);
+
522 if (res == -1) {
+
523 fuse_log(FUSE_LOG_ERR, "fuse: failed to access mountpoint %s: %s\n",
+
524 mnt, strerror(errno));
+
525 return -1;
+
526 }
+
527
+
528 fd = open(devname, O_RDWR | O_CLOEXEC);
+
529 if (fd == -1) {
+
530 if (errno == ENODEV || errno == ENOENT)
+
531 fuse_log(FUSE_LOG_ERR,
+
532 "fuse: device %s not found. Kernel module not loaded?\n",
+
533 devname);
+
534 else
+
535 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n",
+
536 devname, strerror(errno));
+
537 return -1;
+
538 }
+
539 if (!O_CLOEXEC)
+
540 fcntl(fd, F_SETFD, FD_CLOEXEC);
+
541
+
542 snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+
543 fd, stbuf.st_mode & S_IFMT, getuid(), getgid());
+
544
+
545 res = fuse_opt_add_opt(&mo->kernel_opts, tmp);
+
546 if (res == -1)
+
547 goto out_close;
+
548
+
549 source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
+
550 (mo->subtype ? strlen(mo->subtype) : 0) +
+
551 strlen(devname) + 32);
+
552
+
553 type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
+
554 if (!type || !source) {
+
555 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate memory\n");
+
556 goto out_close;
+
557 }
+
558
+
559 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+
560 if (mo->subtype) {
+
561 strcat(type, ".");
+
562 strcat(type, mo->subtype);
+
563 }
+
564 strcpy(source,
+
565 mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
+
566
+
567 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+
568 if (res == -1 && errno == ENODEV && mo->subtype) {
+
569 /* Probably missing subtype support */
+
570 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+
571 if (mo->fsname) {
+
572 if (!mo->blkdev)
+
573 sprintf(source, "%s#%s", mo->subtype,
+
574 mo->fsname);
+
575 } else {
+
576 strcpy(source, type);
+
577 }
+
578 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+
579 }
+
580 if (res == -1) {
+
581 /*
+
582 * Maybe kernel doesn't support unprivileged mounts, in this
+
583 * case try falling back to fusermount3
+
584 */
+
585 if (errno == EPERM) {
+
586 res = -2;
+
587 } else {
+
588 int errno_save = errno;
+
589 if (mo->blkdev && errno == ENODEV &&
+
590 !fuse_mnt_check_fuseblk())
+
591 fuse_log(FUSE_LOG_ERR,
+
592 "fuse: 'fuseblk' support missing\n");
+
593 else
+
594 fuse_log(FUSE_LOG_ERR, "fuse: mount failed: %s\n",
+
595 strerror(errno_save));
+
596 }
+
597
+
598 goto out_close;
+
599 }
+
600
+
601#ifndef IGNORE_MTAB
+
602 if (geteuid() == 0) {
+
603 char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
+
604 res = -1;
+
605 if (!newmnt)
+
606 goto out_umount;
+
607
+
608 res = fuse_mnt_add_mount("fuse", source, newmnt, type,
+
609 mnt_opts);
+
610 free(newmnt);
+
611 if (res == -1)
+
612 goto out_umount;
+
613 }
+
614#endif /* IGNORE_MTAB */
+
615 free(type);
+
616 free(source);
+
617
+
618 return fd;
+
619
+
620out_umount:
+
621 umount2(mnt, 2); /* lazy umount */
+
622out_close:
+
623 free(type);
+
624 free(source);
+
625 close(fd);
+
626 return res;
+
627}
+
628
+
629static int get_mnt_flag_opts(char **mnt_optsp, int flags)
+
630{
+
631 int i;
+
632
+
633 if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1)
+
634 return -1;
+
635
+
636 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
637 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+
638 fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1)
+
639 return -1;
+
640 }
+
641 return 0;
+
642}
+
643
+
644struct mount_opts *parse_mount_opts(struct fuse_args *args)
+
645{
+
646 struct mount_opts *mo;
+
647
+
648 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
+
649 if (mo == NULL)
+
650 return NULL;
+
651
+
652 memset(mo, 0, sizeof(struct mount_opts));
+
653 mo->flags = MS_NOSUID | MS_NODEV;
+
654
+
655 if (args &&
+
656 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+
657 goto err_out;
+
658
+
659 return mo;
+
660
+
661err_out:
+
662 destroy_mount_opts(mo);
+
663 return NULL;
+
664}
+
665
+
666void destroy_mount_opts(struct mount_opts *mo)
+
667{
+
668 free(mo->fsname);
+
669 free(mo->subtype);
+
670 free(mo->fusermount_opts);
+
671 free(mo->subtype_opt);
+
672 free(mo->kernel_opts);
+
673 free(mo->mtab_opts);
+
674 free(mo);
+
675}
+
676
+
677
+
678int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
+
679{
+
680 int res = -1;
+
681 char *mnt_opts = NULL;
+
682
+
683 res = -1;
+
684 if (get_mnt_flag_opts(&mnt_opts, mo->flags) == -1)
+
685 goto out;
+
686 if (mo->kernel_opts && fuse_opt_add_opt(&mnt_opts, mo->kernel_opts) == -1)
+
687 goto out;
+
688 if (mo->mtab_opts && fuse_opt_add_opt(&mnt_opts, mo->mtab_opts) == -1)
+
689 goto out;
+
690
+
691 res = fuse_mount_sys(mountpoint, mo, mnt_opts);
+
692 if (res >= 0 && mo->auto_unmount) {
+
693 if(0 > setup_auto_unmount(mountpoint, 0)) {
+
694 // Something went wrong, let's umount like in fuse_mount_sys.
+
695 umount2(mountpoint, MNT_DETACH); /* lazy umount */
+
696 res = -1;
+
697 }
+
698 } else if (res == -2) {
+
699 if (mo->fusermount_opts &&
+
700 fuse_opt_add_opt(&mnt_opts, mo->fusermount_opts) == -1)
+
701 goto out;
+
702
+
703 if (mo->subtype) {
+
704 char *tmp_opts = NULL;
+
705
+
706 res = -1;
+
707 if (fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1 ||
+
708 fuse_opt_add_opt(&tmp_opts, mo->subtype_opt) == -1) {
+
709 free(tmp_opts);
+
710 goto out;
+
711 }
+
712
+
713 res = fuse_mount_fusermount(mountpoint, mo, tmp_opts, 1);
+
714 free(tmp_opts);
+
715 if (res == -1)
+
716 res = fuse_mount_fusermount(mountpoint, mo,
+
717 mnt_opts, 0);
+
718 } else {
+
719 res = fuse_mount_fusermount(mountpoint, mo, mnt_opts, 0);
+
720 }
+
721 }
+
722out:
+
723 free(mnt_opts);
+
724 return res;
+
725}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
#define FUSE_OPT_KEY_OPT
Definition fuse_opt.h:129
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2mount__bsd_8c_source.html b/doc/html/fuse-3_818_82_2lib_2mount__bsd_8c_source.html new file mode 100644 index 0000000..83dd617 --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2mount__bsd_8c_source.html @@ -0,0 +1,336 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/mount_bsd.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_bsd.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2005-2008 Csaba Henk <csaba.henk@creo.hu>
+
4
+
5 Architecture specific file system mounting (FreeBSD).
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt.
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_i.h"
+
13#include "fuse_misc.h"
+
14#include "fuse_opt.h"
+
15#include "util.h"
+
16
+
17#include <sys/param.h>
+
18#include "fuse_mount_compat.h"
+
19
+
20#include <sys/wait.h>
+
21#include <stdio.h>
+
22#include <stdlib.h>
+
23#include <unistd.h>
+
24#include <stddef.h>
+
25#include <fcntl.h>
+
26#include <errno.h>
+
27#include <string.h>
+
28
+
29#define FUSERMOUNT_PROG "mount_fusefs"
+
30#define FUSE_DEV_TRUNK "/dev/fuse"
+
31
+
32enum {
+
33 KEY_RO,
+
34 KEY_KERN
+
35};
+
36
+
37struct mount_opts {
+
38 int allow_other;
+
39 char *kernel_opts;
+
40 unsigned max_read;
+
41};
+
42
+
43#define FUSE_DUAL_OPT_KEY(templ, key) \
+
44 FUSE_OPT_KEY(templ, key), FUSE_OPT_KEY("no" templ, key)
+
45
+
46static const struct fuse_opt fuse_mount_opts[] = {
+
47 { "allow_other", offsetof(struct mount_opts, allow_other), 1 },
+
48 { "max_read=%u", offsetof(struct mount_opts, max_read), 1 },
+
49 FUSE_OPT_KEY("-r", KEY_RO),
+
50 /* standard FreeBSD mount options */
+
51 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
+
52 FUSE_DUAL_OPT_KEY("async", KEY_KERN),
+
53 FUSE_DUAL_OPT_KEY("atime", KEY_KERN),
+
54 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
+
55 FUSE_DUAL_OPT_KEY("exec", KEY_KERN),
+
56 FUSE_DUAL_OPT_KEY("suid", KEY_KERN),
+
57 FUSE_DUAL_OPT_KEY("symfollow", KEY_KERN),
+
58 FUSE_DUAL_OPT_KEY("rdonly", KEY_KERN),
+
59 FUSE_DUAL_OPT_KEY("sync", KEY_KERN),
+
60 FUSE_DUAL_OPT_KEY("union", KEY_KERN),
+
61 FUSE_DUAL_OPT_KEY("userquota", KEY_KERN),
+
62 FUSE_DUAL_OPT_KEY("groupquota", KEY_KERN),
+
63 FUSE_DUAL_OPT_KEY("clusterr", KEY_KERN),
+
64 FUSE_DUAL_OPT_KEY("clusterw", KEY_KERN),
+
65 FUSE_DUAL_OPT_KEY("suiddir", KEY_KERN),
+
66 FUSE_DUAL_OPT_KEY("snapshot", KEY_KERN),
+
67 FUSE_DUAL_OPT_KEY("multilabel", KEY_KERN),
+
68 FUSE_DUAL_OPT_KEY("acls", KEY_KERN),
+
69 FUSE_DUAL_OPT_KEY("force", KEY_KERN),
+
70 FUSE_DUAL_OPT_KEY("update", KEY_KERN),
+
71 FUSE_DUAL_OPT_KEY("ro", KEY_KERN),
+
72 FUSE_DUAL_OPT_KEY("rw", KEY_KERN),
+
73 FUSE_DUAL_OPT_KEY("auto", KEY_KERN),
+
74 FUSE_DUAL_OPT_KEY("automounted", KEY_KERN),
+
75 /* options supported under both Linux and FBSD */
+
76 FUSE_DUAL_OPT_KEY("allow_other", KEY_KERN),
+
77 FUSE_DUAL_OPT_KEY("default_permissions",KEY_KERN),
+
78 FUSE_OPT_KEY("max_read=", KEY_KERN),
+
79 FUSE_OPT_KEY("subtype=", KEY_KERN),
+
80 /* FBSD FUSE specific mount options */
+
81 FUSE_DUAL_OPT_KEY("private", KEY_KERN),
+
82 FUSE_DUAL_OPT_KEY("neglect_shares", KEY_KERN),
+
83 FUSE_DUAL_OPT_KEY("push_symlinks_in", KEY_KERN),
+
84#if __FreeBSD_version >= 1200519
+
85 FUSE_DUAL_OPT_KEY("intr", KEY_KERN),
+
86#endif
+
87 /* stock FBSD mountopt parsing routine lets anything be negated... */
+
88 /*
+
89 * Linux specific mount options, but let just the mount util
+
90 * handle them
+
91 */
+
92 FUSE_OPT_KEY("fsname=", KEY_KERN),
+ +
94};
+
95
+
96void fuse_mount_version(void)
+
97{
+
98 system(FUSERMOUNT_PROG " --version");
+
99}
+
100
+
101unsigned get_max_read(struct mount_opts *o)
+
102{
+
103 return o->max_read;
+
104}
+
105
+
106static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+
107 struct fuse_args *outargs)
+
108{
+
109 (void) outargs;
+
110 struct mount_opts *mo = data;
+
111
+
112 switch (key) {
+
113 case KEY_RO:
+
114 arg = "ro";
+
115 /* fall through */
+
116
+
117 case KEY_KERN:
+
118 return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
119 }
+
120
+
121 /* Pass through unknown options */
+
122 return 1;
+
123}
+
124
+
125void fuse_kern_unmount(const char *mountpoint, int fd)
+
126{
+
127 if (close(fd) < 0)
+
128 fuse_log(FUSE_LOG_ERR, "closing FD %d failed: %s", fd, strerror(errno));
+
129 if (unmount(mountpoint, MNT_FORCE) < 0)
+
130 fuse_log(FUSE_LOG_ERR, "unmounting %s failed: %s",
+
131 mountpoint, strerror(errno));
+
132}
+
133
+
134static int fuse_mount_core(const char *mountpoint, const char *opts)
+
135{
+
136 const char *mountprog = FUSERMOUNT_PROG;
+
137 long fd;
+
138 char *fdnam, *dev;
+
139 pid_t pid, cpid;
+
140 int status;
+
141 int err;
+
142
+
143 fdnam = getenv("FUSE_DEV_FD");
+
144
+
145 if (fdnam) {
+
146 err = libfuse_strtol(fdnam, &fd);
+
147 if (err || fd < 0) {
+
148 fuse_log(FUSE_LOG_ERR, "invalid value given in FUSE_DEV_FD\n");
+
149 return -1;
+
150 }
+
151
+
152 goto mount;
+
153 }
+
154
+
155 dev = getenv("FUSE_DEV_NAME");
+
156
+
157 if (! dev)
+
158 dev = (char *)FUSE_DEV_TRUNK;
+
159
+
160 if ((fd = open(dev, O_RDWR)) < 0) {
+
161 perror("fuse: failed to open fuse device");
+
162 return -1;
+
163 }
+
164
+
165mount:
+
166 if (getenv("FUSE_NO_MOUNT") || ! mountpoint)
+
167 goto out;
+
168
+
169 pid = fork();
+
170 cpid = pid;
+
171
+
172 if (pid == -1) {
+
173 perror("fuse: fork() failed");
+
174 close(fd);
+
175 return -1;
+
176 }
+
177
+
178 if (pid == 0) {
+
179 pid = fork();
+
180
+
181 if (pid == -1) {
+
182 perror("fuse: fork() failed");
+
183 close(fd);
+
184 _exit(EXIT_FAILURE);
+
185 }
+
186
+
187 if (pid == 0) {
+
188 const char *argv[32];
+
189 int a = 0;
+
190 int ret = -1;
+
191
+
192 if (! fdnam)
+
193 {
+
194 ret = asprintf(&fdnam, "%ld", fd);
+
195 if(ret == -1)
+
196 {
+
197 perror("fuse: failed to assemble mount arguments");
+
198 close(fd);
+
199 _exit(EXIT_FAILURE);
+
200 }
+
201 }
+
202
+
203 argv[a++] = mountprog;
+
204 if (opts) {
+
205 argv[a++] = "-o";
+
206 argv[a++] = opts;
+
207 }
+
208 argv[a++] = fdnam;
+
209 argv[a++] = mountpoint;
+
210 argv[a++] = NULL;
+
211 execvp(mountprog, (char **) argv);
+
212 perror("fuse: failed to exec mount program");
+
213 free(fdnam);
+
214 _exit(EXIT_FAILURE);
+
215 }
+
216
+
217 waitpid(pid, &status, 0);
+
218 if (!WIFEXITED(status))
+
219 _exit(EXIT_FAILURE);
+
220 _exit(WEXITSTATUS(status));
+
221 }
+
222
+
223 if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) {
+
224 perror("fuse: failed to mount file system");
+
225 if (close(fd) < 0)
+
226 perror("fuse: closing FD");
+
227 return -1;
+
228 }
+
229
+
230out:
+
231 return fd;
+
232}
+
233
+
234struct mount_opts *parse_mount_opts(struct fuse_args *args)
+
235{
+
236 struct mount_opts *mo;
+
237
+
238 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
+
239 if (mo == NULL)
+
240 return NULL;
+
241
+
242 memset(mo, 0, sizeof(struct mount_opts));
+
243
+
244 if (args &&
+
245 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+
246 goto err_out;
+
247
+
248 return mo;
+
249
+
250err_out:
+
251 destroy_mount_opts(mo);
+
252 return NULL;
+
253}
+
254
+
255void destroy_mount_opts(struct mount_opts *mo)
+
256{
+
257 free(mo->kernel_opts);
+
258 free(mo);
+
259}
+
260
+
261int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
+
262{
+
263 /* mount util should not try to spawn the daemon */
+
264 setenv("MOUNT_FUSEFS_SAFE", "1", 1);
+
265 /* to notify the mount util it's called from lib */
+
266 setenv("MOUNT_FUSEFS_CALL_BY_LIB", "1", 1);
+
267
+
268 return fuse_mount_core(mountpoint, mo->kernel_opts);
+
269}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2mount__util_8c_source.html b/doc/html/fuse-3_818_82_2lib_2mount__util_8c_source.html new file mode 100644 index 0000000..44bf343 --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2mount__util_8c_source.html @@ -0,0 +1,437 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/mount_util.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_util.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Architecture-independent mounting code.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt.
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "mount_util.h"
+
13
+
14#include <stdio.h>
+
15#include <unistd.h>
+
16#include <stdlib.h>
+
17#include <string.h>
+
18#include <signal.h>
+
19#include <dirent.h>
+
20#include <errno.h>
+
21#include <fcntl.h>
+
22#include <limits.h>
+
23#include <paths.h>
+
24#if !defined( __NetBSD__) && !defined(__FreeBSD__) && !defined(__DragonFly__) && !defined(__ANDROID__)
+
25#include <mntent.h>
+
26#else
+
27#define IGNORE_MTAB
+
28#endif
+
29#include <sys/stat.h>
+
30#include <sys/wait.h>
+
31
+
32#include "fuse_mount_compat.h"
+
33
+
34#include <sys/param.h>
+
35
+
36#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
+
37#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0)
+
38#endif
+
39
+
40#ifdef IGNORE_MTAB
+
41#define mtab_needs_update(mnt) 0
+
42#else
+
43static int mtab_needs_update(const char *mnt)
+
44{
+
45 int res;
+
46 struct stat stbuf;
+
47
+
48 /* If mtab is within new mount, don't touch it */
+
49 if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 &&
+
50 _PATH_MOUNTED[strlen(mnt)] == '/')
+
51 return 0;
+
52
+
53 /*
+
54 * Skip mtab update if /etc/mtab:
+
55 *
+
56 * - doesn't exist,
+
57 * - is on a read-only filesystem.
+
58 */
+
59 res = lstat(_PATH_MOUNTED, &stbuf);
+
60 if (res == -1) {
+
61 if (errno == ENOENT)
+
62 return 0;
+
63 } else {
+
64 uid_t ruid;
+
65 int err;
+
66
+
67 ruid = getuid();
+
68 if (ruid != 0)
+
69 setreuid(0, -1);
+
70
+
71 res = access(_PATH_MOUNTED, W_OK);
+
72 err = (res == -1) ? errno : 0;
+
73 if (ruid != 0)
+
74 setreuid(ruid, -1);
+
75
+
76 if (err == EROFS)
+
77 return 0;
+
78
+
79 res = access("/run/mount/utab", F_OK);
+
80 if (res == -1)
+
81 return 0;
+
82 }
+
83
+
84 return 1;
+
85}
+
86#endif /* IGNORE_MTAB */
+
87
+
88static int add_mount(const char *progname, const char *fsname,
+
89 const char *mnt, const char *type, const char *opts)
+
90{
+
91 int res;
+
92 int status;
+
93 sigset_t blockmask;
+
94 sigset_t oldmask;
+
95
+
96 sigemptyset(&blockmask);
+
97 sigaddset(&blockmask, SIGCHLD);
+
98 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
99 if (res == -1) {
+
100 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
101 return -1;
+
102 }
+
103
+
104 res = fork();
+
105 if (res == -1) {
+
106 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
107 goto out_restore;
+
108 }
+
109 if (res == 0) {
+
110 char *env = NULL;
+
111
+
112 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
113
+
114 if(setuid(geteuid()) == -1) {
+
115 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
116 res = -1;
+
117 goto out_restore;
+
118 }
+
119
+
120 execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
+
121 "-f", "-t", type, "-o", opts, fsname, mnt, NULL, &env);
+
122 fprintf(stderr, "%s: failed to execute /bin/mount: %s\n",
+
123 progname, strerror(errno));
+
124 exit(1);
+
125 }
+
126 res = waitpid(res, &status, 0);
+
127 if (res == -1)
+
128 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
129
+
130 if (status != 0)
+
131 res = -1;
+
132
+
133 out_restore:
+
134 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
135
+
136 return res;
+
137}
+
138
+
139int fuse_mnt_add_mount(const char *progname, const char *fsname,
+
140 const char *mnt, const char *type, const char *opts)
+
141{
+
142 if (!mtab_needs_update(mnt))
+
143 return 0;
+
144
+
145 return add_mount(progname, fsname, mnt, type, opts);
+
146}
+
147
+
148static int exec_umount(const char *progname, const char *rel_mnt, int lazy)
+
149{
+
150 int res;
+
151 int status;
+
152 sigset_t blockmask;
+
153 sigset_t oldmask;
+
154
+
155 sigemptyset(&blockmask);
+
156 sigaddset(&blockmask, SIGCHLD);
+
157 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
158 if (res == -1) {
+
159 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
160 return -1;
+
161 }
+
162
+
163 res = fork();
+
164 if (res == -1) {
+
165 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
166 goto out_restore;
+
167 }
+
168 if (res == 0) {
+
169 char *env = NULL;
+
170
+
171 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
172
+
173 if(setuid(geteuid()) == -1) {
+
174 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
175 res = -1;
+
176 goto out_restore;
+
177 }
+
178
+
179 if (lazy) {
+
180 execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+
181 "-l", NULL, &env);
+
182 } else {
+
183 execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+
184 NULL, &env);
+
185 }
+
186 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+
187 progname, strerror(errno));
+
188 exit(1);
+
189 }
+
190 res = waitpid(res, &status, 0);
+
191 if (res == -1)
+
192 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
193
+
194 if (status != 0) {
+
195 res = -1;
+
196 }
+
197
+
198 out_restore:
+
199 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
200 return res;
+
201
+
202}
+
203
+
204int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+
205 const char *rel_mnt, int lazy)
+
206{
+
207 int res;
+
208
+
209 if (!mtab_needs_update(abs_mnt)) {
+
210 res = umount2(rel_mnt, lazy ? 2 : 0);
+
211 if (res == -1)
+
212 fprintf(stderr, "%s: failed to unmount %s: %s\n",
+
213 progname, abs_mnt, strerror(errno));
+
214 return res;
+
215 }
+
216
+
217 return exec_umount(progname, rel_mnt, lazy);
+
218}
+
219
+
220static int remove_mount(const char *progname, const char *mnt)
+
221{
+
222 int res;
+
223 int status;
+
224 sigset_t blockmask;
+
225 sigset_t oldmask;
+
226
+
227 sigemptyset(&blockmask);
+
228 sigaddset(&blockmask, SIGCHLD);
+
229 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
230 if (res == -1) {
+
231 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
232 return -1;
+
233 }
+
234
+
235 res = fork();
+
236 if (res == -1) {
+
237 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
238 goto out_restore;
+
239 }
+
240 if (res == 0) {
+
241 char *env = NULL;
+
242
+
243 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
244
+
245 if(setuid(geteuid()) == -1) {
+
246 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
247 res = -1;
+
248 goto out_restore;
+
249 }
+
250
+
251 execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i",
+
252 "--fake", mnt, NULL, &env);
+
253 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+
254 progname, strerror(errno));
+
255 exit(1);
+
256 }
+
257 res = waitpid(res, &status, 0);
+
258 if (res == -1)
+
259 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
260
+
261 if (status != 0)
+
262 res = -1;
+
263
+
264 out_restore:
+
265 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
266 return res;
+
267}
+
268
+
269int fuse_mnt_remove_mount(const char *progname, const char *mnt)
+
270{
+
271 if (!mtab_needs_update(mnt))
+
272 return 0;
+
273
+
274 return remove_mount(progname, mnt);
+
275}
+
276
+
277char *fuse_mnt_resolve_path(const char *progname, const char *orig)
+
278{
+
279 char buf[PATH_MAX];
+
280 char *copy;
+
281 char *dst;
+
282 char *end;
+
283 char *lastcomp;
+
284 const char *toresolv;
+
285
+
286 if (!orig[0]) {
+
287 fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname,
+
288 orig);
+
289 return NULL;
+
290 }
+
291
+
292 copy = strdup(orig);
+
293 if (copy == NULL) {
+
294 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
295 return NULL;
+
296 }
+
297
+
298 toresolv = copy;
+
299 lastcomp = NULL;
+
300 for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --);
+
301 if (end[0] != '/') {
+
302 char *tmp;
+
303 end[1] = '\0';
+
304 tmp = strrchr(copy, '/');
+
305 if (tmp == NULL) {
+
306 lastcomp = copy;
+
307 toresolv = ".";
+
308 } else {
+
309 lastcomp = tmp + 1;
+
310 if (tmp == copy)
+
311 toresolv = "/";
+
312 }
+
313 if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) {
+
314 lastcomp = NULL;
+
315 toresolv = copy;
+
316 }
+
317 else if (tmp)
+
318 tmp[0] = '\0';
+
319 }
+
320 if (realpath(toresolv, buf) == NULL) {
+
321 fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig,
+
322 strerror(errno));
+
323 free(copy);
+
324 return NULL;
+
325 }
+
326 if (lastcomp == NULL)
+
327 dst = strdup(buf);
+
328 else {
+
329 dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1);
+
330 if (dst) {
+
331 unsigned buflen = strlen(buf);
+
332 if (buflen && buf[buflen-1] == '/')
+
333 sprintf(dst, "%s%s", buf, lastcomp);
+
334 else
+
335 sprintf(dst, "%s/%s", buf, lastcomp);
+
336 }
+
337 }
+
338 free(copy);
+
339 if (dst == NULL)
+
340 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
341 return dst;
+
342}
+
343
+
344int fuse_mnt_check_fuseblk(void)
+
345{
+
346 char buf[256];
+
347 FILE *f = fopen("/proc/filesystems", "r");
+
348 if (!f)
+
349 return 1;
+
350
+
351 while (fgets(buf, sizeof(buf), f))
+
352 if (strstr(buf, "fuseblk\n")) {
+
353 fclose(f);
+
354 return 1;
+
355 }
+
356
+
357 fclose(f);
+
358 return 0;
+
359}
+
360
+
361int fuse_mnt_parse_fuse_fd(const char *mountpoint)
+
362{
+
363 int fd = -1;
+
364 int len = 0;
+
365
+
366 if (mountpoint == NULL) {
+
367 fprintf(stderr, "Invalid null-ptr mount-point!\n");
+
368 return -1;
+
369 }
+
370
+
371 if (sscanf(mountpoint, "/dev/fd/%u%n", &fd, &len) == 1 &&
+
372 len == strlen(mountpoint)) {
+
373 return fd;
+
374 }
+
375
+
376 return -1;
+
377}
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2mount__util_8h_source.html b/doc/html/fuse-3_818_82_2lib_2mount__util_8h_source.html new file mode 100644 index 0000000..05f9f0e --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2mount__util_8h_source.html @@ -0,0 +1,78 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/mount_util.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_util.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#include <sys/types.h>
+
10
+
11int fuse_mnt_add_mount(const char *progname, const char *fsname,
+
12 const char *mnt, const char *type, const char *opts);
+
13int fuse_mnt_remove_mount(const char *progname, const char *mnt);
+
14int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+
15 const char *rel_mnt, int lazy);
+
16char *fuse_mnt_resolve_path(const char *progname, const char *orig);
+
17int fuse_mnt_check_fuseblk(void);
+
18int fuse_mnt_parse_fuse_fd(const char *mountpoint);
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2usdt_8h_source.html b/doc/html/fuse-3_818_82_2lib_2usdt_8h_source.html new file mode 100644 index 0000000..40f781e --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2usdt_8h_source.html @@ -0,0 +1,595 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/usdt.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
usdt.h
+
+
+
1/*
+
2 * Copied from https://github.com/libbpf/usdt/
+
3 */
+
4
+
5// SPDX-License-Identifier: BSD-2-Clause
+
6/*
+
7 * This single-header library defines a collection of variadic macros for
+
8 * defining and triggering USDTs (User Statically-Defined Tracepoints):
+
9 *
+
10 * - For USDTs without associated semaphore:
+
11 * USDT(group, name, args...)
+
12 *
+
13 * - For USDTs with implicit (transparent to the user) semaphore:
+
14 * USDT_WITH_SEMA(group, name, args...)
+
15 * USDT_IS_ACTIVE(group, name)
+
16 *
+
17 * - For USDTs with explicit (user-defined and provided) semaphore:
+
18 * USDT_WITH_EXPLICIT_SEMA(sema, group, name, args...)
+
19 * USDT_SEMA_IS_ACTIVE(sema)
+
20 *
+
21 * all of which emit a NOP instruction into the instruction stream, and so
+
22 * have *zero* overhead for the surrounding code. USDTs are identified by
+
23 * a combination of `group` and `name` identifiers, which is used by external
+
24 * tracing tooling (tracers) for identifying exact USDTs of interest.
+
25 *
+
26 * USDTs can have an associated (2-byte) activity counter (USDT semaphore),
+
27 * automatically maintained by Linux kernel whenever any correctly written
+
28 * BPF-based tracer is attached to the USDT. This USDT semaphore can be used
+
29 * to check whether there is a need to do any extra data collection and
+
30 * processing for a given USDT (if necessary), and otherwise avoid extra work
+
31 * for a common case of USDT not being traced ("active").
+
32 *
+
33 * See documentation for USDT_WITH_SEMA()/USDT_IS_ACTIVE() or
+
34 * USDT_WITH_EXPLICIT_SEMA()/USDT_SEMA_IS_ACTIVE() APIs below for details on
+
35 * working with USDTs with implicitly or explicitly associated
+
36 * USDT semaphores, respectively.
+
37 *
+
38 * There is also some additional data recorded into an auxiliary note
+
39 * section. The data in the note section describes the operands, in terms of
+
40 * size and location, used by tracing tooling to know where to find USDT
+
41 * arguments. Each location is encoded as an assembler operand string.
+
42 * Tracing tools (bpftrace and BPF-based tracers, systemtap, etc) insert
+
43 * breakpoints on top of the nop, and decode the location operand-strings,
+
44 * like an assembler, to find the values being passed.
+
45 *
+
46 * The operand strings are selected by the compiler for each operand.
+
47 * They are constrained by inline-assembler codes.The default is:
+
48 *
+
49 * #define USDT_ARG_CONSTRAINT nor
+
50 *
+
51 * This is a good default if the operands tend to be integral and
+
52 * moderate in number (smaller than number of registers). In other
+
53 * cases, the compiler may report "'asm' requires impossible reload" or
+
54 * similar. In this case, consider simplifying the macro call (fewer
+
55 * and simpler operands), reduce optimization, or override the default
+
56 * constraints string via:
+
57 *
+
58 * #define USDT_ARG_CONSTRAINT g
+
59 * #include <usdt.h>
+
60 *
+
61 * For some historical description of USDT v3 format (the one used by this
+
62 * library and generally recognized and assumed by BPF-based tracing tools)
+
63 * see [0]. The more formal specification can be found at [1]. Additional
+
64 * argument constraints information can be found at [2].
+
65 *
+
66 * Original SystemTap's sys/sdt.h implementation ([3]) was used as a base for
+
67 * this USDT library implementation. Current implementation differs *a lot* in
+
68 * terms of exposed user API and general usability, which was the main goal
+
69 * and focus of the reimplementation work. Nevertheless, underlying recorded
+
70 * USDT definitions are fully binary compatible and any USDT-based tooling
+
71 * should work equally well with USDTs defined by either SystemTap's or this
+
72 * library's USDT implementation.
+
73 *
+
74 * [0] https://ecos.sourceware.org/ml/systemtap/2010-q3/msg00145.html
+
75 * [1] https://sourceware.org/systemtap/wiki/UserSpaceProbeImplementation
+
76 * [2] https://gcc.gnu.org/onlinedocs/gcc/Constraints.html
+
77 * [3] https://sourceware.org/git/?p=systemtap.git;a=blob;f=includes/sys/sdt.h
+
78 */
+
79#ifndef __USDT_H
+
80#define __USDT_H
+
81
+
82/*
+
83 * Changelog:
+
84 *
+
85 * 0.1.0
+
86 * -----
+
87 * - Initial release
+
88 */
+
89#define USDT_MAJOR_VERSION 0
+
90#define USDT_MINOR_VERSION 1
+
91#define USDT_PATCH_VERSION 0
+
92
+
93/* C++20 and C23 added __VA_OPT__ as a standard replacement for non-standard `##__VA_ARGS__` extension */
+
94#if (defined(__STDC_VERSION__) && __STDC_VERSION__ > 201710L) || (defined(__cplusplus) && __cplusplus > 201703L)
+
95#define __usdt_va_opt 1
+
96#define __usdt_va_args(...) __VA_OPT__(,) __VA_ARGS__
+
97#else
+
98#define __usdt_va_args(...) , ##__VA_ARGS__
+
99#endif
+
100
+
101/*
+
102 * Trigger USDT with `group`:`name` identifier and pass through `args` as its
+
103 * arguments. Zero arguments are acceptable as well. No USDT semaphore is
+
104 * associated with this USDT.
+
105 *
+
106 * Such "semaphoreless" USDTs are commonly used when there is no extra data
+
107 * collection or processing needed to collect and prepare USDT arguments and
+
108 * they are just available in the surrounding code. USDT() macro will just
+
109 * record their locations in CPU registers or in memory for tracing tooling to
+
110 * be able to access them, if necessary.
+
111 */
+
112#ifdef __usdt_va_opt
+
113#define USDT(group, name, ...) \
+
114 __usdt_probe(group, name, __usdt_sema_none, 0 __VA_OPT__(,) __VA_ARGS__)
+
115#else
+
116#define USDT(group, name, ...) \
+
117 __usdt_probe(group, name, __usdt_sema_none, 0, ##__VA_ARGS__)
+
118#endif
+
119
+
120/*
+
121 * Trigger USDT with `group`:`name` identifier and pass through `args` as its
+
122 * arguments. Zero arguments are acceptable as well. USDT also get an
+
123 * implicitly-defined associated USDT semaphore, which will be "activated" by
+
124 * tracing tooling and can be used to check whether USDT is being actively
+
125 * observed.
+
126 *
+
127 * USDTs with semaphore are commonly used when there is a need to perform
+
128 * additional data collection and processing to prepare USDT arguments, which
+
129 * otherwise might not be necessary for the rest of application logic. In such
+
130 * case, USDT semaphore can be used to avoid unnecessary extra work. If USDT
+
131 * is not traced (which is presumed to be a common situation), the associated
+
132 * USDT semaphore is "inactive", and so there is no need to waste resources to
+
133 * prepare USDT arguments. Use USDT_IS_ACTIVE(group, name) to check whether
+
134 * USDT is "active".
+
135 *
+
136 * N.B. There is an inherent (albeit short) gap between checking whether USDT
+
137 * is active and triggering corresponding USDT, in which external tracer can
+
138 * be attached to an USDT and activate USDT semaphore after the activity check.
+
139 * If such a race occurs, tracers might miss one USDT execution. Tracers are
+
140 * expected to accommodate such possibility and this is expected to not be
+
141 * a problem for applications and tracers.
+
142 *
+
143 * N.B. Implicit USDT semaphore defined by USDT_WITH_SEMA() is contained
+
144 * within a single executable or shared library and is not shared outside
+
145 * them. I.e., if you use USDT_WITH_SEMA() with the same USDT group and name
+
146 * identifier across executable and shared library, it will work and won't
+
147 * conflict, per se, but will define independent USDT semaphores, one for each
+
148 * shared library/executable in which USDT_WITH_SEMA(group, name) is used.
+
149 * That is, if you attach to this USDT in one shared library (or executable),
+
150 * then only USDT semaphore within that shared library (or executable) will be
+
151 * updated by the kernel, while other libraries (or executable) will not see
+
152 * activated USDT semaphore. In short, it's best to use unique USDT group:name
+
153 * identifiers across different shared libraries (and, equivalently, between
+
154 * executable and shared library). This is advanced consideration and is
+
155 * rarely (if ever) seen in practice, but just to avoid surprises this is
+
156 * called out here. (Static libraries become a part of final executable, once
+
157 * linked by linker, so the above considerations don't apply to them.)
+
158 */
+
159#ifdef __usdt_va_opt
+
160#define USDT_WITH_SEMA(group, name, ...) \
+
161 __usdt_probe(group, name, \
+
162 __usdt_sema_implicit, __usdt_sema_name(group, name) \
+
163 __VA_OPT__(,) __VA_ARGS__)
+
164#else
+
165#define USDT_WITH_SEMA(group, name, ...) \
+
166 __usdt_probe(group, name, \
+
167 __usdt_sema_implicit, __usdt_sema_name(group, name), \
+
168 ##__VA_ARGS__)
+
169#endif
+
170
+
171struct usdt_sema { volatile unsigned short active; };
+
172
+
173/*
+
174 * Check if USDT with `group`:`name` identifier is "active" (i.e., whether it
+
175 * is attached to by external tracing tooling and is actively observed).
+
176 *
+
177 * This macro can be used to decide whether any additional and potentially
+
178 * expensive data collection or processing should be done to pass extra
+
179 * information into the given USDT. It is assumed that USDT is triggered with
+
180 * USDT_WITH_SEMA() macro which will implicitly define associated USDT
+
181 * semaphore. (If one needs more control over USDT semaphore, see
+
182 * USDT_DEFINE_SEMA() and USDT_WITH_EXPLICIT_SEMA() macros below.)
+
183 *
+
184 * N.B. Such checks are necessarily racy and speculative. Between checking
+
185 * whether USDT is active and triggering the USDT itself, tracer can be
+
186 * detached with no notification. This race should be extremely rare and worst
+
187 * case should result in one-time wasted extra data collection and processing.
+
188 */
+
189#define USDT_IS_ACTIVE(group, name) ({ \
+
190 extern struct usdt_sema __usdt_sema_name(group, name) \
+
191 __usdt_asm_name(__usdt_sema_name(group, name)); \
+
192 __usdt_sema_implicit(__usdt_sema_name(group, name)); \
+
193 __usdt_sema_name(group, name).active > 0; \
+
194})
+
195
+
196/*
+
197 * APIs for working with user-defined explicit USDT semaphores.
+
198 *
+
199 * This is a less commonly used advanced API for use cases in which user needs
+
200 * an explicit control over (potentially shared across multiple USDTs) USDT
+
201 * semaphore instance. This can be used when there is a group of logically
+
202 * related USDTs that all need extra data collection and processing whenever
+
203 * any of a family of related USDTs are "activated" (i.e., traced). In such
+
204 * a case, all such related USDTs will be associated with the same shared USDT
+
205 * semaphore defined with USDT_DEFINE_SEMA() and the USDTs themselves will be
+
206 * triggered with USDT_WITH_EXPLICIT_SEMA() macros, taking an explicit extra
+
207 * USDT semaphore identifier as an extra parameter.
+
208 */
+
209
+
215#define USDT_SEMA(sema) __usdt_sema_##sema
+
216
+
217/*
+
218 * Define storage for user-defined USDT semaphore `sema`.
+
219 *
+
220 * Should be used only once in non-header source file to let compiler allocate
+
221 * space for the semaphore variable. Just like with any other global variable.
+
222 *
+
223 * This macro can be used anywhere where global variable declaration is
+
224 * allowed. Just like with global variable definitions, there should be only
+
225 * one definition of user-defined USDT semaphore with given `sema` identifier,
+
226 * otherwise compiler or linker will complain about duplicate variable
+
227 * definition.
+
228 *
+
229 * For C++, it is allowed to use USDT_DEFINE_SEMA() both in global namespace
+
230 * and inside namespaces (including nested namespaces). Just make sure that
+
231 * USDT_DECLARE_SEMA() is placed within the namespace where this semaphore is
+
232 * referenced, or any of its parent namespaces, so the C++ language-level
+
233 * identifier is visible to the code that needs to reference the semaphore.
+
234 * At the lowest layer, USDT semaphores have global naming and visibility
+
235 * (they have a corresponding `__usdt_sema_<name>` symbol, which can be linked
+
236 * against from C or C++ code, if necessary). To keep it simple, putting
+
237 * USDT_DECLARE_SEMA() declarations into global namespaces is the simplest
+
238 * no-brainer solution. All these aspects are irrelevant for plain C, because
+
239 * C doesn't have namespaces and everything is always in the global namespace.
+
240 *
+
241 * N.B. Due to USDT metadata being recorded in non-allocatable ELF note
+
242 * section, it has limitations when it comes to relocations, which, in
+
243 * practice, means that it's not possible to correctly share USDT semaphores
+
244 * between main executable and shared libraries, or even between multiple
+
245 * shared libraries. USDT semaphore has to be contained to individual shared
+
246 * library or executable to avoid unpleasant surprises with half-working USDT
+
247 * semaphores. We enforce this by marking semaphore ELF symbols as having
+
248 * a hidden visibility. This is quite an advanced use case and consideration
+
249 * and for most users this should have no consequences whatsoever.
+
250 */
+
251#define USDT_DEFINE_SEMA(sema) \
+
252 struct usdt_sema __usdt_sema_sec USDT_SEMA(sema) \
+
253 __usdt_asm_name(USDT_SEMA(sema)) \
+
254 __attribute__((visibility("hidden"))) = { 0 }
+
255
+
256/*
+
257 * Declare extern reference to user-defined USDT semaphore `sema`.
+
258 *
+
259 * Refers to a variable defined in another compilation unit by
+
260 * USDT_DEFINE_SEMA() and allows to use the same USDT semaphore across
+
261 * multiple compilation units (i.e., .c and .cpp files).
+
262 *
+
263 * See USDT_DEFINE_SEMA() notes above for C++ language usage peculiarities.
+
264 */
+
265#define USDT_DECLARE_SEMA(sema) \
+
266 extern struct usdt_sema USDT_SEMA(sema) __usdt_asm_name(USDT_SEMA(sema))
+
267
+
268/*
+
269 * Check if user-defined USDT semaphore `sema` is "active" (i.e., whether it
+
270 * is attached to by external tracing tooling and is actively observed).
+
271 *
+
272 * This macro can be used to decide whether any additional and potentially
+
273 * expensive data collection or processing should be done to pass extra
+
274 * information into USDT(s) associated with USDT semaphore `sema`.
+
275 *
+
276 * N.B. Such checks are necessarily racy. Between checking the state of USDT
+
277 * semaphore and triggering associated USDT(s), the active tracer might attach
+
278 * or detach. This race should be extremely rare and worst case should result
+
279 * in one-time missed USDT event or wasted extra data collection and
+
280 * processing. USDT-using tracers should be written with this in mind and is
+
281 * not a concern of the application defining USDTs with associated semaphore.
+
282 */
+
283#define USDT_SEMA_IS_ACTIVE(sema) (USDT_SEMA(sema).active > 0)
+
284
+
285/*
+
286 * Invoke USDT specified by `group` and `name` identifiers and associate
+
287 * explicitly user-defined semaphore `sema` with it. Pass through `args` as
+
288 * USDT arguments. `args` are optional and zero arguments are acceptable.
+
289 *
+
290 * Semaphore is defined with the help of USDT_DEFINE_SEMA() macro and can be
+
291 * checked whether active with USDT_SEMA_IS_ACTIVE().
+
292 */
+
293#ifdef __usdt_va_opt
+
294#define USDT_WITH_EXPLICIT_SEMA(sema, group, name, ...) \
+
295 __usdt_probe(group, name, __usdt_sema_explicit, USDT_SEMA(sema), ##__VA_ARGS__)
+
296#else
+
297#define USDT_WITH_EXPLICIT_SEMA(sema, group, name, ...) \
+
298 __usdt_probe(group, name, __usdt_sema_explicit, USDT_SEMA(sema) __VA_OPT__(,) __VA_ARGS__)
+
299#endif
+
300
+
301/*
+
302 * Adjustable implementation aspects
+
303 */
+
304#ifndef USDT_ARG_CONSTRAINT
+
305#if defined __powerpc__
+
306#define USDT_ARG_CONSTRAINT nZr
+
307#elif defined __arm__
+
308#define USDT_ARG_CONSTRAINT g
+
309#elif defined __loongarch__
+
310#define USDT_ARG_CONSTRAINT nmr
+
311#else
+
312#define USDT_ARG_CONSTRAINT nor
+
313#endif
+
314#endif /* USDT_ARG_CONSTRAINT */
+
315
+
316#ifndef USDT_NOP
+
317#if defined(__ia64__) || defined(__s390__) || defined(__s390x__)
+
318#define USDT_NOP nop 0
+
319#else
+
320#define USDT_NOP nop
+
321#endif
+
322#endif /* USDT_NOP */
+
323
+
324/*
+
325 * Implementation details
+
326 */
+
327/* USDT name for implicitly-defined USDT semaphore, derived from group:name */
+
328#define __usdt_sema_name(group, name) __usdt_sema_##group##__##name
+
329/* ELF section into which USDT semaphores are put */
+
330#define __usdt_sema_sec __attribute__((section(".probes")))
+
331
+
332#define __usdt_concat(a, b) a ## b
+
333#define __usdt_apply(fn, n) __usdt_concat(fn, n)
+
334
+
335#ifndef __usdt_nth
+
336#define __usdt_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, N, ...) N
+
337#endif
+
338
+
339#ifndef __usdt_narg
+
340#ifdef __usdt_va_opt
+
341#define __usdt_narg(...) __usdt_nth(_ __VA_OPT__(,) __VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
+
342#else
+
343#define __usdt_narg(...) __usdt_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
+
344#endif
+
345#endif /* __usdt_narg */
+
346
+
347#define __usdt_hash #
+
348#define __usdt_str_(x) #x
+
349#define __usdt_str(x) __usdt_str_(x)
+
350
+
351#ifndef __usdt_asm_name
+
352#define __usdt_asm_name(name) __asm__(__usdt_str(name))
+
353#endif
+
354
+
355#define __usdt_asm1(a) __usdt_str(a) "\n"
+
356#define __usdt_asm2(a,b) __usdt_str(a) "," __usdt_str(b) "\n"
+
357#define __usdt_asm3(a,b,c) __usdt_str(a) "," __usdt_str(b) "," __usdt_str(c) "\n"
+
358#define __usdt_asm5(a,b,c,d,e) __usdt_str(a) "," __usdt_str(b) "," __usdt_str(c) "," \
+
359 __usdt_str(d) "," __usdt_str(e) "\n"
+
360
+
361#ifdef __LP64__
+
362#define __usdt_asm_addr .8byte
+
363#else
+
364#define __usdt_asm_addr .4byte
+
365#endif
+
366
+
367#define __usdt_asm_strz_(x) __usdt_asm1(.asciz #x)
+
368#define __usdt_asm_strz(x) __usdt_asm_strz_(x)
+
369#define __usdt_asm_str_(x) __usdt_asm1(.ascii #x)
+
370#define __usdt_asm_str(x) __usdt_asm_str_(x)
+
371
+
372/* "semaphoreless" USDT case */
+
373#ifndef __usdt_sema_none
+
374#define __usdt_sema_none(sema)
+
375#endif
+
376
+
377/* implicitly defined __usdt_sema__group__name semaphore (using weak symbols) */
+
378#ifndef __usdt_sema_implicit
+
379#define __usdt_sema_implicit(sema) \
+
380 __asm__ __volatile__ ( \
+
381 __usdt_asm1(.ifndef sema) \
+
382 __usdt_asm3( .pushsection .probes, "aw", "progbits") \
+
383 __usdt_asm1( .weak sema) \
+
384 __usdt_asm1( .hidden sema) \
+
385 __usdt_asm1( .align 2) \
+
386 __usdt_asm1(sema:) \
+
387 __usdt_asm1( .zero 2) \
+
388 __usdt_asm2( .type sema, @object) \
+
389 __usdt_asm2( .size sema, 2) \
+
390 __usdt_asm1( .popsection) \
+
391 __usdt_asm1(.endif) \
+
392 );
+
393#endif
+
394
+
395/* externally defined semaphore using USDT_DEFINE_SEMA() and passed explicitly by user */
+
396#ifndef __usdt_sema_explicit
+
397#define __usdt_sema_explicit(sema) \
+
398 __asm__ __volatile__ ("" :: "m" (sema));
+
399#endif
+
400
+
401/* main USDT definition (nop and .note.stapsdt metadata) */
+
402#define __usdt_probe(group, name, sema_def, sema, ...) do { \
+
403 sema_def(sema) \
+
404 __asm__ __volatile__ ( \
+
405 __usdt_asm1(990: USDT_NOP) \
+
406 __usdt_asm3( .pushsection .note.stapsdt, "", "note") \
+
407 __usdt_asm1( .balign 4) \
+
408 __usdt_asm3( .4byte 992f-991f,994f-993f,3) \
+
409 __usdt_asm1(991: .asciz "stapsdt") \
+
410 __usdt_asm1(992: .balign 4) \
+
411 __usdt_asm1(993: __usdt_asm_addr 990b) \
+
412 __usdt_asm1( __usdt_asm_addr _.stapsdt.base) \
+
413 __usdt_asm1( __usdt_asm_addr sema) \
+
414 __usdt_asm_strz(group) \
+
415 __usdt_asm_strz(name) \
+
416 __usdt_asm_args(__VA_ARGS__) \
+
417 __usdt_asm1( .ascii "\0") \
+
418 __usdt_asm1(994: .balign 4) \
+
419 __usdt_asm1( .popsection) \
+
420 __usdt_asm1(.ifndef _.stapsdt.base) \
+
421 __usdt_asm5( .pushsection .stapsdt.base,"aG","progbits",.stapsdt.base,comdat)\
+
422 __usdt_asm1( .weak _.stapsdt.base) \
+
423 __usdt_asm1( .hidden _.stapsdt.base) \
+
424 __usdt_asm1(_.stapsdt.base:) \
+
425 __usdt_asm1( .space 1) \
+
426 __usdt_asm2( .size _.stapsdt.base, 1) \
+
427 __usdt_asm1( .popsection) \
+
428 __usdt_asm1(.endif) \
+
429 :: __usdt_asm_ops(__VA_ARGS__) \
+
430 ); \
+
431} while (0)
+
432
+
433/*
+
434 * NB: gdb PR24541 highlighted an unspecified corner of the sdt.h
+
435 * operand note format.
+
436 *
+
437 * The named register may be a longer or shorter (!) alias for the
+
438 * storage where the value in question is found. For example, on
+
439 * i386, 64-bit value may be put in register pairs, and a register
+
440 * name stored would identify just one of them. Previously, gcc was
+
441 * asked to emit the %w[id] (16-bit alias of some registers holding
+
442 * operands), even when a wider 32-bit value was used.
+
443 *
+
444 * Bottom line: the byte-width given before the @ sign governs. If
+
445 * there is a mismatch between that width and that of the named
+
446 * register, then a sys/sdt.h note consumer may need to employ
+
447 * architecture-specific heuristics to figure out where the compiler
+
448 * has actually put the complete value.
+
449 */
+
450#if defined(__powerpc__) || defined(__powerpc64__)
+
451#define __usdt_argref(id) %I[id]%[id]
+
452#elif defined(__i386__)
+
453#define __usdt_argref(id) %k[id] /* gcc.gnu.org/PR80115 sourceware.org/PR24541 */
+
454#else
+
455#define __usdt_argref(id) %[id]
+
456#endif
+
457
+
458#define __usdt_asm_arg(n) __usdt_asm_str(%c[__usdt_asz##n]) \
+
459 __usdt_asm1(.ascii "@") \
+
460 __usdt_asm_str(__usdt_argref(__usdt_aval##n))
+
461
+
462#define __usdt_asm_args0 /* no arguments */
+
463#define __usdt_asm_args1 __usdt_asm_arg(1)
+
464#define __usdt_asm_args2 __usdt_asm_args1 __usdt_asm1(.ascii " ") __usdt_asm_arg(2)
+
465#define __usdt_asm_args3 __usdt_asm_args2 __usdt_asm1(.ascii " ") __usdt_asm_arg(3)
+
466#define __usdt_asm_args4 __usdt_asm_args3 __usdt_asm1(.ascii " ") __usdt_asm_arg(4)
+
467#define __usdt_asm_args5 __usdt_asm_args4 __usdt_asm1(.ascii " ") __usdt_asm_arg(5)
+
468#define __usdt_asm_args6 __usdt_asm_args5 __usdt_asm1(.ascii " ") __usdt_asm_arg(6)
+
469#define __usdt_asm_args7 __usdt_asm_args6 __usdt_asm1(.ascii " ") __usdt_asm_arg(7)
+
470#define __usdt_asm_args8 __usdt_asm_args7 __usdt_asm1(.ascii " ") __usdt_asm_arg(8)
+
471#define __usdt_asm_args9 __usdt_asm_args8 __usdt_asm1(.ascii " ") __usdt_asm_arg(9)
+
472#define __usdt_asm_args10 __usdt_asm_args9 __usdt_asm1(.ascii " ") __usdt_asm_arg(10)
+
473#define __usdt_asm_args11 __usdt_asm_args10 __usdt_asm1(.ascii " ") __usdt_asm_arg(11)
+
474#define __usdt_asm_args12 __usdt_asm_args11 __usdt_asm1(.ascii " ") __usdt_asm_arg(12)
+
475#define __usdt_asm_args(...) __usdt_apply(__usdt_asm_args, __usdt_narg(__VA_ARGS__))
+
476
+
477#define __usdt_is_arr(x) (__builtin_classify_type(x) == 14 || __builtin_classify_type(x) == 5)
+
478#define __usdt_arg_size(x) (__usdt_is_arr(x) ? sizeof(void *) : sizeof(x))
+
479
+
480/*
+
481 * We can't use __builtin_choose_expr() in C++, so fall back to table-based
+
482 * signedness determination for known types, utilizing templates magic.
+
483 */
+
484#ifdef __cplusplus
+
485
+
486#define __usdt_is_signed(x) (!__usdt_is_arr(x) && __usdt_t<__typeof(x)>::is_signed)
+
487
+
488#include <cstddef>
+
489
+
490template<typename T> struct __usdt_t { static const bool is_signed = false; };
+
491template<typename A> struct __usdt_t<A[]> : public __usdt_t<A *> {};
+
492template<typename A, size_t N> struct __usdt_t<A[N]> : public __usdt_t<A *> {};
+
493
+
494#define __usdt_def_signed(T) \
+
495template<> struct __usdt_t<T> { static const bool is_signed = true; }; \
+
496template<> struct __usdt_t<const T> { static const bool is_signed = true; }; \
+
497template<> struct __usdt_t<volatile T> { static const bool is_signed = true; }; \
+
498template<> struct __usdt_t<const volatile T> { static const bool is_signed = true; }
+
499#define __usdt_maybe_signed(T) \
+
500template<> struct __usdt_t<T> { static const bool is_signed = (T)-1 < (T)1; }; \
+
501template<> struct __usdt_t<const T> { static const bool is_signed = (T)-1 < (T)1; }; \
+
502template<> struct __usdt_t<volatile T> { static const bool is_signed = (T)-1 < (T)1; }; \
+
503template<> struct __usdt_t<const volatile T> { static const bool is_signed = (T)-1 < (T)1; }
+
504
+
505__usdt_def_signed(signed char);
+
506__usdt_def_signed(short);
+
507__usdt_def_signed(int);
+
508__usdt_def_signed(long);
+
509__usdt_def_signed(long long);
+
510__usdt_maybe_signed(char);
+
511__usdt_maybe_signed(wchar_t);
+
512
+
513#else /* !__cplusplus */
+
514
+
515#define __usdt_is_inttype(x) (__builtin_classify_type(x) >= 1 && __builtin_classify_type(x) <= 4)
+
516#define __usdt_inttype(x) __typeof(__builtin_choose_expr(__usdt_is_inttype(x), (x), 0U))
+
517#define __usdt_is_signed(x) ((__usdt_inttype(x))-1 < (__usdt_inttype(x))1)
+
518
+
519#endif /* __cplusplus */
+
520
+
521#define __usdt_asm_op(n, x) \
+
522 [__usdt_asz##n] "n" ((__usdt_is_signed(x) ? (int)-1 : 1) * (int)__usdt_arg_size(x)), \
+
523 [__usdt_aval##n] __usdt_str(USDT_ARG_CONSTRAINT)(x)
+
524
+
525#define __usdt_asm_ops0() [__usdt_dummy] "g" (0)
+
526#define __usdt_asm_ops1(x) __usdt_asm_op(1, x)
+
527#define __usdt_asm_ops2(a,x) __usdt_asm_ops1(a), __usdt_asm_op(2, x)
+
528#define __usdt_asm_ops3(a,b,x) __usdt_asm_ops2(a,b), __usdt_asm_op(3, x)
+
529#define __usdt_asm_ops4(a,b,c,x) __usdt_asm_ops3(a,b,c), __usdt_asm_op(4, x)
+
530#define __usdt_asm_ops5(a,b,c,d,x) __usdt_asm_ops4(a,b,c,d), __usdt_asm_op(5, x)
+
531#define __usdt_asm_ops6(a,b,c,d,e,x) __usdt_asm_ops5(a,b,c,d,e), __usdt_asm_op(6, x)
+
532#define __usdt_asm_ops7(a,b,c,d,e,f,x) __usdt_asm_ops6(a,b,c,d,e,f), __usdt_asm_op(7, x)
+
533#define __usdt_asm_ops8(a,b,c,d,e,f,g,x) __usdt_asm_ops7(a,b,c,d,e,f,g), __usdt_asm_op(8, x)
+
534#define __usdt_asm_ops9(a,b,c,d,e,f,g,h,x) __usdt_asm_ops8(a,b,c,d,e,f,g,h), __usdt_asm_op(9, x)
+
535#define __usdt_asm_ops10(a,b,c,d,e,f,g,h,i,x) __usdt_asm_ops9(a,b,c,d,e,f,g,h,i), __usdt_asm_op(10, x)
+
536#define __usdt_asm_ops11(a,b,c,d,e,f,g,h,i,j,x) __usdt_asm_ops10(a,b,c,d,e,f,g,h,i,j), __usdt_asm_op(11, x)
+
537#define __usdt_asm_ops12(a,b,c,d,e,f,g,h,i,j,k,x) __usdt_asm_ops11(a,b,c,d,e,f,g,h,i,j,k), __usdt_asm_op(12, x)
+
538#define __usdt_asm_ops(...) __usdt_apply(__usdt_asm_ops, __usdt_narg(__VA_ARGS__))(__VA_ARGS__)
+
539
+
540#endif /* __USDT_H */
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2util_8c_source.html b/doc/html/fuse-3_818_82_2lib_2util_8c_source.html new file mode 100644 index 0000000..f1f9347 --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2util_8c_source.html @@ -0,0 +1,113 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/util.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
util.c
+
+
+
1
+
2#include "fuse_config.h"
+
3
+
4#ifdef HAVE_PTHREAD_SETNAME_NP
+
5#define _GNU_SOURCE
+
6#include <pthread.h>
+
7#endif
+
8
+
9#include <errno.h>
+
10#include <stdlib.h>
+
11#include <errno.h>
+
12
+
13#ifndef FUSE_USE_VERSION
+
14#define FUSE_USE_VERSION (FUSE_MAKE_VERSION(3, 18))
+
15#endif
+
16
+
17#include "util.h"
+
18#include "fuse_log.h"
+
19#include "fuse_lowlevel.h"
+
20#include <stdio.h>
+
21
+
22int libfuse_strtol(const char *str, long *res)
+
23{
+
24 char *endptr;
+
25 int base = 10;
+
26 long val;
+
27
+
28 errno = 0;
+
29
+
30 if (!str)
+
31 return -EINVAL;
+
32
+
33 val = strtol(str, &endptr, base);
+
34
+
35 if (errno)
+
36 return -errno;
+
37
+
38 if (endptr == str || *endptr != '\0')
+
39 return -EINVAL;
+
40
+
41 *res = val;
+
42 return 0;
+
43}
+
44
+
45void fuse_set_thread_name(const char *name)
+
46{
+
47#ifdef HAVE_PTHREAD_SETNAME_NP
+
48 pthread_setname_np(pthread_self(), name);
+
49#else
+
50 (void)name;
+
51#endif
+
52}
+
53
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2lib_2util_8h_source.html b/doc/html/fuse-3_818_82_2lib_2util_8h_source.html new file mode 100644 index 0000000..ae3159f --- /dev/null +++ b/doc/html/fuse-3_818_82_2lib_2util_8h_source.html @@ -0,0 +1,104 @@ + + + + + + + +libfuse: fuse-3.18.2/lib/util.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
util.h
+
+
+
1#ifndef FUSE_UTIL_H_
+
2#define FUSE_UTIL_H_
+
3
+
4#include <stdint.h>
+
5#include <stdbool.h>
+
6
+
7#define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1))
+
8
+
9#define likely(x) __builtin_expect(!!(x), 1)
+
10#define unlikely(x) __builtin_expect(!!(x), 0)
+
11
+
12struct fuse_conn_info;
+
13
+
14int libfuse_strtol(const char *str, long *res);
+
15void fuse_set_thread_name(const char *name);
+
16
+
20static inline uint32_t fuse_lower_32_bits(uint64_t nr)
+
21{
+
22 return (uint32_t)(nr & 0xffffffff);
+
23}
+
24
+
28static inline uint64_t fuse_higher_32_bits(uint64_t nr)
+
29{
+
30 return nr & ~0xffffffffULL;
+
31}
+
32
+
33#ifndef FUSE_VAR_UNUSED
+
34#define FUSE_VAR_UNUSED __attribute__((__unused__))
+
35#endif
+
36
+
37#define container_of(ptr, type, member) \
+
38 ({ \
+
39 unsigned long __mptr = (unsigned long)(ptr); \
+
40 ((type *)(__mptr - offsetof(type, member))); \
+
41 })
+
42
+
43#if __has_attribute(__fallthrough__)
+
44#define fallthrough __attribute__((__fallthrough__))
+
45#else
+
46#define fallthrough do {} while (0)
+
47#endif
+
48
+
49#endif /* FUSE_UTIL_H_ */
+ +
+ + + + diff --git a/doc/html/fuse-3_818_82_2test_2hello_8c.html b/doc/html/fuse-3_818_82_2test_2hello_8c.html new file mode 100644 index 0000000..8a401b9 --- /dev/null +++ b/doc/html/fuse-3_818_82_2test_2hello_8c.html @@ -0,0 +1,265 @@ + + + + + + + +libfuse: fuse-3.18.2/test/hello.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using high-level API

+

Compile with:

gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <stddef.h>
+
#include <assert.h>
+
+
/*
+
* Command line options
+
*
+
* We can't set default values for the char* fields here because
+
* fuse_opt_parse would attempt to free() them when the user specifies
+
* different values on the command line.
+
*/
+
static struct options {
+
const char *filename;
+
const char *contents;
+
int show_help;
+
} options;
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--name=%s", filename),
+
OPTION("--contents=%s", contents),
+
OPTION("-h", show_help),
+
OPTION("--help", show_help),
+ +
};
+
+
static void *hello_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->kernel_cache = 1;
+
+
/* Test setting flags the old way */
+ + +
+
return NULL;
+
}
+
+
static int hello_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res = 0;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
} else if (strcmp(path+1, options.filename) == 0) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(options.contents);
+
} else
+
res = -ENOENT;
+
+
return res;
+
}
+
+
static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int hello_open(const char *path, struct fuse_file_info *fi)
+
{
+
if (strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
+
return 0;
+
}
+
+
static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
size_t len;
+
(void) fi;
+
if(strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
len = strlen(options.contents);
+
if (offset < len) {
+
if (offset + size > len)
+
size = len - offset;
+
memcpy(buf, options.contents + offset, size);
+
} else
+
size = 0;
+
+
return size;
+
}
+
+
static const struct fuse_operations hello_oper = {
+
.init = hello_init,
+
.getattr = hello_getattr,
+
.readdir = hello_readdir,
+
.open = hello_open,
+
.read = hello_read,
+
};
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --name=<s> Name of the \"hello\" file\n"
+
" (default: \"hello\")\n"
+
" --contents=<s> Contents \"hello\" file\n"
+
" (default \"Hello, World!\\n\")\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[])
+
{
+
int ret;
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
+
/* Set defaults -- we have to use strdup so that
+
fuse_opt_parse can free the defaults if other
+
values are specified */
+
options.filename = strdup("hello");
+
options.contents = strdup("Hello World!\n");
+
+
/* Parse options */
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
/* When --help is specified, first print our own file-system
+
specific help text, then signal fuse_main to show
+
additional help (by adding `--help` to the options again)
+
without usage: line (by setting argv[0] to the empty
+
string) */
+
if (options.show_help) {
+
show_help(argv[0]);
+
assert(fuse_opt_add_arg(&args, "--help") == 0);
+
args.argv[0][0] = '\0';
+
}
+
+
ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+ +
return ret;
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_ASYNC_READ
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file hello.c.

+
+ + + + diff --git a/doc/html/fuse-3_818_82_2test_2hello_8c_source.html b/doc/html/fuse-3_818_82_2test_2hello_8c_source.html new file mode 100644 index 0000000..1f70262 --- /dev/null +++ b/doc/html/fuse-3_818_82_2test_2hello_8c_source.html @@ -0,0 +1,250 @@ + + + + + + + +libfuse: fuse-3.18.2/test/hello.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello.c
+
+
+Go to the documentation of this file.
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 * Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 *
+
5 * This program can be distributed under the terms of the GNU GPLv2.
+
6 * See the file GPL2.txt.
+
7 */
+
8
+
21#define FUSE_USE_VERSION 31
+
22
+
23#include <fuse.h>
+
24#include <stdio.h>
+
25#include <string.h>
+
26#include <errno.h>
+
27#include <fcntl.h>
+
28#include <stddef.h>
+
29#include <assert.h>
+
30
+
31/*
+
32 * Command line options
+
33 *
+
34 * We can't set default values for the char* fields here because
+
35 * fuse_opt_parse would attempt to free() them when the user specifies
+
36 * different values on the command line.
+
37 */
+
38static struct options {
+
39 const char *filename;
+
40 const char *contents;
+
41 int show_help;
+
42} options;
+
43
+
44#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
45static const struct fuse_opt option_spec[] = {
+
46 OPTION("--name=%s", filename), OPTION("--contents=%s", contents),
+
47 OPTION("-h", show_help), OPTION("--help", show_help), FUSE_OPT_END
+
48};
+
49
+
50static void *hello_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
51{
+
52 (void)conn;
+
53 cfg->kernel_cache = 1;
+
54
+
55 /* Test setting flags the old way */
+ +
57 conn->want &= ~FUSE_CAP_ASYNC_READ;
+
58
+
59 return NULL;
+
60}
+
61
+
62static int hello_getattr(const char *path, struct stat *stbuf,
+
63 struct fuse_file_info *fi)
+
64{
+
65 (void)fi;
+
66 int res = 0;
+
67
+
68 memset(stbuf, 0, sizeof(struct stat));
+
69 if (strcmp(path, "/") == 0) {
+
70 stbuf->st_mode = S_IFDIR | 0755;
+
71 stbuf->st_nlink = 2;
+
72 } else if (strcmp(path + 1, options.filename) == 0) {
+
73 stbuf->st_mode = S_IFREG | 0444;
+
74 stbuf->st_nlink = 1;
+
75 stbuf->st_size = strlen(options.contents);
+
76 } else
+
77 res = -ENOENT;
+
78
+
79 return res;
+
80}
+
81
+
82static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
83 off_t offset, struct fuse_file_info *fi,
+
84 enum fuse_readdir_flags flags)
+
85{
+
86 (void)offset;
+
87 (void)fi;
+
88 (void)flags;
+
89
+
90 if (strcmp(path, "/") != 0)
+
91 return -ENOENT;
+
92
+
93 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
94 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
95 filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
96
+
97 return 0;
+
98}
+
99
+
100static int hello_open(const char *path, struct fuse_file_info *fi)
+
101{
+
102 if (strcmp(path + 1, options.filename) != 0)
+
103 return -ENOENT;
+
104
+
105 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
106 return -EACCES;
+
107
+
108 return 0;
+
109}
+
110
+
111static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
112 struct fuse_file_info *fi)
+
113{
+
114 size_t len;
+
115 (void)fi;
+
116 if (strcmp(path + 1, options.filename) != 0)
+
117 return -ENOENT;
+
118
+
119 len = strlen(options.contents);
+
120 if (offset < len) {
+
121 if (offset + size > len)
+
122 size = len - offset;
+
123 memcpy(buf, options.contents + offset, size);
+
124 } else
+
125 size = 0;
+
126
+
127 return size;
+
128}
+
129
+
130static const struct fuse_operations hello_oper = {
+
131 .init = hello_init,
+
132 .getattr = hello_getattr,
+
133 .readdir = hello_readdir,
+
134 .open = hello_open,
+
135 .read = hello_read,
+
136};
+
137
+
138static void show_help(const char *progname)
+
139{
+
140 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
141 printf("File-system specific options:\n"
+
142 " --name=<s> Name of the \"hello\" file\n"
+
143 " (default: \"hello\")\n"
+
144 " --contents=<s> Contents \"hello\" file\n"
+
145 " (default \"Hello, World!\\n\")\n"
+
146 "\n");
+
147}
+
148
+
149int main(int argc, char *argv[])
+
150{
+
151 int ret;
+
152 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
153
+
154 /* Set defaults -- we have to use strdup so that
+
155 * fuse_opt_parse can free the defaults if other
+
156 * values are specified
+
157 */
+
158 options.filename = strdup("hello");
+
159 options.contents = strdup("Hello World!\n");
+
160
+
161 /* Parse options */
+
162 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
163 return 1;
+
164
+
165 /* When --help is specified, first print our own file-system
+
166 * specific help text, then signal fuse_main to show
+
167 * additional help (by adding `--help` to the options again)
+
168 * without usage: line (by setting argv[0] to the empty
+
169 * string)
+
170 */
+
171 if (options.show_help) {
+
172 show_help(argv[0]);
+
173 assert(fuse_opt_add_arg(&args, "--help") == 0);
+
174 args.argv[0][0] = '\0';
+
175 }
+
176
+
177 ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+
178 fuse_opt_free_args(&args);
+
179 return ret;
+
180}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_CAP_ASYNC_READ
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2test_2readdir__inode_8c_source.html b/doc/html/fuse-3_818_82_2test_2readdir__inode_8c_source.html new file mode 100644 index 0000000..690fb5c --- /dev/null +++ b/doc/html/fuse-3_818_82_2test_2readdir__inode_8c_source.html @@ -0,0 +1,117 @@ + + + + + + + +libfuse: fuse-3.18.2/test/readdir_inode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
readdir_inode.c
+
+
+
1/*
+
2 * Prints each directory entry, its inode and d_type as returned by 'readdir'.
+
3 * Skips '.' and '..' because readdir is not required to return them and
+
4 * some of our examples don't. However if they are returned, their d_type
+
5 * should be valid.
+
6 */
+
7
+
8#include <stdio.h>
+
9#include <string.h>
+
10#include <sys/types.h>
+
11#include <dirent.h>
+
12#include <errno.h>
+
13
+
14int main(int argc, char* argv[])
+
15{
+
16 DIR* dirp;
+
17 struct dirent* dent;
+
18
+
19 if (argc != 2) {
+
20 fprintf(stderr, "Usage: readdir_inode dir\n");
+
21 return 1;
+
22 }
+
23
+
24 dirp = opendir(argv[1]);
+
25 if (dirp == NULL) {
+
26 perror("failed to open directory");
+
27 return 2;
+
28 }
+
29
+
30 errno = 0;
+
31 dent = readdir(dirp);
+
32 while (dent != NULL) {
+
33 if (strcmp(dent->d_name, ".") != 0 && strcmp(dent->d_name, "..") != 0) {
+
34 printf("%llu %d %s\n", (unsigned long long)dent->d_ino,
+
35 (int)dent->d_type, dent->d_name);
+
36 if ((long long)dent->d_ino < 0)
+
37 fprintf(stderr,"%s : bad d_ino %llu\n",
+
38 dent->d_name, (unsigned long long)dent->d_ino);
+
39 if ((dent->d_type < 1) || (dent->d_type > 15))
+
40 fprintf(stderr,"%s : bad d_type %d\n",
+
41 dent->d_name, (int)dent->d_type);
+
42 } else {
+
43 if (dent->d_type != DT_DIR)
+
44 fprintf(stderr,"%s : bad d_type %d\n",
+
45 dent->d_name, (int)dent->d_type);
+
46 }
+
47 dent = readdir(dirp);
+
48 }
+
49 if (errno != 0) {
+
50 perror("failed to read directory entry");
+
51 return 3;
+
52 }
+
53
+
54 closedir(dirp);
+
55
+
56 return 0;
+
57}
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2test_2release__unlink__race_8c_source.html b/doc/html/fuse-3_818_82_2test_2release__unlink__race_8c_source.html new file mode 100644 index 0000000..9dcccda --- /dev/null +++ b/doc/html/fuse-3_818_82_2test_2release__unlink__race_8c_source.html @@ -0,0 +1,183 @@ + + + + + + + +libfuse: fuse-3.18.2/test/release_unlink_race.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
release_unlink_race.c
+
+
+
1/*
+
2 This program can be distributed under the terms of the GNU GPLv2.
+
3 See the file GPL2.txt.
+
4*/
+
5
+
6#define FUSE_USE_VERSION 31
+
7
+
8#define _GNU_SOURCE
+
9
+
10#include <fuse.h>
+
11
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <unistd.h>
+
15#include <errno.h>
+
16
+
17static void *xmp_init(struct fuse_conn_info *conn,
+
18 struct fuse_config *cfg)
+
19{
+
20 (void) conn;
+
21
+
22 cfg->use_ino = 1;
+
23 cfg->nullpath_ok = 1;
+
24 cfg->entry_timeout = 0;
+
25 cfg->attr_timeout = 0;
+
26 cfg->negative_timeout = 0;
+
27
+
28 return NULL;
+
29}
+
30
+
31static int xmp_getattr(const char *path, struct stat *stbuf,
+
32 struct fuse_file_info *fi)
+
33{
+
34 int res;
+
35
+
36 (void) path;
+
37
+
38 if(fi)
+
39 res = fstat(fi->fh, stbuf);
+
40 else
+
41 res = lstat(path, stbuf);
+
42 if (res == -1)
+
43 return -errno;
+
44
+
45 return 0;
+
46}
+
47
+
48static int xmp_unlink(const char *path)
+
49{
+
50 int res;
+
51
+
52 res = unlink(path);
+
53 if (res == -1)
+
54 return -errno;
+
55
+
56 return 0;
+
57}
+
58
+
59static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
60{
+
61 int res;
+
62
+
63 if (flags)
+
64 return -EINVAL;
+
65
+
66 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
+
67
+
68 res = rename(from, to);
+
69 if (res == -1)
+
70 return -errno;
+
71
+
72 return 0;
+
73}
+
74
+
75static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
76{
+
77 int fd;
+
78
+
79 fd = open(path, fi->flags, mode);
+
80 if (fd == -1)
+
81 return -errno;
+
82
+
83 fi->fh = fd;
+
84 return 0;
+
85}
+
86
+
87static int xmp_release(const char *path, struct fuse_file_info *fi)
+
88{
+
89 (void) path;
+
90
+
91 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
+
92
+
93 close(fi->fh);
+
94
+
95 return 0;
+
96}
+
97
+
98static const struct fuse_operations xmp_oper = {
+
99 .init = xmp_init,
+
100 .getattr = xmp_getattr,
+
101 .unlink = xmp_unlink,
+
102 .rename = xmp_rename,
+
103 .create = xmp_create,
+
104 .release = xmp_release,
+
105};
+
106
+
107int main(int argc, char *argv[])
+
108{
+
109 umask(0);
+
110 return fuse_main(argc, argv, &xmp_oper, NULL);
+
111}
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2test_2stracedecode_8c_source.html b/doc/html/fuse-3_818_82_2test_2stracedecode_8c_source.html new file mode 100644 index 0000000..4198b76 --- /dev/null +++ b/doc/html/fuse-3_818_82_2test_2stracedecode_8c_source.html @@ -0,0 +1,259 @@ + + + + + + + +libfuse: fuse-3.18.2/test/stracedecode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
stracedecode.c
+
+
+
1#include <stdio.h>
+
2#include <string.h>
+
3#include "fuse_kernel.h"
+
4
+
5static struct {
+
6 const char *name;
+
7} fuse_ll_ops[] = {
+
8 [FUSE_LOOKUP] = { "LOOKUP" },
+
9 [FUSE_FORGET] = { "FORGET" },
+
10 [FUSE_GETATTR] = { "GETATTR" },
+
11 [FUSE_SETATTR] = { "SETATTR" },
+
12 [FUSE_READLINK] = { "READLINK" },
+
13 [FUSE_SYMLINK] = { "SYMLINK" },
+
14 [FUSE_MKNOD] = { "MKNOD" },
+
15 [FUSE_MKDIR] = { "MKDIR" },
+
16 [FUSE_UNLINK] = { "UNLINK" },
+
17 [FUSE_RMDIR] = { "RMDIR" },
+
18 [FUSE_RENAME] = { "RENAME" },
+
19 [FUSE_LINK] = { "LINK" },
+
20 [FUSE_OPEN] = { "OPEN" },
+
21 [FUSE_READ] = { "READ" },
+
22 [FUSE_WRITE] = { "WRITE" },
+
23 [FUSE_STATFS] = { "STATFS" },
+
24 [FUSE_RELEASE] = { "RELEASE" },
+
25 [FUSE_FSYNC] = { "FSYNC" },
+
26 [FUSE_SETXATTR] = { "SETXATTR" },
+
27 [FUSE_GETXATTR] = { "GETXATTR" },
+
28 [FUSE_LISTXATTR] = { "LISTXATTR" },
+
29 [FUSE_REMOVEXATTR] = { "REMOVEXATTR" },
+
30 [FUSE_FLUSH] = { "FLUSH" },
+
31 [FUSE_INIT] = { "INIT" },
+
32 [FUSE_OPENDIR] = { "OPENDIR" },
+
33 [FUSE_READDIR] = { "READDIR" },
+
34 [FUSE_RELEASEDIR] = { "RELEASEDIR" },
+
35 [FUSE_FSYNCDIR] = { "FSYNCDIR" },
+
36 [FUSE_GETLK] = { "GETLK" },
+
37 [FUSE_SETLK] = { "SETLK" },
+
38 [FUSE_SETLKW] = { "SETLKW" },
+
39 [FUSE_ACCESS] = { "ACCESS" },
+
40 [FUSE_CREATE] = { "CREATE" },
+
41 [FUSE_TMPFILE] = { "TMPFILE" },
+
42 [FUSE_INTERRUPT] = { "INTERRUPT" },
+
43 [FUSE_BMAP] = { "BMAP" },
+
44 [FUSE_DESTROY] = { "DESTROY" },
+
45 [FUSE_READDIRPLUS] = { "READDIRPLUS" },
+
46};
+
47
+
48#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
+
49
+
50static const char *opname(enum fuse_opcode opcode)
+
51{
+
52 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
+
53 return "???";
+
54 else
+
55 return fuse_ll_ops[opcode].name;
+
56}
+
57
+
58
+
59static void process_buf(int dir, char *buf, int len)
+
60{
+
61 static unsigned long long prevuniq = -1;
+
62 static int prevopcode;
+
63
+
64 if (!dir) {
+
65 struct fuse_in_header *in = (struct fuse_in_header *) buf;
+
66 buf += sizeof(struct fuse_in_header);
+
67
+
68 printf("unique: %llu, opcode: %s (%i), nodeid: %lu, len: %i, insize: %i\n",
+
69 (unsigned long long) in->unique,
+
70 opname((enum fuse_opcode) in->opcode), in->opcode,
+
71 (unsigned long) in->nodeid, in->len, len);
+
72
+
73 switch (in->opcode) {
+
74 case FUSE_READ: {
+
75 struct fuse_read_in *arg = (struct fuse_read_in *) buf;
+
76 printf("-READ fh:%llu off:%llu siz:%u rfl:%u own:%llu fl:%u\n",
+
77 arg->fh, arg->offset, arg->size, arg->read_flags,
+
78 arg->lock_owner, arg->flags);
+
79 break;
+
80 }
+
81 case FUSE_WRITE: {
+
82 struct fuse_write_in *arg = (struct fuse_write_in *) buf;
+
83 printf("-WRITE fh:%llu off:%llu siz:%u wfl:%u own:%llu fl:%u\n",
+
84 arg->fh, arg->offset, arg->size, arg->write_flags,
+
85 arg->lock_owner, arg->flags);
+
86 break;
+
87 }
+
88 }
+
89 prevuniq = in->unique;
+
90 prevopcode = in->opcode;
+
91 } else {
+
92 struct fuse_out_header *out = (struct fuse_out_header *) buf;
+
93 buf += sizeof(struct fuse_out_header);
+
94
+
95 printf(" unique: %llu, error: %i (%s), len: %i, outsize: %i\n",
+
96 (unsigned long long) out->unique, out->error,
+
97 strerror(-out->error), out->len, len);
+
98
+
99 if (out->unique == prevuniq) {
+
100 switch (prevopcode) {
+
101 case FUSE_GETATTR: {
+
102 struct fuse_attr_out *arg = (struct fuse_attr_out *) buf;
+
103 printf("+ATTR v:%llu.%09u i:%llu s:%llu b:%llu\n",
+
104 arg->attr_valid, arg->attr_valid_nsec,
+
105 arg->attr.ino, arg->attr.size, arg->attr.blocks);
+
106 break;
+
107 }
+
108 case FUSE_LOOKUP: {
+
109 struct fuse_entry_out *arg = (struct fuse_entry_out *) buf;
+
110 printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n",
+
111 arg->nodeid, arg->attr_valid, arg->attr_valid_nsec,
+
112 arg->attr.ino, arg->attr.size, arg->attr.blocks);
+
113 break;
+
114 }
+
115 }
+
116 }
+
117 }
+
118
+
119}
+
120
+
121int main(void)
+
122{
+
123 FILE *in = stdin;
+
124 while (1) {
+
125 int dir;
+
126 int res;
+
127 char buf[1048576];
+
128 unsigned len = 0;
+
129
+
130 memset(buf, 0, sizeof(buf));
+
131 while (1) {
+
132 char str[32];
+
133
+
134 res = fscanf(in, "%30s", str);
+
135 if (res != 1 && feof(in))
+
136 return 0;
+
137
+
138 if (res == 0)
+
139 continue;
+
140
+
141 if (strncmp(str, "read(", 5) == 0) {
+
142 dir = 0;
+
143 break;
+
144 } else if (strncmp(str, "writev(", 7) == 0) {
+
145 dir = 1;
+
146 break;
+
147 }
+
148 }
+
149
+
150 while (1) {
+
151 int c = getc(in);
+
152 if (c == '"') {
+
153 while (1) {
+
154 int val;
+
155
+
156 c = getc(in);
+
157 if (c == EOF) {
+
158 fprintf(stderr, "eof in string\n");
+
159 break;
+
160 }
+
161 if (c == '\n') {
+
162 fprintf(stderr, "eol in string\n");
+
163 break;
+
164 }
+
165 if (c == '"')
+
166 break;
+
167 if (c != '\\') {
+
168 val = c;
+
169 } else {
+
170 c = getc(in);
+
171 switch (c) {
+
172 case 'n': val = '\n'; break;
+
173 case 'r': val = '\r'; break;
+
174 case 't': val = '\t'; break;
+
175 case '"': val = '"'; break;
+
176 case '\\': val = '\\'; break;
+
177 case 'x':
+
178 res = scanf("%x", &val);
+
179 if (res != 1) {
+
180 fprintf(stderr, "parse error\n");
+
181 continue;
+
182 }
+
183 break;
+
184 default:
+
185 fprintf(stderr, "unknown sequence: '\\%c'\n", c);
+
186 continue;
+
187 }
+
188 }
+
189 buf[len++] = val;
+
190 }
+
191 }
+
192 if (c == '\n')
+
193 break;
+
194 }
+
195 process_buf(dir, buf, len);
+
196 memset(buf, 0, len);
+
197 len = 0;
+
198 }
+
199}
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2test_2test__abi_8c_source.html b/doc/html/fuse-3_818_82_2test_2test__abi_8c_source.html new file mode 100644 index 0000000..ced10d8 --- /dev/null +++ b/doc/html/fuse-3_818_82_2test_2test__abi_8c_source.html @@ -0,0 +1,80 @@ + + + + + + + +libfuse: fuse-3.18.2/test/test_abi.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_abi.c
+
+
+
1#define FUSE_USE_VERSION 30
+
2
+
3#include "fuse.h"
+
4
+
5#include <stdio.h>
+
6#include <stdlib.h>
+
7
+
8int main(void)
+
9{
+
10 if (sizeof(struct fuse_file_info) != 64) {
+
11 fprintf(stderr, "struct fuse_file_info size mismatch\n");
+
12 exit(1);
+
13 }
+
14 if (sizeof(struct fuse_conn_info) != 128) {
+
15 fprintf(stderr, "struct fuse_conn_info size mismatch\n");
+
16 exit(1);
+
17 }
+
18}
+ + +
+ + + + diff --git a/doc/html/fuse-3_818_82_2test_2test__setattr_8c_source.html b/doc/html/fuse-3_818_82_2test_2test__setattr_8c_source.html new file mode 100644 index 0000000..e0a8cdf --- /dev/null +++ b/doc/html/fuse-3_818_82_2test_2test__setattr_8c_source.html @@ -0,0 +1,272 @@ + + + + + + + +libfuse: fuse-3.18.2/test/test_setattr.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_setattr.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
9
+
10#define FUSE_USE_VERSION 30
+
11
+
12/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
+
13#include <fuse.h>
+
14
+
15#include <fuse_config.h>
+
16#include <fuse_lowlevel.h>
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <errno.h>
+
21#include <fcntl.h>
+
22#include <assert.h>
+
23#include <stddef.h>
+
24#include <unistd.h>
+
25#include <pthread.h>
+
26
+
27#ifndef __linux__
+
28#include <limits.h>
+
29#else
+
30#include <linux/limits.h>
+
31#endif
+
32
+
33#define FILE_INO 2
+
34#define FILE_NAME "truncate_me"
+
35
+
36static int got_fh;
+
37static mode_t file_mode = S_IFREG | 0644;
+
38
+
39static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
40 stbuf->st_ino = ino;
+
41 if (ino == FUSE_ROOT_ID) {
+
42 stbuf->st_mode = S_IFDIR | 0755;
+
43 stbuf->st_nlink = 1;
+
44 }
+
45
+
46 else if (ino == FILE_INO) {
+
47 stbuf->st_mode = file_mode;
+
48 stbuf->st_nlink = 1;
+
49 stbuf->st_size = 0;
+
50 }
+
51
+
52 else
+
53 return -1;
+
54
+
55 return 0;
+
56}
+
57
+
58static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
59 const char *name) {
+
60 struct fuse_entry_param e;
+
61 memset(&e, 0, sizeof(e));
+
62
+
63 if (parent != FUSE_ROOT_ID)
+
64 goto err_out;
+
65 else if (strcmp(name, FILE_NAME) == 0)
+
66 e.ino = FILE_INO;
+
67 else
+
68 goto err_out;
+
69
+
70 if (tfs_stat(e.ino, &e.attr) != 0)
+
71 goto err_out;
+
72 fuse_reply_entry(req, &e);
+
73 return;
+
74
+
75err_out:
+
76 fuse_reply_err(req, ENOENT);
+
77}
+
78
+
79static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
80 struct fuse_file_info *fi) {
+
81 struct stat stbuf;
+
82
+
83 (void) fi;
+
84
+
85 memset(&stbuf, 0, sizeof(stbuf));
+
86 if (tfs_stat(ino, &stbuf) != 0)
+
87 fuse_reply_err(req, ENOENT);
+
88 else
+
89 fuse_reply_attr(req, &stbuf, 5);
+
90}
+
91
+
92static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
93 struct fuse_file_info *fi) {
+
94 if (ino == FUSE_ROOT_ID)
+
95 fuse_reply_err(req, EISDIR);
+
96 else {
+
97 assert(ino == FILE_INO);
+
98 fi->fh = FILE_INO;
+
99 fuse_reply_open(req, fi);
+
100 }
+
101}
+
102
+
103static void tfs_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
104 int to_set, struct fuse_file_info *fi) {
+
105 if(ino != FILE_INO ||
+
106 !(to_set & FUSE_SET_ATTR_MODE)) {
+
107 fuse_reply_err(req, EINVAL);
+
108 return;
+
109 }
+
110
+
111 if(fi == NULL)
+
112 fprintf(stderr, "setattr with fi == NULL\n");
+
113 else if (fi->fh != FILE_INO)
+
114 fprintf(stderr, "setattr with wrong fi->fh\n");
+
115 else {
+
116 fprintf(stderr, "setattr ok\n");
+
117 got_fh = 1;
+
118 file_mode = attr->st_mode;
+
119 }
+
120
+
121 tfs_getattr(req, ino, fi);
+
122}
+
123
+
124static struct fuse_lowlevel_ops tfs_oper = {
+
125 .lookup = tfs_lookup,
+
126 .getattr = tfs_getattr,
+
127 .open = tfs_open,
+
128 .setattr = tfs_setattr,
+
129};
+
130
+
131static void* run_fs(void *data) {
+
132 struct fuse_session *se = (struct fuse_session*) data;
+
133 assert(fuse_session_loop(se) == 0);
+
134 return NULL;
+
135}
+
136
+
137static void test_fs(char *mountpoint) {
+
138 char fname[PATH_MAX];
+
139 int fd;
+
140
+
141 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
+
142 mountpoint) > 0);
+
143 fd = open(fname, O_WRONLY);
+
144 if (fd == -1) {
+
145 perror(fname);
+
146 assert(0);
+
147 }
+
148
+
149 assert(fchmod(fd, 0600) == 0);
+
150 close(fd);
+
151}
+
152
+
153int main(int argc, char *argv[]) {
+
154 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
155 struct fuse_session *se;
+
156 struct fuse_cmdline_opts fuse_opts;
+
157 pthread_t fs_thread;
+
158
+
159 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
+
160#ifndef __FreeBSD__
+
161 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
+
162#endif
+
163 se = fuse_session_new(&args, &tfs_oper,
+
164 sizeof(tfs_oper), NULL);
+
165 assert (se != NULL);
+
166 assert(fuse_set_signal_handlers(se) == 0);
+
167 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
+
168
+
169 /* Start file-system thread */
+
170 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
+
171
+
172 /* Do test */
+
173 test_fs(fuse_opts.mountpoint);
+
174
+
175 /* Stop file system */
+
176 assert(pthread_cancel(fs_thread) == 0);
+
177
+ +
179 assert(got_fh == 1);
+ + +
182
+
183 printf("Test completed successfully.\n");
+
184 return 0;
+
185}
+
186
+
187
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
uint64_t fuse_ino_t
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
+
fuse_ino_t ino
+ + + +
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2test_2test__signals_8c_source.html b/doc/html/fuse-3_818_82_2test_2test__signals_8c_source.html new file mode 100644 index 0000000..0aba7ad --- /dev/null +++ b/doc/html/fuse-3_818_82_2test_2test__signals_8c_source.html @@ -0,0 +1,279 @@ + + + + + + + +libfuse: fuse-3.18.2/test/test_signals.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_signals.c
+
+
+
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 * Copyright (C) 2025 Bernd Schubert <bernd@bsbernd.com>
+
4 *
+
5 * Test for signal handling in libfuse.
+
6 *
+
7 * This program can be distributed under the terms of the GNU LGPLv2.
+
8 * See the file GPL2.txt
+
9 */
+
10
+
11#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17)
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_lowlevel.h"
+
15#include "fuse_i.h"
+
16
+
17#include <pthread.h>
+
18#include <stdatomic.h>
+
19#include <stdio.h>
+
20#include <stdlib.h>
+
21#include <string.h>
+
22#include <unistd.h>
+
23#include <signal.h>
+
24#include <errno.h>
+
25#include <sys/types.h>
+
26#include <sys/wait.h>
+
27
+
28static void test_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
29{
+
30 (void)parent;
+
31 (void)name;
+
32 /* Simulate slow lookup to test signal interruption */
+
33 sleep(2);
+
34 fuse_reply_err(req, ENOENT);
+
35}
+
36
+
37static void test_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
38 struct fuse_file_info *fi)
+
39{
+
40 (void)ino;
+
41 (void)fi;
+
42 /* Simulate slow getattr to test signal interruption */
+
43 sleep(2);
+
44 fuse_reply_err(req, ENOENT);
+
45}
+
46
+
47static const struct fuse_lowlevel_ops test_ll_ops = {
+
48 .lookup = test_ll_lookup,
+
49 .getattr = test_ll_getattr,
+
50};
+
51
+
52static void *signal_sender_thread(void *arg)
+
53{
+
54 (void)arg;
+
55
+
56 usleep(2 * 1000 * 1000);
+
57
+
58 /* Send SIGTERM to the process */
+
59 kill(getpid(), SIGTERM);
+
60 return NULL;
+
61}
+
62
+
63static void fork_child(void)
+
64{
+
65 struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+
66 struct fuse_session *se;
+
67 struct fuse_loop_config *loop_config;
+
68 pthread_t sig_thread;
+
69 char *mountpoint = NULL;
+
70 int ret = -1;
+
71
+
72 /* Add the program name to arg[0] */
+
73 if (fuse_opt_add_arg(&args, "test_signals")) {
+
74 fprintf(stderr, "Failed to add argument\n");
+
75 goto out_free_mountpoint;
+
76 }
+
77
+
78 /* Add debug flag to see more output */
+
79 fuse_opt_add_arg(&args, "-d");
+
80
+
81 /* Create temporary mount point */
+
82 mountpoint = strdup("/tmp/fuse_test_XXXXXX");
+
83 if (!mountpoint || !mkdtemp(mountpoint)) {
+
84 fprintf(stderr, "Failed to create temp dir\n");
+
85 goto out_free_args;
+
86 }
+
87
+
88 /* Create session */
+
89 se = fuse_session_new(&args, &test_ll_ops, sizeof(test_ll_ops), NULL);
+
90 if (!se) {
+
91 fprintf(stderr, "Failed to create FUSE session\n");
+
92 goto out_free_mountpoint;
+
93 }
+
94
+
95 /* Mount filesystem */
+
96 if (fuse_session_mount(se, mountpoint)) {
+
97 fprintf(stderr, "Failed to mount FUSE filesystem\n");
+
98 goto out_destroy_session;
+
99 }
+
100
+
101 /* Create loop config */
+
102 loop_config = fuse_loop_cfg_create();
+
103 if (!loop_config) {
+
104 fprintf(stderr, "Failed to create loop config\n");
+
105 goto out_unmount;
+
106 }
+
107 fuse_loop_cfg_set_clone_fd(loop_config, 0);
+
108 fuse_loop_cfg_set_max_threads(loop_config, 2);
+
109
+
110 /* Set up signal handlers */
+
111 if (fuse_set_signal_handlers(se)) {
+
112 fprintf(stderr, "Failed to set up signal handlers\n");
+
113 goto out_destroy_config;
+
114 }
+
115
+
116 /* Create thread that will send signals */
+
117 if (pthread_create(&sig_thread, NULL, signal_sender_thread, NULL)) {
+
118 fprintf(stderr, "Failed to create signal sender thread\n");
+
119 goto out_remove_handlers;
+
120 }
+
121
+
122 /* Enter FUSE loop */
+
123 ret = fuse_session_loop_mt_312(se, loop_config);
+
124
+
125 printf("Debug: fuse_session_loop_mt_312 returned %d\n", ret);
+
126 printf("Debug: session exited state: %d\n", fuse_session_exited(se));
+
127 printf("Debug: session status: %d\n", se->error);
+
128
+
129 /* Check exit status before cleanup */
+
130 int clean_exit = (fuse_session_exited(se) && se->error == SIGTERM);
+
131
+
132 /* Clean up */
+
133 pthread_join(sig_thread, NULL);
+ + + +
137 fuse_loop_cfg_destroy(loop_config);
+
138 rmdir(mountpoint);
+
139 free(mountpoint);
+
140 fuse_opt_free_args(&args);
+
141
+
142 /* Use saved exit status */
+
143 if (clean_exit) {
+
144 printf("Debug: Clean shutdown via SIGTERM\n");
+
145 exit(0);
+
146 }
+
147 printf("Debug: Exiting with status %d\n", ret != 0);
+
148 exit(ret != 0);
+
149
+
150out_remove_handlers:
+ +
152out_destroy_config:
+
153 fuse_loop_cfg_destroy(loop_config);
+
154out_unmount:
+ +
156out_destroy_session:
+ +
158out_free_mountpoint:
+
159 rmdir(mountpoint);
+
160 free(mountpoint);
+
161out_free_args:
+
162 fuse_opt_free_args(&args);
+
163 exit(1);
+
164}
+
165
+
166static void run_test_in_child(void)
+
167{
+
168 pid_t child;
+
169 int status;
+
170
+
171 child = fork();
+
172 if (child == -1) {
+
173 perror("fork");
+
174 exit(1);
+
175 }
+
176
+
177 if (child == 0)
+
178 fork_child();
+
179
+
180 /* In parent process */
+
181 if (waitpid(child, &status, 0) == -1) {
+
182 perror("waitpid");
+
183 exit(1);
+
184 }
+
185
+
186 /* Check if child exited due to SIGTERM - this is expected */
+
187 if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) {
+
188 printf("Child process terminated by SIGTERM as expected\n");
+
189 exit(0);
+
190 }
+
191
+
192 /* For any other type of exit, maintain existing behavior */
+
193 exit(WIFEXITED(status) ? WEXITSTATUS(status) : 1);
+
194}
+
195
+
196int main(void)
+
197{
+
198 printf("Testing SIGTERM handling in libfuse\n");
+
199 run_test_in_child();
+
200 printf("SIGTERM handling test passed\n");
+
201 return 0;
+
202}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_exited(struct fuse_session *se)
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
uint64_t fuse_ino_t
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + + + +
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2test_2test__syscalls_8c_source.html b/doc/html/fuse-3_818_82_2test_2test__syscalls_8c_source.html new file mode 100644 index 0000000..317270b --- /dev/null +++ b/doc/html/fuse-3_818_82_2test_2test__syscalls_8c_source.html @@ -0,0 +1,2307 @@ + + + + + + + +libfuse: fuse-3.18.2/test/test_syscalls.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_syscalls.c
+
+
+
1#define _GNU_SOURCE
+
2#include "fuse_config.h"
+
3
+
4#include <stdio.h>
+
5#include <stdlib.h>
+
6#include <stdarg.h>
+
7#include <string.h>
+
8#include <unistd.h>
+
9#include <fcntl.h>
+
10#include <dirent.h>
+
11#include <utime.h>
+
12#include <errno.h>
+
13#include <assert.h>
+
14#include <time.h>
+
15#include <sys/socket.h>
+
16#include <sys/types.h>
+
17#include <sys/stat.h>
+
18#include <sys/un.h>
+
19
+
20#ifndef ALLPERMS
+
21# define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)/* 07777 */
+
22#endif
+
23
+
24
+
25static const char *basepath;
+
26static const char *basepath_r;
+
27static char testfile[1024];
+
28static char testfile2[1024];
+
29static char testdir[1024];
+
30static char testdir2[1024];
+
31static char testsock[1024];
+
32static char subfile[1280];
+
33
+
34static char testfile_r[1024];
+
35static char testfile2_r[1024];
+
36static char testdir_r[1024];
+
37static char testdir2_r[1024];
+
38static char subfile_r[1280];
+
39
+
40static char testname[256];
+
41static char testdata[] = "abcdefghijklmnopqrstuvwxyz";
+
42static char testdata2[] = "1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./";
+
43static const char *testdir_files[] = { "f1", "f2", NULL};
+
44static long seekdir_offsets[4];
+
45static char zerodata[4096];
+
46static int testdatalen = sizeof(testdata) - 1;
+
47static int testdata2len = sizeof(testdata2) - 1;
+
48static unsigned int testnum = 0;
+
49static unsigned int select_test = 0;
+
50static unsigned int skip_test = 0;
+
51static unsigned int unlinked_test = 0;
+
52
+
53#define MAX_ENTRIES 1024
+
54#define MAX_TESTS 100
+
55
+
56static struct test {
+
57 int fd;
+
58 struct stat stat;
+
59} tests[MAX_TESTS];
+
60
+
61static void test_perror(const char *func, const char *msg)
+
62{
+
63 fprintf(stderr, "%s %s() - %s: %s\n", testname, func, msg,
+
64 strerror(errno));
+
65}
+
66
+
67static void test_error(const char *func, const char *msg, ...)
+
68 __attribute__ ((format (printf, 2, 3)));
+
69
+
70static void __start_test(const char *fmt, ...)
+
71 __attribute__ ((format (printf, 1, 2)));
+
72
+
73static void test_error(const char *func, const char *msg, ...)
+
74{
+
75 va_list ap;
+
76 fprintf(stderr, "%s %s() - ", testname, func);
+
77 va_start(ap, msg);
+
78 vfprintf(stderr, msg, ap);
+
79 va_end(ap);
+
80 fprintf(stderr, "\n");
+
81}
+
82
+
83static int is_dot_or_dotdot(const char *name) {
+
84 return name[0] == '.' &&
+
85 (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
+
86}
+
87
+
88static void success(void)
+
89{
+
90 fprintf(stderr, "%s OK\n", testname);
+
91}
+
92
+
93#define this_test (&tests[testnum-1])
+
94#define next_test (&tests[testnum])
+
95
+
96static void __start_test(const char *fmt, ...)
+
97{
+
98 unsigned int n;
+
99 va_list ap;
+
100 n = sprintf(testname, "%3i [", testnum);
+
101 va_start(ap, fmt);
+
102 n += vsprintf(testname + n, fmt, ap);
+
103 va_end(ap);
+
104 sprintf(testname + n, "]");
+
105 // Use dedicated testfile per test
+
106 sprintf(testfile, "%s/testfile.%d", basepath, testnum);
+
107 sprintf(testfile_r, "%s/testfile.%d", basepath_r, testnum);
+
108 if (testnum > MAX_TESTS) {
+
109 fprintf(stderr, "%s - too many tests\n", testname);
+
110 exit(1);
+
111 }
+
112 this_test->fd = -1;
+
113}
+
114
+
115#define start_test(msg, args...) { \
+
116 testnum++; \
+
117 if ((select_test && testnum != select_test) || \
+
118 (testnum == skip_test)) { \
+
119 return 0; \
+
120 } \
+
121 __start_test(msg, ##args); \
+
122}
+
123
+
124#define PERROR(msg) test_perror(__FUNCTION__, msg)
+
125#define ERROR(msg, args...) test_error(__FUNCTION__, msg, ##args)
+
126
+
127#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
128
+
129static int st_check_size(struct stat *st, int len)
+
130{
+
131 if (st->st_size != len) {
+
132 ERROR("length %u instead of %u", (int) st->st_size,
+
133 (int) len);
+
134 return -1;
+
135 }
+
136 return 0;
+
137}
+
138
+
139static int check_size(const char *path, int len)
+
140{
+
141 struct stat stbuf;
+
142 int res = stat(path, &stbuf);
+
143 if (res == -1) {
+
144 PERROR("stat");
+
145 return -1;
+
146 }
+
147 return st_check_size(&stbuf, len);
+
148}
+
149
+
150static int check_testfile_size(const char *path, int len)
+
151{
+
152 this_test->stat.st_size = len;
+
153 return check_size(path, len);
+
154}
+
155
+
156static int st_check_type(struct stat *st, mode_t type)
+
157{
+
158 if ((st->st_mode & S_IFMT) != type) {
+
159 ERROR("type 0%o instead of 0%o", st->st_mode & S_IFMT, type);
+
160 return -1;
+
161 }
+
162 return 0;
+
163}
+
164
+
165static int check_type(const char *path, mode_t type)
+
166{
+
167 struct stat stbuf;
+
168 int res = lstat(path, &stbuf);
+
169 if (res == -1) {
+
170 PERROR("lstat");
+
171 return -1;
+
172 }
+
173 return st_check_type(&stbuf, type);
+
174}
+
175
+
176static int st_check_mode(struct stat *st, mode_t mode)
+
177{
+
178 if ((st->st_mode & ALLPERMS) != mode) {
+
179 ERROR("mode 0%o instead of 0%o", st->st_mode & ALLPERMS,
+
180 mode);
+
181 return -1;
+
182 }
+
183 return 0;
+
184}
+
185
+
186static int check_mode(const char *path, mode_t mode)
+
187{
+
188 struct stat stbuf;
+
189 int res = lstat(path, &stbuf);
+
190 if (res == -1) {
+
191 PERROR("lstat");
+
192 return -1;
+
193 }
+
194 return st_check_mode(&stbuf, mode);
+
195}
+
196
+
197static int check_testfile_mode(const char *path, mode_t mode)
+
198{
+
199 this_test->stat.st_mode &= ~ALLPERMS;
+
200 this_test->stat.st_mode |= mode;
+
201 return check_mode(path, mode);
+
202}
+
203
+
204static int check_times(const char *path, time_t atime, time_t mtime)
+
205{
+
206 int err = 0;
+
207 struct stat stbuf;
+
208 int res = lstat(path, &stbuf);
+
209 if (res == -1) {
+
210 PERROR("lstat");
+
211 return -1;
+
212 }
+
213 if (stbuf.st_atime != atime) {
+
214 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
+
215 err--;
+
216 }
+
217 if (stbuf.st_mtime != mtime) {
+
218 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
+
219 err--;
+
220 }
+
221 if (err)
+
222 return -1;
+
223
+
224 return 0;
+
225}
+
226
+
227#if 0
+
228static int fcheck_times(int fd, time_t atime, time_t mtime)
+
229{
+
230 int err = 0;
+
231 struct stat stbuf;
+
232 int res = fstat(fd, &stbuf);
+
233 if (res == -1) {
+
234 PERROR("fstat");
+
235 return -1;
+
236 }
+
237 if (stbuf.st_atime != atime) {
+
238 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
+
239 err--;
+
240 }
+
241 if (stbuf.st_mtime != mtime) {
+
242 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
+
243 err--;
+
244 }
+
245 if (err)
+
246 return -1;
+
247
+
248 return 0;
+
249}
+
250#endif
+
251
+
252static int st_check_nlink(struct stat *st, nlink_t nlink)
+
253{
+
254 if (st->st_nlink != nlink) {
+
255 ERROR("nlink %li instead of %li", (long) st->st_nlink,
+
256 (long) nlink);
+
257 return -1;
+
258 }
+
259 return 0;
+
260}
+
261
+
262static int check_nlink(const char *path, nlink_t nlink)
+
263{
+
264 struct stat stbuf;
+
265 int res = lstat(path, &stbuf);
+
266 if (res == -1) {
+
267 PERROR("lstat");
+
268 return -1;
+
269 }
+
270 return st_check_nlink(&stbuf, nlink);
+
271}
+
272
+
273static int fcheck_stat(int fd, int flags, struct stat *st)
+
274{
+
275 struct stat stbuf;
+
276 int res = fstat(fd, &stbuf);
+
277 if (res == -1) {
+
278 if (flags & O_PATH) {
+
279 // With O_PATH fd, the server does not have to keep
+
280 // the inode alive so FUSE inode may be stale or bad
+
281 if (errno == ESTALE || errno == EIO ||
+
282 errno == ENOENT || errno == EBADF)
+
283 return 0;
+
284 }
+
285 PERROR("fstat");
+
286 return -1;
+
287 }
+
288
+
289 int err = 0;
+
290 err += st_check_type(&stbuf, st->st_mode & S_IFMT);
+
291 err += st_check_mode(&stbuf, st->st_mode & ALLPERMS);
+
292 err += st_check_size(&stbuf, st->st_size);
+
293 err += st_check_nlink(&stbuf, st->st_nlink);
+
294
+
295 return err;
+
296}
+
297
+
298static int check_nonexist(const char *path)
+
299{
+
300 struct stat stbuf;
+
301 int res = lstat(path, &stbuf);
+
302 if (res == 0) {
+
303 ERROR("file should not exist");
+
304 return -1;
+
305 }
+
306 if (errno != ENOENT) {
+
307 ERROR("file should not exist: %s", strerror(errno));
+
308 return -1;
+
309 }
+
310 return 0;
+
311}
+
312
+
313static int check_buffer(const char *buf, const char *data, unsigned len)
+
314{
+
315 if (memcmp(buf, data, len) != 0) {
+
316 ERROR("data mismatch");
+
317 return -1;
+
318 }
+
319 return 0;
+
320}
+
321
+
322static int check_data(const char *path, const char *data, int offset,
+
323 unsigned len)
+
324{
+
325 char buf[4096];
+
326 int res;
+
327 int fd = open(path, O_RDONLY);
+
328 if (fd == -1) {
+
329 PERROR("open");
+
330 return -1;
+
331 }
+
332 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
+
333 PERROR("lseek");
+
334 close(fd);
+
335 return -1;
+
336 }
+
337 while (len) {
+
338 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
+
339 res = read(fd, buf, rdlen);
+
340 if (res == -1) {
+
341 PERROR("read");
+
342 close(fd);
+
343 return -1;
+
344 }
+
345 if (res != rdlen) {
+
346 ERROR("short read: %u instead of %u", res, rdlen);
+
347 close(fd);
+
348 return -1;
+
349 }
+
350 if (check_buffer(buf, data, rdlen) != 0) {
+
351 close(fd);
+
352 return -1;
+
353 }
+
354 data += rdlen;
+
355 len -= rdlen;
+
356 }
+
357 res = close(fd);
+
358 if (res == -1) {
+
359 PERROR("close");
+
360 return -1;
+
361 }
+
362 return 0;
+
363}
+
364
+
365static int fcheck_data(int fd, const char *data, int offset,
+
366 unsigned len)
+
367{
+
368 char buf[4096];
+
369 int res;
+
370 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
+
371 PERROR("lseek");
+
372 return -1;
+
373 }
+
374 while (len) {
+
375 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
+
376 res = read(fd, buf, rdlen);
+
377 if (res == -1) {
+
378 PERROR("read");
+
379 return -1;
+
380 }
+
381 if (res != rdlen) {
+
382 ERROR("short read: %u instead of %u", res, rdlen);
+
383 return -1;
+
384 }
+
385 if (check_buffer(buf, data, rdlen) != 0) {
+
386 return -1;
+
387 }
+
388 data += rdlen;
+
389 len -= rdlen;
+
390 }
+
391 return 0;
+
392}
+
393
+
394static int check_dir_contents(const char *path, const char **contents)
+
395{
+
396 int i;
+
397 int res;
+
398 int err = 0;
+
399 int found[MAX_ENTRIES];
+
400 const char *cont[MAX_ENTRIES];
+
401 DIR *dp;
+
402
+
403 for (i = 0; contents[i]; i++) {
+
404 assert(i < MAX_ENTRIES - 3);
+
405 found[i] = 0;
+
406 cont[i] = contents[i];
+
407 }
+
408 cont[i] = NULL;
+
409
+
410 dp = opendir(path);
+
411 if (dp == NULL) {
+
412 PERROR("opendir");
+
413 return -1;
+
414 }
+
415 memset(found, 0, sizeof(found));
+
416 while(1) {
+
417 struct dirent *de;
+
418 errno = 0;
+
419 de = readdir(dp);
+
420 if (de == NULL) {
+
421 if (errno) {
+
422 PERROR("readdir");
+
423 closedir(dp);
+
424 return -1;
+
425 }
+
426 break;
+
427 }
+
428 if (is_dot_or_dotdot(de->d_name))
+
429 continue;
+
430 for (i = 0; cont[i] != NULL; i++) {
+
431 assert(i < MAX_ENTRIES);
+
432 if (strcmp(cont[i], de->d_name) == 0) {
+
433 if (found[i]) {
+
434 ERROR("duplicate entry <%s>",
+
435 de->d_name);
+
436 err--;
+
437 } else
+
438 found[i] = 1;
+
439 break;
+
440 }
+
441 }
+
442 if (!cont[i]) {
+
443 ERROR("unexpected entry <%s>", de->d_name);
+
444 err --;
+
445 }
+
446 }
+
447 for (i = 0; cont[i] != NULL; i++) {
+
448 if (!found[i]) {
+
449 ERROR("missing entry <%s>", cont[i]);
+
450 err--;
+
451 }
+
452 }
+
453 res = closedir(dp);
+
454 if (res == -1) {
+
455 PERROR("closedir");
+
456 return -1;
+
457 }
+
458 if (err)
+
459 return -1;
+
460
+
461 return 0;
+
462}
+
463
+
464static int create_file(const char *path, const char *data, int len)
+
465{
+
466 int res;
+
467 int fd;
+
468
+
469 unlink(path);
+
470 fd = creat(path, 0644);
+
471 if (fd == -1) {
+
472 PERROR("creat");
+
473 return -1;
+
474 }
+
475 if (len) {
+
476 res = write(fd, data, len);
+
477 if (res == -1) {
+
478 PERROR("write");
+
479 close(fd);
+
480 return -1;
+
481 }
+
482 if (res != len) {
+
483 ERROR("write is short: %u instead of %u", res, len);
+
484 close(fd);
+
485 return -1;
+
486 }
+
487 }
+
488 res = close(fd);
+
489 if (res == -1) {
+
490 PERROR("close");
+
491 return -1;
+
492 }
+
493 res = check_type(path, S_IFREG);
+
494 if (res == -1)
+
495 return -1;
+
496 res = check_mode(path, 0644);
+
497 if (res == -1)
+
498 return -1;
+
499 res = check_nlink(path, 1);
+
500 if (res == -1)
+
501 return -1;
+
502 res = check_size(path, len);
+
503 if (res == -1)
+
504 return -1;
+
505
+
506 if (len) {
+
507 res = check_data(path, data, 0, len);
+
508 if (res == -1)
+
509 return -1;
+
510 }
+
511
+
512 return 0;
+
513}
+
514
+
515static int create_path_fd(const char *path, const char *data, int len)
+
516{
+
517 int path_fd;
+
518 int res;
+
519
+
520 res = create_file(path, data, len);
+
521 if (res == -1)
+
522 return -1;
+
523
+
524 path_fd = open(path, O_PATH);
+
525 if (path_fd == -1)
+
526 PERROR("open(O_PATH)");
+
527
+
528 return path_fd;
+
529}
+
530
+
531// Can be called once per test
+
532static int create_testfile(const char *path, const char *data, int len)
+
533{
+
534 struct test *t = this_test;
+
535 struct stat *st = &t->stat;
+
536 int res, fd;
+
537
+
538 if (t->fd > 0) {
+
539 ERROR("testfile already created");
+
540 return -1;
+
541 }
+
542
+
543 fd = create_path_fd(path, data, len);
+
544 if (fd == -1)
+
545 return -1;
+
546
+
547 t->fd = fd;
+
548
+
549 res = fstat(fd, st);
+
550 if (res == -1) {
+
551 PERROR("fstat");
+
552 return -1;
+
553 }
+
554
+
555 return 0;
+
556}
+
557
+
558static int check_unlinked_testfile(int fd)
+
559{
+
560 struct stat *st = &this_test->stat;
+
561
+
562 st->st_nlink = 0;
+
563 return fcheck_stat(fd, O_PATH, st);
+
564}
+
565
+
566// Check recorded testfiles after all tests completed
+
567static int check_unlinked_testfiles(void)
+
568{
+
569 int fd;
+
570 int res, err = 0;
+
571 int num = testnum;
+
572
+
573 if (!unlinked_test)
+
574 return 0;
+
575
+
576 testnum = 0;
+
577 while (testnum < num) {
+
578 fd = next_test->fd;
+
579 start_test("check_unlinked_testfile");
+
580 if (fd == -1)
+
581 continue;
+
582
+
583 err += check_unlinked_testfile(fd);
+
584 res = close(fd);
+
585 if (res == -1) {
+
586 PERROR("close(test_fd)");
+
587 err--;
+
588 }
+
589 }
+
590
+
591 if (err) {
+
592 fprintf(stderr, "%i unlinked testfile checks failed\n", -err);
+
593 return 1;
+
594 }
+
595
+
596 return err;
+
597}
+
598
+
599static int cleanup_dir(const char *path, const char **dir_files, int quiet)
+
600{
+
601 int i;
+
602 int err = 0;
+
603
+
604 for (i = 0; dir_files[i]; i++) {
+
605 int res;
+
606 char fpath[1280];
+
607 sprintf(fpath, "%s/%s", path, dir_files[i]);
+
608 res = unlink(fpath);
+
609 if (res == -1 && !quiet) {
+
610 PERROR("unlink");
+
611 err --;
+
612 }
+
613 }
+
614 if (err)
+
615 return -1;
+
616
+
617 return 0;
+
618}
+
619
+
620static int create_dir(const char *path, const char **dir_files)
+
621{
+
622 int res;
+
623 int i;
+
624
+
625 rmdir(path);
+
626 res = mkdir(path, 0755);
+
627 if (res == -1) {
+
628 PERROR("mkdir");
+
629 return -1;
+
630 }
+
631 res = check_type(path, S_IFDIR);
+
632 if (res == -1)
+
633 return -1;
+
634 res = check_mode(path, 0755);
+
635 if (res == -1)
+
636 return -1;
+
637
+
638 for (i = 0; dir_files[i]; i++) {
+
639 char fpath[1280];
+
640 sprintf(fpath, "%s/%s", path, dir_files[i]);
+
641 res = create_file(fpath, "", 0);
+
642 if (res == -1) {
+
643 cleanup_dir(path, dir_files, 1);
+
644 return -1;
+
645 }
+
646 }
+
647 res = check_dir_contents(path, dir_files);
+
648 if (res == -1) {
+
649 cleanup_dir(path, dir_files, 1);
+
650 return -1;
+
651 }
+
652
+
653 return 0;
+
654}
+
655
+
656static int test_truncate(int len)
+
657{
+
658 const char *data = testdata;
+
659 int datalen = testdatalen;
+
660 int res;
+
661
+
662 start_test("truncate(%u)", (int) len);
+
663 res = create_testfile(testfile, data, datalen);
+
664 if (res == -1)
+
665 return -1;
+
666
+
667 res = truncate(testfile, len);
+
668 if (res == -1) {
+
669 PERROR("truncate");
+
670 return -1;
+
671 }
+
672 res = check_testfile_size(testfile, len);
+
673 if (res == -1)
+
674 return -1;
+
675
+
676 if (len > 0) {
+
677 if (len <= datalen) {
+
678 res = check_data(testfile, data, 0, len);
+
679 if (res == -1)
+
680 return -1;
+
681 } else {
+
682 res = check_data(testfile, data, 0, datalen);
+
683 if (res == -1)
+
684 return -1;
+
685 res = check_data(testfile, zerodata, datalen,
+
686 len - datalen);
+
687 if (res == -1)
+
688 return -1;
+
689 }
+
690 }
+
691 res = unlink(testfile);
+
692 if (res == -1) {
+
693 PERROR("unlink");
+
694 return -1;
+
695 }
+
696 res = check_nonexist(testfile);
+
697 if (res == -1)
+
698 return -1;
+
699
+
700 success();
+
701 return 0;
+
702}
+
703
+
704static int test_ftruncate(int len, int mode)
+
705{
+
706 const char *data = testdata;
+
707 int datalen = testdatalen;
+
708 int res;
+
709 int fd;
+
710
+
711 start_test("ftruncate(%u) mode: 0%03o", len, mode);
+
712 res = create_testfile(testfile, data, datalen);
+
713 if (res == -1)
+
714 return -1;
+
715
+
716 fd = open(testfile, O_WRONLY);
+
717 if (fd == -1) {
+
718 PERROR("open");
+
719 return -1;
+
720 }
+
721
+
722 res = fchmod(fd, mode);
+
723 if (res == -1) {
+
724 PERROR("fchmod");
+
725 close(fd);
+
726 return -1;
+
727 }
+
728 res = check_testfile_mode(testfile, mode);
+
729 if (res == -1) {
+
730 close(fd);
+
731 return -1;
+
732 }
+
733 res = ftruncate(fd, len);
+
734 if (res == -1) {
+
735 PERROR("ftruncate");
+
736 close(fd);
+
737 return -1;
+
738 }
+
739 close(fd);
+
740 res = check_testfile_size(testfile, len);
+
741 if (res == -1)
+
742 return -1;
+
743
+
744 if (len > 0) {
+
745 if (len <= datalen) {
+
746 res = check_data(testfile, data, 0, len);
+
747 if (res == -1)
+
748 return -1;
+
749 } else {
+
750 res = check_data(testfile, data, 0, datalen);
+
751 if (res == -1)
+
752 return -1;
+
753 res = check_data(testfile, zerodata, datalen,
+
754 len - datalen);
+
755 if (res == -1)
+
756 return -1;
+
757 }
+
758 }
+
759 res = unlink(testfile);
+
760 if (res == -1) {
+
761 PERROR("unlink");
+
762 return -1;
+
763 }
+
764 res = check_nonexist(testfile);
+
765 if (res == -1)
+
766 return -1;
+
767
+
768 success();
+
769 return 0;
+
770}
+
771
+
772static int test_seekdir(void)
+
773{
+
774 int i;
+
775 int res;
+
776 DIR *dp;
+
777 struct dirent *de = NULL;
+
778
+
779 start_test("seekdir");
+
780 res = create_dir(testdir, testdir_files);
+
781 if (res == -1)
+
782 return res;
+
783
+
784 dp = opendir(testdir);
+
785 if (dp == NULL) {
+
786 PERROR("opendir");
+
787 return -1;
+
788 }
+
789
+
790 /* Remember dir offsets */
+
791 for (i = 0; i < ARRAY_SIZE(seekdir_offsets); i++) {
+
792 seekdir_offsets[i] = telldir(dp);
+
793 errno = 0;
+
794 de = readdir(dp);
+
795 if (de == NULL) {
+
796 if (errno) {
+
797 PERROR("readdir");
+
798 goto fail;
+
799 }
+
800 break;
+
801 }
+
802 }
+
803
+
804 /* Walk until the end of directory */
+
805 while (de)
+
806 de = readdir(dp);
+
807
+
808 /* Start from the last valid dir offset and seek backwards */
+
809 for (i--; i >= 0; i--) {
+
810 seekdir(dp, seekdir_offsets[i]);
+
811 de = readdir(dp);
+
812 if (de == NULL) {
+
813 ERROR("Unexpected end of directory after seekdir()");
+
814 goto fail;
+
815 }
+
816 }
+
817
+
818 closedir(dp);
+
819 res = cleanup_dir(testdir, testdir_files, 0);
+
820 if (!res)
+
821 success();
+
822 return res;
+
823fail:
+
824 closedir(dp);
+
825 cleanup_dir(testdir, testdir_files, 1);
+
826 return -1;
+
827}
+
828
+
829#ifdef HAVE_COPY_FILE_RANGE
+
830static int test_copy_file_range(void)
+
831{
+
832 const char *data = testdata;
+
833 int datalen = testdatalen;
+
834 int err = 0;
+
835 int res;
+
836 int fd_in, fd_out;
+
837 off_t pos_in = 0, pos_out = 0;
+
838
+
839 start_test("copy_file_range");
+
840 unlink(testfile);
+
841 fd_in = open(testfile, O_CREAT | O_RDWR, 0644);
+
842 if (fd_in == -1) {
+
843 PERROR("creat");
+
844 return -1;
+
845 }
+
846 res = write(fd_in, data, datalen);
+
847 if (res == -1) {
+
848 PERROR("write");
+
849 close(fd_in);
+
850 return -1;
+
851 }
+
852 if (res != datalen) {
+
853 ERROR("write is short: %u instead of %u", res, datalen);
+
854 close(fd_in);
+
855 return -1;
+
856 }
+
857
+
858 unlink(testfile2);
+
859 fd_out = creat(testfile2, 0644);
+
860 if (fd_out == -1) {
+
861 PERROR("creat");
+
862 close(fd_in);
+
863 return -1;
+
864 }
+
865 res = copy_file_range(fd_in, &pos_in, fd_out, &pos_out, datalen, 0);
+
866 if (res == -1) {
+
867 PERROR("copy_file_range");
+
868 close(fd_in);
+
869 close(fd_out);
+
870 return -1;
+
871 }
+
872 if (res != datalen) {
+
873 ERROR("copy is short: %u instead of %u", res, datalen);
+
874 close(fd_in);
+
875 close(fd_out);
+
876 return -1;
+
877 }
+
878
+
879 res = close(fd_in);
+
880 if (res == -1) {
+
881 PERROR("close");
+
882 close(fd_out);
+
883 return -1;
+
884 }
+
885 res = close(fd_out);
+
886 if (res == -1) {
+
887 PERROR("close");
+
888 return -1;
+
889 }
+
890
+
891 err = check_data(testfile2, data, 0, datalen);
+
892
+
893 res = unlink(testfile);
+
894 if (res == -1) {
+
895 PERROR("unlink");
+
896 return -1;
+
897 }
+
898 res = check_nonexist(testfile);
+
899 if (res == -1)
+
900 return -1;
+
901 if (err)
+
902 return -1;
+
903
+
904 res = unlink(testfile2);
+
905 if (res == -1) {
+
906 PERROR("unlink");
+
907 return -1;
+
908 }
+
909 res = check_nonexist(testfile2);
+
910 if (res == -1)
+
911 return -1;
+
912 if (err)
+
913 return -1;
+
914
+
915 success();
+
916 return 0;
+
917}
+
918#else
+
919static int test_copy_file_range(void)
+
920{
+
921 return 0;
+
922}
+
923#endif
+
924
+
925#ifdef HAVE_STATX
+
926static int test_statx(void)
+
927{
+
928 struct statx sb;
+
929 char msg[] = "hi";
+
930 size_t msg_size = sizeof(msg);
+
931 struct timespec tp;
+
932 int res;
+
933
+
934 memset(&sb, 0, sizeof(sb));
+
935 unlink(testfile);
+
936
+
937 start_test("statx");
+
938
+
939 res = create_testfile(testfile, msg, msg_size);
+
940 if (res == -1)
+
941 return -1;
+
942
+
943 res = statx(-1, testfile, AT_EMPTY_PATH,
+
944 STATX_BASIC_STATS | STATX_BTIME, &sb);
+
945 if (res == -1)
+
946 return -1;
+
947
+
948 if (sb.stx_size != msg_size)
+
949 return -1;
+
950
+
951 clock_gettime(CLOCK_REALTIME, &tp);
+
952
+
953 if (sb.stx_btime.tv_sec > tp.tv_sec)
+
954 return -1;
+
955
+
956 if (sb.stx_btime.tv_sec == tp.tv_sec &&
+
957 sb.stx_btime.tv_nsec >= tp.tv_nsec)
+
958 return -1;
+
959
+
960 unlink(testfile);
+
961
+
962 success();
+
963 return 0;
+
964}
+
965#else
+
966static int test_statx(void)
+
967{
+
968 return 0;
+
969}
+
970#endif
+
971
+
972static int test_utime(void)
+
973{
+
974 struct utimbuf utm;
+
975 time_t atime = 987631200;
+
976 time_t mtime = 123116400;
+
977 int res;
+
978
+
979 start_test("utime");
+
980 res = create_testfile(testfile, NULL, 0);
+
981 if (res == -1)
+
982 return -1;
+
983
+
984 utm.actime = atime;
+
985 utm.modtime = mtime;
+
986 res = utime(testfile, &utm);
+
987 if (res == -1) {
+
988 PERROR("utime");
+
989 return -1;
+
990 }
+
991 res = check_times(testfile, atime, mtime);
+
992 if (res == -1) {
+
993 return -1;
+
994 }
+
995 res = unlink(testfile);
+
996 if (res == -1) {
+
997 PERROR("unlink");
+
998 return -1;
+
999 }
+
1000 res = check_nonexist(testfile);
+
1001 if (res == -1)
+
1002 return -1;
+
1003
+
1004 success();
+
1005 return 0;
+
1006}
+
1007
+
1008static int test_create(void)
+
1009{
+
1010 const char *data = testdata;
+
1011 int datalen = testdatalen;
+
1012 int err = 0;
+
1013 int res;
+
1014 int fd;
+
1015
+
1016 start_test("create");
+
1017 unlink(testfile);
+
1018 fd = creat(testfile, 0644);
+
1019 if (fd == -1) {
+
1020 PERROR("creat");
+
1021 return -1;
+
1022 }
+
1023 res = write(fd, data, datalen);
+
1024 if (res == -1) {
+
1025 PERROR("write");
+
1026 close(fd);
+
1027 return -1;
+
1028 }
+
1029 if (res != datalen) {
+
1030 ERROR("write is short: %u instead of %u", res, datalen);
+
1031 close(fd);
+
1032 return -1;
+
1033 }
+
1034 res = close(fd);
+
1035 if (res == -1) {
+
1036 PERROR("close");
+
1037 return -1;
+
1038 }
+
1039 res = check_type(testfile, S_IFREG);
+
1040 if (res == -1)
+
1041 return -1;
+
1042 err += check_mode(testfile, 0644);
+
1043 err += check_nlink(testfile, 1);
+
1044 err += check_size(testfile, datalen);
+
1045 err += check_data(testfile, data, 0, datalen);
+
1046 res = unlink(testfile);
+
1047 if (res == -1) {
+
1048 PERROR("unlink");
+
1049 return -1;
+
1050 }
+
1051 res = check_nonexist(testfile);
+
1052 if (res == -1)
+
1053 return -1;
+
1054 if (err)
+
1055 return -1;
+
1056
+
1057 success();
+
1058 return 0;
+
1059}
+
1060
+
1061static int test_create_unlink(void)
+
1062{
+
1063 const char *data = testdata;
+
1064 int datalen = testdatalen;
+
1065 int err = 0;
+
1066 int res;
+
1067 int fd;
+
1068
+
1069 start_test("create+unlink");
+
1070 unlink(testfile);
+
1071 fd = open(testfile, O_CREAT | O_RDWR | O_TRUNC, 0644);
+
1072 if (fd == -1) {
+
1073 PERROR("creat");
+
1074 return -1;
+
1075 }
+
1076 res = unlink(testfile);
+
1077 if (res == -1) {
+
1078 PERROR("unlink");
+
1079 close(fd);
+
1080 return -1;
+
1081 }
+
1082 res = check_nonexist(testfile);
+
1083 if (res == -1) {
+
1084 close(fd);
+
1085 return -1;
+
1086 }
+
1087 res = write(fd, data, datalen);
+
1088 if (res == -1) {
+
1089 PERROR("write");
+
1090 close(fd);
+
1091 return -1;
+
1092 }
+
1093 if (res != datalen) {
+
1094 ERROR("write is short: %u instead of %u", res, datalen);
+
1095 close(fd);
+
1096 return -1;
+
1097 }
+
1098 struct stat st = {
+
1099 .st_mode = S_IFREG | 0644,
+
1100 .st_size = datalen,
+
1101 };
+
1102 err = fcheck_stat(fd, O_RDWR, &st);
+
1103 err += fcheck_data(fd, data, 0, datalen);
+
1104 res = close(fd);
+
1105 if (res == -1) {
+
1106 PERROR("close");
+
1107 err--;
+
1108 }
+
1109 if (err)
+
1110 return -1;
+
1111
+
1112 success();
+
1113 return 0;
+
1114}
+
1115
+
1116static int test_mknod(void)
+
1117{
+
1118 int err = 0;
+
1119 int res;
+
1120
+
1121 start_test("mknod");
+
1122 unlink(testfile);
+
1123 res = mknod(testfile, 0644, 0);
+
1124 if (res == -1) {
+
1125 PERROR("mknod");
+
1126 return -1;
+
1127 }
+
1128 res = check_type(testfile, S_IFREG);
+
1129 if (res == -1)
+
1130 return -1;
+
1131 err += check_mode(testfile, 0644);
+
1132 err += check_nlink(testfile, 1);
+
1133 err += check_size(testfile, 0);
+
1134 res = unlink(testfile);
+
1135 if (res == -1) {
+
1136 PERROR("unlink");
+
1137 return -1;
+
1138 }
+
1139 res = check_nonexist(testfile);
+
1140 if (res == -1)
+
1141 return -1;
+
1142 if (err)
+
1143 return -1;
+
1144
+
1145 success();
+
1146 return 0;
+
1147}
+
1148
+
1149#define test_open(exist, flags, mode) do_test_open(exist, flags, #flags, mode)
+
1150
+
1151static int do_test_open(int exist, int flags, const char *flags_str, int mode)
+
1152{
+
1153 char buf[4096];
+
1154 const char *data = testdata;
+
1155 int datalen = testdatalen;
+
1156 unsigned currlen = 0;
+
1157 int err = 0;
+
1158 int res;
+
1159 int fd;
+
1160 off_t off;
+
1161
+
1162 start_test("open(%s, %s, 0%03o)", exist ? "+" : "-", flags_str, mode);
+
1163 unlink(testfile);
+
1164 if (exist) {
+
1165 res = create_file(testfile_r, testdata2, testdata2len);
+
1166 if (res == -1)
+
1167 return -1;
+
1168
+
1169 currlen = testdata2len;
+
1170 }
+
1171
+
1172 fd = open(testfile, flags, mode);
+
1173 if ((flags & O_CREAT) && (flags & O_EXCL) && exist) {
+
1174 if (fd != -1) {
+
1175 ERROR("open should have failed");
+
1176 close(fd);
+
1177 return -1;
+
1178 } else if (errno == EEXIST)
+
1179 goto succ;
+
1180 }
+
1181 if (!(flags & O_CREAT) && !exist) {
+
1182 if (fd != -1) {
+
1183 ERROR("open should have failed");
+
1184 close(fd);
+
1185 return -1;
+
1186 } else if (errno == ENOENT)
+
1187 goto succ;
+
1188 }
+
1189 if (fd == -1) {
+
1190 PERROR("open");
+
1191 return -1;
+
1192 }
+
1193
+
1194 if (flags & O_TRUNC)
+
1195 currlen = 0;
+
1196
+
1197 err += check_type(testfile, S_IFREG);
+
1198 if (exist)
+
1199 err += check_mode(testfile, 0644);
+
1200 else
+
1201 err += check_mode(testfile, mode);
+
1202 err += check_nlink(testfile, 1);
+
1203 err += check_size(testfile, currlen);
+
1204 if (exist && !(flags & O_TRUNC) && (mode & S_IRUSR))
+
1205 err += check_data(testfile, testdata2, 0, testdata2len);
+
1206
+
1207 res = write(fd, data, datalen);
+
1208 if ((flags & O_ACCMODE) != O_RDONLY) {
+
1209 if (res == -1) {
+
1210 PERROR("write");
+
1211 err --;
+
1212 } else if (res != datalen) {
+
1213 ERROR("write is short: %u instead of %u", res, datalen);
+
1214 err --;
+
1215 } else {
+
1216 if (datalen > (int) currlen)
+
1217 currlen = datalen;
+
1218
+
1219 err += check_size(testfile, currlen);
+
1220
+
1221 if (mode & S_IRUSR) {
+
1222 err += check_data(testfile, data, 0, datalen);
+
1223 if (exist && !(flags & O_TRUNC) &&
+
1224 testdata2len > datalen)
+
1225 err += check_data(testfile,
+
1226 testdata2 + datalen,
+
1227 datalen,
+
1228 testdata2len - datalen);
+
1229 }
+
1230 }
+
1231 } else {
+
1232 if (res != -1) {
+
1233 ERROR("write should have failed");
+
1234 err --;
+
1235 } else if (errno != EBADF) {
+
1236 PERROR("write");
+
1237 err --;
+
1238 }
+
1239 }
+
1240 off = lseek(fd, SEEK_SET, 0);
+
1241 if (off == (off_t) -1) {
+
1242 PERROR("lseek");
+
1243 err--;
+
1244 } else if (off != 0) {
+
1245 ERROR("offset should have returned 0");
+
1246 err --;
+
1247 }
+
1248 res = read(fd, buf, sizeof(buf));
+
1249 if ((flags & O_ACCMODE) != O_WRONLY) {
+
1250 if (res == -1) {
+
1251 PERROR("read");
+
1252 err--;
+
1253 } else {
+
1254 int readsize =
+
1255 currlen < sizeof(buf) ? currlen : sizeof(buf);
+
1256 if (res != readsize) {
+
1257 ERROR("read is short: %i instead of %u",
+
1258 res, readsize);
+
1259 err--;
+
1260 } else {
+
1261 if ((flags & O_ACCMODE) != O_RDONLY) {
+
1262 err += check_buffer(buf, data, datalen);
+
1263 if (exist && !(flags & O_TRUNC) &&
+
1264 testdata2len > datalen)
+
1265 err += check_buffer(buf + datalen,
+
1266 testdata2 + datalen,
+
1267 testdata2len - datalen);
+
1268 } else if (exist)
+
1269 err += check_buffer(buf, testdata2,
+
1270 testdata2len);
+
1271 }
+
1272 }
+
1273 } else {
+
1274 if (res != -1) {
+
1275 ERROR("read should have failed");
+
1276 err --;
+
1277 } else if (errno != EBADF) {
+
1278 PERROR("read");
+
1279 err --;
+
1280 }
+
1281 }
+
1282
+
1283 res = close(fd);
+
1284 if (res == -1) {
+
1285 PERROR("close");
+
1286 return -1;
+
1287 }
+
1288 res = unlink(testfile);
+
1289 if (res == -1) {
+
1290 PERROR("unlink");
+
1291 return -1;
+
1292 }
+
1293 res = check_nonexist(testfile);
+
1294 if (res == -1)
+
1295 return -1;
+
1296 res = check_nonexist(testfile_r);
+
1297 if (res == -1)
+
1298 return -1;
+
1299 if (err)
+
1300 return -1;
+
1301
+
1302succ:
+
1303 success();
+
1304 return 0;
+
1305}
+
1306
+
1307#define test_open_acc(flags, mode, err) \
+
1308 do_test_open_acc(flags, #flags, mode, err)
+
1309
+
1310static int do_test_open_acc(int flags, const char *flags_str, int mode, int err)
+
1311{
+
1312 const char *data = testdata;
+
1313 int datalen = testdatalen;
+
1314 int res;
+
1315 int fd;
+
1316
+
1317 start_test("open_acc(%s) mode: 0%03o message: '%s'", flags_str, mode,
+
1318 strerror(err));
+
1319 unlink(testfile);
+
1320 res = create_testfile(testfile, data, datalen);
+
1321 if (res == -1)
+
1322 return -1;
+
1323
+
1324 res = chmod(testfile, mode);
+
1325 if (res == -1) {
+
1326 PERROR("chmod");
+
1327 return -1;
+
1328 }
+
1329
+
1330 res = check_testfile_mode(testfile, mode);
+
1331 if (res == -1)
+
1332 return -1;
+
1333
+
1334 fd = open(testfile, flags);
+
1335 if (fd == -1) {
+
1336 if (err != errno) {
+
1337 PERROR("open");
+
1338 return -1;
+
1339 }
+
1340 } else {
+
1341 if (err) {
+
1342 ERROR("open should have failed");
+
1343 close(fd);
+
1344 return -1;
+
1345 }
+
1346 close(fd);
+
1347 }
+
1348
+
1349 res = unlink(testfile);
+
1350 if (res == -1) {
+
1351 PERROR("unlink");
+
1352 return -1;
+
1353 }
+
1354 res = check_nonexist(testfile);
+
1355 if (res == -1)
+
1356 return -1;
+
1357 res = check_nonexist(testfile_r);
+
1358 if (res == -1)
+
1359 return -1;
+
1360
+
1361 success();
+
1362 return 0;
+
1363}
+
1364
+
1365static int test_symlink(void)
+
1366{
+
1367 char buf[1024];
+
1368 const char *data = testdata;
+
1369 int datalen = testdatalen;
+
1370 int linklen = strlen(testfile);
+
1371 int err = 0;
+
1372 int res;
+
1373
+
1374 start_test("symlink");
+
1375 res = create_testfile(testfile, data, datalen);
+
1376 if (res == -1)
+
1377 return -1;
+
1378
+
1379 unlink(testfile2);
+
1380 res = symlink(testfile, testfile2);
+
1381 if (res == -1) {
+
1382 PERROR("symlink");
+
1383 return -1;
+
1384 }
+
1385 res = check_type(testfile2, S_IFLNK);
+
1386 if (res == -1)
+
1387 return -1;
+
1388 err += check_mode(testfile2, 0777);
+
1389 err += check_nlink(testfile2, 1);
+
1390 res = readlink(testfile2, buf, sizeof(buf));
+
1391 if (res == -1) {
+
1392 PERROR("readlink");
+
1393 err--;
+
1394 }
+
1395 if (res != linklen) {
+
1396 ERROR("short readlink: %u instead of %u", res, linklen);
+
1397 err--;
+
1398 }
+
1399 if (memcmp(buf, testfile, linklen) != 0) {
+
1400 ERROR("link mismatch");
+
1401 err--;
+
1402 }
+
1403 err += check_size(testfile2, datalen);
+
1404 err += check_data(testfile2, data, 0, datalen);
+
1405 res = unlink(testfile2);
+
1406 if (res == -1) {
+
1407 PERROR("unlink");
+
1408 return -1;
+
1409 }
+
1410 res = check_nonexist(testfile2);
+
1411 if (res == -1)
+
1412 return -1;
+
1413 if (err)
+
1414 return -1;
+
1415
+
1416 res = unlink(testfile);
+
1417 if (res == -1) {
+
1418 PERROR("unlink");
+
1419 return -1;
+
1420 }
+
1421 res = check_nonexist(testfile);
+
1422 if (res == -1)
+
1423 return -1;
+
1424
+
1425 success();
+
1426 return 0;
+
1427}
+
1428
+
1429static int test_link(void)
+
1430{
+
1431 const char *data = testdata;
+
1432 int datalen = testdatalen;
+
1433 int err = 0;
+
1434 int res;
+
1435
+
1436 start_test("link");
+
1437 res = create_testfile(testfile, data, datalen);
+
1438 if (res == -1)
+
1439 return -1;
+
1440
+
1441 unlink(testfile2);
+
1442 res = link(testfile, testfile2);
+
1443 if (res == -1) {
+
1444 PERROR("link");
+
1445 return -1;
+
1446 }
+
1447 res = check_type(testfile2, S_IFREG);
+
1448 if (res == -1)
+
1449 return -1;
+
1450 err += check_mode(testfile2, 0644);
+
1451 err += check_nlink(testfile2, 2);
+
1452 err += check_size(testfile2, datalen);
+
1453 err += check_data(testfile2, data, 0, datalen);
+
1454 res = unlink(testfile);
+
1455 if (res == -1) {
+
1456 PERROR("unlink");
+
1457 return -1;
+
1458 }
+
1459 res = check_nonexist(testfile);
+
1460 if (res == -1)
+
1461 return -1;
+
1462
+
1463 err += check_nlink(testfile2, 1);
+
1464 res = unlink(testfile2);
+
1465 if (res == -1) {
+
1466 PERROR("unlink");
+
1467 return -1;
+
1468 }
+
1469 res = check_nonexist(testfile2);
+
1470 if (res == -1)
+
1471 return -1;
+
1472 if (err)
+
1473 return -1;
+
1474
+
1475 success();
+
1476 return 0;
+
1477}
+
1478
+
1479static int test_link2(void)
+
1480{
+
1481 const char *data = testdata;
+
1482 int datalen = testdatalen;
+
1483 int err = 0;
+
1484 int res;
+
1485
+
1486 start_test("link-unlink-link");
+
1487 res = create_testfile(testfile, data, datalen);
+
1488 if (res == -1)
+
1489 return -1;
+
1490
+
1491 unlink(testfile2);
+
1492 res = link(testfile, testfile2);
+
1493 if (res == -1) {
+
1494 PERROR("link");
+
1495 return -1;
+
1496 }
+
1497 res = unlink(testfile);
+
1498 if (res == -1) {
+
1499 PERROR("unlink");
+
1500 return -1;
+
1501 }
+
1502 res = check_nonexist(testfile);
+
1503 if (res == -1)
+
1504 return -1;
+
1505 res = link(testfile2, testfile);
+
1506 if (res == -1) {
+
1507 PERROR("link");
+
1508 }
+
1509 res = check_type(testfile, S_IFREG);
+
1510 if (res == -1)
+
1511 return -1;
+
1512 err += check_mode(testfile, 0644);
+
1513 err += check_nlink(testfile, 2);
+
1514 err += check_size(testfile, datalen);
+
1515 err += check_data(testfile, data, 0, datalen);
+
1516
+
1517 res = unlink(testfile2);
+
1518 if (res == -1) {
+
1519 PERROR("unlink");
+
1520 return -1;
+
1521 }
+
1522 err += check_nlink(testfile, 1);
+
1523 res = unlink(testfile);
+
1524 if (res == -1) {
+
1525 PERROR("unlink");
+
1526 return -1;
+
1527 }
+
1528 res = check_nonexist(testfile);
+
1529 if (res == -1)
+
1530 return -1;
+
1531 if (err)
+
1532 return -1;
+
1533
+
1534 success();
+
1535 return 0;
+
1536}
+
1537
+
1538static int test_rename_file(void)
+
1539{
+
1540 const char *data = testdata;
+
1541 int datalen = testdatalen;
+
1542 int err = 0;
+
1543 int res;
+
1544
+
1545 start_test("rename file");
+
1546 res = create_testfile(testfile, data, datalen);
+
1547 if (res == -1)
+
1548 return -1;
+
1549
+
1550 unlink(testfile2);
+
1551 res = rename(testfile, testfile2);
+
1552 if (res == -1) {
+
1553 PERROR("rename");
+
1554 return -1;
+
1555 }
+
1556 res = check_nonexist(testfile);
+
1557 if (res == -1)
+
1558 return -1;
+
1559 res = check_type(testfile2, S_IFREG);
+
1560 if (res == -1)
+
1561 return -1;
+
1562 err += check_mode(testfile2, 0644);
+
1563 err += check_nlink(testfile2, 1);
+
1564 err += check_size(testfile2, datalen);
+
1565 err += check_data(testfile2, data, 0, datalen);
+
1566 res = unlink(testfile2);
+
1567 if (res == -1) {
+
1568 PERROR("unlink");
+
1569 return -1;
+
1570 }
+
1571 res = check_nonexist(testfile2);
+
1572 if (res == -1)
+
1573 return -1;
+
1574 if (err)
+
1575 return -1;
+
1576
+
1577 success();
+
1578 return 0;
+
1579}
+
1580
+
1581static int test_rename_dir(void)
+
1582{
+
1583 int err = 0;
+
1584 int res;
+
1585
+
1586 start_test("rename dir");
+
1587 res = create_dir(testdir, testdir_files);
+
1588 if (res == -1)
+
1589 return -1;
+
1590
+
1591 rmdir(testdir2);
+
1592 res = rename(testdir, testdir2);
+
1593 if (res == -1) {
+
1594 PERROR("rename");
+
1595 cleanup_dir(testdir, testdir_files, 1);
+
1596 return -1;
+
1597 }
+
1598 res = check_nonexist(testdir);
+
1599 if (res == -1) {
+
1600 cleanup_dir(testdir, testdir_files, 1);
+
1601 return -1;
+
1602 }
+
1603 res = check_type(testdir2, S_IFDIR);
+
1604 if (res == -1) {
+
1605 cleanup_dir(testdir2, testdir_files, 1);
+
1606 return -1;
+
1607 }
+
1608 err += check_mode(testdir2, 0755);
+
1609 err += check_dir_contents(testdir2, testdir_files);
+
1610 err += cleanup_dir(testdir2, testdir_files, 0);
+
1611 res = rmdir(testdir2);
+
1612 if (res == -1) {
+
1613 PERROR("rmdir");
+
1614 return -1;
+
1615 }
+
1616 res = check_nonexist(testdir2);
+
1617 if (res == -1)
+
1618 return -1;
+
1619 if (err)
+
1620 return -1;
+
1621
+
1622 success();
+
1623 return 0;
+
1624}
+
1625
+
1626static int test_rename_dir_loop(void)
+
1627{
+
1628#define PATH(p) (snprintf(path, sizeof path, "%s/%s", testdir, p), path)
+
1629#define PATH2(p) (snprintf(path2, sizeof path2, "%s/%s", testdir, p), path2)
+
1630
+
1631 char path[1280], path2[1280];
+
1632 int err = 0;
+
1633 int res;
+
1634
+
1635 start_test("rename dir loop");
+
1636
+
1637 res = create_dir(testdir, testdir_files);
+
1638 if (res == -1)
+
1639 return -1;
+
1640
+
1641 res = mkdir(PATH("a"), 0755);
+
1642 if (res == -1) {
+
1643 PERROR("mkdir");
+
1644 goto fail;
+
1645 }
+
1646
+
1647 res = rename(PATH("a"), PATH2("a"));
+
1648 if (res == -1) {
+
1649 PERROR("rename");
+
1650 goto fail;
+
1651 }
+
1652
+
1653 errno = 0;
+
1654 res = rename(PATH("a"), PATH2("a/b"));
+
1655 if (res == 0 || errno != EINVAL) {
+
1656 PERROR("rename");
+
1657 goto fail;
+
1658 }
+
1659
+
1660 res = mkdir(PATH("a/b"), 0755);
+
1661 if (res == -1) {
+
1662 PERROR("mkdir");
+
1663 goto fail;
+
1664 }
+
1665
+
1666 res = mkdir(PATH("a/b/c"), 0755);
+
1667 if (res == -1) {
+
1668 PERROR("mkdir");
+
1669 goto fail;
+
1670 }
+
1671
+
1672 errno = 0;
+
1673 res = rename(PATH("a"), PATH2("a/b/c"));
+
1674 if (res == 0 || errno != EINVAL) {
+
1675 PERROR("rename");
+
1676 goto fail;
+
1677 }
+
1678
+
1679 errno = 0;
+
1680 res = rename(PATH("a"), PATH2("a/b/c/a"));
+
1681 if (res == 0 || errno != EINVAL) {
+
1682 PERROR("rename");
+
1683 goto fail;
+
1684 }
+
1685
+
1686 errno = 0;
+
1687 res = rename(PATH("a/b/c"), PATH2("a"));
+
1688 if (res == 0 || errno != ENOTEMPTY) {
+
1689 PERROR("rename");
+
1690 goto fail;
+
1691 }
+
1692
+
1693 res = open(PATH("a/foo"), O_CREAT, 0644);
+
1694 if (res == -1) {
+
1695 PERROR("open");
+
1696 goto fail;
+
1697 }
+
1698 close(res);
+
1699
+
1700 res = rename(PATH("a/foo"), PATH2("a/bar"));
+
1701 if (res == -1) {
+
1702 PERROR("rename");
+
1703 goto fail;
+
1704 }
+
1705
+
1706 res = rename(PATH("a/bar"), PATH2("a/foo"));
+
1707 if (res == -1) {
+
1708 PERROR("rename");
+
1709 goto fail;
+
1710 }
+
1711
+
1712 res = rename(PATH("a/foo"), PATH2("a/b/bar"));
+
1713 if (res == -1) {
+
1714 PERROR("rename");
+
1715 goto fail;
+
1716 }
+
1717
+
1718 res = rename(PATH("a/b/bar"), PATH2("a/foo"));
+
1719 if (res == -1) {
+
1720 PERROR("rename");
+
1721 goto fail;
+
1722 }
+
1723
+
1724 res = rename(PATH("a/foo"), PATH2("a/b/c/bar"));
+
1725 if (res == -1) {
+
1726 PERROR("rename");
+
1727 goto fail;
+
1728 }
+
1729
+
1730 res = rename(PATH("a/b/c/bar"), PATH2("a/foo"));
+
1731 if (res == -1) {
+
1732 PERROR("rename");
+
1733 goto fail;
+
1734 }
+
1735
+
1736 res = open(PATH("a/bar"), O_CREAT, 0644);
+
1737 if (res == -1) {
+
1738 PERROR("open");
+
1739 goto fail;
+
1740 }
+
1741 close(res);
+
1742
+
1743 res = rename(PATH("a/foo"), PATH2("a/bar"));
+
1744 if (res == -1) {
+
1745 PERROR("rename");
+
1746 goto fail;
+
1747 }
+
1748
+
1749 unlink(PATH("a/bar"));
+
1750
+
1751 res = rename(PATH("a/b"), PATH2("a/d"));
+
1752 if (res == -1) {
+
1753 PERROR("rename");
+
1754 goto fail;
+
1755 }
+
1756
+
1757 res = rename(PATH("a/d"), PATH2("a/b"));
+
1758 if (res == -1) {
+
1759 PERROR("rename");
+
1760 goto fail;
+
1761 }
+
1762
+
1763 res = mkdir(PATH("a/d"), 0755);
+
1764 if (res == -1) {
+
1765 PERROR("mkdir");
+
1766 goto fail;
+
1767 }
+
1768
+
1769 res = rename(PATH("a/b"), PATH2("a/d"));
+
1770 if (res == -1) {
+
1771 PERROR("rename");
+
1772 goto fail;
+
1773 }
+
1774
+
1775 res = rename(PATH("a/d"), PATH2("a/b"));
+
1776 if (res == -1) {
+
1777 PERROR("rename");
+
1778 goto fail;
+
1779 }
+
1780
+
1781 res = mkdir(PATH("a/d"), 0755);
+
1782 if (res == -1) {
+
1783 PERROR("mkdir");
+
1784 goto fail;
+
1785 }
+
1786
+
1787 res = mkdir(PATH("a/d/e"), 0755);
+
1788 if (res == -1) {
+
1789 PERROR("mkdir");
+
1790 goto fail;
+
1791 }
+
1792
+
1793 errno = 0;
+
1794 res = rename(PATH("a/b"), PATH2("a/d"));
+
1795 if (res == 0 || (errno != ENOTEMPTY && errno != EEXIST)) {
+
1796 PERROR("rename");
+
1797 goto fail;
+
1798 }
+
1799
+
1800 rmdir(PATH("a/d/e"));
+
1801 rmdir(PATH("a/d"));
+
1802
+
1803 rmdir(PATH("a/b/c"));
+
1804 rmdir(PATH("a/b"));
+
1805 rmdir(PATH("a"));
+
1806
+
1807 err += cleanup_dir(testdir, testdir_files, 0);
+
1808 res = rmdir(testdir);
+
1809 if (res == -1) {
+
1810 PERROR("rmdir");
+
1811 goto fail;
+
1812 }
+
1813 res = check_nonexist(testdir);
+
1814 if (res == -1)
+
1815 return -1;
+
1816 if (err)
+
1817 return -1;
+
1818
+
1819 success();
+
1820 return 0;
+
1821
+
1822fail:
+
1823 unlink(PATH("a/bar"));
+
1824
+
1825 rmdir(PATH("a/d/e"));
+
1826 rmdir(PATH("a/d"));
+
1827
+
1828 rmdir(PATH("a/b/c"));
+
1829 rmdir(PATH("a/b"));
+
1830 rmdir(PATH("a"));
+
1831
+
1832 cleanup_dir(testdir, testdir_files, 1);
+
1833 rmdir(testdir);
+
1834
+
1835 return -1;
+
1836
+
1837#undef PATH2
+
1838#undef PATH
+
1839}
+
1840
+
1841static int test_mkfifo(void)
+
1842{
+
1843 int res;
+
1844 int err = 0;
+
1845
+
1846 start_test("mkfifo");
+
1847 unlink(testfile);
+
1848 res = mkfifo(testfile, 0644);
+
1849 if (res == -1) {
+
1850 PERROR("mkfifo");
+
1851 return -1;
+
1852 }
+
1853 res = check_type(testfile, S_IFIFO);
+
1854 if (res == -1)
+
1855 return -1;
+
1856 err += check_mode(testfile, 0644);
+
1857 err += check_nlink(testfile, 1);
+
1858 res = unlink(testfile);
+
1859 if (res == -1) {
+
1860 PERROR("unlink");
+
1861 return -1;
+
1862 }
+
1863 res = check_nonexist(testfile);
+
1864 if (res == -1)
+
1865 return -1;
+
1866 if (err)
+
1867 return -1;
+
1868
+
1869 success();
+
1870 return 0;
+
1871}
+
1872
+
1873static int test_mkdir(void)
+
1874{
+
1875 int res;
+
1876 int err = 0;
+
1877 const char *dir_contents[] = {NULL};
+
1878
+
1879 start_test("mkdir");
+
1880 rmdir(testdir);
+
1881 res = mkdir(testdir, 0755);
+
1882 if (res == -1) {
+
1883 PERROR("mkdir");
+
1884 return -1;
+
1885 }
+
1886 res = check_type(testdir, S_IFDIR);
+
1887 if (res == -1)
+
1888 return -1;
+
1889 err += check_mode(testdir, 0755);
+
1890 /* Some file systems (like btrfs) don't track link
+
1891 count for directories */
+
1892 //err += check_nlink(testdir, 2);
+
1893 err += check_dir_contents(testdir, dir_contents);
+
1894 res = rmdir(testdir);
+
1895 if (res == -1) {
+
1896 PERROR("rmdir");
+
1897 return -1;
+
1898 }
+
1899 res = check_nonexist(testdir);
+
1900 if (res == -1)
+
1901 return -1;
+
1902 if (err)
+
1903 return -1;
+
1904
+
1905 success();
+
1906 return 0;
+
1907}
+
1908
+
1909static int test_socket(void)
+
1910{
+
1911 struct sockaddr_un su;
+
1912 int fd;
+
1913 int res;
+
1914 int err = 0;
+
1915 const size_t test_sock_len = strlen(testsock) + 1;
+
1916
+
1917 start_test("socket");
+
1918 if (test_sock_len > sizeof(su.sun_path)) {
+
1919 fprintf(stderr, "Need to shorten mount point by %zu chars\n",
+
1920 strlen(testsock) + 1 - sizeof(su.sun_path));
+
1921 return -1;
+
1922 }
+
1923 unlink(testsock);
+
1924 fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
1925 if (fd < 0) {
+
1926 PERROR("socket");
+
1927 return -1;
+
1928 }
+
1929 su.sun_family = AF_UNIX;
+
1930
+
1931 strncpy(su.sun_path, testsock, test_sock_len);
+
1932 su.sun_path[sizeof(su.sun_path) - 1] = '\0';
+
1933 res = bind(fd, (struct sockaddr*)&su, sizeof(su));
+
1934 if (res == -1) {
+
1935 PERROR("bind");
+
1936 return -1;
+
1937 }
+
1938
+
1939 res = check_type(testsock, S_IFSOCK);
+
1940 if (res == -1) {
+
1941 close(fd);
+
1942 return -1;
+
1943 }
+
1944 err += check_nlink(testsock, 1);
+
1945 close(fd);
+
1946 res = unlink(testsock);
+
1947 if (res == -1) {
+
1948 PERROR("unlink");
+
1949 return -1;
+
1950 }
+
1951 res = check_nonexist(testsock);
+
1952 if (res == -1)
+
1953 return -1;
+
1954 if (err)
+
1955 return -1;
+
1956
+
1957 success();
+
1958 return 0;
+
1959}
+
1960
+
1961#define test_create_ro_dir(flags) \
+
1962 do_test_create_ro_dir(flags, #flags)
+
1963
+
1964static int do_test_create_ro_dir(int flags, const char *flags_str)
+
1965{
+
1966 int res;
+
1967 int err = 0;
+
1968 int fd;
+
1969
+
1970 start_test("open(%s) in read-only directory", flags_str);
+
1971 rmdir(testdir);
+
1972 res = mkdir(testdir, 0555);
+
1973 if (res == -1) {
+
1974 PERROR("mkdir");
+
1975 return -1;
+
1976 }
+
1977 fd = open(subfile, flags, 0644);
+
1978 if (fd != -1) {
+
1979 close(fd);
+
1980 unlink(subfile);
+
1981 ERROR("open should have failed");
+
1982 err--;
+
1983 } else {
+
1984 res = check_nonexist(subfile);
+
1985 if (res == -1)
+
1986 err--;
+
1987 }
+
1988 unlink(subfile);
+
1989 res = rmdir(testdir);
+
1990 if (res == -1) {
+
1991 PERROR("rmdir");
+
1992 return -1;
+
1993 }
+
1994 res = check_nonexist(testdir);
+
1995 if (res == -1)
+
1996 return -1;
+
1997 if (err)
+
1998 return -1;
+
1999
+
2000 success();
+
2001 return 0;
+
2002}
+
2003
+
2004#ifndef __FreeBSD__
+
2005/* this tests open with O_TMPFILE
+
2006 note that this will only work with the fuse low level api
+
2007 you will get ENOTSUP with the high level api */
+
2008static int test_create_tmpfile(void)
+
2009{
+
2010 rmdir(testdir);
+
2011 int res = mkdir(testdir, 0777);
+
2012 if (res)
+
2013 return -1;
+
2014
+
2015 start_test("create tmpfile");
+
2016
+
2017 int fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
+
2018 if(fd == -1) {
+
2019 if (errno == ENOTSUP) {
+
2020 /* don't bother if we're working on an old kernel
+
2021 or on the high level API */
+
2022 return 0;
+
2023 }
+
2024
+
2025 PERROR("open O_TMPFILE | O_RDWR");
+
2026 return -1;
+
2027 }
+
2028 close(fd);
+
2029
+
2030 fd = open(testdir, O_TMPFILE | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
+
2031 if(fd == -1){
+
2032 PERROR("open with O_TMPFILE | O_WRONLY | O_EXCL");
+
2033 return -1;
+
2034 };
+
2035 close(fd);
+
2036
+
2037 fd = open(testdir, O_TMPFILE | O_RDONLY, S_IRUSR);
+
2038 if (fd != -1) {
+
2039 ERROR("open with O_TMPFILE | O_RDONLY succeeded");
+
2040 return -1;
+
2041 }
+
2042
+
2043 success();
+
2044 return 0;
+
2045}
+
2046
+
2047static int test_create_and_link_tmpfile(void)
+
2048{
+
2049 /* skip this test for now since the github runner will fail in the linkat call below */
+
2050 return 0;
+
2051
+
2052 rmdir(testdir);
+
2053 unlink(testfile);
+
2054
+
2055 int res = mkdir(testdir, 0777);
+
2056 if (res)
+
2057 return -1;
+
2058
+
2059 start_test("create and link tmpfile");
+
2060
+
2061 int fd = open(testdir, O_TMPFILE | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR);
+
2062 if(fd == -1) {
+
2063 if (errno == ENOTSUP) {
+
2064 /* don't bother if we're working on an old kernel
+
2065 or on the high level API */
+
2066 return 0;
+
2067 }
+
2068 PERROR("open with O_TMPFILE | O_RDWR | O_EXCL");
+
2069 return -1;
+
2070 }
+
2071
+
2072 if (!linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
+
2073 ERROR("linkat succeeded on a tmpfile opened with O_EXCL");
+
2074 return -1;
+
2075 }
+
2076 close(fd);
+
2077
+
2078 fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
+
2079 if(fd == -1) {
+
2080 PERROR("open O_TMPFILE");
+
2081 return -1;
+
2082 }
+
2083
+
2084 if (check_nonexist(testfile)) {
+
2085 return -1;
+
2086 }
+
2087
+
2088 if (linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
+
2089 PERROR("linkat tempfile");
+
2090 return -1;
+
2091 }
+
2092 close(fd);
+
2093
+
2094 if (check_nlink(testfile, 1)) {
+
2095 return -1;
+
2096 }
+
2097 unlink(testfile);
+
2098
+
2099 success();
+
2100 return 0;
+
2101}
+
2102#endif
+
2103
+
2104int main(int argc, char *argv[])
+
2105{
+
2106 int err = 0;
+
2107 int a;
+
2108 int is_root;
+
2109
+
2110 umask(0);
+
2111 if (argc < 2 || argc > 4) {
+
2112 fprintf(stderr, "usage: %s testdir [:realdir] [[-]test#] [-u]\n", argv[0]);
+
2113 return 1;
+
2114 }
+
2115 basepath = argv[1];
+
2116 basepath_r = basepath;
+
2117 for (a = 2; a < argc; a++) {
+
2118 char *endptr;
+
2119 char *arg = argv[a];
+
2120 if (arg[0] == ':') {
+
2121 basepath_r = arg + 1;
+
2122 } else {
+
2123 if (arg[0] == '-') {
+
2124 arg++;
+
2125 if (arg[0] == 'u') {
+
2126 unlinked_test = 1;
+
2127 endptr = arg + 1;
+
2128 } else {
+
2129 skip_test = strtoul(arg, &endptr, 10);
+
2130 }
+
2131 } else {
+
2132 select_test = strtoul(arg, &endptr, 10);
+
2133 }
+
2134 if (arg[0] == '\0' || *endptr != '\0') {
+
2135 fprintf(stderr, "invalid option: '%s'\n", argv[a]);
+
2136 return 1;
+
2137 }
+
2138 }
+
2139 }
+
2140 assert(strlen(basepath) < 512);
+
2141 assert(strlen(basepath_r) < 512);
+
2142 if (basepath[0] != '/') {
+
2143 fprintf(stderr, "testdir must be an absolute path\n");
+
2144 return 1;
+
2145 }
+
2146
+
2147 sprintf(testfile, "%s/testfile", basepath);
+
2148 sprintf(testfile2, "%s/testfile2", basepath);
+
2149 sprintf(testdir, "%s/testdir", basepath);
+
2150 sprintf(testdir2, "%s/testdir2", basepath);
+
2151 sprintf(subfile, "%s/subfile", testdir2);
+
2152 sprintf(testsock, "%s/testsock", basepath);
+
2153
+
2154 sprintf(testfile_r, "%s/testfile", basepath_r);
+
2155 sprintf(testfile2_r, "%s/testfile2", basepath_r);
+
2156 sprintf(testdir_r, "%s/testdir", basepath_r);
+
2157 sprintf(testdir2_r, "%s/testdir2", basepath_r);
+
2158 sprintf(subfile_r, "%s/subfile", testdir2_r);
+
2159
+
2160 is_root = (geteuid() == 0);
+
2161
+
2162 err += test_create();
+
2163 err += test_create_unlink();
+
2164 err += test_symlink();
+
2165 err += test_link();
+
2166 err += test_link2();
+
2167 err += test_mknod();
+
2168 err += test_mkfifo();
+
2169 err += test_mkdir();
+
2170 err += test_rename_file();
+
2171 err += test_rename_dir();
+
2172 err += test_rename_dir_loop();
+
2173 err += test_seekdir();
+
2174 err += test_socket();
+
2175 err += test_utime();
+
2176 err += test_truncate(0);
+
2177 err += test_truncate(testdatalen / 2);
+
2178 err += test_truncate(testdatalen);
+
2179 err += test_truncate(testdatalen + 100);
+
2180 err += test_ftruncate(0, 0600);
+
2181 err += test_ftruncate(testdatalen / 2, 0600);
+
2182 err += test_ftruncate(testdatalen, 0600);
+
2183 err += test_ftruncate(testdatalen + 100, 0600);
+
2184 err += test_ftruncate(0, 0400);
+
2185 err += test_ftruncate(0, 0200);
+
2186 err += test_ftruncate(0, 0000);
+
2187 err += test_open(0, O_RDONLY, 0);
+
2188 err += test_open(1, O_RDONLY, 0);
+
2189 err += test_open(1, O_RDWR, 0);
+
2190 err += test_open(1, O_WRONLY, 0);
+
2191 err += test_open(0, O_RDWR | O_CREAT, 0600);
+
2192 err += test_open(1, O_RDWR | O_CREAT, 0600);
+
2193 err += test_open(0, O_RDWR | O_CREAT | O_TRUNC, 0600);
+
2194 err += test_open(1, O_RDWR | O_CREAT | O_TRUNC, 0600);
+
2195 err += test_open(0, O_RDONLY | O_CREAT, 0600);
+
2196 err += test_open(0, O_RDONLY | O_CREAT, 0400);
+
2197 err += test_open(0, O_RDONLY | O_CREAT, 0200);
+
2198 err += test_open(0, O_RDONLY | O_CREAT, 0000);
+
2199 err += test_open(0, O_WRONLY | O_CREAT, 0600);
+
2200 err += test_open(0, O_WRONLY | O_CREAT, 0400);
+
2201 err += test_open(0, O_WRONLY | O_CREAT, 0200);
+
2202 err += test_open(0, O_WRONLY | O_CREAT, 0000);
+
2203 err += test_open(0, O_RDWR | O_CREAT, 0400);
+
2204 err += test_open(0, O_RDWR | O_CREAT, 0200);
+
2205 err += test_open(0, O_RDWR | O_CREAT, 0000);
+
2206 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0600);
+
2207 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0600);
+
2208 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0000);
+
2209 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0000);
+
2210 err += test_open_acc(O_RDONLY, 0600, 0);
+
2211 err += test_open_acc(O_WRONLY, 0600, 0);
+
2212 err += test_open_acc(O_RDWR, 0600, 0);
+
2213 err += test_open_acc(O_RDONLY, 0400, 0);
+
2214 err += test_open_acc(O_WRONLY, 0200, 0);
+
2215 if(!is_root) {
+
2216 err += test_open_acc(O_RDONLY | O_TRUNC, 0400, EACCES);
+
2217 err += test_open_acc(O_WRONLY, 0400, EACCES);
+
2218 err += test_open_acc(O_RDWR, 0400, EACCES);
+
2219 err += test_open_acc(O_RDONLY, 0200, EACCES);
+
2220 err += test_open_acc(O_RDWR, 0200, EACCES);
+
2221 err += test_open_acc(O_RDONLY, 0000, EACCES);
+
2222 err += test_open_acc(O_WRONLY, 0000, EACCES);
+
2223 err += test_open_acc(O_RDWR, 0000, EACCES);
+
2224 }
+
2225 err += test_create_ro_dir(O_CREAT);
+
2226 err += test_create_ro_dir(O_CREAT | O_EXCL);
+
2227 err += test_create_ro_dir(O_CREAT | O_WRONLY);
+
2228 err += test_create_ro_dir(O_CREAT | O_TRUNC);
+
2229 err += test_copy_file_range();
+
2230 err += test_statx();
+
2231#ifndef __FreeBSD__
+
2232 err += test_create_tmpfile();
+
2233 err += test_create_and_link_tmpfile();
+
2234#endif
+
2235
+
2236 unlink(testfile2);
+
2237 unlink(testsock);
+
2238 rmdir(testdir);
+
2239 rmdir(testdir2);
+
2240
+
2241 if (err) {
+
2242 fprintf(stderr, "%i tests failed\n", -err);
+
2243 return 1;
+
2244 }
+
2245
+
2246 return check_unlinked_testfiles();
+
2247}
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2test_2test__want__conversion_8c_source.html b/doc/html/fuse-3_818_82_2test_2test__want__conversion_8c_source.html new file mode 100644 index 0000000..02798fd --- /dev/null +++ b/doc/html/fuse-3_818_82_2test_2test__want__conversion_8c_source.html @@ -0,0 +1,264 @@ + + + + + + + +libfuse: fuse-3.18.2/test/test_want_conversion.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_want_conversion.c
+
+
+
1#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17)
+
2
+
3#include "util.h"
+
4#include "fuse_i.h"
+
5#include "fuse_lowlevel.h"
+
6#include <stdio.h>
+
7#include <assert.h>
+
8#include <inttypes.h>
+
9#include <stdbool.h>
+
10#include <err.h>
+
11
+
12static void print_conn_info(const char *prefix, struct fuse_conn_info *conn)
+
13{
+
14 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
15
+
16 printf("%s: want=0x%" PRIx32 " want_ext=0x%" PRIx64
+
17 " want_default=0x%" PRIx32 " want_ext_default=0x%" PRIx64 "\n",
+
18 prefix, conn->want, conn->want_ext, se->conn_want,
+
19 se->conn_want_ext);
+
20}
+
21
+
22static void application_init_old_style(struct fuse_conn_info *conn)
+
23{
+
24 /* Simulate application init the old style */
+ +
26 conn->want &= ~FUSE_CAP_SPLICE_READ;
+
27
+
28 /*
+
29 * Also use new style API, as that might happen through
+
30 * fuse_apply_conn_info_opts()
+
31 */
+ +
33}
+
34
+
35static void application_init_new_style(struct fuse_conn_info *conn)
+
36{
+
37 /* Simulate application init the new style */
+ + + +
41}
+
42
+
43static void test_fuse_fs_init(struct fuse_conn_info *conn, bool new_style)
+
44{
+
45 /* High-level init */
+ +
47
+
48 if (new_style)
+
49 application_init_new_style(conn);
+
50 else
+
51 application_init_old_style(conn);
+
52}
+
53
+
54static void test_do_init(struct fuse_conn_info *conn, bool new_style)
+
55{
+
56 /* Initial setup */
+ + + + +
61 conn->capable = fuse_lower_32_bits(conn->capable_ext);
+
62
+ + + +
66
+
67 print_conn_info("Initial state", conn);
+
68
+
69 int rc;
+
70
+
71 test_fuse_fs_init(conn, new_style);
+
72 print_conn_info("After init", conn);
+
73
+ +
75 assert(rc == 0);
+
76
+
77 /* Verify all expected flags are set */
+
78 assert(!(conn->want_ext & FUSE_CAP_SPLICE_READ));
+
79 assert(conn->want_ext & FUSE_CAP_SPLICE_WRITE);
+
80 assert(conn->want_ext & FUSE_CAP_SPLICE_MOVE);
+
81 assert(conn->want_ext & FUSE_CAP_EXPORT_SUPPORT);
+
82 assert(conn->want_ext & FUSE_CAP_ASYNC_READ);
+
83 assert(conn->want_ext & FUSE_CAP_IOCTL_DIR);
+
84
+
85 /* Verify no other flags are set */
+
86 assert(conn->want_ext ==
+ + + +
90
+
91 print_conn_info("After init", conn);
+
92}
+
93
+
94static void test_want_conversion_basic(void)
+
95{
+
96 const struct fuse_lowlevel_ops ops = { 0 };
+
97 struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+
98 struct fuse_session *se;
+
99 struct fuse_conn_info *conn;
+
100
+
101 /* Add the program name to arg[0] */
+
102 if (fuse_opt_add_arg(&args, "test_signals")) {
+
103 fprintf(stderr, "Failed to add argument\n");
+
104 errx(1, "Failed to add argument");
+
105 }
+
106
+
107
+
108 se = fuse_session_new(&args, &ops, sizeof(ops), NULL);
+
109 assert(se);
+
110 conn = &se->conn;
+
111 printf("\nTesting basic want conversion, old style:\n");
+
112 test_do_init(conn, false);
+ +
114
+
115 se = fuse_session_new(&args, &ops, sizeof(ops), NULL);
+
116 assert(se);
+
117 conn = &se->conn;
+
118 printf("\nTesting basic want conversion, new style:\n");
+
119 test_do_init(conn, true);
+
120 print_conn_info("After init", conn);
+ +
122
+
123 fuse_opt_free_args(&args);
+
124
+
125}
+
126
+
127static void test_want_conversion_conflict(void)
+
128{
+
129 struct fuse_conn_info conn = { 0 };
+
130 int rc;
+
131
+
132 printf("\nTesting want conversion conflict:\n");
+
133
+
134 /* Test conflicting values */
+
135 /* Initialize like fuse_lowlevel.c does */
+ + + +
139 conn.capable = fuse_lower_32_bits(conn.capable_ext);
+
140 conn.want_ext = conn.capable_ext;
+
141 conn.want = fuse_lower_32_bits(conn.want_ext);
+
142 print_conn_info("Test conflict initial", &conn);
+
143
+
144 /* Simulate application init modifying capabilities */
+
145 conn.want_ext |= FUSE_CAP_ATOMIC_O_TRUNC; /* Add new capability */
+
146 conn.want &= ~FUSE_CAP_SPLICE_READ; /* Remove a capability */
+
147
+ +
149 assert(rc == -EINVAL);
+
150 print_conn_info("Test conflict after", &conn);
+
151
+
152 printf("Want conversion conflict test passed\n");
+
153}
+
154
+
155static void test_want_conversion_high_bits(void)
+
156{
+
157 struct fuse_conn_info conn = { 0 };
+
158 int rc;
+
159
+
160 printf("\nTesting want conversion high bits preservation:\n");
+
161
+
162 /* Test high bits preservation */
+
163 conn.want_ext = (1ULL << 33) | FUSE_CAP_ASYNC_READ;
+
164 conn.want = fuse_lower_32_bits(conn.want_ext);
+
165 print_conn_info("Test high bits initial", &conn);
+
166
+ +
168 assert(rc == 0);
+
169 assert(conn.want_ext == ((1ULL << 33) | FUSE_CAP_ASYNC_READ));
+
170 print_conn_info("Test high bits after", &conn);
+
171
+
172 printf("Want conversion high bits test passed\n");
+
173}
+
174
+
175int main(void)
+
176{
+
177 test_want_conversion_basic();
+
178 test_want_conversion_conflict();
+
179 test_want_conversion_high_bits();
+
180 return 0;
+
181}
+
#define FUSE_CAP_IOCTL_DIR
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + + +
uint64_t capable_ext
+
uint32_t capable
+
uint64_t want_ext
+ +
+ + + + diff --git a/doc/html/fuse-3_818_82_2test_2test__write__cache_8c_source.html b/doc/html/fuse-3_818_82_2test_2test__write__cache_8c_source.html new file mode 100644 index 0000000..4f230c9 --- /dev/null +++ b/doc/html/fuse-3_818_82_2test_2test__write__cache_8c_source.html @@ -0,0 +1,412 @@ + + + + + + + +libfuse: fuse-3.18.2/test/test_write_cache.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_write_cache.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
9#define FUSE_USE_VERSION 30
+
10
+
11/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
+
12#include <fuse.h>
+
13
+
14#include <fuse_config.h>
+
15#include <fuse_lowlevel.h>
+
16#include <stdio.h>
+
17#include <stdlib.h>
+
18#include <string.h>
+
19#include <errno.h>
+
20#include <fcntl.h>
+
21#include <assert.h>
+
22#include <stddef.h>
+
23#include <unistd.h>
+
24#include <sys/stat.h>
+
25#include <pthread.h>
+
26#include <stdatomic.h>
+
27
+
28#ifndef __linux__
+
29#include <limits.h>
+
30#else
+
31#include <linux/limits.h>
+
32#endif
+
33
+
34#define FILE_INO 2
+
35#define FILE_NAME "write_me"
+
36
+
37/* Command line parsing */
+
38struct options {
+
39 int writeback;
+
40 int data_size;
+
41 int delay_ms;
+
42} options = {
+
43 .writeback = 0,
+
44 .data_size = 2048,
+
45 .delay_ms = 0,
+
46};
+
47
+
48#define WRITE_SYSCALLS 64
+
49
+
50#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
51static const struct fuse_opt option_spec[] = {
+
52 OPTION("writeback_cache", writeback),
+
53 OPTION("--data-size=%d", data_size), OPTION("--delay_ms=%d", delay_ms),
+ +
55};
+
56static int got_write;
+
57static atomic_int write_cnt;
+
58
+
59pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+
60pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
61static int write_start, write_done;
+
62
+
63static void tfs_init(void *userdata, struct fuse_conn_info *conn)
+
64{
+
65 (void)userdata;
+
66
+
67 if (options.writeback) {
+ + +
70 }
+
71}
+
72
+
73static int tfs_stat(fuse_ino_t ino, struct stat *stbuf)
+
74{
+
75 stbuf->st_ino = ino;
+
76 if (ino == FUSE_ROOT_ID) {
+
77 stbuf->st_mode = S_IFDIR | 0755;
+
78 stbuf->st_nlink = 1;
+
79 }
+
80
+
81 else if (ino == FILE_INO) {
+
82 stbuf->st_mode = S_IFREG | 0222;
+
83 stbuf->st_nlink = 1;
+
84 stbuf->st_size = 0;
+
85 }
+
86
+
87 else
+
88 return -1;
+
89
+
90 return 0;
+
91}
+
92
+
93static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
94{
+
95 struct fuse_entry_param e;
+
96
+
97 memset(&e, 0, sizeof(e));
+
98
+
99 if (parent != FUSE_ROOT_ID)
+
100 goto err_out;
+
101 else if (strcmp(name, FILE_NAME) == 0)
+
102 e.ino = FILE_INO;
+
103 else
+
104 goto err_out;
+
105
+
106 if (tfs_stat(e.ino, &e.attr) != 0)
+
107 goto err_out;
+
108 fuse_reply_entry(req, &e);
+
109 return;
+
110
+
111err_out:
+
112 fuse_reply_err(req, ENOENT);
+
113}
+
114
+
115static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
116 struct fuse_file_info *fi)
+
117{
+
118 struct stat stbuf;
+
119
+
120 (void)fi;
+
121
+
122 memset(&stbuf, 0, sizeof(stbuf));
+
123 if (tfs_stat(ino, &stbuf) != 0)
+
124 fuse_reply_err(req, ENOENT);
+
125 else
+
126 fuse_reply_attr(req, &stbuf, 5);
+
127}
+
128
+
129static void tfs_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
130{
+
131 if (ino == FUSE_ROOT_ID)
+
132 fuse_reply_err(req, EISDIR);
+
133 else {
+
134 assert(ino == FILE_INO);
+
135 /* Test close(rofd) does not block waiting for pending writes */
+
136 fi->noflush = !options.writeback && options.delay_ms &&
+
137 (fi->flags & O_ACCMODE) == O_RDONLY;
+
138 fuse_reply_open(req, fi);
+
139 }
+
140}
+
141
+
142static void tfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+
143 size_t size, off_t off, struct fuse_file_info *fi)
+
144{
+
145 (void)fi;
+
146 (void)buf;
+
147 (void)off;
+
148 size_t expected;
+
149
+
150 assert(ino == FILE_INO);
+
151 expected = options.data_size;
+
152 if (options.writeback)
+
153 expected *= 2;
+
154
+
155 write_cnt++;
+
156
+
157 if (size != expected && !options.writeback)
+
158 fprintf(stderr, "ERROR: Expected %zd bytes, got %zd\n!",
+
159 expected, size);
+
160 else
+
161 got_write = 1;
+
162
+
163 /* Simulate waiting for pending writes */
+
164 if (options.delay_ms) {
+
165 pthread_mutex_lock(&lock);
+
166 write_start = 1;
+
167 pthread_cond_signal(&cond);
+
168 pthread_mutex_unlock(&lock);
+
169
+
170 usleep(options.delay_ms * 1000);
+
171
+
172 pthread_mutex_lock(&lock);
+
173 write_done = 1;
+
174 pthread_cond_signal(&cond);
+
175 pthread_mutex_unlock(&lock);
+
176 }
+
177
+
178 fuse_reply_write(req, size);
+
179}
+
180
+
181static struct fuse_lowlevel_ops tfs_oper = {
+
182 .init = tfs_init,
+
183 .lookup = tfs_lookup,
+
184 .getattr = tfs_getattr,
+
185 .open = tfs_open,
+
186 .write = tfs_write,
+
187};
+
188
+
189static void *close_rofd(void *data)
+
190{
+
191 int rofd = (int)(long)data;
+
192
+
193 /* Wait for first write to start */
+
194 pthread_mutex_lock(&lock);
+
195 while (!write_start && !write_done)
+
196 pthread_cond_wait(&cond, &lock);
+
197 pthread_mutex_unlock(&lock);
+
198
+
199 close(rofd);
+
200 printf("rofd closed. write_start: %d write_done: %d\n", write_start,
+
201 write_done);
+
202
+
203 /* First write should not have been completed */
+
204 if (write_done)
+
205 fprintf(stderr, "ERROR: close(rofd) blocked on write!\n");
+
206
+
207 return NULL;
+
208}
+
209
+
210static void *run_fs(void *data)
+
211{
+
212 struct fuse_session *se = (struct fuse_session *)data;
+
213
+
214 assert(fuse_session_loop(se) == 0);
+
215 return NULL;
+
216}
+
217
+
218static void test_fs(char *mountpoint)
+
219{
+
220 char fname[PATH_MAX];
+
221 char *buf;
+
222 const size_t iosize = options.data_size;
+
223 const size_t dsize = options.data_size * WRITE_SYSCALLS;
+
224 int fd, rofd;
+
225 pthread_t rofd_thread;
+
226 off_t off = 0;
+
227
+
228 buf = malloc(dsize);
+
229 assert(buf != NULL);
+
230 assert((fd = open("/dev/urandom", O_RDONLY)) != -1);
+
231 assert(read(fd, buf, dsize) == dsize);
+
232 close(fd);
+
233
+
234 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME, mountpoint) > 0);
+
235 fd = open(fname, O_WRONLY);
+
236 if (fd == -1) {
+
237 perror(fname);
+
238 assert(0);
+
239 }
+
240
+
241 if (options.delay_ms) {
+
242 /* Verify that close(rofd) does not block waiting for pending writes */
+
243 rofd = open(fname, O_RDONLY);
+
244 assert(pthread_create(&rofd_thread, NULL, close_rofd,
+
245 (void *)(long)rofd) == 0);
+
246 /* Give close_rofd time to start */
+
247 usleep(options.delay_ms * 1000);
+
248 }
+
249
+
250 for (int cnt = 0; cnt < WRITE_SYSCALLS; cnt++) {
+
251 assert(pwrite(fd, buf + off, iosize, off) == iosize);
+
252 off += iosize;
+
253 assert(off <= dsize);
+
254 }
+
255 free(buf);
+
256 close(fd);
+
257
+
258 if (options.delay_ms) {
+
259 printf("rwfd closed. write_start: %d write_done: %d\n",
+
260 write_start, write_done);
+
261 assert(pthread_join(rofd_thread, NULL) == 0);
+
262 }
+
263}
+
264
+
265int main(int argc, char *argv[])
+
266{
+
267 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
268 struct fuse_session *se;
+
269 struct fuse_cmdline_opts fuse_opts;
+
270 pthread_t fs_thread;
+
271
+
272 assert(fuse_opt_parse(&args, &options, option_spec, NULL) == 0);
+
273 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
+
274#ifndef __FreeBSD__
+
275 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
+
276#endif
+
277 se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), NULL);
+
278 fuse_opt_free_args(&args);
+
279 assert(se != NULL);
+
280 assert(fuse_set_signal_handlers(se) == 0);
+
281 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
+
282
+
283 /* Start file-system thread */
+
284 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
+
285
+
286 /* Write test data */
+
287 test_fs(fuse_opts.mountpoint);
+
288 free(fuse_opts.mountpoint);
+
289
+
290 /* Stop file system */
+ + +
293 assert(pthread_join(fs_thread, NULL) == 0);
+
294
+
295 assert(got_write == 1);
+
296
+
297 /*
+
298 * when writeback cache is enabled, kernel side can merge requests, but
+
299 * memory pressure, system 'sync' might trigger data flushes before - flush
+
300 * might happen in between write syscalls - merging subpage writes into
+
301 * a single page and pages into large fuse requests might or might not work.
+
302 * Though we can expect that that at least some (but maybe all) write
+
303 * system calls can be merged.
+
304 */
+
305 if (options.writeback)
+
306 assert(write_cnt < WRITE_SYSCALLS);
+
307 else
+
308 assert(write_cnt == WRITE_SYSCALLS);
+
309
+ + +
312
+
313 printf("Test completed successfully.\n");
+
314 return 0;
+
315}
+
316
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_WRITEBACK_CACHE
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
uint64_t fuse_ino_t
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
+
fuse_ino_t ino
+ +
uint32_t noflush
Definition fuse_common.h:93
+ + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_818_82_2test_2wrong__command_8c_source.html b/doc/html/fuse-3_818_82_2test_2wrong__command_8c_source.html new file mode 100644 index 0000000..67ccfbb --- /dev/null +++ b/doc/html/fuse-3_818_82_2test_2wrong__command_8c_source.html @@ -0,0 +1,76 @@ + + + + + + + +libfuse: fuse-3.18.2/test/wrong_command.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
wrong_command.c
+
+
+
1#include <stdio.h>
+
2
+
3int main(void) {
+
4#ifdef MESON_IS_SUBPROJECT
+
5 fprintf(stderr, "libfuse tests were skipped because it's a meson subproject.\n"
+
6 "If you wish to run them try:\n"
+
7 "'cd <srcdir>/subprojects/libfuse && meson . build && cd build && python3 -m pytest test/' instead");
+
8 return 77; /* report as a skipped test */
+
9#else
+
10 fprintf(stderr, "\x1B[31m\e[1m"
+
11 "This is not the command you are looking for.\n"
+
12 "You probably want to run 'python3 -m pytest test/' instead"
+
13 "\e[0m\n");
+
14 return 1;
+
15#endif
+
16}
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2util_2fusermount_8c_source.html b/doc/html/fuse-3_818_82_2util_2fusermount_8c_source.html new file mode 100644 index 0000000..a6bfc97 --- /dev/null +++ b/doc/html/fuse-3_818_82_2util_2fusermount_8c_source.html @@ -0,0 +1,1843 @@ + + + + + + + +libfuse: fuse-3.18.2/util/fusermount.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fusermount.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8/* This program does the mounting and unmounting of FUSE filesystems */
+
9
+
10#define _GNU_SOURCE /* for clone,strchrnul and close_range */
+
11#include "fuse_config.h"
+
12#include "mount_util.h"
+
13#include "util.h"
+
14
+
15#include <stdio.h>
+
16#include <stdlib.h>
+
17#include <string.h>
+
18#include <ctype.h>
+
19#include <unistd.h>
+
20#include <getopt.h>
+
21#include <errno.h>
+
22#include <fcntl.h>
+
23#include <pwd.h>
+
24#include <paths.h>
+
25#include <mntent.h>
+
26#include <sys/wait.h>
+
27#include <sys/stat.h>
+
28#include <sys/param.h>
+
29
+
30#include "fuse_mount_compat.h"
+
31
+
32#include <sys/fsuid.h>
+
33#include <sys/socket.h>
+
34#include <sys/utsname.h>
+
35#include <sched.h>
+
36#include <stdbool.h>
+
37#include <sys/vfs.h>
+
38
+
39#if defined HAVE_CLOSE_RANGE && defined linux
+
40#include <linux/close_range.h>
+
41#endif
+
42
+
43#if defined HAVE_LISTMOUNT
+
44#include <linux/mount.h>
+
45#include <syscall.h>
+
46#include <stdint.h>
+
47#endif
+
48
+
49#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
+
50#define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE"
+
51
+
52#define FUSE_DEV "/dev/fuse"
+
53
+
54static const char *progname;
+
55
+
56static int user_allow_other = 0;
+
57static int mount_max = 1000;
+
58
+
59static int auto_unmount = 0;
+
60
+
61#ifdef GETMNTENT_NEEDS_UNESCAPING
+
62// Older versions of musl libc don't unescape entries in /etc/mtab
+
63
+
64// unescapes octal sequences like \040 in-place
+
65// That's ok, because unescaping can not extend the length of the string.
+
66static void unescape(char *buf) {
+
67 char *src = buf;
+
68 char *dest = buf;
+
69 while (1) {
+
70 char *next_src = strchrnul(src, '\\');
+
71 int offset = next_src - src;
+
72 memmove(dest, src, offset);
+
73 src = next_src;
+
74 dest += offset;
+
75
+
76 if(*src == '\0') {
+
77 *dest = *src;
+
78 return;
+
79 }
+
80 src++;
+
81
+
82 if('0' <= src[0] && src[0] < '2' &&
+
83 '0' <= src[1] && src[1] < '8' &&
+
84 '0' <= src[2] && src[2] < '8') {
+
85 *dest++ = (src[0] - '0') << 6
+
86 | (src[1] - '0') << 3
+
87 | (src[2] - '0') << 0;
+
88 src += 3;
+
89 } else if (src[0] == '\\') {
+
90 *dest++ = '\\';
+
91 src += 1;
+
92 } else {
+
93 *dest++ = '\\';
+
94 }
+
95 }
+
96}
+
97
+
98static struct mntent *GETMNTENT(FILE *stream)
+
99{
+
100 struct mntent *entp = getmntent(stream);
+
101 if(entp != NULL) {
+
102 unescape(entp->mnt_fsname);
+
103 unescape(entp->mnt_dir);
+
104 unescape(entp->mnt_type);
+
105 unescape(entp->mnt_opts);
+
106 }
+
107 return entp;
+
108}
+
109#else
+
110#define GETMNTENT getmntent
+
111#endif // GETMNTENT_NEEDS_UNESCAPING
+
112
+
113/*
+
114 * Take a ',' separated option string and extract "x-" options
+
115 */
+
116static int extract_x_options(const char *original, char **non_x_opts,
+
117 char **x_opts)
+
118{
+
119 size_t orig_len;
+
120 const char *opt, *opt_end;
+
121
+
122 orig_len = strlen(original) + 1;
+
123
+
124 *non_x_opts = calloc(1, orig_len);
+
125 *x_opts = calloc(1, orig_len);
+
126
+
127 size_t non_x_opts_len = orig_len;
+
128 size_t x_opts_len = orig_len;
+
129
+
130 if (*non_x_opts == NULL || *x_opts == NULL) {
+
131 fprintf(stderr, "%s: Failed to allocate %zuB.\n",
+
132 __func__, orig_len);
+
133 return -ENOMEM;
+
134 }
+
135
+
136 for (opt = original; opt < original + orig_len; opt = opt_end + 1) {
+
137 char *opt_buf;
+
138
+
139 opt_end = strchr(opt, ',');
+
140 if (opt_end == NULL)
+
141 opt_end = original + orig_len;
+
142
+
143 size_t opt_len = opt_end - opt;
+
144 size_t opt_len_left = orig_len - (opt - original);
+
145 size_t buf_len;
+
146 bool is_x_opts;
+
147
+
148 if (strncmp(opt, "x-", MIN(2, opt_len_left)) == 0) {
+
149 buf_len = x_opts_len;
+
150 is_x_opts = true;
+
151 opt_buf = *x_opts;
+
152 } else {
+
153 buf_len = non_x_opts_len;
+
154 is_x_opts = false;
+
155 opt_buf = *non_x_opts;
+
156 }
+
157
+
158 if (buf_len < orig_len) {
+
159 strncat(opt_buf, ",", 2);
+
160 buf_len -= 1;
+
161 }
+
162
+
163 /* omits ',' */
+
164 if ((ssize_t)(buf_len - opt_len) < 0) {
+
165 /* This would be a bug */
+
166 fprintf(stderr, "%s: no buf space left in copy, orig='%s'\n",
+
167 __func__, original);
+
168 return -EIO;
+
169 }
+
170
+
171 strncat(opt_buf, opt, opt_end - opt);
+
172 buf_len -= opt_len;
+
173
+
174 if (is_x_opts)
+
175 x_opts_len = buf_len;
+
176 else
+
177 non_x_opts_len = buf_len;
+
178 }
+
179
+
180 return 0;
+
181}
+
182
+
183static const char *get_user_name(void)
+
184{
+
185 struct passwd *pw = getpwuid(getuid());
+
186 if (pw != NULL && pw->pw_name != NULL)
+
187 return pw->pw_name;
+
188 else {
+
189 fprintf(stderr, "%s: could not determine username\n", progname);
+
190 return NULL;
+
191 }
+
192}
+
193
+
194static uid_t oldfsuid;
+
195static gid_t oldfsgid;
+
196
+
197static void drop_privs(void)
+
198{
+
199 if (getuid() != 0) {
+
200 oldfsuid = setfsuid(getuid());
+
201 oldfsgid = setfsgid(getgid());
+
202 }
+
203}
+
204
+
205static void restore_privs(void)
+
206{
+
207 if (getuid() != 0) {
+
208 setfsuid(oldfsuid);
+
209 setfsgid(oldfsgid);
+
210 }
+
211}
+
212
+
213#ifndef IGNORE_MTAB
+
214/*
+
215 * Make sure that /etc/mtab is checked and updated atomically
+
216 */
+
217static int lock_umount(void)
+
218{
+
219 const char *mtab_lock = _PATH_MOUNTED ".fuselock";
+
220 int mtablock;
+
221 int res;
+
222 struct stat mtab_stat;
+
223
+
224 /* /etc/mtab could be a symlink to /proc/mounts */
+
225 if (lstat(_PATH_MOUNTED, &mtab_stat) == 0 && S_ISLNK(mtab_stat.st_mode))
+
226 return -1;
+
227
+
228 mtablock = open(mtab_lock, O_RDWR | O_CREAT, 0600);
+
229 if (mtablock == -1) {
+
230 fprintf(stderr, "%s: unable to open fuse lock file: %s\n",
+
231 progname, strerror(errno));
+
232 return -1;
+
233 }
+
234 res = lockf(mtablock, F_LOCK, 0);
+
235 if (res < 0) {
+
236 fprintf(stderr, "%s: error getting lock: %s\n", progname,
+
237 strerror(errno));
+
238 close(mtablock);
+
239 return -1;
+
240 }
+
241
+
242 return mtablock;
+
243}
+
244
+
245static void unlock_umount(int mtablock)
+
246{
+
247 if (mtablock >= 0) {
+
248 int res;
+
249
+
250 res = lockf(mtablock, F_ULOCK, 0);
+
251 if (res < 0) {
+
252 fprintf(stderr, "%s: error releasing lock: %s\n",
+
253 progname, strerror(errno));
+
254 }
+
255 close(mtablock);
+
256 }
+
257}
+
258
+
259static int add_mount(const char *source, const char *mnt, const char *type,
+
260 const char *opts)
+
261{
+
262 return fuse_mnt_add_mount(progname, source, mnt, type, opts);
+
263}
+
264
+
265static int may_unmount(const char *mnt, int quiet)
+
266{
+
267 struct mntent *entp;
+
268 FILE *fp;
+
269 const char *user = NULL;
+
270 char uidstr[32];
+
271 unsigned uidlen = 0;
+
272 int found;
+
273 const char *mtab = _PATH_MOUNTED;
+
274
+
275 user = get_user_name();
+
276 if (user == NULL)
+
277 return -1;
+
278
+
279 fp = setmntent(mtab, "r");
+
280 if (fp == NULL) {
+
281 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
+
282 strerror(errno));
+
283 return -1;
+
284 }
+
285
+
286 uidlen = sprintf(uidstr, "%u", getuid());
+
287
+
288 found = 0;
+
289 while ((entp = GETMNTENT(fp)) != NULL) {
+
290 if (!found && strcmp(entp->mnt_dir, mnt) == 0 &&
+
291 (strcmp(entp->mnt_type, "fuse") == 0 ||
+
292 strcmp(entp->mnt_type, "fuseblk") == 0 ||
+
293 strncmp(entp->mnt_type, "fuse.", 5) == 0 ||
+
294 strncmp(entp->mnt_type, "fuseblk.", 8) == 0)) {
+
295 char *p = strstr(entp->mnt_opts, "user=");
+
296 if (p &&
+
297 (p == entp->mnt_opts || *(p-1) == ',') &&
+
298 strcmp(p + 5, user) == 0) {
+
299 found = 1;
+
300 break;
+
301 }
+
302 /* /etc/mtab is a link pointing to
+
303 /proc/mounts: */
+
304 else if ((p =
+
305 strstr(entp->mnt_opts, "user_id=")) &&
+
306 (p == entp->mnt_opts ||
+
307 *(p-1) == ',') &&
+
308 strncmp(p + 8, uidstr, uidlen) == 0 &&
+
309 (*(p+8+uidlen) == ',' ||
+
310 *(p+8+uidlen) == '\0')) {
+
311 found = 1;
+
312 break;
+
313 }
+
314 }
+
315 }
+
316 endmntent(fp);
+
317
+
318 if (!found) {
+
319 if (!quiet)
+
320 fprintf(stderr,
+
321 "%s: entry for %s not found in %s\n",
+
322 progname, mnt, mtab);
+
323 return -1;
+
324 }
+
325
+
326 return 0;
+
327}
+
328#endif
+
329
+
330/*
+
331 * Check whether the file specified in "fusermount3 -u" is really a
+
332 * mountpoint and not a symlink. This is necessary otherwise the user
+
333 * could move the mountpoint away and replace it with a symlink
+
334 * pointing to an arbitrary mount, thereby tricking fusermount3 into
+
335 * unmounting that (umount(2) will follow symlinks).
+
336 *
+
337 * This is the child process running in a separate mount namespace, so
+
338 * we don't mess with the global namespace and if the process is
+
339 * killed for any reason, mounts are automatically cleaned up.
+
340 *
+
341 * First make sure nothing is propagated back into the parent
+
342 * namespace by marking all mounts "private".
+
343 *
+
344 * Then bind mount parent onto a stable base where the user can't move
+
345 * it around.
+
346 *
+
347 * Finally check /proc/mounts for an entry matching the requested
+
348 * mountpoint. If it's found then we are OK, and the user can't move
+
349 * it around within the parent directory as rename() will return
+
350 * EBUSY. Be careful to ignore any mounts that existed before the
+
351 * bind.
+
352 */
+
353static int check_is_mount_child(void *p)
+
354{
+
355 const char **a = p;
+
356 const char *last = a[0];
+
357 const char *mnt = a[1];
+
358 const char *type = a[2];
+
359 int res;
+
360 const char *procmounts = "/proc/mounts";
+
361 int found;
+
362 FILE *fp;
+
363 struct mntent *entp;
+
364 int count;
+
365
+
366 res = mount("", "/", "", MS_PRIVATE | MS_REC, NULL);
+
367 if (res == -1) {
+
368 fprintf(stderr, "%s: failed to mark mounts private: %s\n",
+
369 progname, strerror(errno));
+
370 return 1;
+
371 }
+
372
+
373 fp = setmntent(procmounts, "r");
+
374 if (fp == NULL) {
+
375 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
+
376 procmounts, strerror(errno));
+
377 return 1;
+
378 }
+
379
+
380 count = 0;
+
381 while (GETMNTENT(fp) != NULL)
+
382 count++;
+
383 endmntent(fp);
+
384
+
385 fp = setmntent(procmounts, "r");
+
386 if (fp == NULL) {
+
387 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
+
388 procmounts, strerror(errno));
+
389 return 1;
+
390 }
+
391
+
392 res = mount(".", "/", "", MS_BIND | MS_REC, NULL);
+
393 if (res == -1) {
+
394 fprintf(stderr, "%s: failed to bind parent to /: %s\n",
+
395 progname, strerror(errno));
+
396 return 1;
+
397 }
+
398
+
399 found = 0;
+
400 while ((entp = GETMNTENT(fp)) != NULL) {
+
401 if (count > 0) {
+
402 count--;
+
403 continue;
+
404 }
+
405 if (entp->mnt_dir[0] == '/' &&
+
406 strcmp(entp->mnt_dir + 1, last) == 0 &&
+
407 (!type || strcmp(entp->mnt_type, type) == 0)) {
+
408 found = 1;
+
409 break;
+
410 }
+
411 }
+
412 endmntent(fp);
+
413
+
414 if (!found) {
+
415 fprintf(stderr, "%s: %s not mounted\n", progname, mnt);
+
416 return 1;
+
417 }
+
418
+
419 return 0;
+
420}
+
421
+
422static pid_t clone_newns(void *a)
+
423{
+
424 char buf[131072];
+
425 char *stack = buf + (sizeof(buf) / 2 - ((size_t) buf & 15));
+
426
+
427#ifdef __ia64__
+
428 extern int __clone2(int (*fn)(void *),
+
429 void *child_stack_base, size_t stack_size,
+
430 int flags, void *arg, pid_t *ptid,
+
431 void *tls, pid_t *ctid);
+
432
+
433 return __clone2(check_is_mount_child, stack, sizeof(buf) / 2,
+
434 CLONE_NEWNS, a, NULL, NULL, NULL);
+
435#else
+
436 return clone(check_is_mount_child, stack, CLONE_NEWNS, a);
+
437#endif
+
438}
+
439
+
440static int check_is_mount(const char *last, const char *mnt, const char *type)
+
441{
+
442 pid_t pid, p;
+
443 int status;
+
444 const char *a[3] = { last, mnt, type };
+
445
+
446 pid = clone_newns((void *) a);
+
447 if (pid == (pid_t) -1) {
+
448 fprintf(stderr, "%s: failed to clone namespace: %s\n",
+
449 progname, strerror(errno));
+
450 return -1;
+
451 }
+
452 p = waitpid(pid, &status, __WCLONE);
+
453 if (p == (pid_t) -1) {
+
454 fprintf(stderr, "%s: waitpid failed: %s\n",
+
455 progname, strerror(errno));
+
456 return -1;
+
457 }
+
458 if (!WIFEXITED(status)) {
+
459 fprintf(stderr, "%s: child terminated abnormally (status %i)\n",
+
460 progname, status);
+
461 return -1;
+
462 }
+
463 if (WEXITSTATUS(status) != 0)
+
464 return -1;
+
465
+
466 return 0;
+
467}
+
468
+
469static int chdir_to_parent(char *copy, const char **lastp)
+
470{
+
471 char *tmp;
+
472 const char *parent;
+
473 char buf[65536];
+
474 int res;
+
475
+
476 tmp = strrchr(copy, '/');
+
477 if (tmp == NULL || tmp[1] == '\0') {
+
478 fprintf(stderr, "%s: internal error: invalid abs path: <%s>\n",
+
479 progname, copy);
+
480 return -1;
+
481 }
+
482 if (tmp != copy) {
+
483 *tmp = '\0';
+
484 parent = copy;
+
485 *lastp = tmp + 1;
+
486 } else if (tmp[1] != '\0') {
+
487 *lastp = tmp + 1;
+
488 parent = "/";
+
489 } else {
+
490 *lastp = ".";
+
491 parent = "/";
+
492 }
+
493
+
494 res = chdir(parent);
+
495 if (res == -1) {
+
496 fprintf(stderr, "%s: failed to chdir to %s: %s\n",
+
497 progname, parent, strerror(errno));
+
498 return -1;
+
499 }
+
500
+
501 if (getcwd(buf, sizeof(buf)) == NULL) {
+
502 fprintf(stderr, "%s: failed to obtain current directory: %s\n",
+
503 progname, strerror(errno));
+
504 return -1;
+
505 }
+
506 if (strcmp(buf, parent) != 0) {
+
507 fprintf(stderr, "%s: mountpoint moved (%s -> %s)\n", progname,
+
508 parent, buf);
+
509 return -1;
+
510
+
511 }
+
512
+
513 return 0;
+
514}
+
515
+
516#ifndef IGNORE_MTAB
+
517static int unmount_fuse_locked(const char *mnt, int quiet, int lazy)
+
518{
+
519 int res;
+
520 char *copy;
+
521 const char *last;
+
522 int umount_flags = (lazy ? UMOUNT_DETACH : 0) | UMOUNT_NOFOLLOW;
+
523
+
524 if (getuid() != 0) {
+
525 res = may_unmount(mnt, quiet);
+
526 if (res == -1)
+
527 return -1;
+
528 }
+
529
+
530 copy = strdup(mnt);
+
531 if (copy == NULL) {
+
532 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
533 return -1;
+
534 }
+
535
+
536 drop_privs();
+
537 res = chdir_to_parent(copy, &last);
+
538 if (res == -1) {
+
539 restore_privs();
+
540 goto out;
+
541 }
+
542
+
543 res = umount2(last, umount_flags);
+
544 restore_privs();
+
545 if (res == -1 && !quiet) {
+
546 fprintf(stderr, "%s: failed to unmount %s: %s\n",
+
547 progname, mnt, strerror(errno));
+
548 }
+
549
+
550out:
+
551 free(copy);
+
552 if (res == -1)
+
553 return -1;
+
554
+
555 res = chdir("/");
+
556 if (res == -1) {
+
557 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
558 return -1;
+
559 }
+
560
+
561 return fuse_mnt_remove_mount(progname, mnt);
+
562}
+
563
+
564static int unmount_fuse(const char *mnt, int quiet, int lazy)
+
565{
+
566 int res;
+
567 int mtablock = lock_umount();
+
568
+
569 res = unmount_fuse_locked(mnt, quiet, lazy);
+
570 unlock_umount(mtablock);
+
571
+
572 return res;
+
573}
+
574
+
575static int count_fuse_fs_mtab(void)
+
576{
+
577 struct mntent *entp;
+
578 int count = 0;
+
579 const char *mtab = _PATH_MOUNTED;
+
580 FILE *fp = setmntent(mtab, "r");
+
581 if (fp == NULL) {
+
582 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
+
583 strerror(errno));
+
584 return -1;
+
585 }
+
586 while ((entp = GETMNTENT(fp)) != NULL) {
+
587 if (strcmp(entp->mnt_type, "fuse") == 0 ||
+
588 strncmp(entp->mnt_type, "fuse.", 5) == 0)
+
589 count ++;
+
590 }
+
591 endmntent(fp);
+
592 return count;
+
593}
+
594
+
595#ifdef HAVE_LISTMOUNT
+
596static int count_fuse_fs_ls_mnt(void)
+
597{
+
598 #define SMBUF_SIZE 1024
+
599 #define MNT_ID_LEN 128
+
600
+
601 int fuse_count = 0;
+
602 int n_mounts = 0;
+
603 int ret = 0;
+
604 uint64_t mnt_ids[MNT_ID_LEN];
+
605 unsigned char smbuf[SMBUF_SIZE];
+
606 struct mnt_id_req req = {
+
607 .size = sizeof(struct mnt_id_req),
+
608 };
+
609 struct statmount *sm;
+
610
+
611 for (;;) {
+
612 req.mnt_id = LSMT_ROOT;
+
613
+
614 n_mounts = syscall(SYS_listmount, &req, &mnt_ids, MNT_ID_LEN, 0);
+
615 if (n_mounts == -1) {
+
616 if (errno != ENOSYS) {
+
617 fprintf(stderr, "%s: failed to list mounts: %s\n", progname,
+
618 strerror(errno));
+
619 }
+
620 return -1;
+
621 }
+
622
+
623 for (int i = 0; i < n_mounts; i++) {
+
624 req.mnt_id = mnt_ids[i];
+
625 req.param = STATMOUNT_FS_TYPE;
+
626 ret = syscall(SYS_statmount, &req, &smbuf, SMBUF_SIZE, 0);
+
627 if (ret) {
+
628 if (errno == ENOENT)
+
629 continue;
+
630
+
631 fprintf(stderr, "%s: failed to stat mount %lld: %s\n", progname,
+
632 req.mnt_id, strerror(errno));
+
633 return -1;
+
634 }
+
635
+
636 sm = (struct statmount *)smbuf;
+
637 if (sm->mask & STATMOUNT_FS_TYPE &&
+
638 strcmp(&sm->str[sm->fs_type], "fuse") == 0)
+
639 fuse_count++;
+
640 }
+
641
+
642 if (n_mounts < MNT_ID_LEN)
+
643 break;
+
644 req.param = mnt_ids[MNT_ID_LEN - 1];
+
645 }
+
646 return fuse_count;
+
647}
+
648
+
649static int count_fuse_fs(void)
+
650{
+
651 int count = count_fuse_fs_ls_mnt();
+
652
+
653 return count >= 0 ? count : count_fuse_fs_mtab();
+
654}
+
655#else
+
656static int count_fuse_fs(void)
+
657{
+
658 return count_fuse_fs_mtab();
+
659}
+
660#endif
+
661
+
662#else /* IGNORE_MTAB */
+
663static int count_fuse_fs(void)
+
664{
+
665 return 0;
+
666}
+
667
+
668static int add_mount(const char *source, const char *mnt, const char *type,
+
669 const char *opts)
+
670{
+
671 (void) source;
+
672 (void) mnt;
+
673 (void) type;
+
674 (void) opts;
+
675 return 0;
+
676}
+
677
+
678static int unmount_fuse(const char *mnt, int quiet, int lazy)
+
679{
+
680 (void) quiet;
+
681 return fuse_mnt_umount(progname, mnt, mnt, lazy);
+
682}
+
683#endif /* IGNORE_MTAB */
+
684
+
685static void strip_line(char *line)
+
686{
+
687 char *s = strchr(line, '#');
+
688 if (s != NULL)
+
689 s[0] = '\0';
+
690 for (s = line + strlen(line) - 1;
+
691 s >= line && isspace((unsigned char) *s); s--);
+
692 s[1] = '\0';
+
693 for (s = line; isspace((unsigned char) *s); s++);
+
694 if (s != line)
+
695 memmove(line, s, strlen(s)+1);
+
696}
+
697
+
698static void parse_line(char *line, int linenum)
+
699{
+
700 int tmp;
+
701 if (strcmp(line, "user_allow_other") == 0)
+
702 user_allow_other = 1;
+
703 else if (sscanf(line, "mount_max = %i", &tmp) == 1)
+
704 mount_max = tmp;
+
705 else if(line[0])
+
706 fprintf(stderr,
+
707 "%s: unknown parameter in %s at line %i: '%s'\n",
+
708 progname, FUSE_CONF, linenum, line);
+
709}
+
710
+
711static void read_conf(void)
+
712{
+
713 FILE *fp = fopen(FUSE_CONF, "r");
+
714 if (fp != NULL) {
+
715 int linenum = 1;
+
716 char line[256];
+
717 int isnewline = 1;
+
718 while (fgets(line, sizeof(line), fp) != NULL) {
+
719 if (isnewline) {
+
720 if (line[strlen(line)-1] == '\n') {
+
721 strip_line(line);
+
722 parse_line(line, linenum);
+
723 } else {
+
724 isnewline = 0;
+
725 }
+
726 } else if(line[strlen(line)-1] == '\n') {
+
727 fprintf(stderr, "%s: reading %s: line %i too long\n", progname, FUSE_CONF, linenum);
+
728
+
729 isnewline = 1;
+
730 }
+
731 if (isnewline)
+
732 linenum ++;
+
733 }
+
734 if (!isnewline) {
+
735 fprintf(stderr, "%s: reading %s: missing newline at end of file\n", progname, FUSE_CONF);
+
736
+
737 }
+
738 if (ferror(fp)) {
+
739 fprintf(stderr, "%s: reading %s: read failed\n", progname, FUSE_CONF);
+
740 exit(1);
+
741 }
+
742 fclose(fp);
+
743 } else if (errno != ENOENT) {
+
744 bool fatal = (errno != EACCES && errno != ELOOP &&
+
745 errno != ENAMETOOLONG && errno != ENOTDIR &&
+
746 errno != EOVERFLOW);
+
747 fprintf(stderr, "%s: failed to open %s: %s\n",
+
748 progname, FUSE_CONF, strerror(errno));
+
749 if (fatal)
+
750 exit(1);
+
751 }
+
752}
+
753
+
754static int begins_with(const char *s, const char *beg)
+
755{
+
756 if (strncmp(s, beg, strlen(beg)) == 0)
+
757 return 1;
+
758 else
+
759 return 0;
+
760}
+
761
+
762struct mount_flags {
+
763 const char *opt;
+
764 unsigned long flag;
+
765 int on;
+
766 int safe;
+
767};
+
768
+
769static struct mount_flags mount_flags[] = {
+
770 {"rw", MS_RDONLY, 0, 1},
+
771 {"ro", MS_RDONLY, 1, 1},
+
772 {"suid", MS_NOSUID, 0, 0},
+
773 {"nosuid", MS_NOSUID, 1, 1},
+
774 {"dev", MS_NODEV, 0, 0},
+
775 {"nodev", MS_NODEV, 1, 1},
+
776 {"exec", MS_NOEXEC, 0, 1},
+
777 {"noexec", MS_NOEXEC, 1, 1},
+
778 {"async", MS_SYNCHRONOUS, 0, 1},
+
779 {"sync", MS_SYNCHRONOUS, 1, 1},
+
780 {"atime", MS_NOATIME, 0, 1},
+
781 {"noatime", MS_NOATIME, 1, 1},
+
782 {"diratime", MS_NODIRATIME, 0, 1},
+
783 {"nodiratime", MS_NODIRATIME, 1, 1},
+
784 {"lazytime", MS_LAZYTIME, 1, 1},
+
785 {"nolazytime", MS_LAZYTIME, 0, 1},
+
786 {"relatime", MS_RELATIME, 1, 1},
+
787 {"norelatime", MS_RELATIME, 0, 1},
+
788 {"strictatime", MS_STRICTATIME, 1, 1},
+
789 {"nostrictatime", MS_STRICTATIME, 0, 1},
+
790 {"dirsync", MS_DIRSYNC, 1, 1},
+
791 {"symfollow", MS_NOSYMFOLLOW, 0, 1},
+
792 {"nosymfollow", MS_NOSYMFOLLOW, 1, 1},
+
793 {NULL, 0, 0, 0}
+
794};
+
795
+
796static int find_mount_flag(const char *s, unsigned len, int *on, int *flag)
+
797{
+
798 int i;
+
799
+
800 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
801 const char *opt = mount_flags[i].opt;
+
802 if (strlen(opt) == len && strncmp(opt, s, len) == 0) {
+
803 *on = mount_flags[i].on;
+
804 *flag = mount_flags[i].flag;
+
805 if (!mount_flags[i].safe && getuid() != 0) {
+
806 *flag = 0;
+
807 fprintf(stderr,
+
808 "%s: unsafe option %s ignored\n",
+
809 progname, opt);
+
810 }
+
811 return 1;
+
812 }
+
813 }
+
814 return 0;
+
815}
+
816
+
817static int add_option(char **optsp, const char *opt, unsigned expand)
+
818{
+
819 char *newopts;
+
820 if (*optsp == NULL)
+
821 newopts = strdup(opt);
+
822 else {
+
823 unsigned oldsize = strlen(*optsp);
+
824 unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1;
+
825 newopts = (char *) realloc(*optsp, newsize);
+
826 if (newopts)
+
827 sprintf(newopts + oldsize, ",%s", opt);
+
828 }
+
829 if (newopts == NULL) {
+
830 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
831 return -1;
+
832 }
+
833 *optsp = newopts;
+
834 return 0;
+
835}
+
836
+
837static int get_mnt_opts(int flags, char *opts, char **mnt_optsp)
+
838{
+
839 int i;
+
840 int l;
+
841
+
842 if (!(flags & MS_RDONLY) && add_option(mnt_optsp, "rw", 0) == -1)
+
843 return -1;
+
844
+
845 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
846 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+
847 add_option(mnt_optsp, mount_flags[i].opt, 0) == -1)
+
848 return -1;
+
849 }
+
850
+
851 if (add_option(mnt_optsp, opts, 0) == -1)
+
852 return -1;
+
853 /* remove comma from end of opts*/
+
854 l = strlen(*mnt_optsp);
+
855 if ((*mnt_optsp)[l-1] == ',')
+
856 (*mnt_optsp)[l-1] = '\0';
+
857 if (getuid() != 0) {
+
858 const char *user = get_user_name();
+
859 if (user == NULL)
+
860 return -1;
+
861
+
862 if (add_option(mnt_optsp, "user=", strlen(user)) == -1)
+
863 return -1;
+
864 strcat(*mnt_optsp, user);
+
865 }
+
866 return 0;
+
867}
+
868
+
869static int opt_eq(const char *s, unsigned len, const char *opt)
+
870{
+
871 if(strlen(opt) == len && strncmp(s, opt, len) == 0)
+
872 return 1;
+
873 else
+
874 return 0;
+
875}
+
876
+
877static int get_string_opt(const char *s, unsigned len, const char *opt,
+
878 char **val)
+
879{
+
880 int i;
+
881 unsigned opt_len = strlen(opt);
+
882 char *d;
+
883
+
884 if (*val)
+
885 free(*val);
+
886 *val = (char *) malloc(len - opt_len + 1);
+
887 if (!*val) {
+
888 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
889 return 0;
+
890 }
+
891
+
892 d = *val;
+
893 s += opt_len;
+
894 len -= opt_len;
+
895 for (i = 0; i < len; i++) {
+
896 if (s[i] == '\\' && i + 1 < len)
+
897 i++;
+
898 *d++ = s[i];
+
899 }
+
900 *d = '\0';
+
901 return 1;
+
902}
+
903
+
904/* The kernel silently truncates the "data" argument to PAGE_SIZE-1 characters.
+
905 * This can be dangerous if it e.g. truncates the option "group_id=1000" to
+
906 * "group_id=1".
+
907 * This wrapper detects this case and bails out with an error.
+
908 */
+
909static int mount_notrunc(const char *source, const char *target,
+
910 const char *filesystemtype, unsigned long mountflags,
+
911 const char *data) {
+
912 if (strlen(data) > sysconf(_SC_PAGESIZE) - 1) {
+
913 fprintf(stderr, "%s: mount options too long\n", progname);
+
914 errno = EINVAL;
+
915 return -1;
+
916 }
+
917 return mount(source, target, filesystemtype, mountflags, data);
+
918}
+
919
+
920
+
921static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
+
922 int fd, const char *opts, const char *dev, char **sourcep,
+
923 char **mnt_optsp)
+
924{
+
925 int res;
+
926 int flags = MS_NOSUID | MS_NODEV;
+
927 char *optbuf;
+
928 char *mnt_opts = NULL;
+
929 const char *s;
+
930 char *d;
+
931 char *fsname = NULL;
+
932 char *subtype = NULL;
+
933 char *source = NULL;
+
934 char *type = NULL;
+
935 int blkdev = 0;
+
936
+
937 optbuf = (char *) malloc(strlen(opts) + 128);
+
938 if (!optbuf) {
+
939 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
940 return -1;
+
941 }
+
942
+
943 for (s = opts, d = optbuf; *s;) {
+
944 unsigned len;
+
945 const char *fsname_str = "fsname=";
+
946 const char *subtype_str = "subtype=";
+
947 bool escape_ok = begins_with(s, fsname_str) ||
+
948 begins_with(s, subtype_str);
+
949 for (len = 0; s[len]; len++) {
+
950 if (escape_ok && s[len] == '\\' && s[len + 1])
+
951 len++;
+
952 else if (s[len] == ',')
+
953 break;
+
954 }
+
955 if (begins_with(s, fsname_str)) {
+
956 if (!get_string_opt(s, len, fsname_str, &fsname))
+
957 goto err;
+
958 } else if (begins_with(s, subtype_str)) {
+
959 if (!get_string_opt(s, len, subtype_str, &subtype))
+
960 goto err;
+
961 } else if (opt_eq(s, len, "blkdev")) {
+
962 if (getuid() != 0) {
+
963 fprintf(stderr,
+
964 "%s: option blkdev is privileged\n",
+
965 progname);
+
966 goto err;
+
967 }
+
968 blkdev = 1;
+
969 } else if (opt_eq(s, len, "auto_unmount")) {
+
970 auto_unmount = 1;
+
971 } else if (!opt_eq(s, len, "nonempty") &&
+
972 !begins_with(s, "fd=") &&
+
973 !begins_with(s, "rootmode=") &&
+
974 !begins_with(s, "user_id=") &&
+
975 !begins_with(s, "group_id=")) {
+
976 int on;
+
977 int flag;
+
978 int skip_option = 0;
+
979 if (opt_eq(s, len, "large_read")) {
+
980 struct utsname utsname;
+
981 unsigned kmaj, kmin;
+
982 res = uname(&utsname);
+
983 if (res == 0 &&
+
984 sscanf(utsname.release, "%u.%u",
+
985 &kmaj, &kmin) == 2 &&
+
986 (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
+
987 fprintf(stderr, "%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n", progname, kmaj, kmin);
+
988 skip_option = 1;
+
989 }
+
990 }
+
991 if (getuid() != 0 && !user_allow_other &&
+
992 (opt_eq(s, len, "allow_other") ||
+
993 opt_eq(s, len, "allow_root"))) {
+
994 fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n", progname, len, s, FUSE_CONF);
+
995 goto err;
+
996 }
+
997 if (!skip_option) {
+
998 if (find_mount_flag(s, len, &on, &flag)) {
+
999 if (on)
+
1000 flags |= flag;
+
1001 else
+
1002 flags &= ~flag;
+
1003 } else if (opt_eq(s, len, "default_permissions") ||
+
1004 opt_eq(s, len, "allow_other") ||
+
1005 begins_with(s, "max_read=") ||
+
1006 begins_with(s, "blksize=")) {
+
1007 memcpy(d, s, len);
+
1008 d += len;
+
1009 *d++ = ',';
+
1010 } else {
+
1011 fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, s);
+
1012 exit(1);
+
1013 }
+
1014 }
+
1015 }
+
1016 s += len;
+
1017 if (*s)
+
1018 s++;
+
1019 }
+
1020 *d = '\0';
+
1021 res = get_mnt_opts(flags, optbuf, &mnt_opts);
+
1022 if (res == -1)
+
1023 goto err;
+
1024
+
1025 sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+
1026 fd, rootmode, getuid(), getgid());
+
1027
+
1028 source = malloc((fsname ? strlen(fsname) : 0) +
+
1029 (subtype ? strlen(subtype) : 0) + strlen(dev) + 32);
+
1030
+
1031 type = malloc((subtype ? strlen(subtype) : 0) + 32);
+
1032 if (!type || !source) {
+
1033 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
1034 goto err;
+
1035 }
+
1036
+
1037 if (subtype)
+
1038 sprintf(type, "%s.%s", blkdev ? "fuseblk" : "fuse", subtype);
+
1039 else
+
1040 strcpy(type, blkdev ? "fuseblk" : "fuse");
+
1041
+
1042 if (fsname)
+
1043 strcpy(source, fsname);
+
1044 else
+
1045 strcpy(source, subtype ? subtype : dev);
+
1046
+
1047 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
1048 if (res == -1 && errno == ENODEV && subtype) {
+
1049 /* Probably missing subtype support */
+
1050 strcpy(type, blkdev ? "fuseblk" : "fuse");
+
1051 if (fsname) {
+
1052 if (!blkdev)
+
1053 sprintf(source, "%s#%s", subtype, fsname);
+
1054 } else {
+
1055 strcpy(source, type);
+
1056 }
+
1057
+
1058 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
1059 }
+
1060 if (res == -1 && errno == EINVAL) {
+
1061 /* It could be an old version not supporting group_id */
+
1062 sprintf(d, "fd=%i,rootmode=%o,user_id=%u",
+
1063 fd, rootmode, getuid());
+
1064 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
1065 }
+
1066 if (res == -1) {
+
1067 int errno_save = errno;
+
1068 if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
+
1069 fprintf(stderr, "%s: 'fuseblk' support missing\n",
+
1070 progname);
+
1071 else
+
1072 fprintf(stderr, "%s: mount failed: %s\n", progname,
+
1073 strerror(errno_save));
+
1074 goto err;
+
1075 }
+
1076 *sourcep = source;
+
1077 *typep = type;
+
1078 *mnt_optsp = mnt_opts;
+
1079 free(fsname);
+
1080 free(optbuf);
+
1081
+
1082 return 0;
+
1083
+
1084err:
+
1085 free(fsname);
+
1086 free(subtype);
+
1087 free(source);
+
1088 free(type);
+
1089 free(mnt_opts);
+
1090 free(optbuf);
+
1091 return -1;
+
1092}
+
1093
+
1094static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)
+
1095{
+
1096 int res;
+
1097 const char *mnt = *mntp;
+
1098 const char *origmnt = mnt;
+
1099 struct statfs fs_buf;
+
1100 size_t i;
+
1101
+
1102 res = lstat(mnt, stbuf);
+
1103 if (res == -1) {
+
1104 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
+
1105 progname, mnt, strerror(errno));
+
1106 return -1;
+
1107 }
+
1108
+
1109 /* No permission checking is done for root */
+
1110 if (getuid() == 0)
+
1111 return 0;
+
1112
+
1113 if (S_ISDIR(stbuf->st_mode)) {
+
1114 res = chdir(mnt);
+
1115 if (res == -1) {
+
1116 fprintf(stderr,
+
1117 "%s: failed to chdir to mountpoint: %s\n",
+
1118 progname, strerror(errno));
+
1119 return -1;
+
1120 }
+
1121 mnt = *mntp = ".";
+
1122 res = lstat(mnt, stbuf);
+
1123 if (res == -1) {
+
1124 fprintf(stderr,
+
1125 "%s: failed to access mountpoint %s: %s\n",
+
1126 progname, origmnt, strerror(errno));
+
1127 return -1;
+
1128 }
+
1129
+
1130 if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) {
+
1131 fprintf(stderr, "%s: mountpoint %s not owned by user\n",
+
1132 progname, origmnt);
+
1133 return -1;
+
1134 }
+
1135
+
1136 res = access(mnt, W_OK);
+
1137 if (res == -1) {
+
1138 fprintf(stderr, "%s: user has no write access to mountpoint %s\n",
+
1139 progname, origmnt);
+
1140 return -1;
+
1141 }
+
1142 } else if (S_ISREG(stbuf->st_mode)) {
+
1143 static char procfile[256];
+
1144 *mountpoint_fd = open(mnt, O_WRONLY);
+
1145 if (*mountpoint_fd == -1) {
+
1146 fprintf(stderr, "%s: failed to open %s: %s\n",
+
1147 progname, mnt, strerror(errno));
+
1148 return -1;
+
1149 }
+
1150 res = fstat(*mountpoint_fd, stbuf);
+
1151 if (res == -1) {
+
1152 fprintf(stderr,
+
1153 "%s: failed to access mountpoint %s: %s\n",
+
1154 progname, mnt, strerror(errno));
+
1155 return -1;
+
1156 }
+
1157 if (!S_ISREG(stbuf->st_mode)) {
+
1158 fprintf(stderr,
+
1159 "%s: mountpoint %s is no longer a regular file\n",
+
1160 progname, mnt);
+
1161 return -1;
+
1162 }
+
1163
+
1164 sprintf(procfile, "/proc/self/fd/%i", *mountpoint_fd);
+
1165 *mntp = procfile;
+
1166 } else {
+
1167 fprintf(stderr,
+
1168 "%s: mountpoint %s is not a directory or a regular file\n",
+
1169 progname, mnt);
+
1170 return -1;
+
1171 }
+
1172
+
1173 /* Do not permit mounting over anything in procfs - it has a couple
+
1174 * places to which we have "write access" without being supposed to be
+
1175 * able to just put anything we want there.
+
1176 * Luckily, without allow_other, we can't get other users to actually
+
1177 * use any fake information we try to put there anyway.
+
1178 * Use a whitelist to be safe. */
+
1179 if (statfs(*mntp, &fs_buf)) {
+
1180 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
+
1181 progname, mnt, strerror(errno));
+
1182 return -1;
+
1183 }
+
1184
+
1185 /* Define permitted filesystems for the mount target. This was
+
1186 * originally the same list as used by the ecryptfs mount helper
+
1187 * (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225)
+
1188 * but got expanded as we found more filesystems that needed to be
+
1189 * overlaid. */
+
1190 typeof(fs_buf.f_type) f_type_whitelist[] = {
+
1191 0x61756673 /* AUFS_SUPER_MAGIC */,
+
1192 0x00000187 /* AUTOFS_SUPER_MAGIC */,
+
1193 0xCA451A4E /* BCACHEFS_STATFS_MAGIC */,
+
1194 0x9123683E /* BTRFS_SUPER_MAGIC */,
+
1195 0x00C36400 /* CEPH_SUPER_MAGIC */,
+
1196 0xFF534D42 /* CIFS_MAGIC_NUMBER */,
+
1197 0x0000F15F /* ECRYPTFS_SUPER_MAGIC */,
+
1198 0X2011BAB0 /* EXFAT_SUPER_MAGIC */,
+
1199 0x0000EF53 /* EXT[234]_SUPER_MAGIC */,
+
1200 0xF2F52010 /* F2FS_SUPER_MAGIC */,
+
1201 0x65735546 /* FUSE_SUPER_MAGIC */,
+
1202 0x01161970 /* GFS2_MAGIC */,
+
1203 0x47504653 /* GPFS_SUPER_MAGIC */,
+
1204 0x0000482b /* HFSPLUS_SUPER_MAGIC */,
+
1205 0x000072B6 /* JFFS2_SUPER_MAGIC */,
+
1206 0x3153464A /* JFS_SUPER_MAGIC */,
+
1207 0x0BD00BD0 /* LL_SUPER_MAGIC */,
+
1208 0X00004D44 /* MSDOS_SUPER_MAGIC */,
+
1209 0x0000564C /* NCP_SUPER_MAGIC */,
+
1210 0x00006969 /* NFS_SUPER_MAGIC */,
+
1211 0x00003434 /* NILFS_SUPER_MAGIC */,
+
1212 0x5346544E /* NTFS_SB_MAGIC */,
+
1213 0x7366746E /* NTFS3_SUPER_MAGIC */,
+
1214 0x5346414f /* OPENAFS_SUPER_MAGIC */,
+
1215 0x794C7630 /* OVERLAYFS_SUPER_MAGIC */,
+
1216 0xAAD7AAEA /* PANFS_SUPER_MAGIC */,
+
1217 0x52654973 /* REISERFS_SUPER_MAGIC */,
+
1218 0xFE534D42 /* SMB2_SUPER_MAGIC */,
+
1219 0x73717368 /* SQUASHFS_MAGIC */,
+
1220 0x01021994 /* TMPFS_MAGIC */,
+
1221 0x24051905 /* UBIFS_SUPER_MAGIC */,
+
1222 0x18031977 /* WEKAFS_SUPER_MAGIC */,
+
1223#if __SIZEOF_LONG__ > 4
+
1224 0x736675005346544e /* UFSD */,
+
1225#endif
+
1226 0x58465342 /* XFS_SB_MAGIC */,
+
1227 0x2FC12FC1 /* ZFS_SUPER_MAGIC */,
+
1228 0x858458f6 /* RAMFS_MAGIC */,
+
1229 };
+
1230 for (i = 0; i < sizeof(f_type_whitelist)/sizeof(f_type_whitelist[0]); i++) {
+
1231 if (f_type_whitelist[i] == fs_buf.f_type)
+
1232 return 0;
+
1233 }
+
1234
+
1235 fprintf(stderr, "%s: mounting over filesystem type %#010lx is forbidden\n",
+
1236 progname, (unsigned long)fs_buf.f_type);
+
1237 return -1;
+
1238}
+
1239
+
1240static int open_fuse_device(const char *dev)
+
1241{
+
1242 int fd;
+
1243
+
1244 drop_privs();
+
1245 fd = open(dev, O_RDWR);
+
1246 if (fd == -1) {
+
1247 if (errno == ENODEV || errno == ENOENT)/* check for ENOENT too, for the udev case */
+
1248 fprintf(stderr,
+
1249 "%s: fuse device %s not found. Kernel module not loaded?\n",
+
1250 progname, dev);
+
1251 else
+
1252 fprintf(stderr,
+
1253 "%s: failed to open %s: %s\n", progname, dev, strerror(errno));
+
1254 }
+
1255 restore_privs();
+
1256 return fd;
+
1257}
+
1258
+
1259static int mount_fuse(const char *mnt, const char *opts, const char **type)
+
1260{
+
1261 int res;
+
1262 int fd;
+
1263 const char *dev = getenv(FUSE_KERN_DEVICE_ENV) ?: FUSE_DEV;
+
1264 struct stat stbuf;
+
1265 char *source = NULL;
+
1266 char *mnt_opts = NULL;
+
1267 const char *real_mnt = mnt;
+
1268 int mountpoint_fd = -1;
+
1269 char *do_mount_opts = NULL;
+
1270 char *x_opts = NULL;
+
1271
+
1272 fd = open_fuse_device(dev);
+
1273 if (fd == -1)
+
1274 return -1;
+
1275
+
1276 drop_privs();
+
1277 read_conf();
+
1278
+
1279 if (getuid() != 0 && mount_max != -1) {
+
1280 int mount_count = count_fuse_fs();
+
1281 if (mount_count >= mount_max) {
+
1282 fprintf(stderr, "%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n", progname, FUSE_CONF);
+
1283 goto fail_close_fd;
+
1284 }
+
1285 }
+
1286
+
1287 // Extract any options starting with "x-"
+
1288 res= extract_x_options(opts, &do_mount_opts, &x_opts);
+
1289 if (res)
+
1290 goto fail_close_fd;
+
1291
+
1292 res = check_perm(&real_mnt, &stbuf, &mountpoint_fd);
+
1293 restore_privs();
+
1294 if (res != -1)
+
1295 res = do_mount(real_mnt, type, stbuf.st_mode & S_IFMT,
+
1296 fd, do_mount_opts, dev, &source, &mnt_opts);
+
1297
+
1298 if (mountpoint_fd != -1)
+
1299 close(mountpoint_fd);
+
1300
+
1301 if (res == -1)
+
1302 goto fail_close_fd;
+
1303
+
1304 res = chdir("/");
+
1305 if (res == -1) {
+
1306 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1307 goto fail_close_fd;
+
1308 }
+
1309
+
1310 if (geteuid() == 0) {
+
1311 if (x_opts && strlen(x_opts) > 0) {
+
1312 /*
+
1313 * Add back the options starting with "x-" to opts from
+
1314 * do_mount. +2 for ',' and '\0'
+
1315 */
+
1316 size_t mnt_opts_len = strlen(mnt_opts);
+
1317 size_t x_mnt_opts_len = mnt_opts_len+
+
1318 strlen(x_opts) + 2;
+
1319 char *x_mnt_opts = calloc(1, x_mnt_opts_len);
+
1320
+
1321 if (mnt_opts_len) {
+
1322 strcpy(x_mnt_opts, mnt_opts);
+
1323 strncat(x_mnt_opts, ",", 2);
+
1324 }
+
1325
+
1326 strncat(x_mnt_opts, x_opts,
+
1327 x_mnt_opts_len - mnt_opts_len - 2);
+
1328
+
1329 free(mnt_opts);
+
1330 mnt_opts = x_mnt_opts;
+
1331 }
+
1332
+
1333 res = add_mount(source, mnt, *type, mnt_opts);
+
1334 if (res == -1) {
+
1335 /* Can't clean up mount in a non-racy way */
+
1336 goto fail_close_fd;
+
1337 }
+
1338 }
+
1339
+
1340out_free:
+
1341 free(source);
+
1342 free(mnt_opts);
+
1343 free(x_opts);
+
1344 free(do_mount_opts);
+
1345
+
1346 return fd;
+
1347
+
1348fail_close_fd:
+
1349 close(fd);
+
1350 fd = -1;
+
1351 goto out_free;
+
1352}
+
1353
+
1354static int send_fd(int sock_fd, int fd)
+
1355{
+
1356 int retval;
+
1357 struct msghdr msg;
+
1358 struct cmsghdr *p_cmsg;
+
1359 struct iovec vec;
+
1360 size_t cmsgbuf[CMSG_SPACE(sizeof(fd)) / sizeof(size_t)];
+
1361 int *p_fds;
+
1362 char sendchar = 0;
+
1363
+
1364 msg.msg_control = cmsgbuf;
+
1365 msg.msg_controllen = sizeof(cmsgbuf);
+
1366 p_cmsg = CMSG_FIRSTHDR(&msg);
+
1367 p_cmsg->cmsg_level = SOL_SOCKET;
+
1368 p_cmsg->cmsg_type = SCM_RIGHTS;
+
1369 p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+
1370 p_fds = (int *) CMSG_DATA(p_cmsg);
+
1371 *p_fds = fd;
+
1372 msg.msg_controllen = p_cmsg->cmsg_len;
+
1373 msg.msg_name = NULL;
+
1374 msg.msg_namelen = 0;
+
1375 msg.msg_iov = &vec;
+
1376 msg.msg_iovlen = 1;
+
1377 msg.msg_flags = 0;
+
1378 /* "To pass file descriptors or credentials you need to send/read at
+
1379 * least one byte" (man 7 unix) */
+
1380 vec.iov_base = &sendchar;
+
1381 vec.iov_len = sizeof(sendchar);
+
1382 while ((retval = sendmsg(sock_fd, &msg, 0)) == -1 && errno == EINTR);
+
1383 if (retval != 1) {
+
1384 perror("sending file descriptor");
+
1385 return -1;
+
1386 }
+
1387 return 0;
+
1388}
+
1389
+
1390/* Helper for should_auto_unmount
+
1391 *
+
1392 * fusermount typically has the s-bit set - initial open of `mnt` was as root
+
1393 * and got EACCESS as 'allow_other' was not specified.
+
1394 * Try opening `mnt` again with uid and guid of the calling process.
+
1395 */
+
1396static int recheck_ENOTCONN_as_owner(const char *mnt)
+
1397{
+
1398 int pid = fork();
+
1399 if(pid == -1) {
+
1400 perror("fuse: recheck_ENOTCONN_as_owner can't fork");
+
1401 _exit(EXIT_FAILURE);
+
1402 } else if(pid == 0) {
+
1403 uid_t uid = getuid();
+
1404 gid_t gid = getgid();
+
1405 if(setresgid(gid, gid, gid) == -1) {
+
1406 perror("fuse: can't set resgid");
+
1407 _exit(EXIT_FAILURE);
+
1408 }
+
1409 if(setresuid(uid, uid, uid) == -1) {
+
1410 perror("fuse: can't set resuid");
+
1411 _exit(EXIT_FAILURE);
+
1412 }
+
1413
+
1414 int fd = open(mnt, O_RDONLY);
+
1415 if(fd == -1 && errno == ENOTCONN)
+
1416 _exit(EXIT_SUCCESS);
+
1417 else
+
1418 _exit(EXIT_FAILURE);
+
1419 } else {
+
1420 int status;
+
1421 int res = waitpid(pid, &status, 0);
+
1422 if (res == -1) {
+
1423 perror("fuse: waiting for child failed");
+
1424 _exit(EXIT_FAILURE);
+
1425 }
+
1426 return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS;
+
1427 }
+
1428}
+
1429
+
1430/* The parent fuse process has died: decide whether to auto_unmount.
+
1431 *
+
1432 * In the normal case (umount or fusermount -u), the filesystem
+
1433 * has already been unmounted. If we simply unmount again we can
+
1434 * cause problems with stacked mounts (e.g. autofs).
+
1435 *
+
1436 * So we unmount here only in abnormal case where fuse process has
+
1437 * died without unmount happening. To detect this, we first look in
+
1438 * the mount table to make sure the mountpoint is still mounted and
+
1439 * has proper type. If so, we then see if opening the mount dir is
+
1440 * returning 'Transport endpoint is not connected'.
+
1441 *
+
1442 * The order of these is important, because if autofs is in use,
+
1443 * opening the dir to check for ENOTCONN will cause a new mount
+
1444 * in the normal case where filesystem has been unmounted cleanly.
+
1445 */
+
1446static int should_auto_unmount(const char *mnt, const char *type)
+
1447{
+
1448 char *copy;
+
1449 const char *last;
+
1450 int result = 0;
+
1451 int fd;
+
1452
+
1453 copy = strdup(mnt);
+
1454 if (copy == NULL) {
+
1455 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
1456 return 0;
+
1457 }
+
1458
+
1459 if (chdir_to_parent(copy, &last) == -1)
+
1460 goto out;
+
1461 if (check_is_mount(last, mnt, type) == -1)
+
1462 goto out;
+
1463
+
1464 fd = open(mnt, O_RDONLY);
+
1465
+
1466 if (fd != -1) {
+
1467 close(fd);
+
1468 } else {
+
1469 switch(errno) {
+
1470 case ENOTCONN:
+
1471 result = 1;
+
1472 break;
+
1473 case EACCES:
+
1474 result = recheck_ENOTCONN_as_owner(mnt);
+
1475 break;
+
1476 default:
+
1477 result = 0;
+
1478 break;
+
1479 }
+
1480 }
+
1481out:
+
1482 free(copy);
+
1483 return result;
+
1484}
+
1485
+
1486static void usage(void)
+
1487{
+
1488 printf("%s: [options] mountpoint\n"
+
1489 "Options:\n"
+
1490 " -h print help\n"
+
1491 " -V print version\n"
+
1492 " -o opt[,opt...] mount options\n"
+
1493 " -u unmount\n"
+
1494 " -q quiet\n"
+
1495 " -z lazy unmount\n",
+
1496 progname);
+
1497 exit(1);
+
1498}
+
1499
+
1500static void show_version(void)
+
1501{
+
1502 printf("fusermount3 version: %s\n", PACKAGE_VERSION);
+
1503 exit(0);
+
1504}
+
1505
+
1506static void close_range_loop(int min_fd, int max_fd, int cfd)
+
1507{
+
1508 for (int fd = min_fd; fd <= max_fd; fd++)
+
1509 if (fd != cfd)
+
1510 close(fd);
+
1511}
+
1512
+
1513/*
+
1514 * Close all inherited fds that are not needed
+
1515 * Ideally these wouldn't come up at all, applications should better
+
1516 * use FD_CLOEXEC / O_CLOEXEC
+
1517 */
+
1518static int close_inherited_fds(int cfd)
+
1519{
+
1520 int rc = -1;
+
1521 int nullfd;
+
1522
+
1523 /* We can't even report an error */
+
1524 if (cfd <= STDERR_FILENO)
+
1525 return -EINVAL;
+
1526
+
1527#ifdef HAVE_CLOSE_RANGE
+
1528 if (cfd < STDERR_FILENO + 2) {
+
1529 close_range_loop(STDERR_FILENO + 1, cfd - 1, cfd);
+
1530 } else {
+
1531 rc = close_range(STDERR_FILENO + 1, cfd - 1, 0);
+
1532 if (rc < 0)
+
1533 goto fallback;
+
1534 }
+
1535
+
1536 /* Close high range */
+
1537 rc = close_range(cfd + 1, ~0U, 0);
+
1538#else
+
1539 goto fallback; /* make use of fallback to avoid compiler warnings */
+
1540#endif
+
1541
+
1542fallback:
+
1543 if (rc < 0) {
+
1544 int max_fd = sysconf(_SC_OPEN_MAX) - 1;
+
1545
+
1546 close_range_loop(STDERR_FILENO + 1, max_fd, cfd);
+
1547 }
+
1548
+
1549 nullfd = open("/dev/null", O_RDWR);
+
1550 if (nullfd < 0) {
+
1551 perror("fusermount: cannot open /dev/null");
+
1552 return -errno;
+
1553 }
+
1554
+
1555 /* Redirect stdin, stdout, stderr to /dev/null */
+
1556 dup2(nullfd, STDIN_FILENO);
+
1557 dup2(nullfd, STDOUT_FILENO);
+
1558 dup2(nullfd, STDERR_FILENO);
+
1559 if (nullfd > STDERR_FILENO)
+
1560 close(nullfd);
+
1561
+
1562 return 0;
+
1563}
+
1564
+
1565int main(int argc, char *argv[])
+
1566{
+
1567 sigset_t sigset;
+
1568 int ch;
+
1569 int fd;
+
1570 int res;
+
1571 char *origmnt;
+
1572 char *mnt;
+
1573 static int unmount = 0;
+
1574 static int lazy = 0;
+
1575 static int quiet = 0;
+
1576 char *commfd = NULL;
+
1577 long cfd;
+
1578 const char *opts = "";
+
1579 const char *type = NULL;
+
1580 int setup_auto_unmount_only = 0;
+
1581
+
1582 static const struct option long_opts[] = {
+
1583 {"unmount", no_argument, NULL, 'u'},
+
1584 {"lazy", no_argument, NULL, 'z'},
+
1585 {"quiet", no_argument, NULL, 'q'},
+
1586 {"help", no_argument, NULL, 'h'},
+
1587 {"version", no_argument, NULL, 'V'},
+
1588 {"options", required_argument, NULL, 'o'},
+
1589 // Note: auto-unmount and comm-fd don't have short versions.
+
1590 // They'ne meant for internal use by mount.c
+
1591 {"auto-unmount", no_argument, NULL, 'U'},
+
1592 {"comm-fd", required_argument, NULL, 'c'},
+
1593 {0, 0, 0, 0}};
+
1594
+
1595 progname = strdup(argc > 0 ? argv[0] : "fusermount");
+
1596 if (progname == NULL) {
+
1597 fprintf(stderr, "%s: failed to allocate memory\n", argv[0]);
+
1598 exit(1);
+
1599 }
+
1600
+
1601 while ((ch = getopt_long(argc, argv, "hVo:uzq", long_opts,
+
1602 NULL)) != -1) {
+
1603 switch (ch) {
+
1604 case 'h':
+
1605 usage();
+
1606 break;
+
1607
+
1608 case 'V':
+
1609 show_version();
+
1610 break;
+
1611
+
1612 case 'o':
+
1613 opts = optarg;
+
1614 break;
+
1615
+
1616 case 'u':
+
1617 unmount = 1;
+
1618 break;
+
1619 case 'U':
+
1620 unmount = 1;
+
1621 auto_unmount = 1;
+
1622 setup_auto_unmount_only = 1;
+
1623 break;
+
1624 case 'c':
+
1625 commfd = optarg;
+
1626 break;
+
1627 case 'z':
+
1628 lazy = 1;
+
1629 break;
+
1630
+
1631 case 'q':
+
1632 quiet = 1;
+
1633 break;
+
1634
+
1635 default:
+
1636 exit(1);
+
1637 }
+
1638 }
+
1639
+
1640 if (lazy && !unmount) {
+
1641 fprintf(stderr, "%s: -z can only be used with -u\n", progname);
+
1642 exit(1);
+
1643 }
+
1644
+
1645 if (optind >= argc) {
+
1646 fprintf(stderr, "%s: missing mountpoint argument\n", progname);
+
1647 exit(1);
+
1648 } else if (argc > optind + 1) {
+
1649 fprintf(stderr, "%s: extra arguments after the mountpoint\n",
+
1650 progname);
+
1651 exit(1);
+
1652 }
+
1653
+
1654 origmnt = argv[optind];
+
1655
+
1656 drop_privs();
+
1657 mnt = fuse_mnt_resolve_path(progname, origmnt);
+
1658 if (mnt != NULL) {
+
1659 res = chdir("/");
+
1660 if (res == -1) {
+
1661 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1662 goto err_out;
+
1663 }
+
1664 }
+
1665 restore_privs();
+
1666 if (mnt == NULL)
+
1667 exit(1);
+
1668
+
1669 umask(033);
+
1670 if (!setup_auto_unmount_only && unmount)
+
1671 goto do_unmount;
+
1672
+
1673 if(commfd == NULL)
+
1674 commfd = getenv(FUSE_COMMFD_ENV);
+
1675 if (commfd == NULL) {
+
1676 fprintf(stderr, "%s: old style mounting not supported\n",
+
1677 progname);
+
1678 goto err_out;
+
1679 }
+
1680
+
1681 res = libfuse_strtol(commfd, &cfd);
+
1682 if (res) {
+
1683 fprintf(stderr,
+
1684 "%s: invalid _FUSE_COMMFD: %s\n",
+
1685 progname, commfd);
+
1686 goto err_out;
+
1687
+
1688 }
+
1689
+
1690 {
+
1691 struct stat statbuf;
+
1692 fstat(cfd, &statbuf);
+
1693 if(!S_ISSOCK(statbuf.st_mode)) {
+
1694 fprintf(stderr,
+
1695 "%s: file descriptor %li is not a socket, can't send fuse fd\n",
+
1696 progname, cfd);
+
1697 goto err_out;
+
1698 }
+
1699 }
+
1700
+
1701 if (setup_auto_unmount_only)
+
1702 goto wait_for_auto_unmount;
+
1703
+
1704 fd = mount_fuse(mnt, opts, &type);
+
1705 if (fd == -1)
+
1706 goto err_out;
+
1707
+
1708 res = send_fd(cfd, fd);
+
1709 if (res != 0) {
+
1710 umount2(mnt, MNT_DETACH); /* lazy umount */
+
1711 goto err_out;
+
1712 }
+
1713 close(fd);
+
1714
+
1715 if (!auto_unmount) {
+
1716 free(mnt);
+
1717 free((void*) type);
+
1718 return 0;
+
1719 }
+
1720
+
1721wait_for_auto_unmount:
+
1722 /* Become a daemon and wait for the parent to exit or die.
+
1723 ie For the control socket to get closed.
+
1724 Btw, we don't want to use daemon() function here because
+
1725 it forks and messes with the file descriptors. */
+
1726
+
1727 res = close_inherited_fds(cfd);
+
1728 if (res < 0)
+
1729 exit(EXIT_FAILURE);
+
1730
+
1731 setsid();
+
1732 res = chdir("/");
+
1733 if (res == -1) {
+
1734 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1735 goto err_out;
+
1736 }
+
1737
+
1738 sigfillset(&sigset);
+
1739 sigprocmask(SIG_BLOCK, &sigset, NULL);
+
1740
+
1741 lazy = 1;
+
1742 quiet = 1;
+
1743
+
1744 while (1) {
+
1745 unsigned char buf[16];
+
1746 int n = recv(cfd, buf, sizeof(buf), 0);
+
1747 if (!n)
+
1748 break;
+
1749
+
1750 if (n < 0) {
+
1751 if (errno == EINTR)
+
1752 continue;
+
1753 break;
+
1754 }
+
1755 }
+
1756
+
1757 if (!should_auto_unmount(mnt, type)) {
+
1758 goto success_out;
+
1759 }
+
1760
+
1761do_unmount:
+
1762 if (geteuid() == 0)
+
1763 res = unmount_fuse(mnt, quiet, lazy);
+
1764 else {
+
1765 res = umount2(mnt, lazy ? UMOUNT_DETACH : 0);
+
1766 if (res == -1 && !quiet)
+
1767 fprintf(stderr,
+
1768 "%s: failed to unmount %s: %s\n",
+
1769 progname, mnt, strerror(errno));
+
1770 }
+
1771 if (res == -1)
+
1772 goto err_out;
+
1773
+
1774success_out:
+
1775 free((void*) type);
+
1776 free(mnt);
+
1777 return 0;
+
1778
+
1779err_out:
+
1780 free((void*) type);
+
1781 free(mnt);
+
1782 exit(1);
+
1783}
+
+ + + + diff --git a/doc/html/fuse-3_818_82_2util_2mount_8fuse_8c_source.html b/doc/html/fuse-3_818_82_2util_2mount_8fuse_8c_source.html new file mode 100644 index 0000000..c0e23bd --- /dev/null +++ b/doc/html/fuse-3_818_82_2util_2mount_8fuse_8c_source.html @@ -0,0 +1,515 @@ + + + + + + + +libfuse: fuse-3.18.2/util/mount.fuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount.fuse.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
9#include "fuse_config.h"
+
10
+
11#include <stdio.h>
+
12#include <stdlib.h>
+
13#include <string.h>
+
14#include <unistd.h>
+
15#include <errno.h>
+
16#include <stdint.h>
+
17#include <fcntl.h>
+
18#include <pwd.h>
+
19#include <sys/wait.h>
+
20
+
21#ifdef linux
+
22#include <sys/prctl.h>
+
23#include <sys/syscall.h>
+
24#include <linux/capability.h>
+
25#include <linux/securebits.h>
+
26/* for 2.6 kernels */
+
27#if !defined(SECBIT_KEEP_CAPS) && defined(SECURE_KEEP_CAPS)
+
28#define SECBIT_KEEP_CAPS (issecure_mask(SECURE_KEEP_CAPS))
+
29#endif
+
30#if !defined(SECBIT_KEEP_CAPS_LOCKED) && defined(SECURE_KEEP_CAPS_LOCKED)
+
31#define SECBIT_KEEP_CAPS_LOCKED (issecure_mask(SECURE_KEEP_CAPS_LOCKED))
+
32#endif
+
33#if !defined(SECBIT_NO_SETUID_FIXUP) && defined(SECURE_NO_SETUID_FIXUP)
+
34#define SECBIT_NO_SETUID_FIXUP (issecure_mask(SECURE_NO_SETUID_FIXUP))
+
35#endif
+
36#if !defined(SECBIT_NO_SETUID_FIXUP_LOCKED) && defined(SECURE_NO_SETUID_FIXUP_LOCKED)
+
37#define SECBIT_NO_SETUID_FIXUP_LOCKED (issecure_mask(SECURE_NO_SETUID_FIXUP_LOCKED))
+
38#endif
+
39#if !defined(SECBIT_NOROOT) && defined(SECURE_NOROOT)
+
40#define SECBIT_NOROOT (issecure_mask(SECURE_NOROOT))
+
41#endif
+
42#if !defined(SECBIT_NOROOT_LOCKED) && defined(SECURE_NOROOT_LOCKED)
+
43#define SECBIT_NOROOT_LOCKED (issecure_mask(SECURE_NOROOT_LOCKED))
+
44#endif
+
45#endif
+
46/* linux < 3.5 */
+
47#ifndef PR_SET_NO_NEW_PRIVS
+
48#define PR_SET_NO_NEW_PRIVS 38
+
49#endif
+
50
+
51#include "fuse.h"
+
52
+
53static char *progname;
+
54
+
55static char *xstrdup(const char *s)
+
56{
+
57 char *t = strdup(s);
+
58 if (!t) {
+
59 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
60 exit(1);
+
61 }
+
62 return t;
+
63}
+
64
+
65static void *xrealloc(void *oldptr, size_t size)
+
66{
+
67 void *ptr = realloc(oldptr, size);
+
68 if (!ptr) {
+
69 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
70 exit(1);
+
71 }
+
72 return ptr;
+
73}
+
74
+
75static void add_arg(char **cmdp, const char *opt)
+
76{
+
77 size_t optlen = strlen(opt);
+
78 size_t cmdlen = *cmdp ? strlen(*cmdp) : 0;
+
79 if (optlen >= (SIZE_MAX - cmdlen - 4)/4) {
+
80 fprintf(stderr, "%s: argument too long\n", progname);
+
81 exit(1);
+
82 }
+
83 char *cmd = xrealloc(*cmdp, cmdlen + optlen * 4 + 4);
+
84 char *s;
+
85 s = cmd + cmdlen;
+
86 if (*cmdp)
+
87 *s++ = ' ';
+
88
+
89 *s++ = '\'';
+
90 for (; *opt; opt++) {
+
91 if (*opt == '\'') {
+
92 *s++ = '\'';
+
93 *s++ = '\\';
+
94 *s++ = '\'';
+
95 *s++ = '\'';
+
96 } else
+
97 *s++ = *opt;
+
98 }
+
99 *s++ = '\'';
+
100 *s = '\0';
+
101 *cmdp = cmd;
+
102}
+
103
+
104static char *add_option(const char *opt, char *options)
+
105{
+
106 int oldlen = options ? strlen(options) : 0;
+
107
+
108 options = xrealloc(options, oldlen + 1 + strlen(opt) + 1);
+
109 if (!oldlen)
+
110 strcpy(options, opt);
+
111 else {
+
112 strcat(options, ",");
+
113 strcat(options, opt);
+
114 }
+
115 return options;
+
116}
+
117
+
118static int prepare_fuse_fd(const char *mountpoint, const char* subtype,
+
119 const char *options)
+
120{
+
121 int fuse_fd = -1;
+
122 int flags = -1;
+
123 int subtype_len = strlen(subtype) + 9;
+
124 char* options_copy = xrealloc(NULL, subtype_len);
+
125
+
126 snprintf(options_copy, subtype_len, "subtype=%s", subtype);
+
127 options_copy = add_option(options, options_copy);
+
128 fuse_fd = fuse_open_channel(mountpoint, options_copy);
+
129 if (fuse_fd == -1) {
+
130 exit(1);
+
131 }
+
132
+
133 flags = fcntl(fuse_fd, F_GETFD);
+
134 if (flags == -1 || fcntl(fuse_fd, F_SETFD, flags & ~FD_CLOEXEC) == 1) {
+
135 fprintf(stderr, "%s: Failed to clear CLOEXEC: %s\n",
+
136 progname, strerror(errno));
+
137 exit(1);
+
138 }
+
139
+
140 return fuse_fd;
+
141}
+
142
+
143#ifdef linux
+
144static uint64_t get_capabilities(void)
+
145{
+
146 /*
+
147 * This invokes the capset syscall directly to avoid the libcap
+
148 * dependency, which isn't really justified just for this.
+
149 */
+
150 struct __user_cap_header_struct header = {
+
151 .version = _LINUX_CAPABILITY_VERSION_3,
+
152 .pid = 0,
+
153 };
+
154 struct __user_cap_data_struct data[2];
+
155 memset(data, 0, sizeof(data));
+
156 if (syscall(SYS_capget, &header, data) == -1) {
+
157 fprintf(stderr, "%s: Failed to get capabilities: %s\n",
+
158 progname, strerror(errno));
+
159 exit(1);
+
160 }
+
161
+
162 return data[0].effective | ((uint64_t) data[1].effective << 32);
+
163}
+
164
+
165static void set_capabilities(uint64_t caps)
+
166{
+
167 /*
+
168 * This invokes the capset syscall directly to avoid the libcap
+
169 * dependency, which isn't really justified just for this.
+
170 */
+
171 struct __user_cap_header_struct header = {
+
172 .version = _LINUX_CAPABILITY_VERSION_3,
+
173 .pid = 0,
+
174 };
+
175 struct __user_cap_data_struct data[2];
+
176 memset(data, 0, sizeof(data));
+
177 data[0].effective = data[0].permitted = caps;
+
178 data[1].effective = data[1].permitted = caps >> 32;
+
179 if (syscall(SYS_capset, &header, data) == -1) {
+
180 fprintf(stderr, "%s: Failed to set capabilities: %s\n",
+
181 progname, strerror(errno));
+
182 exit(1);
+
183 }
+
184}
+
185
+
186static void drop_and_lock_capabilities(void)
+
187{
+
188 /* Set and lock securebits. */
+
189 if (prctl(PR_SET_SECUREBITS,
+
190 SECBIT_KEEP_CAPS_LOCKED |
+
191 SECBIT_NO_SETUID_FIXUP |
+
192 SECBIT_NO_SETUID_FIXUP_LOCKED |
+
193 SECBIT_NOROOT |
+
194 SECBIT_NOROOT_LOCKED) == -1) {
+
195 fprintf(stderr, "%s: Failed to set securebits %s\n",
+
196 progname, strerror(errno));
+
197 exit(1);
+
198 }
+
199
+
200 /* Clear the capability bounding set. */
+
201 int cap;
+
202 for (cap = 0; ; cap++) {
+
203 int cap_status = prctl(PR_CAPBSET_READ, cap);
+
204 if (cap_status == 0) {
+
205 continue;
+
206 }
+
207 if (cap_status == -1 && errno == EINVAL) {
+
208 break;
+
209 }
+
210
+
211 if (cap_status != 1) {
+
212 fprintf(stderr,
+
213 "%s: Failed to get capability %u: %s\n",
+
214 progname, cap, strerror(errno));
+
215 exit(1);
+
216 }
+
217 if (prctl(PR_CAPBSET_DROP, cap) == -1) {
+
218 fprintf(stderr,
+
219 "%s: Failed to drop capability %u: %s\n",
+
220 progname, cap, strerror(errno));
+
221 }
+
222 }
+
223
+
224 /* Drop capabilities. */
+
225 set_capabilities(0);
+
226
+
227 /* Prevent re-acquisition of privileges. */
+
228 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
+
229 fprintf(stderr, "%s: Failed to set no_new_privs: %s\n",
+
230 progname, strerror(errno));
+
231 exit(1);
+
232 }
+
233}
+
234#endif
+
235
+
236int main(int argc, char *argv[])
+
237{
+
238 char *type = NULL;
+
239 char *source;
+
240 char *dup_source = NULL;
+
241 const char *mountpoint;
+
242 char *basename;
+
243 char *options = NULL;
+
244 char *command = NULL;
+
245 char *setuid_name = NULL;
+
246 int i;
+
247 int dev = 1;
+
248 int suid = 1;
+
249 int pass_fuse_fd = 0;
+
250 int fuse_fd = 0;
+
251 int drop_privileges = 0;
+
252 char *dev_fd_mountpoint = NULL;
+
253
+
254 progname = argv[0];
+
255 basename = strrchr(argv[0], '/');
+
256 if (basename)
+
257 basename++;
+
258 else
+
259 basename = argv[0];
+
260
+
261 if (strncmp(basename, "mount.fuse.", 11) == 0)
+
262 type = basename + 11;
+
263 if (strncmp(basename, "mount.fuseblk.", 14) == 0)
+
264 type = basename + 14;
+
265
+
266 if (type && !type[0])
+
267 type = NULL;
+
268
+
269 if (argc < 3) {
+
270 fprintf(stderr,
+
271 "usage: %s %s destination [-t type] [-o opt[,opts...]]\n",
+
272 progname, type ? "source" : "type#[source]");
+
273 exit(1);
+
274 }
+
275
+
276 source = argv[1];
+
277 if (!source[0])
+
278 source = NULL;
+
279
+
280 mountpoint = argv[2];
+
281
+
282 for (i = 3; i < argc; i++) {
+
283 if (strcmp(argv[i], "-v") == 0) {
+
284 continue;
+
285 } else if (strcmp(argv[i], "-t") == 0) {
+
286 i++;
+
287
+
288 if (i == argc) {
+
289 fprintf(stderr,
+
290 "%s: missing argument to option '-t'\n",
+
291 progname);
+
292 exit(1);
+
293 }
+
294 type = argv[i];
+
295 if (strncmp(type, "fuse.", 5) == 0)
+
296 type += 5;
+
297 else if (strncmp(type, "fuseblk.", 8) == 0)
+
298 type += 8;
+
299
+
300 if (!type[0]) {
+
301 fprintf(stderr,
+
302 "%s: empty type given as argument to option '-t'\n",
+
303 progname);
+
304 exit(1);
+
305 }
+
306 } else if (strcmp(argv[i], "-o") == 0) {
+
307 char *opts;
+
308 char *opt;
+
309 i++;
+
310 if (i == argc)
+
311 break;
+
312
+
313 opts = xstrdup(argv[i]);
+
314 opt = strtok(opts, ",");
+
315 while (opt) {
+
316 int j;
+
317 int ignore = 0;
+
318 const char *ignore_opts[] = { "",
+
319 "user",
+
320 "nofail",
+
321 "nouser",
+
322 "users",
+
323 "auto",
+
324 "noauto",
+
325 "_netdev",
+
326 NULL};
+
327 if (strncmp(opt, "setuid=", 7) == 0) {
+
328 setuid_name = xstrdup(opt + 7);
+
329 ignore = 1;
+
330 } else if (strcmp(opt,
+
331 "drop_privileges") == 0) {
+
332 pass_fuse_fd = 1;
+
333 drop_privileges = 1;
+
334 ignore = 1;
+
335 }
+
336 for (j = 0; ignore_opts[j]; j++)
+
337 if (strcmp(opt, ignore_opts[j]) == 0)
+
338 ignore = 1;
+
339
+
340 if (!ignore) {
+
341 if (strcmp(opt, "nodev") == 0)
+
342 dev = 0;
+
343 else if (strcmp(opt, "nosuid") == 0)
+
344 suid = 0;
+
345
+
346 options = add_option(opt, options);
+
347 }
+
348 opt = strtok(NULL, ",");
+
349 }
+
350 free(opts);
+
351 }
+
352 }
+
353
+
354 if (drop_privileges) {
+
355 uint64_t required_caps = CAP_TO_MASK(CAP_SETPCAP) |
+
356 CAP_TO_MASK(CAP_SYS_ADMIN);
+
357 if ((get_capabilities() & required_caps) != required_caps) {
+
358 fprintf(stderr, "%s: drop_privileges was requested, which launches the FUSE file system fully unprivileged. In order to do so %s must be run with privileges, please invoke with CAP_SYS_ADMIN and CAP_SETPCAP (e.g. as root).\n",
+
359 progname, progname);
+
360 exit(1);
+
361 }
+
362 }
+
363
+
364 if (dev)
+
365 options = add_option("dev", options);
+
366 if (suid)
+
367 options = add_option("suid", options);
+
368
+
369 if (!type) {
+
370 if (source) {
+
371 dup_source = xstrdup(source);
+
372 type = dup_source;
+
373 source = strchr(type, '#');
+
374 if (source)
+
375 *source++ = '\0';
+
376 if (!type[0]) {
+
377 fprintf(stderr, "%s: empty filesystem type\n",
+
378 progname);
+
379 exit(1);
+
380 }
+
381 } else {
+
382 fprintf(stderr, "%s: empty source\n", progname);
+
383 exit(1);
+
384 }
+
385 }
+
386
+
387 if (setuid_name && setuid_name[0]) {
+
388#ifdef linux
+
389 if (drop_privileges) {
+
390 /*
+
391 * Make securebits more permissive before calling
+
392 * setuid(). Specifically, if SECBIT_KEEP_CAPS and
+
393 * SECBIT_NO_SETUID_FIXUP weren't set, setuid() would
+
394 * have the side effect of dropping all capabilities,
+
395 * and we need to retain CAP_SETPCAP in order to drop
+
396 * all privileges before exec().
+
397 */
+
398 if (prctl(PR_SET_SECUREBITS,
+
399 SECBIT_KEEP_CAPS |
+
400 SECBIT_NO_SETUID_FIXUP) == -1) {
+
401 fprintf(stderr,
+
402 "%s: Failed to set securebits %s\n",
+
403 progname, strerror(errno));
+
404 exit(1);
+
405 }
+
406 }
+
407#endif
+
408
+
409 struct passwd *pwd = getpwnam(setuid_name);
+
410 if (!pwd || setgid(pwd->pw_gid) == -1 || setuid(pwd->pw_uid) == -1) {
+
411 fprintf(stderr, "%s: Failed to setuid to %s: %s\n",
+
412 progname, setuid_name, strerror(errno));
+
413 exit(1);
+
414 }
+
415 } else if (!getenv("HOME")) {
+
416 /* Hack to make filesystems work in the boot environment */
+
417 setenv("HOME", "/root", 0);
+
418 }
+
419
+
420 if (pass_fuse_fd) {
+
421 fuse_fd = prepare_fuse_fd(mountpoint, type, options);
+
422 dev_fd_mountpoint = xrealloc(NULL, 20);
+
423 snprintf(dev_fd_mountpoint, 20, "/dev/fd/%u", fuse_fd);
+
424 mountpoint = dev_fd_mountpoint;
+
425 }
+
426
+
427#ifdef linux
+
428 if (drop_privileges) {
+
429 drop_and_lock_capabilities();
+
430 }
+
431#endif
+
432 add_arg(&command, type);
+
433 if (source)
+
434 add_arg(&command, source);
+
435 add_arg(&command, mountpoint);
+
436 if (options) {
+
437 add_arg(&command, "-o");
+
438 add_arg(&command, options);
+
439 }
+
440
+
441 free(options);
+
442 free(dev_fd_mountpoint);
+
443 free(dup_source);
+
444 free(setuid_name);
+
445
+
446 execl("/bin/sh", "/bin/sh", "-c", command, NULL);
+
447 fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
+
448 strerror(errno));
+
449
+
450 if (pass_fuse_fd)
+
451 close(fuse_fd);
+
452 free(command);
+
453 return 1;
+
454}
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:482
+
+ + + + diff --git a/doc/html/globals.html b/doc/html/globals.html new file mode 100644 index 0000000..3146349 --- /dev/null +++ b/doc/html/globals.html @@ -0,0 +1,210 @@ + + + + + + + +libfuse: Globals + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Here is a list of all documented functions, variables, defines, enums, and typedefs with links to the documentation:
+ +

- f -

+
+ + + + diff --git a/doc/html/globals_defs.html b/doc/html/globals_defs.html new file mode 100644 index 0000000..3d68a63 --- /dev/null +++ b/doc/html/globals_defs.html @@ -0,0 +1,92 @@ + + + + + + + +libfuse: Globals + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Here is a list of all documented macros with links to the documentation:
+ +

- f -

+
+ + + + diff --git a/doc/html/globals_enum.html b/doc/html/globals_enum.html new file mode 100644 index 0000000..f0980bc --- /dev/null +++ b/doc/html/globals_enum.html @@ -0,0 +1,56 @@ + + + + + + + +libfuse: Globals + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Here is a list of all documented enums with links to the documentation:
+
+ + + + diff --git a/doc/html/globals_eval.html b/doc/html/globals_eval.html new file mode 100644 index 0000000..e04cd97 --- /dev/null +++ b/doc/html/globals_eval.html @@ -0,0 +1,59 @@ + + + + + + + +libfuse: Globals + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Here is a list of all documented enum values with links to the documentation:
+
+ + + + diff --git a/doc/html/globals_func.html b/doc/html/globals_func.html new file mode 100644 index 0000000..503783e --- /dev/null +++ b/doc/html/globals_func.html @@ -0,0 +1,148 @@ + + + + + + + +libfuse: Globals + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Here is a list of all documented functions with links to the documentation:
+ +

- f -

+
+ + + + diff --git a/doc/html/globals_type.html b/doc/html/globals_type.html new file mode 100644 index 0000000..23143c8 --- /dev/null +++ b/doc/html/globals_type.html @@ -0,0 +1,57 @@ + + + + + + + +libfuse: Globals + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Here is a list of all documented typedefs with links to the documentation:
+
+ + + + diff --git a/doc/html/include_2cuse__lowlevel_8h_source.html b/doc/html/include_2cuse__lowlevel_8h_source.html new file mode 100644 index 0000000..d750158 --- /dev/null +++ b/doc/html/include_2cuse__lowlevel_8h_source.html @@ -0,0 +1,152 @@ + + + + + + + +libfuse: include/cuse_lowlevel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_lowlevel.h
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file LGPL2.txt.
+
8
+
9 Read example/cusexmp.c for usages.
+
10*/
+
11
+
12#ifndef CUSE_LOWLEVEL_H_
+
13#define CUSE_LOWLEVEL_H_
+
14
+
15#ifndef FUSE_USE_VERSION
+
16#define FUSE_USE_VERSION 29
+
17#endif
+
18
+
19#include "fuse_lowlevel.h"
+
20
+
21#include <fcntl.h>
+
22#include <sys/types.h>
+
23#include <sys/uio.h>
+
24
+
25#ifdef __cplusplus
+
26extern "C" {
+
27#endif
+
28
+
29#define CUSE_UNRESTRICTED_IOCTL (1 << 0) /* use unrestricted ioctl */
+
30
+
31struct fuse_session;
+
32
+
33struct cuse_info {
+
34 unsigned dev_major;
+
35 unsigned dev_minor;
+
36 unsigned dev_info_argc;
+
37 const char **dev_info_argv;
+
38 unsigned flags;
+
39};
+
40
+
41/*
+
42 * Most ops behave almost identically to the matching fuse_lowlevel
+
43 * ops except that they don't take @ino.
+
44 *
+
45 * init_done : called after initialization is complete
+
46 * read/write : always direct IO, simultaneous operations allowed
+
47 * ioctl : might be in unrestricted mode depending on ci->flags
+
48 */
+
49struct cuse_lowlevel_ops {
+
50 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
51 void (*init_done) (void *userdata);
+
52 void (*destroy) (void *userdata);
+
53 void (*open) (fuse_req_t req, struct fuse_file_info *fi);
+
54 void (*read) (fuse_req_t req, size_t size, off_t off,
+
55 struct fuse_file_info *fi);
+
56 void (*write) (fuse_req_t req, const char *buf, size_t size, off_t off,
+
57 struct fuse_file_info *fi);
+
58 void (*flush) (fuse_req_t req, struct fuse_file_info *fi);
+
59 void (*release) (fuse_req_t req, struct fuse_file_info *fi);
+
60 void (*fsync) (fuse_req_t req, int datasync, struct fuse_file_info *fi);
+
61 void (*ioctl) (fuse_req_t req, int cmd, void *arg,
+
62 struct fuse_file_info *fi, unsigned int flags,
+
63 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
64 void (*poll) (fuse_req_t req, struct fuse_file_info *fi,
+
65 struct fuse_pollhandle *ph);
+
66};
+
67
+
68struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
69 const struct cuse_info *ci,
+
70 const struct cuse_lowlevel_ops *clop,
+
71 void *userdata);
+
72
+
73struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
74 const struct cuse_info *ci,
+
75 const struct cuse_lowlevel_ops *clop,
+
76 int *multithreaded, void *userdata);
+
77
+
78void cuse_lowlevel_teardown(struct fuse_session *se);
+
79
+
80int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
81 const struct cuse_lowlevel_ops *clop, void *userdata);
+
82
+
83#ifdef __cplusplus
+
84}
+
85#endif
+
86
+
87#endif /* CUSE_LOWLEVEL_H_ */
+
struct fuse_req * fuse_req_t
+ + + + +
+ + + + diff --git a/doc/html/include_2fuse_8h.html b/doc/html/include_2fuse_8h.html new file mode 100644 index 0000000..2496d00 --- /dev/null +++ b/doc/html/include_2fuse_8h.html @@ -0,0 +1,839 @@ + + + + + + + +libfuse: include/fuse.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse.h File Reference
+
+
+
#include "fuse_common.h"
+#include <fcntl.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + + + +

+Data Structures

struct  fuse_config
 
struct  fuse_operations
 
struct  fuse_context
 
+ + + +

+Macros

#define FUSE_REGISTER_MODULE(name_, factory_)    fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
 
+ + + + + +

+Typedefs

typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
 
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
 
+ + + + + +

+Enumerations

enum  fuse_readdir_flags { FUSE_READDIR_DEFAULTS = 0 +, FUSE_READDIR_PLUS = (1 << 0) + }
 
enum  fuse_fill_dir_flags { FUSE_FILL_DIR_DEFAULTS = 0 +, FUSE_FILL_DIR_PLUS = (1 << 1) + }
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

int fuse_main_real_versioned (int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
 
void fuse_lib_help (struct fuse_args *args)
 
int fuse_mount (struct fuse *f, const char *mountpoint)
 
void fuse_unmount (struct fuse *f)
 
void fuse_destroy (struct fuse *f)
 
int fuse_loop (struct fuse *f)
 
void fuse_exit (struct fuse *f)
 
struct fuse_contextfuse_get_context (void)
 
int fuse_getgroups (int size, gid_t list[])
 
int fuse_interrupted (void)
 
int fuse_invalidate_path (struct fuse *f, const char *path)
 
int fuse_start_cleanup_thread (struct fuse *fuse)
 
void fuse_stop_cleanup_thread (struct fuse *fuse)
 
int fuse_clean_cache (struct fuse *fuse)
 
struct fuse_fs * fuse_fs_new (const struct fuse_operations *op, size_t op_size, void *private_data)
 
struct fuse_session * fuse_get_session (struct fuse *f)
 
int fuse_open_channel (const char *mountpoint, const char *options)
 
+

Detailed Description

+

This file defines the library interface of FUSE

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header.

+ +

Definition in file fuse.h.

+

Macro Definition Documentation

+ +

◆ FUSE_REGISTER_MODULE

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_REGISTER_MODULE( name_,
 factory_ 
)    fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+

Register filesystem module

+

If the "-omodules=*name*_:..." option is present, filesystem objects are created and pushed onto the stack with the factory_ function.

+
Parameters
+ + + +
name_the name of this filesystem module
factory_the factory function for this filesystem module
+
+
+ +

Definition at line 1414 of file fuse.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_fill_dir_t

+ +
+
+ + + + +
typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
+
+

Function to add an entry in a readdir() operation

+

The off parameter can be any non-zero value that enables the filesystem to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to indicate that seeking in directories is not supported.

+
Parameters
+ + + + + + +
bufthe buffer passed to the readdir() operation
namethe file name of the directory entry
stbuffile attributes, can be NULL
offoffset of the next entry or zero
flagsfill flags
+
+
+
Returns
1 if buffer is full, zero otherwise
+ +

Definition at line 93 of file fuse.h.

+ +
+
+ +

◆ fuse_module_factory_t

+ +
+
+ + + + +
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
+
+

Factory for creating filesystem objects

+

The function may use and remove options from 'args' that belong to this module.

+

For now the 'fs' vector always contains exactly one filesystem. This is the filesystem which will be below the newly created filesystem in the stack.

+
Parameters
+ + + +
argsthe command line arguments
fsNULL terminated filesystem object vector
+
+
+
Returns
the new filesystem object
+ +

Definition at line 1385 of file fuse.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_fill_dir_flags

+ +
+
+ + + + +
enum fuse_fill_dir_flags
+
+

Readdir flags, passed to fuse_fill_dir_t callback.

+ + +
Enumerator
FUSE_FILL_DIR_DEFAULTS 

"Plus" mode: file attributes are valid

+

The attributes are used by the kernel to prefill the inode cache during a readdir.

+

It is okay to set FUSE_FILL_DIR_PLUS if FUSE_READDIR_PLUS is not set and vice versa.

+

This does not make libfuse honor the 'st_ino' field. That is controlled by the 'use_ino' option instead.

+
+ +

Definition at line 61 of file fuse.h.

+ +
+
+ +

◆ fuse_readdir_flags

+ +
+
+ + + + +
enum fuse_readdir_flags
+
+

Readdir flags, passed to ->readdir()

+ + +
Enumerator
FUSE_READDIR_DEFAULTS 

"Plus" mode.

+

The kernel wants to prefill the inode cache during readdir. The filesystem may honour this by filling in the attributes and setting FUSE_FILL_DIR_FLAGS for the filler function. The filesystem may also just ignore this flag completely.

+
+ +

Definition at line 45 of file fuse.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_clean_cache()

+ +
+
+ + + + + + + + +
int fuse_clean_cache (struct fuse * fuse)
+
+

Iterate over cache removing stale entries use in conjunction with "-oremember"

+

NOTE: This is already done for the standard sessions

+
Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
the number of seconds until the next cleanup
+ +

Definition at line 4433 of file fuse.c.

+ +
+
+ +

◆ fuse_destroy()

+ +
+
+ + + + + + + + +
void fuse_destroy (struct fuse * f)
+
+

Destroy the FUSE handle.

+

NOTE: This function does not unmount the filesystem. If this is needed, call fuse_unmount() before calling this function.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5146 of file fuse.c.

+ +
+
+ +

◆ fuse_exit()

+ +
+
+ + + + + + + + +
void fuse_exit (struct fuse * f)
+
+

Flag session as terminated

+

This function will cause any running event loops to exit on the next opportunity.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 4639 of file fuse.c.

+ +
+
+ +

◆ fuse_fs_new()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
struct fuse_fs * fuse_fs_new (const struct fuse_operationsop,
size_t op_size,
void * private_data 
)
+
+

Create a new fuse filesystem object

+

This is usually called from the factory of a fuse module to create a new instance of a filesystem.

+
Parameters
+ + + + +
opthe filesystem operations
op_sizethe size of the fuse_operations structure
private_dataInitial value for the private_data field of struct fuse_context. May be overridden by the struct fuse_operations.init handler.
+
+
+
Returns
a new filesystem object
+ +

Definition at line 4854 of file fuse.c.

+ +
+
+ +

◆ fuse_get_context()

+ +
+
+ + + + + + + + +
struct fuse_context * fuse_get_context (void )
+
+

Get the current context

+

The context is only valid for the duration of a filesystem operation, and thus must not be stored and used later.

+
Returns
the context
+ +

Definition at line 4644 of file fuse.c.

+ +
+
+ +

◆ fuse_get_session()

+ +
+
+ + + + + + + + +
struct fuse_session * fuse_get_session (struct fuse * f)
+
+

Get session from fuse object

+ +

Definition at line 4520 of file fuse.c.

+ +
+
+ +

◆ fuse_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_getgroups (int size,
gid_t list[] 
)
+
+

Get the current supplementary group IDs for the current request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + +
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 4654 of file fuse.c.

+ +
+
+ +

◆ fuse_interrupted()

+ +
+
+ + + + + + + + +
int fuse_interrupted (void )
+
+

Check if the current request has already been interrupted

+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 4663 of file fuse.c.

+ +
+
+ +

◆ fuse_invalidate_path()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_invalidate_path (struct fuse * f,
const char * path 
)
+
+

Invalidates cache for the given path.

+

This calls fuse_lowlevel_notify_inval_inode internally.

+
Returns
0 on successful invalidation, negative error value otherwise. This routine may return -ENOENT to indicate that there was no entry to be invalidated, e.g., because the path has not been seen before or has been forgotten; this should not be considered to be an error.
+ +

Definition at line 4673 of file fuse.c.

+ +
+
+ +

◆ fuse_lib_help()

+ +
+
+ + + + + + + + +
void fuse_lib_help (struct fuse_argsargs)
+
+

Print available options (high- and low-level) to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+

The function looks at the argument vector only to determine if there are additional modules to be loaded (module=foo option), and attempts to call their help functions as well.

+
Parameters
+ + +
argsthe argument vector.
+
+
+ +

Definition at line 4744 of file fuse.c.

+ +
+
+ +

◆ fuse_loop()

+ +
+
+ + + + + + + + +
int fuse_loop (struct fuse * f)
+
+

FUSE event loop.

+

Requests from the kernel are processed, and the appropriate operations are called.

+

For a description of the return value and the conditions when the event loop exits, refer to the documentation of fuse_session_loop().

+
Parameters
+ + +
fthe FUSE handle
+
+
+
Returns
see fuse_session_loop()
+

See also: fuse_loop_mt()

+ +

Definition at line 4577 of file fuse.c.

+ +
+
+ +

◆ fuse_main_real_versioned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_main_real_versioned (int argc,
char * argv[],
const struct fuse_operationsop,
size_t op_size,
struct libfuse_versionversion,
void * user_data 
)
+
+

The real main function

+

Do not call this directly, use fuse_main()

+ +

Definition at line 307 of file helper.c.

+ +
+
+ +

◆ fuse_mount()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_mount (struct fuse * f,
const char * mountpoint 
)
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
fthe FUSE handle
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 5197 of file fuse.c.

+ +
+
+ +

◆ fuse_open_channel()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_open_channel (const char * mountpoint,
const char * options 
)
+
+

Open a FUSE file descriptor and set up the mount for the given mountpoint and flags.

+
Parameters
+ + + +
mountpointreference to the mount in the file system
optionsmount options
+
+
+
Returns
the FUSE file descriptor or -1 upon error
+ +

Definition at line 482 of file helper.c.

+ +
+
+ +

◆ fuse_start_cleanup_thread()

+ +
+
+ + + + + + + + +
int fuse_start_cleanup_thread (struct fuse * fuse)
+
+

Start the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
0 on success and -1 on error
+ +

Definition at line 4906 of file fuse.c.

+ +
+
+ +

◆ fuse_stop_cleanup_thread()

+ +
+
+ + + + + + + + +
void fuse_stop_cleanup_thread (struct fuse * fuse)
+
+

Stop the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+ +

Definition at line 4914 of file fuse.c.

+ +
+
+ +

◆ fuse_unmount()

+ +
+
+ + + + + + + + +
void fuse_unmount (struct fuse * f)
+
+

Unmount a FUSE file system.

+

See fuse_session_unmount() for additional information.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5202 of file fuse.c.

+ +
+
+
+ + + + diff --git a/doc/html/include_2fuse_8h_source.html b/doc/html/include_2fuse_8h_source.html new file mode 100644 index 0000000..785125a --- /dev/null +++ b/doc/html/include_2fuse_8h_source.html @@ -0,0 +1,650 @@ + + + + + + + +libfuse: include/fuse.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#ifndef FUSE_H_
+
10#define FUSE_H_
+
11
+
19#include "fuse_common.h"
+
20
+
21#include <fcntl.h>
+
22#include <time.h>
+
23#include <sys/types.h>
+
24#include <sys/stat.h>
+
25#include <sys/statvfs.h>
+
26#include <sys/uio.h>
+
27
+
28#ifdef __cplusplus
+
29extern "C" {
+
30#endif
+
31
+
32/* ----------------------------------------------------------- *
+
33 * Basic FUSE API *
+
34 * ----------------------------------------------------------- */
+
35
+
36/* Forward declaration */
+
37struct statx;
+
38
+
40struct fuse;
+
41
+
+ + +
55 FUSE_READDIR_PLUS = (1 << 0)
+
56};
+
+
57
+
+ + +
75 FUSE_FILL_DIR_PLUS = (1 << 1)
+
76};
+
+
77
+
93typedef int (*fuse_fill_dir_t) (void *buf, const char *name,
+
94 const struct stat *stbuf, off_t off,
+
95 enum fuse_fill_dir_flags flags);
+
107struct fuse_config {
+
112 int32_t set_gid;
+
113 uint32_t gid;
+
114
+
119 int32_t set_uid;
+
120 uint32_t uid;
+
121
+
126 int32_t set_mode;
+
127 uint32_t umask;
+
128
+
133 double entry_timeout;
+
134
+
143 double negative_timeout;
+
144
+
149 double attr_timeout;
+
150
+
154 int32_t intr;
+
155
+
161 int32_t intr_signal;
+
162
+
173 int32_t remember;
+
174
+
191 int32_t hard_remove;
+
192
+
204 int32_t use_ino;
+
205
+
213 int32_t readdir_ino;
+
214
+
232 int32_t direct_io;
+
233
+
251 int32_t kernel_cache;
+
252
+
259 int32_t auto_cache;
+
260
+
261 /*
+
262 * The timeout in seconds for which file attributes are cached
+
263 * for the purpose of checking if auto_cache should flush the
+
264 * file data on open.
+
265 */
+
266 int32_t ac_attr_timeout_set;
+
267 double ac_attr_timeout;
+
268
+
279 int32_t nullpath_ok;
+
280
+
285 int32_t show_help;
+
286 char *modules;
+
287 int32_t debug;
+
288
+
294 uint32_t fmask;
+
295 uint32_t dmask;
+
296
+
303 int32_t no_rofd_flush;
+
304
+ +
319
+
320
+
324 uint32_t flags;
+
325
+
329 uint64_t reserved[48];
+
330};
+
331
+
332
+
355struct fuse_operations {
+
367 int (*getattr) (const char *, struct stat *, struct fuse_file_info *fi);
+
368
+
377 int (*readlink) (const char *, char *, size_t);
+
378
+
385 int (*mknod) (const char *, mode_t, dev_t);
+
386
+
393 int (*mkdir) (const char *, mode_t);
+
394
+
396 int (*unlink) (const char *);
+
397
+
399 int (*rmdir) (const char *);
+
400
+
402 int (*symlink) (const char *, const char *);
+
403
+
413 int (*rename) (const char *, const char *, unsigned int flags);
+
414
+
416 int (*link) (const char *, const char *);
+
417
+
423 int (*chmod) (const char *, mode_t, struct fuse_file_info *fi);
+
424
+
433 int (*chown) (const char *, uid_t, gid_t, struct fuse_file_info *fi);
+
434
+
443 int (*truncate) (const char *, off_t, struct fuse_file_info *fi);
+
444
+
492 int (*open) (const char *, struct fuse_file_info *);
+
493
+
503 int (*read) (const char *, char *, size_t, off_t,
+
504 struct fuse_file_info *);
+
505
+
515 int (*write) (const char *, const char *, size_t, off_t,
+
516 struct fuse_file_info *);
+
517
+
522 int (*statfs) (const char *, struct statvfs *);
+
523
+
552 int (*flush) (const char *, struct fuse_file_info *);
+
553
+
566 int (*release) (const char *, struct fuse_file_info *);
+
567
+
573 int (*fsync) (const char *, int, struct fuse_file_info *);
+
574
+
576 int (*setxattr) (const char *, const char *, const char *, size_t, int);
+
577
+
579 int (*getxattr) (const char *, const char *, char *, size_t);
+
580
+
582 int (*listxattr) (const char *, char *, size_t);
+
583
+
585 int (*removexattr) (const char *, const char *);
+
586
+
595 int (*opendir) (const char *, struct fuse_file_info *);
+
596
+
619 int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t,
+
620 struct fuse_file_info *, enum fuse_readdir_flags);
+
621
+
627 int (*releasedir) (const char *, struct fuse_file_info *);
+
628
+
637 int (*fsyncdir) (const char *, int, struct fuse_file_info *);
+
638
+
647 void *(*init) (struct fuse_conn_info *conn,
+
648 struct fuse_config *cfg);
+
649
+
655 void (*destroy) (void *private_data);
+
656
+
666 int (*access) (const char *, int);
+
667
+
678 int (*create) (const char *, mode_t, struct fuse_file_info *);
+
679
+
710 int (*lock) (const char *, struct fuse_file_info *, int cmd,
+
711 struct flock *);
+
712
+
725 int (*utimens) (const char *, const struct timespec tv[2],
+
726 struct fuse_file_info *fi);
+
727
+
734 int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
+
735
+
736#if FUSE_USE_VERSION < 35
+
737 int (*ioctl) (const char *, int cmd, void *arg,
+
738 struct fuse_file_info *, unsigned int flags, void *data);
+
739#else
+
756 int (*ioctl) (const char *, unsigned int cmd, void *arg,
+
757 struct fuse_file_info *, unsigned int flags, void *data);
+
758#endif
+
759
+
775 int (*poll) (const char *, struct fuse_file_info *,
+
776 struct fuse_pollhandle *ph, unsigned *reventsp);
+
777
+
787 int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off,
+
788 struct fuse_file_info *);
+
789
+
804 int (*read_buf) (const char *, struct fuse_bufvec **bufp,
+
805 size_t size, off_t off, struct fuse_file_info *);
+
824 int (*flock) (const char *, struct fuse_file_info *, int op);
+
825
+
834 int (*fallocate) (const char *, int, off_t, off_t,
+
835 struct fuse_file_info *);
+
836
+
849 ssize_t (*copy_file_range) (const char *path_in,
+
850 struct fuse_file_info *fi_in,
+
851 off_t offset_in, const char *path_out,
+
852 struct fuse_file_info *fi_out,
+
853 off_t offset_out, size_t size, int flags);
+
854
+
858 off_t (*lseek) (const char *, off_t off, int whence, struct fuse_file_info *);
+
859
+
868 int (*statx)(const char *path, int flags, int mask, struct statx *stxbuf,
+
869 struct fuse_file_info *fi);
+
870};
+
871
+
877struct fuse_context {
+
879 struct fuse *fuse;
+
880
+
882 uid_t uid;
+
883
+
885 gid_t gid;
+
886
+
888 pid_t pid;
+
889
+
891 void *private_data;
+
892
+
894 mode_t umask;
+
895};
+
896
+
902int fuse_main_real_versioned(int argc, char *argv[],
+
903 const struct fuse_operations *op, size_t op_size,
+
904 struct libfuse_version *version, void *user_data);
+
905static inline int fuse_main_real(int argc, char *argv[],
+
906 const struct fuse_operations *op,
+
907 size_t op_size, void *user_data)
+
908{
+
909 struct libfuse_version version = { .major = FUSE_MAJOR_VERSION,
+
910 .minor = FUSE_MINOR_VERSION,
+
911 .hotfix = FUSE_HOTFIX_VERSION,
+
912 .padding = 0 };
+
913
+
914 fuse_log(FUSE_LOG_ERR,
+
915 "%s is a libfuse internal function, please use fuse_main()\n",
+
916 __func__);
+
917
+
918 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
919 user_data);
+
920}
+
921
+
976static inline int fuse_main_fn(int argc, char *argv[],
+
977 const struct fuse_operations *op,
+
978 void *user_data)
+
979{
+
980 struct libfuse_version version = {
+
981 .major = FUSE_MAJOR_VERSION,
+
982 .minor = FUSE_MINOR_VERSION,
+
983 .hotfix = FUSE_HOTFIX_VERSION,
+
984 .padding = 0
+
985 };
+
986
+
987 return fuse_main_real_versioned(argc, argv, op, sizeof(*(op)), &version,
+
988 user_data);
+
989}
+
990#define fuse_main(argc, argv, op, user_data) \
+
991 fuse_main_fn(argc, argv, op, user_data)
+
992
+
993/* ----------------------------------------------------------- *
+
994 * More detailed API *
+
995 * ----------------------------------------------------------- */
+
996
+
1008void fuse_lib_help(struct fuse_args *args);
+
1009
+
1010/* Do not call this directly, use fuse_new() instead */
+
1011struct fuse *_fuse_new_30(struct fuse_args *args,
+
1012 const struct fuse_operations *op, size_t op_size,
+
1013 struct libfuse_version *version, void *user_data);
+
1014struct fuse *_fuse_new_31(struct fuse_args *args,
+
1015 const struct fuse_operations *op, size_t op_size,
+
1016 struct libfuse_version *version, void *user_data);
+
1017
+
1045#if FUSE_USE_VERSION == 30
+
1046static inline struct fuse *fuse_new_fn(struct fuse_args *args,
+
1047 const struct fuse_operations *op,
+
1048 size_t op_size, void *user_data)
+
1049{
+
1050 struct libfuse_version version = {
+
1051 .major = FUSE_MAJOR_VERSION,
+
1052 .minor = FUSE_MINOR_VERSION,
+
1053 .hotfix = FUSE_HOTFIX_VERSION,
+
1054 .padding = 0
+
1055 };
+
1056
+
1057 return _fuse_new_30(args, op, op_size, &version, user_data);
+
1058}
+
1059#else /* FUSE_USE_VERSION */
+
1060static inline struct fuse *fuse_new_fn(struct fuse_args *args,
+
1061 const struct fuse_operations *op,
+
1062 size_t op_size, void *user_data)
+
1063{
+
1064 struct libfuse_version version = {
+
1065 .major = FUSE_MAJOR_VERSION,
+
1066 .minor = FUSE_MINOR_VERSION,
+
1067 .hotfix = FUSE_HOTFIX_VERSION,
+
1068 .padding = 0
+
1069 };
+
1070
+
1071 return _fuse_new_31(args, op, op_size, &version, user_data);
+
1072}
+
1073#endif
+
1074#define fuse_new(args, op, size, data) fuse_new_fn(args, op, size, data)
+
1075
+
1084int fuse_mount(struct fuse *f, const char *mountpoint);
+
1085
+
1093void fuse_unmount(struct fuse *f);
+
1094
+
1103void fuse_destroy(struct fuse *f);
+
1104
+
1120int fuse_loop(struct fuse *f);
+
1121
+
1130void fuse_exit(struct fuse *f);
+
1131
+
1132#if FUSE_USE_VERSION < 32
+
1133int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
1134#define fuse_loop_mt(f, clone_fd) fuse_loop_mt_31(f, clone_fd)
+
1135#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
1136int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config *config);
+
1137#define fuse_loop_mt(f, config) fuse_loop_mt_32(f, config)
+
1138#else
+
1170#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
1171int fuse_loop_mt(struct fuse *f, struct fuse_loop_config *config);
+
1172#else
+
1173#define fuse_loop_mt(f, config) fuse_loop_mt_312(f, config)
+
1174#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
1175#endif
+
1176
+
1177
+
1186struct fuse_context *fuse_get_context(void);
+
1187
+
1206int fuse_getgroups(int size, gid_t list[]);
+
1207
+
1213int fuse_interrupted(void);
+
1214
+
1226int fuse_invalidate_path(struct fuse *f, const char *path);
+
1227
+ +
1236
+
1243void fuse_stop_cleanup_thread(struct fuse *fuse);
+
1244
+
1254int fuse_clean_cache(struct fuse *fuse);
+
1255
+
1256/*
+
1257 * Stacking API
+
1258 */
+
1259
+
1265struct fuse_fs;
+
1266
+
1267/*
+
1268 * These functions call the relevant filesystem operation, and return
+
1269 * the result.
+
1270 *
+
1271 * If the operation is not defined, they return -ENOSYS, with the
+
1272 * exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir,
+
1273 * fuse_fs_releasedir and fuse_fs_statfs, which return 0.
+
1274 */
+
1275
+
1276int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1277 struct fuse_file_info *fi);
+
1278int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1279 const char *newpath, unsigned int flags);
+
1280int fuse_fs_unlink(struct fuse_fs *fs, const char *path);
+
1281int fuse_fs_rmdir(struct fuse_fs *fs, const char *path);
+
1282int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname,
+
1283 const char *path);
+
1284int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath);
+
1285int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1286 struct fuse_file_info *fi);
+
1287int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1288 struct fuse_file_info *fi);
+
1289int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size,
+
1290 off_t off, struct fuse_file_info *fi);
+
1291int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1292 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1293 struct fuse_file_info *fi);
+
1294int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf,
+
1295 size_t size, off_t off, struct fuse_file_info *fi);
+
1296int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1297 struct fuse_bufvec *buf, off_t off,
+
1298 struct fuse_file_info *fi);
+
1299int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1300 struct fuse_file_info *fi);
+
1301int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1302 struct fuse_file_info *fi);
+
1303int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf);
+
1304int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1305 struct fuse_file_info *fi);
+
1306int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
1307 fuse_fill_dir_t filler, off_t off,
+
1308 struct fuse_file_info *fi, enum fuse_readdir_flags flags);
+
1309int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1310 struct fuse_file_info *fi);
+
1311int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1312 struct fuse_file_info *fi);
+
1313int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
1314 struct fuse_file_info *fi);
+
1315int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
1316 struct fuse_file_info *fi, int cmd, struct flock *lock);
+
1317int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
1318 struct fuse_file_info *fi, int op);
+
1319int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1320 struct fuse_file_info *fi);
+
1321int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid,
+
1322 struct fuse_file_info *fi);
+
1323int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
1324 struct fuse_file_info *fi);
+
1325int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
1326 const struct timespec tv[2], struct fuse_file_info *fi);
+
1327int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask);
+
1328int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
1329 size_t len);
+
1330int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1331 dev_t rdev);
+
1332int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode);
+
1333int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1334 const char *value, size_t size, int flags);
+
1335int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1336 char *value, size_t size);
+
1337int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
1338 size_t size);
+
1339int fuse_fs_removexattr(struct fuse_fs *fs, const char *path,
+
1340 const char *name);
+
1341int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
1342 uint64_t *idx);
+
1343#if FUSE_USE_VERSION < 35
+
1344int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd,
+
1345 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1346 void *data);
+
1347#else
+
1348int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
1349 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1350 void *data);
+
1351#endif
+
1352int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
1353 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
1354 unsigned *reventsp);
+
1355int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
1356 off_t offset, off_t length, struct fuse_file_info *fi);
+
1357ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
1358 struct fuse_file_info *fi_in, off_t off_in,
+
1359 const char *path_out,
+
1360 struct fuse_file_info *fi_out, off_t off_out,
+
1361 size_t len, int flags);
+
1362off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
1363 struct fuse_file_info *fi);
+
1364int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask,
+
1365 struct statx *stxbuf, struct fuse_file_info *fi);
+
1366void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
1367 struct fuse_config *cfg);
+
1368void fuse_fs_destroy(struct fuse_fs *fs);
+
1369
+
1370int fuse_notify_poll(struct fuse_pollhandle *ph);
+
1371
+
1385struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
1386 void *private_data);
+
1387
+
1402typedef struct fuse_fs *(*fuse_module_factory_t)(struct fuse_args *args,
+
1403 struct fuse_fs *fs[]);
+
+
1414#define FUSE_REGISTER_MODULE(name_, factory_) \
+
1415 fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+
1416
+
1418struct fuse_session *fuse_get_session(struct fuse *f);
+
1419
+
1428int fuse_open_channel(const char *mountpoint, const char *options);
+
1429
+
1430#ifdef __cplusplus
+
1431}
+
1432#endif
+
1433
+
1434#endif /* FUSE_H_ */
+
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4654
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5197
+
int fuse_interrupted(void)
Definition fuse.c:4663
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5146
+
int fuse_main_real_versioned(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
Definition helper.c:307
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4906
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4639
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4433
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:482
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5202
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4914
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
@ FUSE_READDIR_DEFAULTS
Definition fuse.h:51
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:93
+
fuse_readdir_flags
Definition fuse.h:45
+ + + + +
uint32_t fmask
Definition fuse.h:288
+
int32_t show_help
Definition fuse.h:279
+
uint32_t flags
Definition fuse.h:318
+
int32_t direct_io
Definition fuse.h:226
+
int32_t no_rofd_flush
Definition fuse.h:297
+
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t intr_signal
Definition fuse.h:155
+
int32_t remember
Definition fuse.h:167
+
int32_t kernel_cache
Definition fuse.h:245
+
int32_t readdir_ino
Definition fuse.h:207
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t set_mode
Definition fuse.h:120
+
int32_t auto_cache
Definition fuse.h:253
+
uint64_t reserved[48]
Definition fuse.h:323
+
int32_t intr
Definition fuse.h:148
+
double negative_timeout
Definition fuse.h:137
+
int32_t set_gid
Definition fuse.h:106
+
int32_t set_uid
Definition fuse.h:113
+
int32_t hard_remove
Definition fuse.h:185
+
double attr_timeout
Definition fuse.h:143
+ + +
void * private_data
Definition fuse.h:874
+
uid_t uid
Definition fuse.h:865
+
pid_t pid
Definition fuse.h:871
+
struct fuse * fuse
Definition fuse.h:862
+
gid_t gid
Definition fuse.h:868
+
mode_t umask
Definition fuse.h:877
+ + + +
int(* mkdir)(const char *, mode_t)
Definition fuse.h:387
+
int(* truncate)(const char *, off_t, struct fuse_file_info *fi)
Definition fuse.h:437
+
int(* mknod)(const char *, mode_t, dev_t)
Definition fuse.h:379
+
int(* utimens)(const char *, const struct timespec tv[2], struct fuse_file_info *fi)
Definition fuse.h:719
+
int(* open)(const char *, struct fuse_file_info *)
Definition fuse.h:486
+
int(* opendir)(const char *, struct fuse_file_info *)
Definition fuse.h:589
+
int(* link)(const char *, const char *)
Definition fuse.h:410
+
int(* lock)(const char *, struct fuse_file_info *, int cmd, struct flock *)
Definition fuse.h:704
+
int(* read_buf)(const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *)
Definition fuse.h:798
+
int(* access)(const char *, int)
Definition fuse.h:660
+
int(* read)(const char *, char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:497
+
int(* poll)(const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp)
Definition fuse.h:769
+
void(* destroy)(void *private_data)
Definition fuse.h:649
+
int(* statfs)(const char *, struct statvfs *)
Definition fuse.h:516
+
int(* fallocate)(const char *, int, off_t, off_t, struct fuse_file_info *)
Definition fuse.h:828
+
int(* removexattr)(const char *, const char *)
Definition fuse.h:579
+
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
int(* releasedir)(const char *, struct fuse_file_info *)
Definition fuse.h:621
+
int(* rename)(const char *, const char *, unsigned int flags)
Definition fuse.h:407
+
off_t(* lseek)(const char *, off_t off, int whence, struct fuse_file_info *)
Definition fuse.h:852
+
int(* write)(const char *, const char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:509
+
int(* write_buf)(const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *)
Definition fuse.h:781
+
int(* unlink)(const char *)
Definition fuse.h:390
+
ssize_t(* copy_file_range)(const char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags)
Definition fuse.h:843
+
int(* fsync)(const char *, int, struct fuse_file_info *)
Definition fuse.h:567
+
int(* create)(const char *, mode_t, struct fuse_file_info *)
Definition fuse.h:672
+
int(* setxattr)(const char *, const char *, const char *, size_t, int)
Definition fuse.h:570
+
int(* statx)(const char *path, int flags, int mask, struct statx *stxbuf, struct fuse_file_info *fi)
Definition fuse.h:868
+
int(* listxattr)(const char *, char *, size_t)
Definition fuse.h:576
+
int(* readlink)(const char *, char *, size_t)
Definition fuse.h:371
+
int(* symlink)(const char *, const char *)
Definition fuse.h:396
+
int(* fsyncdir)(const char *, int, struct fuse_file_info *)
Definition fuse.h:631
+
int(* release)(const char *, struct fuse_file_info *)
Definition fuse.h:560
+
int(* chown)(const char *, uid_t, gid_t, struct fuse_file_info *fi)
Definition fuse.h:427
+
int(* chmod)(const char *, mode_t, struct fuse_file_info *fi)
Definition fuse.h:417
+
int(* rmdir)(const char *)
Definition fuse.h:393
+
int(* flush)(const char *, struct fuse_file_info *)
Definition fuse.h:546
+
int(* flock)(const char *, struct fuse_file_info *, int op)
Definition fuse.h:818
+
int(* ioctl)(const char *, unsigned int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data)
Definition fuse.h:750
+
int(* readdir)(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags)
Definition fuse.h:613
+
int(* getxattr)(const char *, const char *, char *, size_t)
Definition fuse.h:573
+
int(* bmap)(const char *, size_t blocksize, uint64_t *idx)
Definition fuse.h:728
+ +
+ + + + diff --git a/doc/html/include_2fuse__common_8h.html b/doc/html/include_2fuse__common_8h.html new file mode 100644 index 0000000..092fe59 --- /dev/null +++ b/doc/html/include_2fuse__common_8h.html @@ -0,0 +1,1303 @@ + + + + + + + +libfuse: include/fuse_common.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_common.h File Reference
+
+
+
#include "libfuse_config.h"
+#include "fuse_opt.h"
+#include "fuse_log.h"
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+ + + + + + + + + + + + + + +

+Data Structures

struct  fuse_file_info
 
struct  fuse_loop_config
 
struct  fuse_conn_info
 
struct  fuse_buf
 
struct  fuse_bufvec
 
struct  libfuse_version
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Macros

#define FUSE_CAP_ASYNC_READ   (1UL << 0)
 
#define FUSE_CAP_POSIX_LOCKS   (1UL << 1)
 
#define FUSE_CAP_ATOMIC_O_TRUNC   (1UL << 3)
 
#define FUSE_CAP_EXPORT_SUPPORT   (1UL << 4)
 
#define FUSE_CAP_DONT_MASK   (1UL << 6)
 
#define FUSE_CAP_SPLICE_WRITE   (1UL << 7)
 
#define FUSE_CAP_SPLICE_MOVE   (1UL << 8)
 
#define FUSE_CAP_SPLICE_READ   (1UL << 9)
 
#define FUSE_CAP_FLOCK_LOCKS   (1UL << 10)
 
#define FUSE_CAP_IOCTL_DIR   (1UL << 11)
 
#define FUSE_CAP_AUTO_INVAL_DATA   (1UL << 12)
 
#define FUSE_CAP_READDIRPLUS   (1UL << 13)
 
#define FUSE_CAP_READDIRPLUS_AUTO   (1UL << 14)
 
#define FUSE_CAP_ASYNC_DIO   (1UL << 15)
 
#define FUSE_CAP_WRITEBACK_CACHE   (1UL << 16)
 
#define FUSE_CAP_NO_OPEN_SUPPORT   (1UL << 17)
 
#define FUSE_CAP_PARALLEL_DIROPS   (1UL << 18)
 
#define FUSE_CAP_POSIX_ACL   (1UL << 19)
 
#define FUSE_CAP_HANDLE_KILLPRIV   (1UL << 20)
 
#define FUSE_CAP_HANDLE_KILLPRIV_V2   (1UL << 21)
 
#define FUSE_CAP_CACHE_SYMLINKS   (1UL << 23)
 
#define FUSE_CAP_NO_OPENDIR_SUPPORT   (1UL << 24)
 
#define FUSE_CAP_EXPLICIT_INVAL_DATA   (1UL << 25)
 
#define FUSE_CAP_EXPIRE_ONLY   (1UL << 26)
 
#define FUSE_CAP_SETXATTR_EXT   (1UL << 27)
 
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP   (1UL << 28)
 
#define FUSE_CAP_PASSTHROUGH   (1UL << 29)
 
#define FUSE_CAP_NO_EXPORT_SUPPORT   (1UL << 30)
 
#define FUSE_CAP_OVER_IO_URING   (1UL << 31)
 
#define FUSE_IOCTL_COMPAT   (1 << 0)
 
#define FUSE_BACKING_STACKED_UNDER   (0)
 
+ + + + + +

+Enumerations

enum  fuse_buf_flags { FUSE_BUF_IS_FD = (1 << 1) +, FUSE_BUF_FD_SEEK = (1 << 2) +, FUSE_BUF_FD_RETRY = (1 << 3) + }
 
enum  fuse_buf_copy_flags { FUSE_BUF_NO_SPLICE = (1 << 1) +, FUSE_BUF_FORCE_SPLICE = (1 << 2) +, FUSE_BUF_SPLICE_MOVE = (1 << 3) +, FUSE_BUF_SPLICE_NONBLOCK = (1 << 4) + }
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_args *args)
 
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
 
int fuse_daemonize (int foreground)
 
int fuse_version (void)
 
const char * fuse_pkgversion (void)
 
void fuse_pollhandle_destroy (struct fuse_pollhandle *ph)
 
size_t fuse_buf_size (const struct fuse_bufvec *bufv)
 
ssize_t fuse_buf_copy (struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
 
int fuse_set_signal_handlers (struct fuse_session *se)
 
int fuse_set_fail_signal_handlers (struct fuse_session *se)
 
void fuse_remove_signal_handlers (struct fuse_session *se)
 
bool fuse_set_feature_flag (struct fuse_conn_info *conn, uint64_t flag)
 
void fuse_unset_feature_flag (struct fuse_conn_info *conn, uint64_t flag)
 
bool fuse_get_feature_flag (struct fuse_conn_info *conn, uint64_t flag)
 
int fuse_convert_to_conn_want_ext (struct fuse_conn_info *conn)
 
+

Macro Definition Documentation

+ +

◆ FUSE_BACKING_STACKED_UNDER

+ +
+
+ + + + +
#define FUSE_BACKING_STACKED_UNDER   (0)
+
+

When FUSE_CAP_PASSTHROUGH is enabled, this is the maximum allowed stacking depth of the backing files. In current kernel, the maximum allowed stack depth if FILESYSTEM_MAX_STACK_DEPTH (2), which includes the FUSE passthrough layer, so the maximum stacking depth for backing files is 1.

+

The default is FUSE_BACKING_STACKED_UNDER (0), meaning that the backing files cannot be on a stacked filesystem, but another stacked filesystem can be stacked over this FUSE passthrough filesystem.

+

Set this to FUSE_BACKING_STACKED_OVER (1) if backing files may be on a stacked filesystem, such as overlayfs or another FUSE passthrough. In this configuration, another stacked filesystem cannot be stacked over this FUSE passthrough filesystem.

+ +

Definition at line 670 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ASYNC_DIO

+ +
+
+ + + + +
#define FUSE_CAP_ASYNC_DIO   (1UL << 15)
+
+

Indicates that the filesystem supports asynchronous direct I/O submission.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read and one pending write request per direct I/O file-handle at any time.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 328 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ASYNC_READ

+ +
+
+ + + + +
#define FUSE_CAP_ASYNC_READ   (1UL << 0)
+
+

Indicates that the filesystem supports asynchronous read requests.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read request per file-handle at any time, and will attempt to order read requests by increasing offset.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 177 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ATOMIC_O_TRUNC

+ +
+
+ + + + +
#define FUSE_CAP_ATOMIC_O_TRUNC   (1UL << 3)
+
+

Indicates that the filesystem supports the O_TRUNC open flag. If disabled, and an application specifies O_TRUNC, fuse first calls truncate() and then open() with O_TRUNC filtered out.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 194 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_AUTO_INVAL_DATA

+ +
+
+ + + + +
#define FUSE_CAP_AUTO_INVAL_DATA   (1UL << 12)
+
+

Traditionally, while a file is open the FUSE kernel module only asks the filesystem for an update of the file's attributes when a client attempts to read beyond EOF. This is unsuitable for e.g. network filesystems, where the file contents may change without the kernel knowing about it.

+

If this flag is set, FUSE will check the validity of the attributes on every read. If the attributes are no longer valid (i.e., if the attr_timeout passed to fuse_reply_attr() or set in struct fuse_entry_param has passed), it will first issue a getattr request. If the new mtime differs from the previous value, any cached file contents will be invalidated as well.

+

This flag should always be set when available. If all file changes go through the kernel, attr_timeout should be set to a very large number to avoid unnecessary getattr() calls.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 281 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_CACHE_SYMLINKS

+ +
+
+ + + + +
#define FUSE_CAP_CACHE_SYMLINKS   (1UL << 23)
+
+

Indicates that the kernel supports caching symlinks in its page cache.

+

When this feature is enabled, symlink targets are saved in the page cache. You can invalidate a cached link by calling: fuse_lowlevel_notify_inval_inode(se, ino, 0, 0);

+

This feature is disabled by default. If the kernel supports it (>= 4.20), you can enable this feature by setting this flag in the want field of the fuse_conn_info structure.

+ +

Definition at line 418 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_DIRECT_IO_ALLOW_MMAP

+ +
+
+ + + + +
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP   (1UL << 28)
+
+

Files opened with FUSE_DIRECT_IO do not support MAP_SHARED mmap. This restriction is relaxed through FUSE_CAP_DIRECT_IO_RELAX (kernel flag: FUSE_DIRECT_IO_RELAX). MAP_SHARED is disabled by default for FUSE_DIRECT_IO, as this flag can be used to ensure coherency between mount points (or network clients) and with kernel page cache as enforced by mmap that cannot be guaranteed anymore.

+ +

Definition at line 488 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_DONT_MASK

+ +
+
+ + + + +
#define FUSE_CAP_DONT_MASK   (1UL << 6)
+
+

Indicates that the kernel should not apply the umask to the file mode on create operations.

+

This feature is disabled by default.

+ +

Definition at line 214 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPIRE_ONLY

+ +
+
+ + + + +
#define FUSE_CAP_EXPIRE_ONLY   (1UL << 26)
+
+

Indicates support that dentries can be expired.

+

Expiring dentries, instead of invalidating them, makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+ +

Definition at line 472 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPLICIT_INVAL_DATA

+ +
+
+ + + + +
#define FUSE_CAP_EXPLICIT_INVAL_DATA   (1UL << 25)
+
+

Indicates support for invalidating cached pages only on explicit request.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports invalidating cached pages only on explicit request by the filesystem through fuse_lowlevel_notify_inval_inode() or fuse_invalidate_path().

+

By setting this flag in the want field of the fuse_conn_info structure, the filesystem is responsible for invalidating cached pages through explicit requests to the kernel.

+

Note that setting this flag does not prevent the cached pages from being flushed by OS itself and/or through user actions.

+

Note that if both FUSE_CAP_EXPLICIT_INVAL_DATA and FUSE_CAP_AUTO_INVAL_DATA are set in the capable field of the fuse_conn_info structure then FUSE_CAP_AUTO_INVAL_DATA takes precedence.

+

This feature is disabled by default.

+ +

Definition at line 456 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPORT_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_EXPORT_SUPPORT   (1UL << 4)
+
+

Indicates that the filesystem supports lookups of "." and "..".

+

When this flag is set, the filesystem must be prepared to receive requests for invalid inodes (i.e., for which a FORGET request was received or which have been used in a previous instance of the filesystem daemon) and must not reuse node-ids (even when setting generation numbers).

+

This feature is disabled by default.

+ +

Definition at line 206 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_FLOCK_LOCKS

+ +
+
+ + + + +
#define FUSE_CAP_FLOCK_LOCKS   (1UL << 10)
+
+

If set, the calls to flock(2) will be emulated using POSIX locks and must then be handled by the filesystem's setlock() handler.

+

If not set, flock(2) calls will be handled by the FUSE kernel module internally (so any access that does not go through the kernel cannot be taken into account).

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a flock() handler.

+ +

Definition at line 252 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_HANDLE_KILLPRIV

+ +
+
+ + + + +
#define FUSE_CAP_HANDLE_KILLPRIV   (1UL << 20)
+
+

Indicates that the filesystem is responsible for unsetting setuid and setgid bits when a file is written, truncated, or its owner is changed.

+

This feature is disabled by default.

+ +

Definition at line 388 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_HANDLE_KILLPRIV_V2

+ +
+
+ + + + +
#define FUSE_CAP_HANDLE_KILLPRIV_V2   (1UL << 21)
+
+

Indicates that the filesystem is responsible for unsetting setuid and setgid bit and additionally cap (stored as xattr) when a file is written, truncated, or its owner is changed. Upon write/truncate suid/sgid is only killed if caller does not have CAP_FSETID. Additionally upon write/truncate sgid is killed only if file has group execute permission. (Same as Linux VFS behavior). KILLPRIV_V2 requires handling of

    +
  • FUSE_OPEN_KILL_SUIDGID (set in struct fuse_create_in::open_flags)
  • +
  • FATTR_KILL_SUIDGID (set in struct fuse_setattr_in::valid)
  • +
  • FUSE_WRITE_KILL_SUIDGID (set in struct fuse_write_in::write_flags)
  • +
+

This feature is disabled by default.

+ +

Definition at line 405 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_IOCTL_DIR

+ +
+
+ + + + +
#define FUSE_CAP_IOCTL_DIR   (1UL << 11)
+
+

Indicates that the filesystem supports ioctl's on directories.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 259 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_EXPORT_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_EXPORT_SUPPORT   (1UL << 30)
+
+

Indicates that the file system cannot handle NFS export

+

If this flag is set NFS export and name_to_handle_at is not going to work at all and will fail with EOPNOTSUPP.

+ +

Definition at line 508 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_OPEN_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_OPEN_SUPPORT   (1UL << 17)
+
+

Indicates support for zero-message opens. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the open() handler to indicate success. Further attempts to open files will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signaled to the caller).

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users wish to have this behavior you must return ENOSYS from the open() handler on supporting kernels.

+ +

Definition at line 352 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_OPENDIR_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_OPENDIR_SUPPORT   (1UL << 24)
+
+

Indicates support for zero-message opendirs. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the opendir() handler to indicate success. Further opendir and releasedir messages will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signalled to the caller.)

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users with to have this behavior you must return ENOSYS from the opendir() handler on supporting kernels.

+ +

Definition at line 433 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_OVER_IO_URING

+ +
+
+ + + + +
#define FUSE_CAP_OVER_IO_URING   (1UL << 31)
+
+

Indicates support for io-uring between fuse-server and fuse-client

+ +

Definition at line 513 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_PARALLEL_DIROPS

+ +
+
+ + + + +
#define FUSE_CAP_PARALLEL_DIROPS   (1UL << 18)
+
+

Indicates support for parallel directory operations. If this flag is unset, the FUSE kernel module will ensure that lookup() and readdir() requests are never issued concurrently for the same directory.

+ +

Definition at line 360 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_PASSTHROUGH

+ +
+
+ + + + +
#define FUSE_CAP_PASSTHROUGH   (1UL << 29)
+
+

Indicates support for passthrough mode access for read/write operations.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports redirecting read/write operations to the backing file instead of letting them to be handled by the FUSE daemon.

+

This feature is disabled by default.

+ +

Definition at line 500 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_POSIX_ACL

+ +
+
+ + + + +
#define FUSE_CAP_POSIX_ACL   (1UL << 19)
+
+

Indicates support for POSIX ACLs.

+

If this feature is enabled, the kernel will cache and have responsibility for enforcing ACLs. ACL will be stored as xattrs and passed to userspace, which is responsible for updating the ACLs in the filesystem, keeping the file mode in sync with the ACL, and ensuring inheritance of default ACLs when new filesystem nodes are created. Note that this requires that the file system is able to parse and interpret the xattr representation of ACLs.

+

Enabling this feature implicitly turns on the default_permissions mount option (even if it was not passed to mount(2)).

+

This feature is disabled by default.

+ +

Definition at line 379 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_POSIX_LOCKS

+ +
+
+ + + + +
#define FUSE_CAP_POSIX_LOCKS   (1UL << 1)
+
+

Indicates that the filesystem supports "remote" locking.

+

This feature is enabled by default when supported by the kernel, and if getlk() and setlk() handlers are implemented.

+ +

Definition at line 185 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_READDIRPLUS

+ +
+
+ + + + +
#define FUSE_CAP_READDIRPLUS   (1UL << 13)
+
+

Indicates that the filesystem supports readdirplus.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a readdirplus() handler.

+ +

Definition at line 289 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_READDIRPLUS_AUTO

+ +
+
+ + + + +
#define FUSE_CAP_READDIRPLUS_AUTO   (1UL << 14)
+
+

Indicates that the filesystem supports adaptive readdirplus.

+

If FUSE_CAP_READDIRPLUS is not set, this flag has no effect.

+

If FUSE_CAP_READDIRPLUS is set and this flag is not set, the kernel will always issue readdirplus() requests to retrieve directory contents.

+

If FUSE_CAP_READDIRPLUS is set and this flag is set, the kernel will issue both readdir() and readdirplus() requests, depending on how much information is expected to be required.

+

As of Linux 4.20, the algorithm is as follows: when userspace starts to read directory entries, issue a READDIRPLUS request to the filesystem. If any entry attributes have been looked up by the time userspace requests the next batch of entries continue with READDIRPLUS, otherwise switch to plain READDIR. This will reasult in eg plain "ls" triggering READDIRPLUS first then READDIR after that because it doesn't do lookups. "ls -l" should result in all READDIRPLUS, except if dentries are already cached.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements both a readdirplus() and a readdir() handler.

+ +

Definition at line 317 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SETXATTR_EXT

+ +
+
+ + + + +
#define FUSE_CAP_SETXATTR_EXT   (1UL << 27)
+
+

Indicates that an extended 'struct fuse_setxattr' is used by the kernel side - extra_flags are passed, which are used (as of now by acl) processing. For example FUSE_SETXATTR_ACL_KILL_SGID might be set.

+ +

Definition at line 479 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_MOVE

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_MOVE   (1UL << 8)
+
+

Indicates that libfuse should try to move pages instead of copying when writing to / reading from the fuse device. This may improve performance.

+

This feature is disabled by default.

+ +

Definition at line 230 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_READ

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_READ   (1UL << 9)
+
+

Indicates that libfuse should try to use splice() when reading from the fuse device. This may improve performance.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a write_buf() handler.

+ +

Definition at line 239 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_WRITE

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_WRITE   (1UL << 7)
+
+

Indicates that libfuse should try to use splice() when writing to the fuse device. This may improve performance.

+

This feature is disabled by default.

+ +

Definition at line 222 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_WRITEBACK_CACHE

+ +
+
+ + + + +
#define FUSE_CAP_WRITEBACK_CACHE   (1UL << 16)
+
+

Indicates that writeback caching should be enabled. This means that individual write request may be buffered and merged in the kernel before they are send to the filesystem.

+

This feature is disabled by default.

+ +

Definition at line 337 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_IOCTL_COMPAT

+ +
+
+ + + + +
#define FUSE_IOCTL_COMPAT   (1 << 0)
+
+

Ioctl flags

+

FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed FUSE_IOCTL_RETRY: retry with new iovecs FUSE_IOCTL_DIR: is a directory

+

FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs

+ +

Definition at line 525 of file fuse_common.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_buf_copy_flags

+ +
+
+ + + + +
enum fuse_buf_copy_flags
+
+

Buffer copy flags

+ + + + + +
Enumerator
FUSE_BUF_NO_SPLICE 

Don't use splice(2)

+

Always fall back to using read and write instead of splice(2) to copy data from one file descriptor to another.

+

If this flag is not set, then only fall back if splice is unavailable.

+
FUSE_BUF_FORCE_SPLICE 

Force splice

+

Always use splice(2) to copy data from one file descriptor to another. If splice is not available, return -EINVAL.

+
FUSE_BUF_SPLICE_MOVE 

Try to move data with splice.

+

If splice is used, try to move pages from the source to the destination instead of copying. See documentation of SPLICE_F_MOVE in splice(2) man page.

+
FUSE_BUF_SPLICE_NONBLOCK 

Don't block on the pipe when copying data with splice

+

Makes the operations on the pipe non-blocking (if the pipe is full or empty). See SPLICE_F_NONBLOCK in the splice(2) man page.

+
+ +

Definition at line 840 of file fuse_common.h.

+ +
+
+ +

◆ fuse_buf_flags

+ +
+
+ + + + +
enum fuse_buf_flags
+
+

Buffer flags

+ + + + +
Enumerator
FUSE_BUF_IS_FD 

Buffer contains a file descriptor

+

If this flag is set, the .fd field is valid, otherwise the .mem fields is valid.

+
FUSE_BUF_FD_SEEK 

Seek on the file descriptor

+

If this flag is set then the .pos field is valid and is used to seek to the given offset before performing operation on file descriptor.

+
FUSE_BUF_FD_RETRY 

Retry operation on file descriptor

+

If this flag is set then retry operation on file descriptor until .size bytes have been copied or an error or EOF is detected.

+
+ +

Definition at line 809 of file fuse_common.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_apply_conn_info_opts()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts * opts,
struct fuse_conn_infoconn 
)
+
+

This function applies the (parsed) parameters in opts to the conn pointer. It may modify the following fields: wants, max_write, max_readahead, congestion_threshold, max_background, time_gran. A field is only set (or unset) if the corresponding option has been explicitly set.

+ +

Definition at line 412 of file helper.c.

+ +
+
+ +

◆ fuse_buf_copy()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
ssize_t fuse_buf_copy (struct fuse_bufvecdst,
struct fuse_bufvecsrc,
enum fuse_buf_copy_flags flags 
)
+
+

Copy data from one buffer vector to another

+
Parameters
+ + + + +
dstdestination buffer vector
srcsource buffer vector
flagsflags controlling the copy
+
+
+
Returns
actual number of bytes copied or -errno on error
+ +

Definition at line 284 of file buffer.c.

+ +
+
+ +

◆ fuse_buf_size()

+ +
+
+ + + + + + + + +
size_t fuse_buf_size (const struct fuse_bufvecbufv)
+
+

Get total size of data in a fuse buffer vector

+
Parameters
+ + +
bufvbuffer vector
+
+
+
Returns
size of data
+ +

Definition at line 22 of file buffer.c.

+ +
+
+ +

◆ fuse_convert_to_conn_want_ext()

+ +
+
+ + + + + + + + +
int fuse_convert_to_conn_want_ext (struct fuse_conn_infoconn)
+
+

Get the wanted capability flags, converting from old format if necessary

+ +

Definition at line 2000 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_daemonize()

+ +
+
+ + + + + + + + +
int fuse_daemonize (int foreground)
+
+

Go into the background

+
Parameters
+ + +
foregroundif true, stay in the foreground
+
+
+
Returns
0 on success, -1 on failure
+ +

Definition at line 253 of file helper.c.

+ +
+
+ +

◆ fuse_get_feature_flag()

+ +
+
+ + + + + + + + + + + + + + + + + + +
bool fuse_get_feature_flag (struct fuse_conn_infoconn,
uint64_t flag 
)
+
+

Get the value of a feature flag in the want_ext field of fuse_conn_info.

+
Parameters
+ + + +
connconnection information
flagfeature flag to be checked
+
+
+
Returns
true if the flag is set, false otherwise
+ +

Definition at line 2061 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_parse_conn_info_opts()

+ +
+
+ + + + + + + + +
struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_argsargs)
+
+

This function parses several command-line options that can be used to override elements of struct fuse_conn_info. The pointer returned by this function should be passed to the fuse_apply_conn_info_opts() method by the file system's init() handler.

+

Before using this function, think twice if you really want these parameters to be adjustable from the command line. In most cases, they should be determined by the file system internally.

+

The following options are recognized:

+

-o max_write=N sets conn->max_write -o max_readahead=N sets conn->max_readahead -o max_background=N sets conn->max_background -o congestion_threshold=N sets conn->congestion_threshold -o async_read sets FUSE_CAP_ASYNC_READ in conn->want -o sync_read unsets FUSE_CAP_ASYNC_READ in conn->want -o atomic_o_trunc sets FUSE_CAP_ATOMIC_O_TRUNC in conn->want -o no_remote_lock Equivalent to -o no_remote_flock,no_remote_posix_lock -o no_remote_flock Unsets FUSE_CAP_FLOCK_LOCKS in conn->want -o no_remote_posix_lock Unsets FUSE_CAP_POSIX_LOCKS in conn->want -o [no_]splice_write (un-)sets FUSE_CAP_SPLICE_WRITE in conn->want -o [no_]splice_move (un-)sets FUSE_CAP_SPLICE_MOVE in conn->want -o [no_]splice_read (un-)sets FUSE_CAP_SPLICE_READ in conn->want -o [no_]auto_inval_data (un-)sets FUSE_CAP_AUTO_INVAL_DATA in conn->want -o readdirplus=no unsets FUSE_CAP_READDIRPLUS in conn->want -o readdirplus=yes sets FUSE_CAP_READDIRPLUS and unsets FUSE_CAP_READDIRPLUS_AUTO in conn->want -o readdirplus=auto sets FUSE_CAP_READDIRPLUS and FUSE_CAP_READDIRPLUS_AUTO in conn->want -o [no_]async_dio (un-)sets FUSE_CAP_ASYNC_DIO in conn->want -o [no_]writeback_cache (un-)sets FUSE_CAP_WRITEBACK_CACHE in conn->want -o time_gran=N sets conn->time_gran

+

Known options will be removed from args, unknown options will be passed through unchanged.

+
Parameters
+ + +
argsargument vector (input+output)
+
+
+
Returns
parsed options
+ +

Definition at line 466 of file helper.c.

+ +
+
+ +

◆ fuse_pkgversion()

+ +
+
+ + + + + + + + +
const char * fuse_pkgversion (void )
+
+

Get the full package version string of the library

+
Returns
the package version
+ +

Definition at line 5211 of file fuse.c.

+ +
+
+ +

◆ fuse_pollhandle_destroy()

+ +
+
+ + + + + + + + +
void fuse_pollhandle_destroy (struct fuse_pollhandle * ph)
+
+

Destroy poll handle

+
Parameters
+ + +
phthe poll handle
+
+
+ +

Definition at line 1903 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_remove_signal_handlers()

+ +
+
+ + + + + + + + +
void fuse_remove_signal_handlers (struct fuse_session * se)
+
+

Restore default signal handlers

+

Resets global session. After this fuse_set_signal_handlers() may be called again.

+
Parameters
+ + +
sethe same session as given in fuse_set_signal_handlers()
+
+
+

See also: fuse_set_signal_handlers()

+ +

Definition at line 187 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_fail_signal_handlers()

+ +
+
+ + + + + + + + +
int fuse_set_fail_signal_handlers (struct fuse_session * se)
+
+

Print a stack backtrace diagnostic on critical signals ()

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 165 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_feature_flag()

+ +
+
+ + + + + + + + + + + + + + + + + + +
bool fuse_set_feature_flag (struct fuse_conn_infoconn,
uint64_t flag 
)
+
+

Config operations. These APIs are introduced in version 312 (FUSE_MAKE_VERSION(3, 12)). Using them in earlier versions will result in errors. Set a feature flag in the want_ext field of fuse_conn_info.

+
Parameters
+ + + +
connconnection information
flagfeature flag to be set
+
+
+
Returns
true if the flag was set, false if the flag is not supported
+ +

Definition at line 2035 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_set_signal_handlers()

+ +
+
+ + + + + + + + +
int fuse_set_signal_handlers (struct fuse_session * se)
+
+

Exit session on HUP, TERM and INT signals and ignore PIPE signal

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 140 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_unset_feature_flag()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_unset_feature_flag (struct fuse_conn_infoconn,
uint64_t flag 
)
+
+

Unset a feature flag in the want_ext field of fuse_conn_info.

+
Parameters
+ + + +
connconnection information
flagfeature flag to be unset
+
+
+ +

Definition at line 2050 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_version()

+ +
+
+ + + + + + + + +
int fuse_version (void )
+
+

Get the version of the library

+
Returns
the version
+ +

Definition at line 5206 of file fuse.c.

+ +
+
+
+ + + + diff --git a/doc/html/include_2fuse__common_8h_source.html b/doc/html/include_2fuse__common_8h_source.html new file mode 100644 index 0000000..7b27673 --- /dev/null +++ b/doc/html/include_2fuse__common_8h_source.html @@ -0,0 +1,469 @@ + + + + + + + +libfuse: include/fuse_common.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_common.h
+
+
+Go to the documentation of this file.
1/* FUSE: Filesystem in Userspace
+
2 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
3
+
4 This program can be distributed under the terms of the GNU LGPLv2.
+
5 See the file LGPL2.txt.
+
6*/
+
7
+
10#if !defined(FUSE_H_) && !defined(FUSE_LOWLEVEL_H_)
+
11#error "Never include <fuse_common.h> directly; use <fuse.h> or <fuse_lowlevel.h> instead."
+
12#endif
+
13
+
14#ifndef FUSE_COMMON_H_
+
15#define FUSE_COMMON_H_
+
16
+
17#ifdef HAVE_LIBFUSE_PRIVATE_CONFIG_H
+
18#include "fuse_config.h"
+
19#endif
+
20
+
21#include "libfuse_config.h"
+
22
+
23#include "fuse_opt.h"
+
24#include "fuse_log.h"
+
25#include <stdint.h>
+
26#include <stdbool.h>
+
27#include <sys/types.h>
+
28#include <assert.h>
+
29
+
30#define FUSE_MAKE_VERSION(maj, min) ((maj) * 100 + (min))
+
31#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION)
+
32
+
33#ifdef __cplusplus
+
34extern "C" {
+
35#endif
+
36
+
50struct fuse_file_info {
+
52 int32_t flags;
+
53
+
60 uint32_t writepage : 1;
+
61
+
63 uint32_t direct_io : 1;
+
64
+
69 uint32_t keep_cache : 1;
+
70
+
74 uint32_t flush : 1;
+
75
+
78 uint32_t nonseekable : 1;
+
79
+
80 /* Indicates that flock locks for this file should be
+
81 released. If set, lock_owner shall contain a valid value.
+
82 May only be set in ->release(). */
+
83 uint32_t flock_release : 1;
+
84
+
89 uint32_t cache_readdir : 1;
+
90
+
93 uint32_t noflush : 1;
+
94
+
97 uint32_t parallel_direct_writes : 1;
+
98
+
100 uint32_t padding : 23;
+
101 uint32_t padding2 : 32;
+
102 uint32_t padding3 : 32;
+
103
+
107 uint64_t fh;
+
108
+
110 uint64_t lock_owner;
+
111
+
114 uint32_t poll_events;
+
115
+
119 int32_t backing_id;
+
120
+
122 uint64_t compat_flags;
+
123
+
124 uint64_t reserved[2];
+
125};
+
126
+
137#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
138struct fuse_loop_config_v1; /* forward declaration */
+
139struct fuse_loop_config {
+
140#else
+
141struct fuse_loop_config_v1 {
+
142#endif
+
147 int clone_fd;
+
148
+
159 unsigned int max_idle_threads;
+
160};
+
161
+
162
+
163/**************************************************************************
+
164 * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want' *
+
165 **************************************************************************/
+
166
+
177#define FUSE_CAP_ASYNC_READ (1UL << 0)
+
178
+
185#define FUSE_CAP_POSIX_LOCKS (1UL << 1)
+
186
+
194#define FUSE_CAP_ATOMIC_O_TRUNC (1UL << 3)
+
195
+
206#define FUSE_CAP_EXPORT_SUPPORT (1UL << 4)
+
207
+
214#define FUSE_CAP_DONT_MASK (1UL << 6)
+
215
+
222#define FUSE_CAP_SPLICE_WRITE (1UL << 7)
+
223
+
230#define FUSE_CAP_SPLICE_MOVE (1UL << 8)
+
231
+
239#define FUSE_CAP_SPLICE_READ (1UL << 9)
+
240
+
252#define FUSE_CAP_FLOCK_LOCKS (1UL << 10)
+
253
+
259#define FUSE_CAP_IOCTL_DIR (1UL << 11)
+
260
+
281#define FUSE_CAP_AUTO_INVAL_DATA (1UL << 12)
+
282
+
289#define FUSE_CAP_READDIRPLUS (1UL << 13)
+
290
+
317#define FUSE_CAP_READDIRPLUS_AUTO (1UL << 14)
+
318
+
328#define FUSE_CAP_ASYNC_DIO (1UL << 15)
+
329
+
337#define FUSE_CAP_WRITEBACK_CACHE (1UL << 16)
+
338
+
352#define FUSE_CAP_NO_OPEN_SUPPORT (1UL << 17)
+
353
+
360#define FUSE_CAP_PARALLEL_DIROPS (1UL << 18)
+
361
+
379#define FUSE_CAP_POSIX_ACL (1UL << 19)
+
380
+
388#define FUSE_CAP_HANDLE_KILLPRIV (1UL << 20)
+
389
+
405#define FUSE_CAP_HANDLE_KILLPRIV_V2 (1UL << 21)
+
406
+
418#define FUSE_CAP_CACHE_SYMLINKS (1UL << 23)
+
419
+
433#define FUSE_CAP_NO_OPENDIR_SUPPORT (1UL << 24)
+
434
+
456#define FUSE_CAP_EXPLICIT_INVAL_DATA (1UL << 25)
+
457
+
472#define FUSE_CAP_EXPIRE_ONLY (1UL << 26)
+
473
+
479#define FUSE_CAP_SETXATTR_EXT (1UL << 27)
+
480
+
488#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP (1UL << 28)
+
489
+
500#define FUSE_CAP_PASSTHROUGH (1UL << 29)
+
501
+
508#define FUSE_CAP_NO_EXPORT_SUPPORT (1UL << 30)
+
509
+
513#define FUSE_CAP_OVER_IO_URING (1UL << 31)
+
514
+
525#define FUSE_IOCTL_COMPAT (1 << 0)
+
526#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
527#define FUSE_IOCTL_RETRY (1 << 2)
+
528#define FUSE_IOCTL_DIR (1 << 4)
+
529
+
530#define FUSE_IOCTL_MAX_IOV 256
+
531
+
543struct fuse_conn_info {
+
547 uint32_t proto_major;
+
548
+
552 uint32_t proto_minor;
+
553
+
557 uint32_t max_write;
+
558
+
571 uint32_t max_read;
+
572
+
576 uint32_t max_readahead;
+
577
+
583 uint32_t capable;
+
584
+
595 uint32_t want;
+
596
+
625 uint32_t max_background;
+
626
+
635 uint32_t congestion_threshold;
+
636
+
652 uint32_t time_gran;
+
653
+
670#define FUSE_BACKING_STACKED_UNDER (0)
+
671#define FUSE_BACKING_STACKED_OVER (1)
+
672 uint32_t max_backing_stack_depth;
+
673
+
682 uint32_t no_interrupt : 1;
+
683
+
684 /* reserved bits for future use */
+
685 uint32_t padding : 31;
+
686
+
691 uint64_t capable_ext;
+
692
+
701 uint64_t want_ext;
+
702
+
707 uint16_t request_timeout;
+
708
+
712 uint16_t reserved[31];
+
713};
+
714
+
715struct fuse_session;
+
716struct fuse_pollhandle;
+
717struct fuse_conn_info_opts;
+
718
+
761struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args);
+
762
+
770void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
771 struct fuse_conn_info *conn);
+
772
+
779int fuse_daemonize(int foreground);
+
780
+
786int fuse_version(void);
+
787
+
793const char *fuse_pkgversion(void);
+
794
+
800void fuse_pollhandle_destroy(struct fuse_pollhandle *ph);
+
801
+
802/* ----------------------------------------------------------- *
+
803 * Data buffer *
+
804 * ----------------------------------------------------------- */
+
805
+
+ +
816 FUSE_BUF_IS_FD = (1 << 1),
+
817
+ +
826
+
834 FUSE_BUF_FD_RETRY = (1 << 3)
+ +
+
836
+
+ + +
851
+ +
859
+ +
868
+ + +
+
878
+
885struct fuse_buf {
+
889 size_t size;
+
890
+
894 enum fuse_buf_flags flags;
+
895
+
901 void *mem;
+
902
+
908 int fd;
+
909
+
915 off_t pos;
+
916
+
923 size_t mem_size;
+
924};
+
925
+
934struct fuse_bufvec {
+
938 size_t count;
+
939
+
943 size_t idx;
+
944
+
948 size_t off;
+
949
+
953 struct fuse_buf buf[1];
+
954};
+
955
+
960struct libfuse_version
+
961{
+
962 uint32_t major;
+
963 uint32_t minor;
+
964 uint32_t hotfix;
+
965 uint32_t padding;
+
966};
+
967
+
968/* Initialize bufvec with a single buffer of given size */
+
969#define FUSE_BUFVEC_INIT(size__) \
+
970 ((struct fuse_bufvec) { \
+
971 /* .count= */ 1, \
+
972 /* .idx = */ 0, \
+
973 /* .off = */ 0, \
+
974 /* .buf = */ { /* [0] = */ { \
+
975 /* .size = */ (size__), \
+
976 /* .flags = */ (enum fuse_buf_flags) 0, \
+
977 /* .mem = */ NULL, \
+
978 /* .fd = */ -1, \
+
979 /* .pos = */ 0, \
+
980 /* .mem_size = */ 0, \
+
981 } } \
+
982 } )
+
983
+
990size_t fuse_buf_size(const struct fuse_bufvec *bufv);
+
991
+
1000ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src,
+
1001 enum fuse_buf_copy_flags flags);
+
1002
+
1003/* ----------------------------------------------------------- *
+
1004 * Signal handling *
+
1005 * ----------------------------------------------------------- */
+
1006
+
1022int fuse_set_signal_handlers(struct fuse_session *se);
+
1023
+
1039int fuse_set_fail_signal_handlers(struct fuse_session *se);
+
1040
+
1052void fuse_remove_signal_handlers(struct fuse_session *se);
+
1053
+
1059#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
1065struct fuse_loop_config *fuse_loop_cfg_create(void);
+
1066
+
1070void fuse_loop_cfg_destroy(struct fuse_loop_config *config);
+
1071
+
1075void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
1076 unsigned int value);
+
1077
+
1081void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
1082 unsigned int value);
+
1083
+
1087void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
1088 unsigned int value);
+
1089
+
1096void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
1097 struct fuse_loop_config_v1 *v1_conf);
+
1098#endif
+
1099
+
1107bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag);
+
1108
+
1115void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag);
+
1116
+
1124bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag);
+
1125
+
1126/*
+
1127 * DO NOT USE: Not part of public API, for internal test use only.
+
1128 * The function signature or any use of it is not guaranteeed to
+
1129 * remain stable. And neither are results of what this function does.
+
1130 */
+ +
1132
+
1133
+
1134
+
1135/* ----------------------------------------------------------- *
+
1136 * Compatibility stuff *
+
1137 * ----------------------------------------------------------- */
+
1138
+
1139#if !defined(FUSE_USE_VERSION) || FUSE_USE_VERSION < 30
+
1140# error only API version 30 or greater is supported
+
1141#endif
+
1142
+
1143#ifdef __cplusplus
+
1144}
+
1145#endif
+
1146
+
1147
+
1148/*
+
1149 * This interface uses 64 bit off_t.
+
1150 *
+
1151 * On 32bit systems please add -D_FILE_OFFSET_BITS=64 to your compile flags!
+
1152 */
+
1153
+
1154#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
+
1155_Static_assert(sizeof(off_t) == 8, "fuse: off_t must be 64bit");
+
1156#else
+
1157struct _fuse_off_t_must_be_64bit_dummy_struct \
+
1158 { unsigned _fuse_off_t_must_be_64bit:((sizeof(off_t) == 8) ? 1 : -1); };
+
1159#endif
+
1160
+
1161#endif /* FUSE_COMMON_H_ */
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:412
+
fuse_buf_flags
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:466
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
int fuse_version(void)
Definition fuse.c:5206
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+ + + + + + + +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:60
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t nonseekable
Definition fuse_common.h:78
+
int32_t backing_id
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t padding
+
uint32_t noflush
Definition fuse_common.h:93
+
uint64_t compat_flags
+
uint32_t flush
Definition fuse_common.h:74
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
+ + + + diff --git a/doc/html/include_2fuse__kernel_8h_source.html b/doc/html/include_2fuse__kernel_8h_source.html new file mode 100644 index 0000000..05f6ee3 --- /dev/null +++ b/doc/html/include_2fuse__kernel_8h_source.html @@ -0,0 +1,1180 @@ + + + + + + + +libfuse: include/fuse_kernel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_kernel.h
+
+
+
1/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */
+
2/*
+
3 This file defines the kernel interface of FUSE
+
4 Copyright (C) 2001-2008 Miklos Szeredi <miklos@szeredi.hu>
+
5
+
6 This program can be distributed under the terms of the GNU GPL.
+
7 See the file COPYING.
+
8
+
9 This -- and only this -- header file may also be distributed under
+
10 the terms of the BSD Licence as follows:
+
11
+
12 Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
+
13
+
14 Redistribution and use in source and binary forms, with or without
+
15 modification, are permitted provided that the following conditions
+
16 are met:
+
17 1. Redistributions of source code must retain the above copyright
+
18 notice, this list of conditions and the following disclaimer.
+
19 2. Redistributions in binary form must reproduce the above copyright
+
20 notice, this list of conditions and the following disclaimer in the
+
21 documentation and/or other materials provided with the distribution.
+
22
+
23 THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+
24 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
25 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
26 ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+
27 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
28 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
29 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
30 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
32 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
33 SUCH DAMAGE.
+
34*/
+
35
+
36/*
+
37 * This file defines the kernel interface of FUSE
+
38 *
+
39 * Protocol changelog:
+
40 *
+
41 * 7.1:
+
42 * - add the following messages:
+
43 * FUSE_SETATTR, FUSE_SYMLINK, FUSE_MKNOD, FUSE_MKDIR, FUSE_UNLINK,
+
44 * FUSE_RMDIR, FUSE_RENAME, FUSE_LINK, FUSE_OPEN, FUSE_READ, FUSE_WRITE,
+
45 * FUSE_RELEASE, FUSE_FSYNC, FUSE_FLUSH, FUSE_SETXATTR, FUSE_GETXATTR,
+
46 * FUSE_LISTXATTR, FUSE_REMOVEXATTR, FUSE_OPENDIR, FUSE_READDIR,
+
47 * FUSE_RELEASEDIR
+
48 * - add padding to messages to accommodate 32-bit servers on 64-bit kernels
+
49 *
+
50 * 7.2:
+
51 * - add FOPEN_DIRECT_IO and FOPEN_KEEP_CACHE flags
+
52 * - add FUSE_FSYNCDIR message
+
53 *
+
54 * 7.3:
+
55 * - add FUSE_ACCESS message
+
56 * - add FUSE_CREATE message
+
57 * - add filehandle to fuse_setattr_in
+
58 *
+
59 * 7.4:
+
60 * - add frsize to fuse_kstatfs
+
61 * - clean up request size limit checking
+
62 *
+
63 * 7.5:
+
64 * - add flags and max_write to fuse_init_out
+
65 *
+
66 * 7.6:
+
67 * - add max_readahead to fuse_init_in and fuse_init_out
+
68 *
+
69 * 7.7:
+
70 * - add FUSE_INTERRUPT message
+
71 * - add POSIX file lock support
+
72 *
+
73 * 7.8:
+
74 * - add lock_owner and flags fields to fuse_release_in
+
75 * - add FUSE_BMAP message
+
76 * - add FUSE_DESTROY message
+
77 *
+
78 * 7.9:
+
79 * - new fuse_getattr_in input argument of GETATTR
+
80 * - add lk_flags in fuse_lk_in
+
81 * - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in
+
82 * - add blksize field to fuse_attr
+
83 * - add file flags field to fuse_read_in and fuse_write_in
+
84 * - Add ATIME_NOW and MTIME_NOW flags to fuse_setattr_in
+
85 *
+
86 * 7.10
+
87 * - add nonseekable open flag
+
88 *
+
89 * 7.11
+
90 * - add IOCTL message
+
91 * - add unsolicited notification support
+
92 * - add POLL message and NOTIFY_POLL notification
+
93 *
+
94 * 7.12
+
95 * - add umask flag to input argument of create, mknod and mkdir
+
96 * - add notification messages for invalidation of inodes and
+
97 * directory entries
+
98 *
+
99 * 7.13
+
100 * - make max number of background requests and congestion threshold
+
101 * tunables
+
102 *
+
103 * 7.14
+
104 * - add splice support to fuse device
+
105 *
+
106 * 7.15
+
107 * - add store notify
+
108 * - add retrieve notify
+
109 *
+
110 * 7.16
+
111 * - add BATCH_FORGET request
+
112 * - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct
+
113 * fuse_ioctl_iovec' instead of ambiguous 'struct iovec'
+
114 * - add FUSE_IOCTL_32BIT flag
+
115 *
+
116 * 7.17
+
117 * - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK
+
118 *
+
119 * 7.18
+
120 * - add FUSE_IOCTL_DIR flag
+
121 * - add FUSE_NOTIFY_DELETE
+
122 *
+
123 * 7.19
+
124 * - add FUSE_FALLOCATE
+
125 *
+
126 * 7.20
+
127 * - add FUSE_AUTO_INVAL_DATA
+
128 *
+
129 * 7.21
+
130 * - add FUSE_READDIRPLUS
+
131 * - send the requested events in POLL request
+
132 *
+
133 * 7.22
+
134 * - add FUSE_ASYNC_DIO
+
135 *
+
136 * 7.23
+
137 * - add FUSE_WRITEBACK_CACHE
+
138 * - add time_gran to fuse_init_out
+
139 * - add reserved space to fuse_init_out
+
140 * - add FATTR_CTIME
+
141 * - add ctime and ctimensec to fuse_setattr_in
+
142 * - add FUSE_RENAME2 request
+
143 * - add FUSE_NO_OPEN_SUPPORT flag
+
144 *
+
145 * 7.24
+
146 * - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support
+
147 *
+
148 * 7.25
+
149 * - add FUSE_PARALLEL_DIROPS
+
150 *
+
151 * 7.26
+
152 * - add FUSE_HANDLE_KILLPRIV
+
153 * - add FUSE_POSIX_ACL
+
154 *
+
155 * 7.27
+
156 * - add FUSE_ABORT_ERROR
+
157 *
+
158 * 7.28
+
159 * - add FUSE_COPY_FILE_RANGE
+
160 * - add FOPEN_CACHE_DIR
+
161 * - add FUSE_MAX_PAGES, add max_pages to init_out
+
162 * - add FUSE_CACHE_SYMLINKS
+
163 *
+
164 * 7.29
+
165 * - add FUSE_NO_OPENDIR_SUPPORT flag
+
166 *
+
167 * 7.30
+
168 * - add FUSE_EXPLICIT_INVAL_DATA
+
169 * - add FUSE_IOCTL_COMPAT_X32
+
170 *
+
171 * 7.31
+
172 * - add FUSE_WRITE_KILL_PRIV flag
+
173 * - add FUSE_SETUPMAPPING and FUSE_REMOVEMAPPING
+
174 * - add map_alignment to fuse_init_out, add FUSE_MAP_ALIGNMENT flag
+
175 *
+
176 * 7.32
+
177 * - add flags to fuse_attr, add FUSE_ATTR_SUBMOUNT, add FUSE_SUBMOUNTS
+
178 *
+
179 * 7.33
+
180 * - add FUSE_HANDLE_KILLPRIV_V2, FUSE_WRITE_KILL_SUIDGID, FATTR_KILL_SUIDGID
+
181 * - add FUSE_OPEN_KILL_SUIDGID
+
182 * - extend fuse_setxattr_in, add FUSE_SETXATTR_EXT
+
183 * - add FUSE_SETXATTR_ACL_KILL_SGID
+
184 *
+
185 * 7.34
+
186 * - add FUSE_SYNCFS
+
187 *
+
188 * 7.35
+
189 * - add FOPEN_NOFLUSH
+
190 *
+
191 * 7.36
+
192 * - extend fuse_init_in with reserved fields, add FUSE_INIT_EXT init flag
+
193 * - add flags2 to fuse_init_in and fuse_init_out
+
194 * - add FUSE_SECURITY_CTX init flag
+
195 * - add security context to create, mkdir, symlink, and mknod requests
+
196 * - add FUSE_HAS_INODE_DAX, FUSE_ATTR_DAX
+
197 *
+
198 * 7.37
+
199 * - add FUSE_TMPFILE
+
200 *
+
201 * 7.38
+
202 * - add FUSE_EXPIRE_ONLY flag to fuse_notify_inval_entry
+
203 * - add FOPEN_PARALLEL_DIRECT_WRITES
+
204 * - add total_extlen to fuse_in_header
+
205 * - add FUSE_MAX_NR_SECCTX
+
206 * - add extension header
+
207 * - add FUSE_EXT_GROUPS
+
208 * - add FUSE_CREATE_SUPP_GROUP
+
209 * - add FUSE_HAS_EXPIRE_ONLY
+
210 *
+
211 * 7.39
+
212 * - add FUSE_DIRECT_IO_ALLOW_MMAP
+
213 * - add FUSE_STATX and related structures
+
214 *
+
215 * 7.40
+
216 * - add max_stack_depth to fuse_init_out, add FUSE_PASSTHROUGH init flag
+
217 * - add backing_id to fuse_open_out, add FOPEN_PASSTHROUGH open flag
+
218 * - add FUSE_NO_EXPORT_SUPPORT init flag
+
219 * - add FUSE_NOTIFY_RESEND, add FUSE_HAS_RESEND init flag
+
220 *
+
221 * 7.41
+
222 * - add FUSE_ALLOW_IDMAP
+
223 * 7.42
+
224 * - Add FUSE_OVER_IO_URING and all other io-uring related flags and data
+
225 * structures:
+
226 * - struct fuse_uring_ent_in_out
+
227 * - struct fuse_uring_req_header
+
228 * - struct fuse_uring_cmd_req
+
229 * - FUSE_URING_IN_OUT_HEADER_SZ
+
230 * - FUSE_URING_OP_IN_OUT_SZ
+
231 * - enum fuse_uring_cmd
+
232 *
+
233 * 7.43
+
234 * - add FUSE_REQUEST_TIMEOUT
+
235 *
+
236 * 7.44
+
237 * - add FUSE_NOTIFY_INC_EPOCH
+
238 *
+
239 * 7.45
+
240 * - add FUSE_COPY_FILE_RANGE_64
+
241 * - add struct fuse_copy_file_range_out
+
242 */
+
243
+
244#ifndef _LINUX_FUSE_H
+
245#define _LINUX_FUSE_H
+
246
+
247#ifdef __KERNEL__
+
248#include <linux/types.h>
+
249#else
+
250#include <stdint.h>
+
251#endif
+
252
+
253/*
+
254 * Version negotiation:
+
255 *
+
256 * Both the kernel and userspace send the version they support in the
+
257 * INIT request and reply respectively.
+
258 *
+
259 * If the major versions match then both shall use the smallest
+
260 * of the two minor versions for communication.
+
261 *
+
262 * If the kernel supports a larger major version, then userspace shall
+
263 * reply with the major version it supports, ignore the rest of the
+
264 * INIT message and expect a new INIT message from the kernel with a
+
265 * matching major version.
+
266 *
+
267 * If the library supports a larger major version, then it shall fall
+
268 * back to the major protocol version sent by the kernel for
+
269 * communication and reply with that major version (and an arbitrary
+
270 * supported minor version).
+
271 */
+
272
+
274#define FUSE_KERNEL_VERSION 7
+
275
+
277#define FUSE_KERNEL_MINOR_VERSION 45
+
278
+
280#define FUSE_ROOT_ID 1
+
281
+
282/* Make sure all structures are padded to 64bit boundary, so 32bit
+
283 userspace works under 64bit kernels */
+
284
+
285struct fuse_attr {
+
286 uint64_t ino;
+
287 uint64_t size;
+
288 uint64_t blocks;
+
289 uint64_t atime;
+
290 uint64_t mtime;
+
291 uint64_t ctime;
+
292 uint32_t atimensec;
+
293 uint32_t mtimensec;
+
294 uint32_t ctimensec;
+
295 uint32_t mode;
+
296 uint32_t nlink;
+
297 uint32_t uid;
+
298 uint32_t gid;
+
299 uint32_t rdev;
+
300 uint32_t blksize;
+
301 uint32_t flags;
+
302};
+
303
+
304/*
+
305 * The following structures are bit-for-bit compatible with the statx(2) ABI in
+
306 * Linux.
+
307 */
+
308struct fuse_sx_time {
+
309 int64_t tv_sec;
+
310 uint32_t tv_nsec;
+
311 int32_t __reserved;
+
312};
+
313
+
314struct fuse_statx {
+
315 uint32_t mask;
+
316 uint32_t blksize;
+
317 uint64_t attributes;
+
318 uint32_t nlink;
+
319 uint32_t uid;
+
320 uint32_t gid;
+
321 uint16_t mode;
+
322 uint16_t __spare0[1];
+
323 uint64_t ino;
+
324 uint64_t size;
+
325 uint64_t blocks;
+
326 uint64_t attributes_mask;
+
327 struct fuse_sx_time atime;
+
328 struct fuse_sx_time btime;
+
329 struct fuse_sx_time ctime;
+
330 struct fuse_sx_time mtime;
+
331 uint32_t rdev_major;
+
332 uint32_t rdev_minor;
+
333 uint32_t dev_major;
+
334 uint32_t dev_minor;
+
335 uint64_t __spare2[14];
+
336};
+
337
+
338struct fuse_kstatfs {
+
339 uint64_t blocks;
+
340 uint64_t bfree;
+
341 uint64_t bavail;
+
342 uint64_t files;
+
343 uint64_t ffree;
+
344 uint32_t bsize;
+
345 uint32_t namelen;
+
346 uint32_t frsize;
+
347 uint32_t padding;
+
348 uint32_t spare[6];
+
349};
+
350
+
351struct fuse_file_lock {
+
352 uint64_t start;
+
353 uint64_t end;
+
354 uint32_t type;
+
355 uint32_t pid; /* tgid */
+
356};
+
357
+
361#define FATTR_MODE (1 << 0)
+
362#define FATTR_UID (1 << 1)
+
363#define FATTR_GID (1 << 2)
+
364#define FATTR_SIZE (1 << 3)
+
365#define FATTR_ATIME (1 << 4)
+
366#define FATTR_MTIME (1 << 5)
+
367#define FATTR_FH (1 << 6)
+
368#define FATTR_ATIME_NOW (1 << 7)
+
369#define FATTR_MTIME_NOW (1 << 8)
+
370#define FATTR_LOCKOWNER (1 << 9)
+
371#define FATTR_CTIME (1 << 10)
+
372#define FATTR_KILL_SUIDGID (1 << 11)
+
373
+
386#define FOPEN_DIRECT_IO (1 << 0)
+
387#define FOPEN_KEEP_CACHE (1 << 1)
+
388#define FOPEN_NONSEEKABLE (1 << 2)
+
389#define FOPEN_CACHE_DIR (1 << 3)
+
390#define FOPEN_STREAM (1 << 4)
+
391#define FOPEN_NOFLUSH (1 << 5)
+
392#define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6)
+
393#define FOPEN_PASSTHROUGH (1 << 7)
+
394
+
451#define FUSE_ASYNC_READ (1 << 0)
+
452#define FUSE_POSIX_LOCKS (1 << 1)
+
453#define FUSE_FILE_OPS (1 << 2)
+
454#define FUSE_ATOMIC_O_TRUNC (1 << 3)
+
455#define FUSE_EXPORT_SUPPORT (1 << 4)
+
456#define FUSE_BIG_WRITES (1 << 5)
+
457#define FUSE_DONT_MASK (1 << 6)
+
458#define FUSE_SPLICE_WRITE (1 << 7)
+
459#define FUSE_SPLICE_MOVE (1 << 8)
+
460#define FUSE_SPLICE_READ (1 << 9)
+
461#define FUSE_FLOCK_LOCKS (1 << 10)
+
462#define FUSE_HAS_IOCTL_DIR (1 << 11)
+
463#define FUSE_AUTO_INVAL_DATA (1 << 12)
+
464#define FUSE_DO_READDIRPLUS (1 << 13)
+
465#define FUSE_READDIRPLUS_AUTO (1 << 14)
+
466#define FUSE_ASYNC_DIO (1 << 15)
+
467#define FUSE_WRITEBACK_CACHE (1 << 16)
+
468#define FUSE_NO_OPEN_SUPPORT (1 << 17)
+
469#define FUSE_PARALLEL_DIROPS (1 << 18)
+
470#define FUSE_HANDLE_KILLPRIV (1 << 19)
+
471#define FUSE_POSIX_ACL (1 << 20)
+
472#define FUSE_ABORT_ERROR (1 << 21)
+
473#define FUSE_MAX_PAGES (1 << 22)
+
474#define FUSE_CACHE_SYMLINKS (1 << 23)
+
475#define FUSE_NO_OPENDIR_SUPPORT (1 << 24)
+
476#define FUSE_EXPLICIT_INVAL_DATA (1 << 25)
+
477#define FUSE_MAP_ALIGNMENT (1 << 26)
+
478#define FUSE_SUBMOUNTS (1 << 27)
+
479#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28)
+
480#define FUSE_SETXATTR_EXT (1 << 29)
+
481#define FUSE_INIT_EXT (1 << 30)
+
482#define FUSE_INIT_RESERVED (1 << 31)
+
483/* bits 32..63 get shifted down 32 bits into the flags2 field */
+
484#define FUSE_SECURITY_CTX (1ULL << 32)
+
485#define FUSE_HAS_INODE_DAX (1ULL << 33)
+
486#define FUSE_CREATE_SUPP_GROUP (1ULL << 34)
+
487#define FUSE_HAS_EXPIRE_ONLY (1ULL << 35)
+
488#define FUSE_DIRECT_IO_ALLOW_MMAP (1ULL << 36)
+
489#define FUSE_PASSTHROUGH (1ULL << 37)
+
490#define FUSE_NO_EXPORT_SUPPORT (1ULL << 38)
+
491#define FUSE_HAS_RESEND (1ULL << 39)
+
492/* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */
+
493#define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP
+
494#define FUSE_ALLOW_IDMAP (1ULL << 40)
+
495#define FUSE_OVER_IO_URING (1ULL << 41)
+
496#define FUSE_REQUEST_TIMEOUT (1ULL << 42)
+
497
+
503#define CUSE_UNRESTRICTED_IOCTL (1 << 0)
+
504
+
508#define FUSE_RELEASE_FLUSH (1 << 0)
+
509#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1)
+
510
+
514#define FUSE_GETATTR_FH (1 << 0)
+
515
+
519#define FUSE_LK_FLOCK (1 << 0)
+
520
+
528#define FUSE_WRITE_CACHE (1 << 0)
+
529#define FUSE_WRITE_LOCKOWNER (1 << 1)
+
530#define FUSE_WRITE_KILL_SUIDGID (1 << 2)
+
531
+
532/* Obsolete alias; this flag implies killing suid/sgid only. */
+
533#define FUSE_WRITE_KILL_PRIV FUSE_WRITE_KILL_SUIDGID
+
534
+
538#define FUSE_READ_LOCKOWNER (1 << 1)
+
539
+
552#define FUSE_IOCTL_COMPAT (1 << 0)
+
553#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
554#define FUSE_IOCTL_RETRY (1 << 2)
+
555#define FUSE_IOCTL_32BIT (1 << 3)
+
556#define FUSE_IOCTL_DIR (1 << 4)
+
557#define FUSE_IOCTL_COMPAT_X32 (1 << 5)
+
558
+
559#define FUSE_IOCTL_MAX_IOV 256
+
560
+
566#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
+
567
+
573#define FUSE_FSYNC_FDATASYNC (1 << 0)
+
574
+
581#define FUSE_ATTR_SUBMOUNT (1 << 0)
+
582#define FUSE_ATTR_DAX (1 << 1)
+
583
+
588#define FUSE_OPEN_KILL_SUIDGID (1 << 0)
+
589
+
594#define FUSE_SETXATTR_ACL_KILL_SGID (1 << 0)
+
595
+
600#define FUSE_EXPIRE_ONLY (1 << 0)
+
601
+
607enum fuse_ext_type {
+
608 /* Types 0..31 are reserved for fuse_secctx_header */
+
609 FUSE_MAX_NR_SECCTX = 31,
+
610 FUSE_EXT_GROUPS = 32,
+
611};
+
612
+
613enum fuse_opcode {
+
614 FUSE_LOOKUP = 1,
+
615 FUSE_FORGET = 2, /* no reply */
+
616 FUSE_GETATTR = 3,
+
617 FUSE_SETATTR = 4,
+
618 FUSE_READLINK = 5,
+
619 FUSE_SYMLINK = 6,
+
620 FUSE_MKNOD = 8,
+
621 FUSE_MKDIR = 9,
+
622 FUSE_UNLINK = 10,
+
623 FUSE_RMDIR = 11,
+
624 FUSE_RENAME = 12,
+
625 FUSE_LINK = 13,
+
626 FUSE_OPEN = 14,
+
627 FUSE_READ = 15,
+
628 FUSE_WRITE = 16,
+
629 FUSE_STATFS = 17,
+
630 FUSE_RELEASE = 18,
+
631 FUSE_FSYNC = 20,
+
632 FUSE_SETXATTR = 21,
+
633 FUSE_GETXATTR = 22,
+
634 FUSE_LISTXATTR = 23,
+
635 FUSE_REMOVEXATTR = 24,
+
636 FUSE_FLUSH = 25,
+
637 FUSE_INIT = 26,
+
638 FUSE_OPENDIR = 27,
+
639 FUSE_READDIR = 28,
+
640 FUSE_RELEASEDIR = 29,
+
641 FUSE_FSYNCDIR = 30,
+
642 FUSE_GETLK = 31,
+
643 FUSE_SETLK = 32,
+
644 FUSE_SETLKW = 33,
+
645 FUSE_ACCESS = 34,
+
646 FUSE_CREATE = 35,
+
647 FUSE_INTERRUPT = 36,
+
648 FUSE_BMAP = 37,
+
649 FUSE_DESTROY = 38,
+
650 FUSE_IOCTL = 39,
+
651 FUSE_POLL = 40,
+
652 FUSE_NOTIFY_REPLY = 41,
+
653 FUSE_BATCH_FORGET = 42,
+
654 FUSE_FALLOCATE = 43,
+
655 FUSE_READDIRPLUS = 44,
+
656 FUSE_RENAME2 = 45,
+
657 FUSE_LSEEK = 46,
+
658 FUSE_COPY_FILE_RANGE = 47,
+
659 FUSE_SETUPMAPPING = 48,
+
660 FUSE_REMOVEMAPPING = 49,
+
661 FUSE_SYNCFS = 50,
+
662 FUSE_TMPFILE = 51,
+
663 FUSE_STATX = 52,
+
664 FUSE_COPY_FILE_RANGE_64 = 53,
+
665
+
666 /* CUSE specific operations */
+
667 CUSE_INIT = 4096,
+
668
+
669 /* Reserved opcodes: helpful to detect structure endian-ness */
+
670 CUSE_INIT_BSWAP_RESERVED = 1048576, /* CUSE_INIT << 8 */
+
671 FUSE_INIT_BSWAP_RESERVED = 436207616, /* FUSE_INIT << 24 */
+
672};
+
673
+
674enum fuse_notify_code {
+
675 FUSE_NOTIFY_POLL = 1,
+
676 FUSE_NOTIFY_INVAL_INODE = 2,
+
677 FUSE_NOTIFY_INVAL_ENTRY = 3,
+
678 FUSE_NOTIFY_STORE = 4,
+
679 FUSE_NOTIFY_RETRIEVE = 5,
+
680 FUSE_NOTIFY_DELETE = 6,
+
681 FUSE_NOTIFY_RESEND = 7,
+
682 FUSE_NOTIFY_INC_EPOCH = 8,
+
683 FUSE_NOTIFY_CODE_MAX,
+
684};
+
685
+
686/* The read buffer is required to be at least 8k, but may be much larger */
+
687#define FUSE_MIN_READ_BUFFER 8192
+
688
+
689#define FUSE_COMPAT_ENTRY_OUT_SIZE 120
+
690
+
691struct fuse_entry_out {
+
692 uint64_t nodeid; /* Inode ID */
+
693 uint64_t generation; /* Inode generation: nodeid:gen must
+
694 be unique for the fs's lifetime */
+
695 uint64_t entry_valid; /* Cache timeout for the name */
+
696 uint64_t attr_valid; /* Cache timeout for the attributes */
+
697 uint32_t entry_valid_nsec;
+
698 uint32_t attr_valid_nsec;
+
699 struct fuse_attr attr;
+
700};
+
701
+
702struct fuse_forget_in {
+
703 uint64_t nlookup;
+
704};
+
705
+
706struct fuse_forget_one {
+
707 uint64_t nodeid;
+
708 uint64_t nlookup;
+
709};
+
710
+
711struct fuse_batch_forget_in {
+
712 uint32_t count;
+
713 uint32_t dummy;
+
714};
+
715
+
716struct fuse_getattr_in {
+
717 uint32_t getattr_flags;
+
718 uint32_t dummy;
+
719 uint64_t fh;
+
720};
+
721
+
722#define FUSE_COMPAT_ATTR_OUT_SIZE 96
+
723
+
724struct fuse_attr_out {
+
725 uint64_t attr_valid; /* Cache timeout for the attributes */
+
726 uint32_t attr_valid_nsec;
+
727 uint32_t dummy;
+
728 struct fuse_attr attr;
+
729};
+
730
+
731struct fuse_statx_in {
+
732 uint32_t getattr_flags;
+
733 uint32_t reserved;
+
734 uint64_t fh;
+
735 uint32_t sx_flags;
+
736 uint32_t sx_mask;
+
737};
+
738
+
739struct fuse_statx_out {
+
740 uint64_t attr_valid; /* Cache timeout for the attributes */
+
741 uint32_t attr_valid_nsec;
+
742 uint32_t flags;
+
743 uint64_t spare[2];
+
744 struct fuse_statx stat;
+
745};
+
746
+
747#define FUSE_COMPAT_MKNOD_IN_SIZE 8
+
748
+
749struct fuse_mknod_in {
+
750 uint32_t mode;
+
751 uint32_t rdev;
+
752 uint32_t umask;
+
753 uint32_t padding;
+
754};
+
755
+
756struct fuse_mkdir_in {
+
757 uint32_t mode;
+
758 uint32_t umask;
+
759};
+
760
+
761struct fuse_rename_in {
+
762 uint64_t newdir;
+
763};
+
764
+
765struct fuse_rename2_in {
+
766 uint64_t newdir;
+
767 uint32_t flags;
+
768 uint32_t padding;
+
769};
+
770
+
771struct fuse_link_in {
+
772 uint64_t oldnodeid;
+
773};
+
774
+
775struct fuse_setattr_in {
+
776 uint32_t valid;
+
777 uint32_t padding;
+
778 uint64_t fh;
+
779 uint64_t size;
+
780 uint64_t lock_owner;
+
781 uint64_t atime;
+
782 uint64_t mtime;
+
783 uint64_t ctime;
+
784 uint32_t atimensec;
+
785 uint32_t mtimensec;
+
786 uint32_t ctimensec;
+
787 uint32_t mode;
+
788 uint32_t unused4;
+
789 uint32_t uid;
+
790 uint32_t gid;
+
791 uint32_t unused5;
+
792};
+
793
+
794struct fuse_open_in {
+
795 uint32_t flags;
+
796 uint32_t open_flags; /* FUSE_OPEN_... */
+
797};
+
798
+
799struct fuse_create_in {
+
800 uint32_t flags;
+
801 uint32_t mode;
+
802 uint32_t umask;
+
803 uint32_t open_flags; /* FUSE_OPEN_... */
+
804};
+
805
+
806struct fuse_open_out {
+
807 uint64_t fh;
+
808 uint32_t open_flags;
+
809 int32_t backing_id;
+
810};
+
811
+
812struct fuse_release_in {
+
813 uint64_t fh;
+
814 uint32_t flags;
+
815 uint32_t release_flags;
+
816 uint64_t lock_owner;
+
817};
+
818
+
819struct fuse_flush_in {
+
820 uint64_t fh;
+
821 uint32_t unused;
+
822 uint32_t padding;
+
823 uint64_t lock_owner;
+
824};
+
825
+
826struct fuse_read_in {
+
827 uint64_t fh;
+
828 uint64_t offset;
+
829 uint32_t size;
+
830 uint32_t read_flags;
+
831 uint64_t lock_owner;
+
832 uint32_t flags;
+
833 uint32_t padding;
+
834};
+
835
+
836#define FUSE_COMPAT_WRITE_IN_SIZE 24
+
837
+
838struct fuse_write_in {
+
839 uint64_t fh;
+
840 uint64_t offset;
+
841 uint32_t size;
+
842 uint32_t write_flags;
+
843 uint64_t lock_owner;
+
844 uint32_t flags;
+
845 uint32_t padding;
+
846};
+
847
+
848struct fuse_write_out {
+
849 uint32_t size;
+
850 uint32_t padding;
+
851};
+
852
+
853#define FUSE_COMPAT_STATFS_SIZE 48
+
854
+
855struct fuse_statfs_out {
+
856 struct fuse_kstatfs st;
+
857};
+
858
+
859struct fuse_fsync_in {
+
860 uint64_t fh;
+
861 uint32_t fsync_flags;
+
862 uint32_t padding;
+
863};
+
864
+
865#define FUSE_COMPAT_SETXATTR_IN_SIZE 8
+
866
+
867struct fuse_setxattr_in {
+
868 uint32_t size;
+
869 uint32_t flags;
+
870 uint32_t setxattr_flags;
+
871 uint32_t padding;
+
872};
+
873
+
874struct fuse_getxattr_in {
+
875 uint32_t size;
+
876 uint32_t padding;
+
877};
+
878
+
879struct fuse_getxattr_out {
+
880 uint32_t size;
+
881 uint32_t padding;
+
882};
+
883
+
884struct fuse_lk_in {
+
885 uint64_t fh;
+
886 uint64_t owner;
+
887 struct fuse_file_lock lk;
+
888 uint32_t lk_flags;
+
889 uint32_t padding;
+
890};
+
891
+
892struct fuse_lk_out {
+
893 struct fuse_file_lock lk;
+
894};
+
895
+
896struct fuse_access_in {
+
897 uint32_t mask;
+
898 uint32_t padding;
+
899};
+
900
+
901struct fuse_init_in {
+
902 uint32_t major;
+
903 uint32_t minor;
+
904 uint32_t max_readahead;
+
905 uint32_t flags;
+
906 uint32_t flags2;
+
907 uint32_t unused[11];
+
908};
+
909
+
910#define FUSE_COMPAT_INIT_OUT_SIZE 8
+
911#define FUSE_COMPAT_22_INIT_OUT_SIZE 24
+
912
+
913struct fuse_init_out {
+
914 uint32_t major;
+
915 uint32_t minor;
+
916 uint32_t max_readahead;
+
917 uint32_t flags;
+
918 uint16_t max_background;
+
919 uint16_t congestion_threshold;
+
920 uint32_t max_write;
+
921 uint32_t time_gran;
+
922 uint16_t max_pages;
+
923 uint16_t map_alignment;
+
924 uint32_t flags2;
+
925 uint32_t max_stack_depth;
+
926 uint16_t request_timeout;
+
927 uint16_t unused[11];
+
928};
+
929
+
930#define CUSE_INIT_INFO_MAX 4096
+
931
+
932struct cuse_init_in {
+
933 uint32_t major;
+
934 uint32_t minor;
+
935 uint32_t unused;
+
936 uint32_t flags;
+
937};
+
938
+
939struct cuse_init_out {
+
940 uint32_t major;
+
941 uint32_t minor;
+
942 uint32_t unused;
+
943 uint32_t flags;
+
944 uint32_t max_read;
+
945 uint32_t max_write;
+
946 uint32_t dev_major; /* chardev major */
+
947 uint32_t dev_minor; /* chardev minor */
+
948 uint32_t spare[10];
+
949};
+
950
+
951struct fuse_interrupt_in {
+
952 uint64_t unique;
+
953};
+
954
+
955struct fuse_bmap_in {
+
956 uint64_t block;
+
957 uint32_t blocksize;
+
958 uint32_t padding;
+
959};
+
960
+
961struct fuse_bmap_out {
+
962 uint64_t block;
+
963};
+
964
+
965struct fuse_ioctl_in {
+
966 uint64_t fh;
+
967 uint32_t flags;
+
968 uint32_t cmd;
+
969 uint64_t arg;
+
970 uint32_t in_size;
+
971 uint32_t out_size;
+
972};
+
973
+
974struct fuse_ioctl_iovec {
+
975 uint64_t base;
+
976 uint64_t len;
+
977};
+
978
+
979struct fuse_ioctl_out {
+
980 int32_t result;
+
981 uint32_t flags;
+
982 uint32_t in_iovs;
+
983 uint32_t out_iovs;
+
984};
+
985
+
986struct fuse_poll_in {
+
987 uint64_t fh;
+
988 uint64_t kh;
+
989 uint32_t flags;
+
990 uint32_t events;
+
991};
+
992
+
993struct fuse_poll_out {
+
994 uint32_t revents;
+
995 uint32_t padding;
+
996};
+
997
+
998struct fuse_notify_poll_wakeup_out {
+
999 uint64_t kh;
+
1000};
+
1001
+
1002struct fuse_fallocate_in {
+
1003 uint64_t fh;
+
1004 uint64_t offset;
+
1005 uint64_t length;
+
1006 uint32_t mode;
+
1007 uint32_t padding;
+
1008};
+
1009
+
1016#define FUSE_UNIQUE_RESEND (1ULL << 63)
+
1017
+
1031#define FUSE_INVALID_UIDGID ((uint32_t)(-1))
+
1032
+
1033struct fuse_in_header {
+
1034 uint32_t len;
+
1035 uint32_t opcode;
+
1036 uint64_t unique;
+
1037 uint64_t nodeid;
+
1038 uint32_t uid;
+
1039 uint32_t gid;
+
1040 uint32_t pid;
+
1041 uint16_t total_extlen; /* length of extensions in 8byte units */
+
1042 uint16_t padding;
+
1043};
+
1044
+
1045struct fuse_out_header {
+
1046 uint32_t len;
+
1047 int32_t error;
+
1048 uint64_t unique;
+
1049};
+
1050
+
1051struct fuse_dirent {
+
1052 uint64_t ino;
+
1053 uint64_t off;
+
1054 uint32_t namelen;
+
1055 uint32_t type;
+
1056 char name[];
+
1057};
+
1058
+
1059/* Align variable length records to 64bit boundary */
+
1060#define FUSE_REC_ALIGN(x) \
+
1061 (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1))
+
1062
+
1063#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name)
+
1064#define FUSE_DIRENT_ALIGN(x) FUSE_REC_ALIGN(x)
+
1065#define FUSE_DIRENT_SIZE(d) \
+
1066 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen)
+
1067
+
1068struct fuse_direntplus {
+
1069 struct fuse_entry_out entry_out;
+
1070 struct fuse_dirent dirent;
+
1071};
+
1072
+
1073#define FUSE_NAME_OFFSET_DIRENTPLUS \
+
1074 offsetof(struct fuse_direntplus, dirent.name)
+
1075#define FUSE_DIRENTPLUS_SIZE(d) \
+
1076 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen)
+
1077
+
1078struct fuse_notify_inval_inode_out {
+
1079 uint64_t ino;
+
1080 int64_t off;
+
1081 int64_t len;
+
1082};
+
1083
+
1084struct fuse_notify_inval_entry_out {
+
1085 uint64_t parent;
+
1086 uint32_t namelen;
+
1087 uint32_t flags;
+
1088};
+
1089
+
1090struct fuse_notify_delete_out {
+
1091 uint64_t parent;
+
1092 uint64_t child;
+
1093 uint32_t namelen;
+
1094 uint32_t padding;
+
1095};
+
1096
+
1097struct fuse_notify_store_out {
+
1098 uint64_t nodeid;
+
1099 uint64_t offset;
+
1100 uint32_t size;
+
1101 uint32_t padding;
+
1102};
+
1103
+
1104struct fuse_notify_retrieve_out {
+
1105 uint64_t notify_unique;
+
1106 uint64_t nodeid;
+
1107 uint64_t offset;
+
1108 uint32_t size;
+
1109 uint32_t padding;
+
1110};
+
1111
+
1112/* Matches the size of fuse_write_in */
+
1113struct fuse_notify_retrieve_in {
+
1114 uint64_t dummy1;
+
1115 uint64_t offset;
+
1116 uint32_t size;
+
1117 uint32_t dummy2;
+
1118 uint64_t dummy3;
+
1119 uint64_t dummy4;
+
1120};
+
1121
+
1122struct fuse_backing_map {
+
1123 int32_t fd;
+
1124 uint32_t flags;
+
1125 uint64_t padding;
+
1126};
+
1127
+
1128/* Device ioctls: */
+
1129#define FUSE_DEV_IOC_MAGIC 229
+
1130#define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t)
+
1131#define FUSE_DEV_IOC_BACKING_OPEN _IOW(FUSE_DEV_IOC_MAGIC, 1, \
+
1132 struct fuse_backing_map)
+
1133#define FUSE_DEV_IOC_BACKING_CLOSE _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t)
+
1134
+
1135struct fuse_lseek_in {
+
1136 uint64_t fh;
+
1137 uint64_t offset;
+
1138 uint32_t whence;
+
1139 uint32_t padding;
+
1140};
+
1141
+
1142struct fuse_lseek_out {
+
1143 uint64_t offset;
+
1144};
+
1145
+
1146struct fuse_copy_file_range_in {
+
1147 uint64_t fh_in;
+
1148 uint64_t off_in;
+
1149 uint64_t nodeid_out;
+
1150 uint64_t fh_out;
+
1151 uint64_t off_out;
+
1152 uint64_t len;
+
1153 uint64_t flags;
+
1154};
+
1155
+
1156/* For FUSE_COPY_FILE_RANGE_64 */
+
1157struct fuse_copy_file_range_out {
+
1158 uint64_t bytes_copied;
+
1159};
+
1160
+
1161#define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0)
+
1162#define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1)
+
1163struct fuse_setupmapping_in {
+
1164 /* An already open handle */
+
1165 uint64_t fh;
+
1166 /* Offset into the file to start the mapping */
+
1167 uint64_t foffset;
+
1168 /* Length of mapping required */
+
1169 uint64_t len;
+
1170 /* Flags, FUSE_SETUPMAPPING_FLAG_* */
+
1171 uint64_t flags;
+
1172 /* Offset in Memory Window */
+
1173 uint64_t moffset;
+
1174};
+
1175
+
1176struct fuse_removemapping_in {
+
1177 /* number of fuse_removemapping_one follows */
+
1178 uint32_t count;
+
1179};
+
1180
+
1181struct fuse_removemapping_one {
+
1182 /* Offset into the dax window start the unmapping */
+
1183 uint64_t moffset;
+
1184 /* Length of mapping required */
+
1185 uint64_t len;
+
1186};
+
1187
+
1188#define FUSE_REMOVEMAPPING_MAX_ENTRY \
+
1189 (PAGE_SIZE / sizeof(struct fuse_removemapping_one))
+
1190
+
1191struct fuse_syncfs_in {
+
1192 uint64_t padding;
+
1193};
+
1194
+
1195/*
+
1196 * For each security context, send fuse_secctx with size of security context
+
1197 * fuse_secctx will be followed by security context name and this in turn
+
1198 * will be followed by actual context label.
+
1199 * fuse_secctx, name, context
+
1200 */
+
1201struct fuse_secctx {
+
1202 uint32_t size;
+
1203 uint32_t padding;
+
1204};
+
1205
+
1206/*
+
1207 * Contains the information about how many fuse_secctx structures are being
+
1208 * sent and what's the total size of all security contexts (including
+
1209 * size of fuse_secctx_header).
+
1210 *
+
1211 */
+
1212struct fuse_secctx_header {
+
1213 uint32_t size;
+
1214 uint32_t nr_secctx;
+
1215};
+
1216
+
1225struct fuse_ext_header {
+
1226 uint32_t size;
+
1227 uint32_t type;
+
1228};
+
1229
+
1235struct fuse_supp_groups {
+
1236 uint32_t nr_groups;
+
1237 uint32_t groups[];
+
1238};
+
1239
+
1243#define FUSE_URING_IN_OUT_HEADER_SZ 128
+
1244#define FUSE_URING_OP_IN_OUT_SZ 128
+
1245
+
1246/* Used as part of the fuse_uring_req_header */
+
1247struct fuse_uring_ent_in_out {
+
1248 uint64_t flags;
+
1249
+
1250 /*
+
1251 * commit ID to be used in a reply to a ring request (see also
+
1252 * struct fuse_uring_cmd_req)
+
1253 */
+
1254 uint64_t commit_id;
+
1255
+
1256 /* size of user payload buffer */
+
1257 uint32_t payload_sz;
+
1258 uint32_t padding;
+
1259
+
1260 uint64_t reserved;
+
1261};
+
1262
+
1266struct fuse_uring_req_header {
+
1267 /* struct fuse_in_header / struct fuse_out_header */
+
1268 char in_out[FUSE_URING_IN_OUT_HEADER_SZ];
+
1269
+
1270 /* per op code header */
+
1271 char op_in[FUSE_URING_OP_IN_OUT_SZ];
+
1272
+
1273 struct fuse_uring_ent_in_out ring_ent_in_out;
+
1274};
+
1275
+
1279enum fuse_uring_cmd {
+
1280 FUSE_IO_URING_CMD_INVALID = 0,
+
1281
+
1282 /* register the request buffer and fetch a fuse request */
+
1283 FUSE_IO_URING_CMD_REGISTER = 1,
+
1284
+
1285 /* commit fuse request result and fetch next request */
+
1286 FUSE_IO_URING_CMD_COMMIT_AND_FETCH = 2,
+
1287};
+
1288
+
1292struct fuse_uring_cmd_req {
+
1293 uint64_t flags;
+
1294
+
1295 /* entry identifier for commits */
+
1296 uint64_t commit_id;
+
1297
+
1298 /* queue the command is for (queue index) */
+
1299 uint16_t qid;
+
1300 uint8_t padding[6];
+
1301};
+
1302
+
1303#endif /* _LINUX_FUSE_H */
+ + + + +
+ + + + diff --git a/doc/html/include_2fuse__log_8h.html b/doc/html/include_2fuse__log_8h.html new file mode 100644 index 0000000..9ee341c --- /dev/null +++ b/doc/html/include_2fuse__log_8h.html @@ -0,0 +1,268 @@ + + + + + + + +libfuse: include/fuse_log.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_log.h File Reference
+
+
+
#include <stdarg.h>
+
+

Go to the source code of this file.

+ + + + +

+Typedefs

typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
 
+ + + +

+Enumerations

enum  fuse_log_level
 
+ + + + + + + + + +

+Functions

void fuse_set_log_func (fuse_log_func_t func)
 
void fuse_log (enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
 
void void fuse_log_enable_syslog (const char *ident, int option, int facility)
 
void fuse_log_close_syslog (void)
 
+

Detailed Description

+

This file defines the logging interface of FUSE

+ +

Definition in file fuse_log.h.

+

Typedef Documentation

+ +

◆ fuse_log_func_t

+ +
+
+ + + + +
typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
+
+

Log message handler function.

+

This function must be thread-safe. It may be called from any libfuse function, including fuse_parse_cmdline() and other functions invoked before a FUSE filesystem is created.

+

Install a custom log message handler function using fuse_set_log_func().

+
Parameters
+ + + + +
levellog severity level
fmtsprintf-style format string including newline
apformat string arguments
+
+
+ +

Definition at line 52 of file fuse_log.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_log_level

+ +
+
+ + + + +
enum fuse_log_level
+
+

Log severity level

+

These levels correspond to syslog(2) log levels since they are widely used.

+ +

Definition at line 28 of file fuse_log.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_log()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_log (enum fuse_log_level level,
const char * fmt,
 ... 
)
+
+

Emit a log message

+
Parameters
+ + + +
levelseverity level (FUSE_LOG_ERR, FUSE_LOG_DEBUG, etc)
fmtsprintf-style format string including newline
+
+
+ +

Definition at line 76 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_close_syslog()

+ +
+
+ + + + + + + + +
void fuse_log_close_syslog (void )
+
+

To be called at teardown to close syslog.

+ +

Definition at line 93 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_enable_syslog()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void void fuse_log_enable_syslog (const char * ident,
int option,
int facility 
)
+
+

Switch default log handler from stderr to syslog

+

Passed options are according to 'man 3 openlog'

+ +

Definition at line 86 of file fuse_log.c.

+ +
+
+ +

◆ fuse_set_log_func()

+ +
+
+ + + + + + + + +
void fuse_set_log_func (fuse_log_func_t func)
+
+

Install a custom log handler function.

+

Log messages are emitted by libfuse functions to report errors and debug information. Messages are printed to stderr by default but this can be overridden by installing a custom log message handler function.

+

The log message handler function is global and affects all FUSE filesystems created within this process.

+
Parameters
+ + +
funca custom log message handler function or NULL to revert to the default
+
+
+ +

Definition at line 69 of file fuse_log.c.

+ +
+
+
+ + + + diff --git a/doc/html/include_2fuse__log_8h_source.html b/doc/html/include_2fuse__log_8h_source.html new file mode 100644 index 0000000..04e4bb4 --- /dev/null +++ b/doc/html/include_2fuse__log_8h_source.html @@ -0,0 +1,113 @@ + + + + + + + +libfuse: include/fuse_log.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_log.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#ifndef FUSE_LOG_H_
+
10#define FUSE_LOG_H_
+
11
+
17#include <stdarg.h>
+
18
+
19#ifdef __cplusplus
+
20extern "C" {
+
21#endif
+
22
+
+ +
29 FUSE_LOG_EMERG,
+
30 FUSE_LOG_ALERT,
+
31 FUSE_LOG_CRIT,
+
32 FUSE_LOG_ERR,
+
33 FUSE_LOG_WARNING,
+
34 FUSE_LOG_NOTICE,
+
35 FUSE_LOG_INFO,
+
36 FUSE_LOG_DEBUG
+
37};
+
+
38
+
52typedef void (*fuse_log_func_t)(enum fuse_log_level level,
+
53 const char *fmt, va_list ap);
+
54
+ +
69
+
76void fuse_log(enum fuse_log_level level, const char *fmt, ...)
+
77 __attribute__((format(printf, 2, 3)));
+
78
+
84void fuse_log_enable_syslog(const char *ident, int option, int facility);
+
85
+
89void fuse_log_close_syslog(void);
+
90
+
91#ifdef __cplusplus
+
92}
+
93#endif
+
94
+
95#endif /* FUSE_LOG_H_ */
+
void fuse_log_close_syslog(void)
Definition fuse_log.c:93
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:86
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:69
+
+ + + + diff --git a/doc/html/include_2fuse__lowlevel_8h.html b/doc/html/include_2fuse__lowlevel_8h.html new file mode 100644 index 0000000..d1a6f62 --- /dev/null +++ b/doc/html/include_2fuse__lowlevel_8h.html @@ -0,0 +1,2530 @@ + + + + + + + +libfuse: include/fuse_lowlevel.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_lowlevel.h File Reference
+
+
+
#include "fuse_common.h"
+#include <stddef.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + + + + + +

+Data Structures

struct  fuse_entry_param
 
struct  fuse_ctx
 
struct  fuse_lowlevel_ops
 
struct  fuse_cmdline_opts
 
+ + + +

+Macros

#define FUSE_ROOT_ID   1
 
+ + + + + + + +

+Typedefs

typedef uint64_t fuse_ino_t
 
typedef struct fuse_req * fuse_req_t
 
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
 
+ + + +

+Enumerations

enum  fuse_notify_entry_flags
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

int fuse_reply_err (fuse_req_t req, int err)
 
void fuse_reply_none (fuse_req_t req)
 
int fuse_reply_entry (fuse_req_t req, const struct fuse_entry_param *e)
 
int fuse_reply_create (fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
 
int fuse_reply_attr (fuse_req_t req, const struct stat *attr, double attr_timeout)
 
int fuse_reply_readlink (fuse_req_t req, const char *link)
 
int fuse_passthrough_open (fuse_req_t req, int fd)
 
int fuse_reply_open (fuse_req_t req, const struct fuse_file_info *fi)
 
int fuse_reply_write (fuse_req_t req, size_t count)
 
int fuse_reply_buf (fuse_req_t req, const char *buf, size_t size)
 
int fuse_reply_data (fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
 
int fuse_reply_iov (fuse_req_t req, const struct iovec *iov, int count)
 
int fuse_reply_statfs (fuse_req_t req, const struct statvfs *stbuf)
 
int fuse_reply_xattr (fuse_req_t req, size_t count)
 
int fuse_reply_lock (fuse_req_t req, const struct flock *lock)
 
int fuse_reply_bmap (fuse_req_t req, uint64_t idx)
 
size_t fuse_add_direntry (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
 
size_t fuse_add_direntry_plus (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
 
int fuse_reply_ioctl_retry (fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
 
int fuse_reply_ioctl (fuse_req_t req, int result, const void *buf, size_t size)
 
int fuse_reply_ioctl_iov (fuse_req_t req, int result, const struct iovec *iov, int count)
 
int fuse_reply_poll (fuse_req_t req, unsigned revents)
 
int fuse_reply_lseek (fuse_req_t req, off_t off)
 
int fuse_reply_statx (fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
 
int fuse_lowlevel_notify_poll (struct fuse_pollhandle *ph)
 
int fuse_lowlevel_notify_inval_inode (struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
 
int fuse_lowlevel_notify_increment_epoch (struct fuse_session *se)
 
int fuse_lowlevel_notify_inval_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_expire_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_delete (struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_store (struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
 
int fuse_lowlevel_notify_retrieve (struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
 
void * fuse_req_userdata (fuse_req_t req)
 
const struct fuse_ctxfuse_req_ctx (fuse_req_t req)
 
int fuse_req_getgroups (fuse_req_t req, int size, gid_t list[])
 
void fuse_req_interrupt_func (fuse_req_t req, fuse_interrupt_func_t func, void *data)
 
int fuse_req_interrupted (fuse_req_t req)
 
void fuse_lowlevel_version (void)
 
void fuse_lowlevel_help (void)
 
void fuse_cmdline_help (void)
 
int fuse_parse_cmdline_30 (struct fuse_args *args, struct fuse_cmdline_opts *opts)
 
int fuse_session_mount (struct fuse_session *se, const char *mountpoint)
 
int fuse_session_loop (struct fuse_session *se)
 
void fuse_session_exit (struct fuse_session *se)
 
void fuse_session_reset (struct fuse_session *se)
 
int fuse_session_exited (struct fuse_session *se)
 
void fuse_session_unmount (struct fuse_session *se)
 
void fuse_session_destroy (struct fuse_session *se)
 
int fuse_session_fd (struct fuse_session *se)
 
void fuse_session_process_buf (struct fuse_session *se, const struct fuse_buf *buf)
 
int fuse_session_receive_buf (struct fuse_session *se, struct fuse_buf *buf)
 
bool fuse_req_is_uring (fuse_req_t req)
 
int fuse_req_get_payload (fuse_req_t req, char **payload, size_t *payload_sz, void **mr)
 
+

Detailed Description

+

Low level API

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header. To use the newest API define it to 35 (recommended for any new application).

+ +

Definition in file fuse_lowlevel.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ROOT_ID

+ +
+
+ + + + +
#define FUSE_ROOT_ID   1
+
+

The node ID of the root inode

+ +

Definition at line 44 of file fuse_lowlevel.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_ino_t

+ +
+
+ + + + +
typedef uint64_t fuse_ino_t
+
+

Inode number type

+ +

Definition at line 47 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_interrupt_func_t

+ +
+
+ + + + +
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
+
+

Callback function for an interrupt

+
Parameters
+ + + +
reqinterrupted request
datauser data
+
+
+ +

Definition at line 1992 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_req_t

+ +
+
+ + + + +
typedef struct fuse_req* fuse_req_t
+
+

Request pointer type

+ +

Definition at line 50 of file fuse_lowlevel.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_notify_entry_flags

+ +
+
+ + + + +
enum fuse_notify_entry_flags
+
+

Flags for fuse_lowlevel_notify_entry() 0 = invalidate entry FUSE_LL_EXPIRE_ONLY = expire entry

+ +

Definition at line 151 of file fuse_lowlevel.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_add_direntry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct stat * stbuf,
off_t off 
)
+
+

Add a directory entry to the buffer

+

Buffer needs to be large enough to hold the entry. If it's not, then the entry is not filled in but the size of the entry is still returned. The caller can check this by comparing the bufsize parameter with the returned entry size. If the entry size is larger than the buffer size, the operation failed.

+

From the 'stbuf' argument the st_ino field and bits 12-15 of the st_mode field are used. The other fields are ignored.

+

off should be any non-zero value that the filesystem can use to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to mean "from the beginning", and should therefore never be used (the first call to fuse_add_direntry should be passed the offset of the second directory entry).

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
stbufthe file attributes
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 286 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_add_direntry_plus()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry_plus (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct fuse_entry_parame,
off_t off 
)
+
+

Add a directory entry to the buffer with the attributes

+

See documentation of fuse_add_direntry() for more details.

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
ethe directory entry
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 376 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_cmdline_help()

+ +
+
+ + + + + + + + +
void fuse_cmdline_help (void )
+
+

Print available options for fuse_parse_cmdline().

+ +

Definition at line 130 of file helper.c.

+ +
+
+ +

◆ fuse_lowlevel_help()

+ +
+
+ + + + + + + + +
void fuse_lowlevel_help (void )
+
+

Print available low-level options to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+ +

Definition at line 3000 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_delete()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_delete (struct fuse_session * se,
fuse_ino_t parent,
fuse_ino_t child,
const char * name,
size_t namelen 
)
+
+

This function behaves like fuse_lowlevel_notify_inval_entry() with the following additional effect (at least as of Linux kernel 4.8):

+

If the provided child inode matches the inode that is currently associated with the cached dentry, and if there are any inotify watches registered for the dentry, then the watchers are informed that the dentry has been deleted.

+

To avoid a deadlock this function must not be called while executing a related filesystem operation or while holding a lock that could be needed to execute such an operation (see the description of fuse_lowlevel_notify_inval_entry() for more details).

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.18. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
parentinode number
childinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2562 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_expire_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_expire_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen 
)
+
+

Notify to expire parent attributes and the dentry matching parent/name

+

Same restrictions apply as for fuse_lowlevel_notify_inval_entry()

+

Compared to invalidating an entry, expiring the entry results not in a forceful removal of that entry from kernel cache but instead the next access to it forces a lookup from the filesystem.

+

This makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+

Added in FUSE protocol version 7.38. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure, -enosys if no kernel support
+ +

Definition at line 2549 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_increment_epoch()

+ +
+
+ + + + + + + + +
int fuse_lowlevel_notify_increment_epoch (struct fuse_session * se)
+
+

Notify to increment the epoch for the current

+

Each fuse connection has an 'epoch', which is initialized during INIT. Caching will then be validated against the epoch value: if the current epoch is higher than an object being revalidated, the object is invalid.

+

This function simply increment the current epoch value.

+
Parameters
+ + +
sethe session object
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 3116 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen 
)
+
+

Notify to invalidate parent attributes and the dentry matching parent/name

+

To avoid a deadlock this function must not be called in the execution path of a related filesystem operation or within any code that could hold a lock that could be needed to execute such an operation. As of kernel 4.18, a "related operation" is a lookup(), symlink(), mknod(), mkdir(), unlink(), rename(), link() or create() request for the parent, and a setattr(), unlink(), rmdir(), rename(), setxattr(), removexattr(), readdir() or readdirplus() request for the inode itself.

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2543 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_inode()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_inode (struct fuse_session * se,
fuse_ino_t ino,
off_t off,
off_t len 
)
+
+

Notify to invalidate cache for an inode.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+

If the filesystem has writeback caching enabled, invalidating an inode will first trigger a writeback of all dirty pages. The call will block until all writeback requests have completed and the inode has been invalidated. It will, however, not wait for completion of pending writeback requests that have been issued before.

+

If there are no dirty pages, this function will never block.

+
Parameters
+ + + + + +
sethe session object
inothe inode number
offthe offset in the inode where to start invalidating or negative to invalidate attributes only
lenthe amount of cache to invalidate or 0 for all
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2475 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_poll()

+ +
+
+ + + + + + + + +
int fuse_lowlevel_notify_poll (struct fuse_pollhandle * ph)
+
+

Notify IO readiness event

+

For more information, please read comment for poll operation.

+
Parameters
+ + +
phpoll handle to notify IO readiness event for
+
+
+ +

Definition at line 2458 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_retrieve()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_retrieve (struct fuse_session * se,
fuse_ino_t ino,
size_t size,
off_t offset,
void * cookie 
)
+
+

Retrieve data from the kernel buffers

+

Retrieve data in the kernel buffers belonging to the given inode. If successful then the retrieve_reply() method will be called with the returned data.

+

Only present pages are returned in the retrieve reply. Retrieving stops when it finds a non-present page and only data prior to that is returned.

+

If this function returns an error, then the retrieve will not be completed and no reply will be sent.

+

This function doesn't change the dirty state of pages in the kernel buffer. For dirty pages the write() method will be called regardless of having been retrieved previously.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
sizethe number of bytes to retrieve
offsetthe starting offset into the file to retrieve from
cookieuser data to supply to the reply callback
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2668 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_store()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_store (struct fuse_session * se,
fuse_ino_t ino,
off_t offset,
struct fuse_bufvecbufv,
enum fuse_buf_copy_flags flags 
)
+
+

Store data to the kernel buffers

+

Synchronously store data in the kernel buffers belonging to the given inode. The stored data is marked up-to-date (no read will be performed against it, unless it's invalidated or evicted from the cache).

+

If the stored data overflows the current file size, then the size is extended, similarly to a write(2) on the filesystem.

+

If this function returns an error, then the store wasn't fully completed, but it may have been partially completed.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
offsetthe starting offset into the file to store to
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2588 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_version()

+ +
+
+ + + + + + + + +
void fuse_lowlevel_version (void )
+
+

Print low-level version information to stdout.

+ +

Definition at line 2993 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_parse_cmdline_30()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_parse_cmdline_30 (struct fuse_argsargs,
struct fuse_cmdline_optsopts 
)
+
+

Utility function to parse common options for simple file systems using the low-level API. A help text that describes the available options can be printed with fuse_cmdline_help. A single non-option argument is treated as the mountpoint. Multiple non-option arguments will result in an error.

+

If neither -o subtype= or -o fsname= options are given, a new subtype option will be added and set to the basename of the program (the fsname will remain unset, and then defaults to "fuse").

+

Known options will be removed from args, unknown options will remain.

+
Parameters
+ + + +
argsargument vector (input+output)
optsoutput argument for parsed options
+
+
+
Returns
0 on success, -1 on failure
+

struct fuse_cmdline_opts got extended in libfuse-3.12

+ +

Definition at line 237 of file helper.c.

+ +
+
+ +

◆ fuse_passthrough_open()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_passthrough_open (fuse_req_t req,
int fd 
)
+
+

Setup passthrough backing file for open reply

+

Currently there should be only one backing id per node / backing file.

+

Possible requests: open, opendir, create

+
Parameters
+ + + +
reqrequest handle
fdbacking file descriptor
+
+
+
Returns
positive backing id for success, 0 for failure
+ +

Definition at line 480 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_attr()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_attr (fuse_req_t req,
const struct stat * attr,
double attr_timeout 
)
+
+

Reply with attributes

+

Possible requests: getattr, setattr

+
Parameters
+ + + + +
reqrequest handle
attrthe attributes
attr_timeoutvalidity timeout (in seconds) for the attributes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 460 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_bmap()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_bmap (fuse_req_t req,
uint64_t idx 
)
+
+

Reply with block index

+

Possible requests: bmap

+
Parameters
+ + + +
reqrequest handle
idxblock index within device
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 973 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_buf (fuse_req_t req,
const char * buf,
size_t size 
)
+
+

Reply with data

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
bufbuffer containing data
sizethe size of data in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 524 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_create()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_create (fuse_req_t req,
const struct fuse_entry_parame,
const struct fuse_file_infofi 
)
+
+

Reply with a directory entry and open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes

+

Possible requests: create

+

Side effects: increments the lookup count on success

+
Parameters
+ + + + +
reqrequest handle
ethe entry parameters
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 444 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_data()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_data (fuse_req_t req,
struct fuse_bufvecbufv,
enum fuse_buf_copy_flags flags 
)
+
+

Reply with data copied/moved from buffer(s)

+

Zero copy data transfer ("splicing") will be used under the following circumstances:

+
    +
  1. FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports splicing from the fuse device (FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.capable), and
  4. +
  5. flags does not contain FUSE_BUF_NO_SPLICE
  6. +
  7. The amount of data that is provided in file-descriptor backed buffers (i.e., buffers for which bufv[n].flags == FUSE_BUF_FD) is at least twice the page size.
  8. +
+

In order for SPLICE_F_MOVE to be used, the following additional conditions have to be fulfilled:

+
    +
  1. FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports it (i.e, FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.capable), and
  4. +
  5. flags contains FUSE_BUF_SPLICE_MOVE
  6. +
+

Note that, if splice is used, the data is actually spliced twice: once into a temporary pipe (to prepend header data), and then again into the kernel. If some of the provided buffers are memory-backed, the data in them is copied in step one and spliced in step two.

+

The FUSE_BUF_SPLICE_FORCE_SPLICE and FUSE_BUF_SPLICE_NONBLOCK flags are silently ignored.

+

Possible requests: read, readdir, getxattr, listxattr

+

Side effects: when used to return data from a readdirplus() (but not readdir()) call, increments the lookup count of each returned entry by one on success.

+
Parameters
+ + + + +
reqrequest handle
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 912 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_entry (fuse_req_t req,
const struct fuse_entry_parame 
)
+
+

Reply with a directory entry

+

Possible requests: lookup, mknod, mkdir, symlink, link

+

Side effects: increments the lookup count on success

+
Parameters
+ + + +
reqrequest handle
ethe entry parameters
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 428 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_err()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_err (fuse_req_t req,
int err 
)
+
+

Reply with an error code or success.

+

Possible requests: all except forget, forget_multi, retrieve_reply

+

Wherever possible, error codes should be chosen from the list of documented error conditions in the corresponding system calls manpage.

+

An error code of ENOSYS is sometimes treated specially. This is indicated in the documentation of the affected handler functions.

+

The following requests may be answered with a zero error code: unlink, rmdir, rename, flush, release, fsync, fsyncdir, setxattr, removexattr, setlk.

+
Parameters
+ + + +
reqrequest handle
errthe positive error value, or zero for success
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 331 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl (fuse_req_t req,
int result,
const void * buf,
size_t size 
)
+
+

Reply to finish ioctl

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
bufbuffer containing output data
sizelength of output data
+
+
+ +

Definition at line 1071 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_iov (fuse_req_t req,
int result,
const struct iovec * iov,
int count 
)
+
+

Reply to finish ioctl with iov buffer

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
iovthe vector containing the data
countthe size of vector
+
+
+ +

Definition at line 1092 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_retry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_retry (fuse_req_t req,
const struct iovec * in_iov,
size_t in_count,
const struct iovec * out_iov,
size_t out_count 
)
+
+

Reply to ask for data fetch and output buffer preparation. ioctl will be retried with the specified input data fetched and output buffer prepared.

+

Possible requests: ioctl

+
Parameters
+ + + + + + +
reqrequest handle
in_ioviovec specifying data to fetch from the caller
in_countnumber of entries in in_iov
out_ioviovec specifying addresses to write output to
out_countnumber of entries in out_iov
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1001 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_iov (fuse_req_t req,
const struct iovec * iov,
int count 
)
+
+

Reply with data vector

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
iovthe vector containing the data
countthe size of vector
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 265 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lock()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_lock (fuse_req_t req,
const struct flock * lock 
)
+
+

Reply with file lock information

+

Possible requests: getlk

+
Parameters
+ + + +
reqrequest handle
lockthe lock information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 956 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lseek()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_lseek (fuse_req_t req,
off_t off 
)
+
+

Reply with offset

+

Possible requests: lseek

+
Parameters
+ + + +
reqrequest handle
offoffset of next data or hole
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1126 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_none()

+ +
+
+ + + + + + + + +
void fuse_reply_none (fuse_req_t req)
+
+

Don't send reply

+

Possible requests: forget forget_multi retrieve_reply

+
Parameters
+ + +
reqrequest handle
+
+
+ +

Definition at line 336 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_open()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_open (fuse_req_t req,
const struct fuse_file_infofi 
)
+
+

Reply with open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes,

+

Possible requests: open, opendir

+
Parameters
+ + + +
reqrequest handle
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 505 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_poll()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_poll (fuse_req_t req,
unsigned revents 
)
+
+

Reply with poll result event mask

+
Parameters
+ + + +
reqrequest handle
reventspoll result event mask
+
+
+ +

Definition at line 1116 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_readlink()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_readlink (fuse_req_t req,
const char * link 
)
+
+

Reply with the contents of a symbolic link

+

Possible requests: readlink

+
Parameters
+ + + +
reqrequest handle
linksymbolic link contents
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 475 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_statfs()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_statfs (fuse_req_t req,
const struct statvfs * stbuf 
)
+
+

Reply with filesystem statistics

+

Possible requests: statfs

+
Parameters
+ + + +
reqrequest handle
stbuffilesystem statistics
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 934 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_statx()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_statx (fuse_req_t req,
int flags,
struct statx * statx,
double attr_timeout 
)
+
+

Reply with extended file attributes.

+

Possible requests: statx

+
Parameters
+ + + + + +
reqrequest handle
flagsstatx flags
statxthe attributes
attr_timeoutvalidity timeout (in seconds) for the attributes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1260 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_write()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_write (fuse_req_t req,
size_t count 
)
+
+

Reply with number of bytes written

+

Possible requests: write

+
Parameters
+ + + +
reqrequest handle
countthe number of bytes written
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 514 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_xattr()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_xattr (fuse_req_t req,
size_t count 
)
+
+

Reply with needed buffer size

+

Possible requests: getxattr, listxattr

+
Parameters
+ + + +
reqrequest handle
countthe buffer size needed in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 946 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_ctx()

+ +
+
+ + + + + + + + +
const struct fuse_ctx * fuse_req_ctx (fuse_req_t req)
+
+

Get the context from the request

+

The pointer returned by this function will only be valid for the request's lifetime

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the context structure
+ +

Definition at line 2718 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_get_payload()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_req_get_payload (fuse_req_t req,
char ** payload,
size_t * payload_sz,
void ** mr 
)
+
+

Get the payload of a request (for requests submitted through fuse-io-uring only)

+

This is useful for a file system that wants to write data directly to the request buffer. With io-uring the req is the buffer owner and the file system can write directly to the buffer and avoid extra copying. For example useful for network file systems.

+
Parameters
+ + + + + +
reqthe request
payloadpointer to the payload
payload_szsize of the payload
mrmemory registration handle, currently unused
+
+
+
Returns
0 on success, -errno on failure
+ +

Definition at line 3386 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_req_getgroups (fuse_req_t req,
int size,
gid_t list[] 
)
+
+

Get the current supplementary group IDs for the specified request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + + +
reqrequest handle
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 3614 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupt_func()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_req_interrupt_func (fuse_req_t req,
fuse_interrupt_func_t func,
void * data 
)
+
+

Register/unregister callback for an interrupt

+

If an interrupt has already happened, then the callback function is called from within this function, hence it's not possible for interrupts to be lost.

+
Parameters
+ + + + +
reqrequest handle
functhe callback function or NULL for unregister
datauser data passed to the callback function
+
+
+ +

Definition at line 2723 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupted()

+ +
+
+ + + + + + + + +
int fuse_req_interrupted (fuse_req_t req)
+
+

Check if a request has already been interrupted

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 2736 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_is_uring()

+ +
+
+ + + + + + + + +
bool fuse_req_is_uring (fuse_req_t req)
+
+

Check if the request is submitted through fuse-io-uring

+ +

Definition at line 3380 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_userdata()

+ +
+
+ + + + + + + + +
void * fuse_req_userdata (fuse_req_t req)
+
+

Get the userdata from the request

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the user data passed to fuse_session_new()
+ +

Definition at line 2713 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_destroy()

+ +
+
+ + + + + + + + +
void fuse_session_destroy (struct fuse_session * se)
+
+

Destroy a session

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 3010 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_exit()

+ +
+
+ + + + + + + + +
void fuse_session_exit (struct fuse_session * se)
+
+

Flag a session as terminated.

+

This will cause any running event loops to terminate on the next opportunity. If this function is called by a thread that is not a FUSE worker thread, the next opportunity will be when FUSE a request is received (which may be far in the future if the filesystem is not currently being used by any clients). One way to avoid this delay is to afterwards sent a signal to the main thread (if fuse_set_signal_handlers() is used, SIGPIPE will cause the main thread to wake-up but otherwise be ignored).

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_exited()

+ +
+
+ + + + + + + + +
int fuse_session_exited (struct fuse_session * se)
+
+

Query the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+
Returns
1 if exited, 0 if not exited
+ +
+
+ +

◆ fuse_session_fd()

+ +
+
+ + + + + + + + +
int fuse_session_fd (struct fuse_session * se)
+
+

Return file descriptor for communication with kernel.

+

The file selector can be used to integrate FUSE with a custom event loop. Whenever data is available for reading on the provided fd, the event loop should call fuse_session_receive_buf followed by fuse_session_process_buf to process the request.

+

The returned file descriptor is valid until fuse_session_unmount is called.

+
Parameters
+ + +
sethe session
+
+
+
Returns
a file descriptor
+ +

Definition at line 3534 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_loop()

+ +
+
+ + + + + + + + +
int fuse_session_loop (struct fuse_session * se)
+
+

Enter a single threaded, blocking event loop.

+

When the event loop terminates because the connection to the FUSE kernel module has been closed, this function returns zero. This happens when the filesystem is unmounted regularly (by the filesystem owner or root running the umount(8) or fusermount(1) command), or if connection is explicitly severed by writing 1 to theabort file in /sys/fs/fuse/connections/NNN. The only way to distinguish between these two conditions is to check if the filesystem is still mounted after the session loop returns.

+

When some error occurs during request processing, the function returns a negated errno(3) value.

+

If the loop has been terminated because of a signal handler installed by fuse_set_signal_handlers(), this function returns the (positive) signal value that triggered the exit.

+
Parameters
+ + +
sethe session
+
+
+
Returns
0, -errno, or a signal value
+ +

Definition at line 19 of file fuse_loop.c.

+ +
+
+ +

◆ fuse_session_mount()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_session_mount (struct fuse_session * se,
const char * mountpoint 
)
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
sesession object
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 3473 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_process_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_session_process_buf (struct fuse_session * se,
const struct fuse_bufbuf 
)
+
+

Process a raw request supplied in a generic buffer

+

The fuse_buf may contain a memory buffer or a pipe file descriptor.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf containing the request
+
+
+ +

Definition at line 2830 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_receive_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_session_receive_buf (struct fuse_session * se,
struct fuse_bufbuf 
)
+
+

Read a raw request from the kernel into the supplied buffer.

+

Depending on file system options, system capabilities, and request size the request is either read into a memory buffer or spliced into a temporary pipe.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf to store the request in
+
+
+
Returns
the actual size of the raw request, or -errno on error
+ +

Definition at line 3285 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_reset()

+ +
+
+ + + + + + + + +
void fuse_session_reset (struct fuse_session * se)
+
+

Reset the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_unmount()

+ +
+
+ + + + + + + + +
void fuse_session_unmount (struct fuse_session * se)
+
+

Ensure that file system is unmounted.

+

In regular operation, the file system is typically unmounted by the user calling umount(8) or fusermount(1), which then terminates the FUSE session loop. However, the session loop may also terminate as a result of an explicit call to fuse_session_exit() (e.g. by a signal handler installed by fuse_set_signal_handler()). In this case the filesystem remains mounted, but any attempt to access it will block (while the filesystem process is still running) or give an ESHUTDOWN error (after the filesystem process has terminated).

+

If the communication channel with the FUSE kernel module is still open (i.e., if the session loop was terminated by an explicit call to fuse_session_exit()), this function will close it and unmount the filesystem. If the communication channel has been closed by the kernel, this method will do (almost) nothing.

+

NOTE: The above semantics mean that if the connection to the kernel is terminated via the /sys/fs/fuse/connections/NNN/abort file, this method will not unmount the filesystem.

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 3539 of file fuse_lowlevel.c.

+ +
+
+
+ + + + diff --git a/doc/html/include_2fuse__lowlevel_8h_source.html b/doc/html/include_2fuse__lowlevel_8h_source.html new file mode 100644 index 0000000..fcbbfcf --- /dev/null +++ b/doc/html/include_2fuse__lowlevel_8h_source.html @@ -0,0 +1,691 @@ + + + + + + + +libfuse: include/fuse_lowlevel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_lowlevel.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#ifndef FUSE_LOWLEVEL_H_
+
10#define FUSE_LOWLEVEL_H_
+
11
+
21#ifndef FUSE_USE_VERSION
+
22#error FUSE_USE_VERSION not defined
+
23#endif
+
24
+
25#include "fuse_common.h"
+
26
+
27#include <stddef.h>
+
28#include <utime.h>
+
29#include <fcntl.h>
+
30#include <sys/types.h>
+
31#include <sys/stat.h>
+
32#include <sys/statvfs.h>
+
33#include <sys/uio.h>
+
34
+
35#ifdef __cplusplus
+
36extern "C" {
+
37#endif
+
38
+
39/* ----------------------------------------------------------- *
+
40 * Miscellaneous definitions *
+
41 * ----------------------------------------------------------- */
+
42
+
44#define FUSE_ROOT_ID 1
+
45
+
47typedef uint64_t fuse_ino_t;
+
48
+
50typedef struct fuse_req *fuse_req_t;
+
51
+
52/* Forward declaration */
+
53struct statx;
+
54
+
60struct fuse_session;
+
61
+
63struct fuse_entry_param {
+ +
72
+
83 uint64_t generation;
+
84
+
92 struct stat attr;
+
93
+
98 double attr_timeout;
+
99
+
104 double entry_timeout;
+
105};
+
106
+
115struct fuse_ctx {
+
117 uid_t uid;
+
118
+
120 gid_t gid;
+
121
+
123 pid_t pid;
+
124
+
126 mode_t umask;
+
127};
+
128
+
129struct fuse_forget_data {
+
130 fuse_ino_t ino;
+
131 uint64_t nlookup;
+
132};
+
133
+
134struct fuse_custom_io {
+
135 ssize_t (*writev)(int fd, struct iovec *iov, int count, void *userdata);
+
136 ssize_t (*read)(int fd, void *buf, size_t buf_len, void *userdata);
+
137 ssize_t (*splice_receive)(int fdin, off_t *offin, int fdout,
+
138 off_t *offout, size_t len,
+
139 unsigned int flags, void *userdata);
+
140 ssize_t (*splice_send)(int fdin, off_t *offin, int fdout,
+
141 off_t *offout, size_t len,
+
142 unsigned int flags, void *userdata);
+
143 int (*clone_fd)(int master_fd);
+
144};
+
145
+
+ +
152 FUSE_LL_INVALIDATE = 0,
+
153 FUSE_LL_EXPIRE_ONLY = (1 << 0),
+
154};
+
+
155
+
156/* 'to_set' flags in setattr */
+
157#define FUSE_SET_ATTR_MODE (1 << 0)
+
158#define FUSE_SET_ATTR_UID (1 << 1)
+
159#define FUSE_SET_ATTR_GID (1 << 2)
+
160#define FUSE_SET_ATTR_SIZE (1 << 3)
+
161#define FUSE_SET_ATTR_ATIME (1 << 4)
+
162#define FUSE_SET_ATTR_MTIME (1 << 5)
+
163#define FUSE_SET_ATTR_ATIME_NOW (1 << 7)
+
164#define FUSE_SET_ATTR_MTIME_NOW (1 << 8)
+
165#define FUSE_SET_ATTR_FORCE (1 << 9)
+
166#define FUSE_SET_ATTR_CTIME (1 << 10)
+
167#define FUSE_SET_ATTR_KILL_SUID (1 << 11)
+
168#define FUSE_SET_ATTR_KILL_SGID (1 << 12)
+
169#define FUSE_SET_ATTR_FILE (1 << 13)
+
170#define FUSE_SET_ATTR_KILL_PRIV (1 << 14)
+
171#define FUSE_SET_ATTR_OPEN (1 << 15)
+
172#define FUSE_SET_ATTR_TIMES_SET (1 << 16)
+
173#define FUSE_SET_ATTR_TOUCH (1 << 17)
+
174
+
175/* ----------------------------------------------------------- *
+
176 * Request methods and replies *
+
177 * ----------------------------------------------------------- */
+
178
+
208struct fuse_lowlevel_ops {
+
225 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
226
+
238 void (*destroy) (void *userdata);
+
239
+
251 void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
252
+
289 void (*forget) (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup);
+
290
+
310 void (*getattr) (fuse_req_t req, fuse_ino_t ino,
+
311 struct fuse_file_info *fi);
+
312
+
347 void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
348 int to_set, struct fuse_file_info *fi);
+
349
+
360 void (*readlink) (fuse_req_t req, fuse_ino_t ino);
+
361
+
378 void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
379 mode_t mode, dev_t rdev);
+
380
+
393 void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
394 mode_t mode);
+
395
+
411 void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
412
+
428 void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
429
+
442 void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent,
+
443 const char *name);
+
444
+
474 void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
475 fuse_ino_t newparent, const char *newname,
+
476 unsigned int flags);
+
477
+
490 void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
491 const char *newname);
+
492
+
556 void (*open) (fuse_req_t req, fuse_ino_t ino,
+
557 struct fuse_file_info *fi);
+
558
+
584 void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
585 struct fuse_file_info *fi);
+
586
+
613 void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf,
+
614 size_t size, off_t off, struct fuse_file_info *fi);
+
615
+
654 void (*flush) (fuse_req_t req, fuse_ino_t ino,
+
655 struct fuse_file_info *fi);
+
656
+
682 void (*release) (fuse_req_t req, fuse_ino_t ino,
+
683 struct fuse_file_info *fi);
+
684
+
704 void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
705 struct fuse_file_info *fi);
+
706
+
736 void (*opendir) (fuse_req_t req, fuse_ino_t ino,
+
737 struct fuse_file_info *fi);
+
738
+
782 void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
783 struct fuse_file_info *fi);
+
784
+
801 void (*releasedir) (fuse_req_t req, fuse_ino_t ino,
+
802 struct fuse_file_info *fi);
+
803
+
826 void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
827 struct fuse_file_info *fi);
+
828
+
839 void (*statfs) (fuse_req_t req, fuse_ino_t ino);
+
840
+
852 void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
853 const char *value, size_t size, int flags);
+
854
+
883 void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
884 size_t size);
+
885
+
914 void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size);
+
915
+
931 void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name);
+
932
+
953 void (*access) (fuse_req_t req, fuse_ino_t ino, int mask);
+
954
+
982 void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
983 mode_t mode, struct fuse_file_info *fi);
+
984
+
997 void (*getlk) (fuse_req_t req, fuse_ino_t ino,
+
998 struct fuse_file_info *fi, struct flock *lock);
+
999
+
1022 void (*setlk) (fuse_req_t req, fuse_ino_t ino,
+
1023 struct fuse_file_info *fi,
+
1024 struct flock *lock, int sleep);
+
1025
+
1046 void (*bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
1047 uint64_t idx);
+
1048
+
1049#if FUSE_USE_VERSION < 35
+
1050 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, int cmd,
+
1051 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1052 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1053#else
+
1082 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
1083 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1084 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1085#endif
+
1086
+
1119 void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
1120 struct fuse_pollhandle *ph);
+
1121
+
1149 void (*write_buf) (fuse_req_t req, fuse_ino_t ino,
+
1150 struct fuse_bufvec *bufv, off_t off,
+
1151 struct fuse_file_info *fi);
+
1152
+
1165 void (*retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino,
+
1166 off_t offset, struct fuse_bufvec *bufv);
+
1167
+
1179 void (*forget_multi) (fuse_req_t req, size_t count,
+
1180 struct fuse_forget_data *forgets);
+
1181
+
1197 void (*flock) (fuse_req_t req, fuse_ino_t ino,
+
1198 struct fuse_file_info *fi, int op);
+
1199
+
1220 void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode,
+
1221 off_t offset, off_t length, struct fuse_file_info *fi);
+
1222
+
1248 void (*readdirplus) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
1249 struct fuse_file_info *fi);
+
1250
+
1281 void (*copy_file_range) (fuse_req_t req, fuse_ino_t ino_in,
+
1282 off_t off_in, struct fuse_file_info *fi_in,
+
1283 fuse_ino_t ino_out, off_t off_out,
+
1284 struct fuse_file_info *fi_out, size_t len,
+
1285 int flags);
+
1286
+
1305 void (*lseek) (fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
1306 struct fuse_file_info *fi);
+
1307
+
1326 void (*tmpfile) (fuse_req_t req, fuse_ino_t parent,
+
1327 mode_t mode, struct fuse_file_info *fi);
+
1328
+
1342 void (*statx)(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
+
1343 struct fuse_file_info *fi);
+
1344};
+
1345
+
1367int fuse_reply_err(fuse_req_t req, int err);
+
1368
+
1379void fuse_reply_none(fuse_req_t req);
+
1380
+
1394int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e);
+
1395
+
1414int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+
1415 const struct fuse_file_info *fi);
+
1416
+
1428int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
1429 double attr_timeout);
+
1430
+
1441int fuse_reply_readlink(fuse_req_t req, const char *link);
+
1442
+
1455int fuse_passthrough_open(fuse_req_t req, int fd);
+
1456int fuse_passthrough_close(fuse_req_t req, int backing_id);
+
1457
+
1472int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi);
+
1473
+
1484int fuse_reply_write(fuse_req_t req, size_t count);
+
1485
+
1497int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size);
+
1498
+
1542int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+ +
1544
+
1556int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count);
+
1557
+
1568int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf);
+
1569
+
1580int fuse_reply_xattr(fuse_req_t req, size_t count);
+
1581
+
1592int fuse_reply_lock(fuse_req_t req, const struct flock *lock);
+
1593
+
1604int fuse_reply_bmap(fuse_req_t req, uint64_t idx);
+
1605
+
1606/* ----------------------------------------------------------- *
+
1607 * Filling a buffer in readdir *
+
1608 * ----------------------------------------------------------- */
+
1609
+
1637size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
1638 const char *name, const struct stat *stbuf,
+
1639 off_t off);
+
1640
+
1654size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
1655 const char *name,
+
1656 const struct fuse_entry_param *e, off_t off);
+
1657
+ +
1674 const struct iovec *in_iov, size_t in_count,
+
1675 const struct iovec *out_iov, size_t out_count);
+
1676
+
1688int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size);
+
1689
+
1701int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1702 int count);
+
1703
+
1710int fuse_reply_poll(fuse_req_t req, unsigned revents);
+
1711
+
1722int fuse_reply_lseek(fuse_req_t req, off_t off);
+
1723
+
1736int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout);
+
1737
+
1738/* ----------------------------------------------------------- *
+
1739 * Notification *
+
1740 * ----------------------------------------------------------- */
+
1741
+
1749int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph);
+
1750
+
1774int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
1775 off_t off, off_t len);
+
1776
+
1789int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se);
+
1790
+
1815int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
1816 const char *name, size_t namelen);
+
1817
+
1846int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
1847 const char *name, size_t namelen);
+
1848
+
1877int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
1878 fuse_ino_t parent, fuse_ino_t child,
+
1879 const char *name, size_t namelen);
+
1880
+
1906int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
1907 off_t offset, struct fuse_bufvec *bufv,
+ +
1938int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
1939 size_t size, off_t offset, void *cookie);
+
1940
+
1941
+
1942/* ----------------------------------------------------------- *
+
1943 * Utility functions *
+
1944 * ----------------------------------------------------------- */
+
1945
+
1952void *fuse_req_userdata(fuse_req_t req);
+
1953
+
1963const struct fuse_ctx *fuse_req_ctx(fuse_req_t req);
+
1964
+
1984int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]);
+
1985
+
1992typedef void (*fuse_interrupt_func_t)(fuse_req_t req, void *data);
+
1993
+ +
2006 void *data);
+
2007
+ +
2015
+
2016
+
2017/* ----------------------------------------------------------- *
+
2018 * Inquiry functions *
+
2019 * ----------------------------------------------------------- */
+
2020
+
2024void fuse_lowlevel_version(void);
+
2025
+
2031void fuse_lowlevel_help(void);
+
2032
+
2036void fuse_cmdline_help(void);
+
2037
+
2038/* ----------------------------------------------------------- *
+
2039 * Filesystem setup & teardown *
+
2040 * ----------------------------------------------------------- */
+
2041
+
2047struct fuse_cmdline_opts {
+
2048 int singlethread;
+
2049 int foreground;
+
2050 int debug;
+
2051 int nodefault_subtype;
+
2052 char *mountpoint;
+
2053 int show_version;
+
2054 int show_help;
+
2055 int clone_fd;
+
2056 unsigned int max_idle_threads; /* discouraged, due to thread
+
2057 * destruct overhead */
+
2058
+
2059 /* Added in libfuse-3.12 */
+
2060 unsigned int max_threads;
+
2061};
+
2062
+
2081#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2082int fuse_parse_cmdline(struct fuse_args *args,
+
2083 struct fuse_cmdline_opts *opts);
+
2084#else
+
2085#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2086int fuse_parse_cmdline_30(struct fuse_args *args,
+
2087 struct fuse_cmdline_opts *opts);
+
2088#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_30(args, opts)
+
2089#else
+
2090int fuse_parse_cmdline_312(struct fuse_args *args,
+
2091 struct fuse_cmdline_opts *opts);
+
2092#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_312(args, opts)
+
2093#endif
+
2094#endif
+
2095
+
2096/* Do not call this directly, use fuse_session_new() instead */
+
2097struct fuse_session *
+
2098fuse_session_new_versioned(struct fuse_args *args,
+
2099 const struct fuse_lowlevel_ops *op, size_t op_size,
+
2100 struct libfuse_version *version, void *userdata);
+
2101
+
2132static inline struct fuse_session *
+
2133fuse_session_new_fn(struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
2134 size_t op_size, void *userdata)
+
2135{
+
2136 struct libfuse_version version = {
+
2137 .major = FUSE_MAJOR_VERSION,
+
2138 .minor = FUSE_MINOR_VERSION,
+
2139 .hotfix = FUSE_HOTFIX_VERSION,
+
2140 .padding = 0
+
2141 };
+
2142
+
2143 return fuse_session_new_versioned(args, op, op_size, &version,
+
2144 userdata);
+
2145}
+
2146#define fuse_session_new(args, op, op_size, userdata) \
+
2147 fuse_session_new_fn(args, op, op_size, userdata)
+
2148
+
2149/*
+
2150 * This should mostly not be called directly, but instead the
+
2151 * fuse_session_custom_io() should be used.
+
2152 */
+
2153int fuse_session_custom_io_317(struct fuse_session *se,
+
2154 const struct fuse_custom_io *io, size_t op_size, int fd);
+
2155
+
2183#if FUSE_MAKE_VERSION(3, 17) <= FUSE_USE_VERSION
+
2184static inline int fuse_session_custom_io(struct fuse_session *se,
+
2185 const struct fuse_custom_io *io, size_t op_size, int fd)
+
2186{
+
2187 return fuse_session_custom_io_317(se, io, op_size, fd);
+
2188}
+
2189#else
+
2190static inline int fuse_session_custom_io(struct fuse_session *se,
+
2191 const struct fuse_custom_io *io, int fd)
+
2192{
+
2193 return fuse_session_custom_io_317(se, io,
+
2194 offsetof(struct fuse_custom_io, clone_fd), fd);
+
2195}
+
2196#endif
+
2197
+
2206int fuse_session_mount(struct fuse_session *se, const char *mountpoint);
+
2207
+
2230int fuse_session_loop(struct fuse_session *se);
+
2231
+
2232#if FUSE_USE_VERSION < 32
+
2233 int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
2234 #define fuse_session_loop_mt(se, clone_fd) fuse_session_loop_mt_31(se, clone_fd)
+
2235#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2236 int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config *config);
+
2237 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_32(se, config)
+
2238#else
+
2239 #if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2251 int fuse_session_loop_mt(struct fuse_session *se, struct fuse_loop_config *config);
+
2252 #else
+
2253 int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
2254 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_312(se, config)
+
2255 #endif
+
2256#endif
+
2257
+
2270void fuse_session_exit(struct fuse_session *se);
+
2271
+
2277void fuse_session_reset(struct fuse_session *se);
+
2278
+
2285int fuse_session_exited(struct fuse_session *se);
+
2286
+
2311void fuse_session_unmount(struct fuse_session *se);
+
2312
+
2318void fuse_session_destroy(struct fuse_session *se);
+
2319
+
2320/* ----------------------------------------------------------- *
+
2321 * Custom event loop support *
+
2322 * ----------------------------------------------------------- */
+
2323
+
2338int fuse_session_fd(struct fuse_session *se);
+
2339
+
2348void fuse_session_process_buf(struct fuse_session *se,
+
2349 const struct fuse_buf *buf);
+
2350
+
2362int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf);
+
2363
+ +
2368
+
2384int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz,
+
2385 void **mr);
+
2386
+
2387#ifdef __cplusplus
+
2388}
+
2389#endif
+
2390
+
2391#endif /* FUSE_LOWLEVEL_H_ */
+
fuse_buf_copy_flags
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
bool fuse_req_is_uring(fuse_req_t req)
+
int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, void **mr)
+
int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+ + + + + + + + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
int32_t backing_id
+ + + +
void(* flock)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op)
+
void(* write)(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* lseek)(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi)
+
void(* listxattr)(fuse_req_t req, fuse_ino_t ino, size_t size)
+
void(* removexattr)(fuse_req_t req, fuse_ino_t ino, const char *name)
+
void(* forget_multi)(fuse_req_t req, size_t count, struct fuse_forget_data *forgets)
+
void(* retrieve_reply)(fuse_req_t req, void *cookie, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv)
+
void(* create)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi)
+
void(* mkdir)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
+
void(* symlink)(fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
+
void(* write_buf)(fuse_req_t req, fuse_ino_t ino, struct fuse_bufvec *bufv, off_t off, struct fuse_file_info *fi)
+
void(* rmdir)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* link)(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
+
void(* rename)(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
+
void(* copy_file_range)(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags)
+
void(* poll)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
void(* opendir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* mknod)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
+
void(* fallocate)(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi)
+
void(* setattr)(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
+
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* fsync)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* destroy)(void *userdata)
+
void(* forget)(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
void(* getattr)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* ioctl)(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
void(* open)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* getxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
+
void(* fsyncdir)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* read)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* setxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags)
+
void(* release)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* access)(fuse_req_t req, fuse_ino_t ino, int mask)
+
void(* releasedir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* statx)(fuse_req_t req, fuse_ino_t ino, int flags, int mask, struct fuse_file_info *fi)
+
void(* tmpfile)(fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi)
+
void(* bmap)(fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx)
+
void(* readlink)(fuse_req_t req, fuse_ino_t ino)
+
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* statfs)(fuse_req_t req, fuse_ino_t ino)
+
void(* readdir)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+
void(* readdirplus)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* flush)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* unlink)(fuse_req_t req, fuse_ino_t parent, const char *name)
+ +
+ + + + diff --git a/doc/html/include_2fuse__mount__compat_8h_source.html b/doc/html/include_2fuse__mount__compat_8h_source.html new file mode 100644 index 0000000..5741ac8 --- /dev/null +++ b/doc/html/include_2fuse__mount__compat_8h_source.html @@ -0,0 +1,109 @@ + + + + + + + +libfuse: include/fuse_mount_compat.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_mount_compat.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2023 Giulio Benetti <giulio.benetti@benettiengineering.com>
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LICENSE
+
9*/
+
10
+
11#ifndef FUSE_MOUNT_COMPAT_H_
+
12#define FUSE_MOUNT_COMPAT_H_
+
13
+
14#include <sys/mount.h>
+
15
+
16/* Some libc don't define MS_*, so define them manually
+
17 * (values taken from https://elixir.bootlin.com/linux/v6.10/source/include/uapi/linux/mount.h#L13 on)
+
18 */
+
19#ifndef MS_DIRSYNC
+
20#define MS_DIRSYNC 128
+
21#endif
+
22
+
23#ifndef MS_NOSYMFOLLOW
+
24#define MS_NOSYMFOLLOW 256
+
25#endif
+
26
+
27#ifndef MS_REC
+
28#define MS_REC 16384
+
29#endif
+
30
+
31#ifndef MS_PRIVATE
+
32#define MS_PRIVATE (1<<18)
+
33#endif
+
34
+
35#ifndef MS_LAZYTIME
+
36#define MS_LAZYTIME (1<<25)
+
37#endif
+
38
+
39#ifndef UMOUNT_DETACH
+
40#define UMOUNT_DETACH 0x00000002 /* Just detach from the tree */
+
41#endif
+
42#ifndef UMOUNT_NOFOLLOW
+
43#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */
+
44#endif
+
45#ifndef UMOUNT_UNUSED
+
46#define UMOUNT_UNUSED 0x80000000 /* Flag guaranteed to be unused */
+
47#endif
+
48
+
49#endif /* FUSE_MOUNT_COMPAT_H_ */
+
+ + + + diff --git a/doc/html/include_2fuse__opt_8h.html b/doc/html/include_2fuse__opt_8h.html new file mode 100644 index 0000000..c0f03e5 --- /dev/null +++ b/doc/html/include_2fuse__opt_8h.html @@ -0,0 +1,587 @@ + + + + + + + +libfuse: include/fuse_opt.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_opt.h File Reference
+
+
+ +

Go to the source code of this file.

+ + + + + + +

+Data Structures

struct  fuse_opt
 
struct  fuse_args
 
+ + + + + + + + + + + + + + + +

+Macros

#define FUSE_OPT_KEY(templ, key)   { templ, -1U, key }
 
#define FUSE_OPT_END   { NULL, 0, 0 }
 
#define FUSE_ARGS_INIT(argc, argv)   { argc, argv, 0 }
 
#define FUSE_OPT_KEY_OPT   -1
 
#define FUSE_OPT_KEY_NONOPT   -2
 
#define FUSE_OPT_KEY_KEEP   -3
 
#define FUSE_OPT_KEY_DISCARD   -4
 
+ + + +

+Typedefs

typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
 
+ + + + + + + + + + + + + + + +

+Functions

int fuse_opt_parse (struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
 
int fuse_opt_add_opt (char **opts, const char *opt)
 
int fuse_opt_add_opt_escaped (char **opts, const char *opt)
 
int fuse_opt_add_arg (struct fuse_args *args, const char *arg)
 
int fuse_opt_insert_arg (struct fuse_args *args, int pos, const char *arg)
 
void fuse_opt_free_args (struct fuse_args *args)
 
int fuse_opt_match (const struct fuse_opt opts[], const char *opt)
 
+

Detailed Description

+

This file defines the option parsing interface of FUSE

+ +

Definition in file fuse_opt.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ARGS_INIT

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_ARGS_INIT( argc,
 argv 
)   { argc, argv, 0 }
+
+

Initializer for 'struct fuse_args'

+ +

Definition at line 123 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_END

+ +
+
+ + + + +
#define FUSE_OPT_END   { NULL, 0, 0 }
+
+

Last option. An array of 'struct fuse_opt' must end with a NULL template value

+ +

Definition at line 104 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_OPT_KEY( templ,
 key 
)   { templ, -1U, key }
+
+

Key option. In case of a match, the processing function will be called with the specified key.

+ +

Definition at line 98 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_DISCARD

+ +
+
+ + + + +
#define FUSE_OPT_KEY_DISCARD   -4
+
+

Special key value for options to discard

+

Argument is not passed to processing function, but behave as if the processing function returned zero

+ +

Definition at line 153 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_KEEP

+ +
+
+ + + + +
#define FUSE_OPT_KEY_KEEP   -3
+
+

Special key value for options to keep

+

Argument is not passed to processing function, but behave as if the processing function returned 1

+ +

Definition at line 145 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_NONOPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_NONOPT   -2
+
+

Key value passed to the processing function for all non-options

+

Non-options are the arguments beginning with a character other than '-' or all arguments after the special '–' option

+ +

Definition at line 137 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_OPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_OPT   -1
+
+

Key value passed to the processing function if an option did not match any template

+ +

Definition at line 129 of file fuse_opt.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_opt_proc_t

+ +
+
+ + + + +
typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
+
+

Processing function

+

This function is called if

    +
  • option did not match any 'struct fuse_opt'
  • +
  • argument is a non-option
  • +
  • option did match and offset was set to -1
  • +
+

The 'arg' parameter will always contain the whole argument or option including the parameter if exists. A two-argument option ("-x foo") is always converted to single argument option of the form "-xfoo" before this function is called.

+

Options of the form '-ofoo' are passed to this function without the '-o' prefix.

+

The return value of this function determines whether this argument is to be inserted into the output argument vector, or discarded.

+
Parameters
+ + + + + +
datais the user data passed to the fuse_opt_parse() function
argis the whole argument or option
keydetermines why the processing function was called
outargsthe current output argument list
+
+
+
Returns
-1 on error, 0 if arg is to be discarded, 1 if arg should be kept
+ +

Definition at line 180 of file fuse_opt.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_opt_add_arg()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_arg (struct fuse_argsargs,
const char * arg 
)
+
+

Add an argument to a NULL terminated argument vector

+
Parameters
+ + + +
argsis the structure containing the current argument list
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 55 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_opt (char ** opts,
const char * opt 
)
+
+

Add an option to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 139 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt_escaped()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_opt_escaped (char ** opts,
const char * opt 
)
+
+

Add an option, escaping commas, to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 144 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_free_args()

+ +
+
+ + + + + + + + +
void fuse_opt_free_args (struct fuse_argsargs)
+
+

Free the contents of argument list

+

The structure itself is not freed

+
Parameters
+ + +
argsis the structure containing the argument list
+
+
+ +

Definition at line 34 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_insert_arg()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_insert_arg (struct fuse_argsargs,
int pos,
const char * arg 
)
+
+

Add an argument at the specified position in a NULL terminated argument vector

+

Adds the argument to the N-th position. This is useful for adding options at the beginning of the array which must not come after the special '–' option.

+
Parameters
+ + + + +
argsis the structure containing the current argument list
posis the position at which to add the argument
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 95 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_match()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_match (const struct fuse_opt opts[],
const char * opt 
)
+
+

Check if an option matches

+
Parameters
+ + + +
optsis the option description array
optis the option to match
+
+
+
Returns
1 if a match is found, 0 if not
+ +
+
+ +

◆ fuse_opt_parse()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_parse (struct fuse_argsargs,
void * data,
const struct fuse_opt opts[],
fuse_opt_proc_t proc 
)
+
+

Option parsing function

+

If 'args' was returned from a previous call to fuse_opt_parse() or it was constructed from

+

A NULL 'args' is equivalent to an empty argument vector

+

A NULL 'opts' is equivalent to an 'opts' array containing a single end marker

+

A NULL 'proc' is equivalent to a processing function always returning '1'

+
Parameters
+ + + + + +
argsis the input and output argument list
datais the user data
optsis the option description array
procis the processing function
+
+
+
Returns
-1 on error, 0 on success
+ +

Definition at line 398 of file fuse_opt.c.

+ +
+
+
+ + + + diff --git a/doc/html/include_2fuse__opt_8h_source.html b/doc/html/include_2fuse__opt_8h_source.html new file mode 100644 index 0000000..d3689c7 --- /dev/null +++ b/doc/html/include_2fuse__opt_8h_source.html @@ -0,0 +1,145 @@ + + + + + + + +libfuse: include/fuse_opt.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_opt.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#ifndef FUSE_OPT_H_
+
10#define FUSE_OPT_H_
+
11
+
17#ifdef __cplusplus
+
18extern "C" {
+
19#endif
+
20
+
77struct fuse_opt {
+
79 const char *templ;
+
80
+
85 unsigned long offset;
+
86
+
91 int value;
+
92};
+
93
+
98#define FUSE_OPT_KEY(templ, key) { templ, -1U, key }
+
99
+
104#define FUSE_OPT_END { NULL, 0, 0 }
+
105
+
109struct fuse_args {
+
111 int argc;
+
112
+
114 char **argv;
+
115
+
117 int allocated;
+
118};
+
119
+
123#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
+
124
+
129#define FUSE_OPT_KEY_OPT -1
+
130
+
137#define FUSE_OPT_KEY_NONOPT -2
+
138
+
145#define FUSE_OPT_KEY_KEEP -3
+
146
+
153#define FUSE_OPT_KEY_DISCARD -4
+
154
+
180typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key,
+
181 struct fuse_args *outargs);
+
182
+
203int fuse_opt_parse(struct fuse_args *args, void *data,
+
204 const struct fuse_opt opts[], fuse_opt_proc_t proc);
+
205
+
213int fuse_opt_add_opt(char **opts, const char *opt);
+
214
+
222int fuse_opt_add_opt_escaped(char **opts, const char *opt);
+
223
+
231int fuse_opt_add_arg(struct fuse_args *args, const char *arg);
+
232
+
246int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg);
+
247
+
255void fuse_opt_free_args(struct fuse_args *args);
+
256
+
257
+
265int fuse_opt_match(const struct fuse_opt opts[], const char *opt);
+
266
+
267#ifdef __cplusplus
+
268}
+
269#endif
+
270
+
271#endif /* FUSE_OPT_H_ */
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
unsigned long offset
Definition fuse_opt.h:85
+
const char * templ
Definition fuse_opt.h:79
+
int value
Definition fuse_opt.h:91
+
+ + + + diff --git a/doc/html/index.html b/doc/html/index.html new file mode 100644 index 0000000..75b077a --- /dev/null +++ b/doc/html/index.html @@ -0,0 +1,69 @@ + + + + + + + +libfuse: libfuse API documentation + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
libfuse API documentation
+
+
+

FUSE (Filesystem in Userspace) is an interface for userspace programs to export a filesystem to the Linux kernel. The FUSE project consists of two components: the fuse kernel module (maintained in the regular kernel repositories) and the libfuse userspace library. libfuse provides the reference implementation for communicating with the FUSE kernel module.

+

A FUSE file system is typically implemented as a standalone application that links with libfuse. libfuse provides functions to mount the file system, unmount it, read requests from the kernel, and send responses back.

+

+Getting started

+

libfuse offers two APIs: a "high-level", synchronous API, and a "low-level" asynchronous API. In both cases, incoming requests from the kernel are passed to the main program using callbacks. When using the high-level API, the callbacks may work with file names and paths instead of inodes, and processing of a request finishes when the callback function returns. When using the low-level API, the callbacks must work with inodes and responses must be sent explicitly using a separate set of API functions.

+

The high-level API that is primarily specified in fuse.h. The low-level API that is primarily documented in fuse_lowlevel.h.

+

+Examples

+

FUSE comes with several examples in the examples directory. A good starting point are hello.c (for the high-level API) and hello_ll.c (for the low-level API).

+

+FUSE internals

+

The authoritative source of information about libfuse internals (including the protocol used for communication with the FUSE kernel module) is the source code.

+

However, some people have kindly documented different aspects of FUSE in a more beginner friendly way. While this information is increasingly out of date, it still provides a good overview:

+ +
+
+ + + + diff --git a/doc/html/jquery.js b/doc/html/jquery.js new file mode 100644 index 0000000..1dffb65 --- /dev/null +++ b/doc/html/jquery.js @@ -0,0 +1,34 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",options:{classes:{},disabled:!1,create:null},_createWidget:function(t,e){e=y(e||this.defaultElement||this)[0],this.element=y(e),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=y(),this.hoverable=y(),this.focusable=y(),this.classesElementLookup={},e!==this&&(y.data(e,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===e&&this.destroy()}}),this.document=y(e.style?e.ownerDocument:e.document||e),this.window=y(this.document[0].defaultView||this.document[0].parentWindow)),this.options=y.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:y.noop,_create:y.noop,_init:y.noop,destroy:function(){var i=this;this._destroy(),y.each(this.classesElementLookup,function(t,e){i._removeClass(e,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:y.noop,widget:function(){return this.element},option:function(t,e){var i,s,n,o=t;if(0===arguments.length)return y.widget.extend({},this.options);if("string"==typeof t)if(o={},t=(i=t.split(".")).shift(),i.length){for(s=o[t]=y.widget.extend({},this.options[t]),n=0;n
"),i=e.children()[0];return y("body").append(e),t=i.offsetWidth,e.css("overflow","scroll"),t===(i=i.offsetWidth)&&(i=e[0].clientWidth),e.remove(),s=t-i},getScrollInfo:function(t){var e=t.isWindow||t.isDocument?"":t.element.css("overflow-x"),i=t.isWindow||t.isDocument?"":t.element.css("overflow-y"),e="scroll"===e||"auto"===e&&t.widthx(D(s),D(n))?o.important="horizontal":o.important="vertical",p.using.call(this,t,o)}),h.offset(y.extend(l,{using:t}))})},y.ui.position={fit:{left:function(t,e){var i=e.within,s=i.isWindow?i.scrollLeft:i.offset.left,n=i.width,o=t.left-e.collisionPosition.marginLeft,h=s-o,a=o+e.collisionWidth-n-s;e.collisionWidth>n?0n?0=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),y.ui.plugin={add:function(t,e,i){var s,n=y.ui[t].prototype;for(s in i)n.plugins[s]=n.plugins[s]||[],n.plugins[s].push([e,i[s]])},call:function(t,e,i,s){var n,o=t.plugins[e];if(o&&(s||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(n=0;n").css({overflow:"hidden",position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.resizable("instance")),this.elementIsWrapper=!0,t={marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom"),marginLeft:this.originalElement.css("marginLeft")},this.element.css(t),this.originalElement.css("margin",0),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css(t),this._proportionallyResize()),this._setupHandles(),e.autoHide&&y(this.element).on("mouseenter",function(){e.disabled||(i._removeClass("ui-resizable-autohide"),i._handles.show())}).on("mouseleave",function(){e.disabled||i.resizing||(i._addClass("ui-resizable-autohide"),i._handles.hide())}),this._mouseInit()},_destroy:function(){this._mouseDestroy(),this._addedHandles.remove();function t(t){y(t).removeData("resizable").removeData("ui-resizable").off(".resizable")}var e;return this.elementIsWrapper&&(t(this.element),e=this.element,this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")}).insertAfter(e),e.remove()),this.originalElement.css("resize",this.originalResizeStyle),t(this.originalElement),this},_setOption:function(t,e){switch(this._super(t,e),t){case"handles":this._removeHandles(),this._setupHandles();break;case"aspectRatio":this._aspectRatio=!!e}},_setupHandles:function(){var t,e,i,s,n,o=this.options,h=this;if(this.handles=o.handles||(y(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this._handles=y(),this._addedHandles=y(),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),i=this.handles.split(","),this.handles={},e=0;e"),this._addClass(n,"ui-resizable-handle "+s),n.css({zIndex:o.zIndex}),this.handles[t]=".ui-resizable-"+t,this.element.children(this.handles[t]).length||(this.element.append(n),this._addedHandles=this._addedHandles.add(n));this._renderAxis=function(t){var e,i,s;for(e in t=t||this.element,this.handles)this.handles[e].constructor===String?this.handles[e]=this.element.children(this.handles[e]).first().show():(this.handles[e].jquery||this.handles[e].nodeType)&&(this.handles[e]=y(this.handles[e]),this._on(this.handles[e],{mousedown:h._mouseDown})),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)&&(i=y(this.handles[e],this.element),s=/sw|ne|nw|se|n|s/.test(e)?i.outerHeight():i.outerWidth(),i=["padding",/ne|nw|n/.test(e)?"Top":/se|sw|s/.test(e)?"Bottom":/^e$/.test(e)?"Right":"Left"].join(""),t.css(i,s),this._proportionallyResize()),this._handles=this._handles.add(this.handles[e])},this._renderAxis(this.element),this._handles=this._handles.add(this.element.find(".ui-resizable-handle")),this._handles.disableSelection(),this._handles.on("mouseover",function(){h.resizing||(this.className&&(n=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),h.axis=n&&n[1]?n[1]:"se")}),o.autoHide&&(this._handles.hide(),this._addClass("ui-resizable-autohide"))},_removeHandles:function(){this._addedHandles.remove()},_mouseCapture:function(t){var e,i,s=!1;for(e in this.handles)(i=y(this.handles[e])[0])!==t.target&&!y.contains(i,t.target)||(s=!0);return!this.options.disabled&&s},_mouseStart:function(t){var e,i,s=this.options,n=this.element;return this.resizing=!0,this._renderProxy(),e=this._num(this.helper.css("left")),i=this._num(this.helper.css("top")),s.containment&&(e+=y(s.containment).scrollLeft()||0,i+=y(s.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:e,top:i},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:n.width(),height:n.height()},this.originalSize=this._helper?{width:n.outerWidth(),height:n.outerHeight()}:{width:n.width(),height:n.height()},this.sizeDiff={width:n.outerWidth()-n.width(),height:n.outerHeight()-n.height()},this.originalPosition={left:e,top:i},this.originalMousePosition={left:t.pageX,top:t.pageY},this.aspectRatio="number"==typeof s.aspectRatio?s.aspectRatio:this.originalSize.width/this.originalSize.height||1,s=y(".ui-resizable-"+this.axis).css("cursor"),y("body").css("cursor","auto"===s?this.axis+"-resize":s),this._addClass("ui-resizable-resizing"),this._propagate("start",t),!0},_mouseDrag:function(t){var e=this.originalMousePosition,i=this.axis,s=t.pageX-e.left||0,e=t.pageY-e.top||0,i=this._change[i];return this._updatePrevProperties(),i&&(e=i.apply(this,[t,s,e]),this._updateVirtualBoundaries(t.shiftKey),(this._aspectRatio||t.shiftKey)&&(e=this._updateRatio(e,t)),e=this._respectSize(e,t),this._updateCache(e),this._propagate("resize",t),e=this._applyChanges(),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),y.isEmptyObject(e)||(this._updatePrevProperties(),this._trigger("resize",t,this.ui()),this._applyChanges())),!1},_mouseStop:function(t){this.resizing=!1;var e,i,s,n=this.options,o=this;return this._helper&&(s=(e=(i=this._proportionallyResizeElements).length&&/textarea/i.test(i[0].nodeName))&&this._hasScroll(i[0],"left")?0:o.sizeDiff.height,i=e?0:o.sizeDiff.width,e={width:o.helper.width()-i,height:o.helper.height()-s},i=parseFloat(o.element.css("left"))+(o.position.left-o.originalPosition.left)||null,s=parseFloat(o.element.css("top"))+(o.position.top-o.originalPosition.top)||null,n.animate||this.element.css(y.extend(e,{top:s,left:i})),o.helper.height(o.size.height),o.helper.width(o.size.width),this._helper&&!n.animate&&this._proportionallyResize()),y("body").css("cursor","auto"),this._removeClass("ui-resizable-resizing"),this._propagate("stop",t),this._helper&&this.helper.remove(),!1},_updatePrevProperties:function(){this.prevPosition={top:this.position.top,left:this.position.left},this.prevSize={width:this.size.width,height:this.size.height}},_applyChanges:function(){var t={};return this.position.top!==this.prevPosition.top&&(t.top=this.position.top+"px"),this.position.left!==this.prevPosition.left&&(t.left=this.position.left+"px"),this.size.width!==this.prevSize.width&&(t.width=this.size.width+"px"),this.size.height!==this.prevSize.height&&(t.height=this.size.height+"px"),this.helper.css(t),t},_updateVirtualBoundaries:function(t){var e,i,s=this.options,n={minWidth:this._isNumber(s.minWidth)?s.minWidth:0,maxWidth:this._isNumber(s.maxWidth)?s.maxWidth:1/0,minHeight:this._isNumber(s.minHeight)?s.minHeight:0,maxHeight:this._isNumber(s.maxHeight)?s.maxHeight:1/0};(this._aspectRatio||t)&&(e=n.minHeight*this.aspectRatio,i=n.minWidth/this.aspectRatio,s=n.maxHeight*this.aspectRatio,t=n.maxWidth/this.aspectRatio,e>n.minWidth&&(n.minWidth=e),i>n.minHeight&&(n.minHeight=i),st.width,h=this._isNumber(t.height)&&e.minHeight&&e.minHeight>t.height,a=this.originalPosition.left+this.originalSize.width,r=this.originalPosition.top+this.originalSize.height,l=/sw|nw|w/.test(i),i=/nw|ne|n/.test(i);return o&&(t.width=e.minWidth),h&&(t.height=e.minHeight),s&&(t.width=e.maxWidth),n&&(t.height=e.maxHeight),o&&l&&(t.left=a-e.minWidth),s&&l&&(t.left=a-e.maxWidth),h&&i&&(t.top=r-e.minHeight),n&&i&&(t.top=r-e.maxHeight),t.width||t.height||t.left||!t.top?t.width||t.height||t.top||!t.left||(t.left=null):t.top=null,t},_getPaddingPlusBorderDimensions:function(t){for(var e=0,i=[],s=[t.css("borderTopWidth"),t.css("borderRightWidth"),t.css("borderBottomWidth"),t.css("borderLeftWidth")],n=[t.css("paddingTop"),t.css("paddingRight"),t.css("paddingBottom"),t.css("paddingLeft")];e<4;e++)i[e]=parseFloat(s[e])||0,i[e]+=parseFloat(n[e])||0;return{height:i[0]+i[2],width:i[1]+i[3]}},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var t,e=0,i=this.helper||this.element;e").css({overflow:"hidden"}),this._addClass(this.helper,this._helper),this.helper.css({width:this.element.outerWidth(),height:this.element.outerHeight(),position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++e.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(t,e){return{width:this.originalSize.width+e}},w:function(t,e){var i=this.originalSize;return{left:this.originalPosition.left+e,width:i.width-e}},n:function(t,e,i){var s=this.originalSize;return{top:this.originalPosition.top+i,height:s.height-i}},s:function(t,e,i){return{height:this.originalSize.height+i}},se:function(t,e,i){return y.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[t,e,i]))},sw:function(t,e,i){return y.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[t,e,i]))},ne:function(t,e,i){return y.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[t,e,i]))},nw:function(t,e,i){return y.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[t,e,i]))}},_propagate:function(t,e){y.ui.plugin.call(this,t,[e,this.ui()]),"resize"!==t&&this._trigger(t,e,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),y.ui.plugin.add("resizable","animate",{stop:function(e){var i=y(this).resizable("instance"),t=i.options,s=i._proportionallyResizeElements,n=s.length&&/textarea/i.test(s[0].nodeName),o=n&&i._hasScroll(s[0],"left")?0:i.sizeDiff.height,h=n?0:i.sizeDiff.width,n={width:i.size.width-h,height:i.size.height-o},h=parseFloat(i.element.css("left"))+(i.position.left-i.originalPosition.left)||null,o=parseFloat(i.element.css("top"))+(i.position.top-i.originalPosition.top)||null;i.element.animate(y.extend(n,o&&h?{top:o,left:h}:{}),{duration:t.animateDuration,easing:t.animateEasing,step:function(){var t={width:parseFloat(i.element.css("width")),height:parseFloat(i.element.css("height")),top:parseFloat(i.element.css("top")),left:parseFloat(i.element.css("left"))};s&&s.length&&y(s[0]).css({width:t.width,height:t.height}),i._updateCache(t),i._propagate("resize",e)}})}}),y.ui.plugin.add("resizable","containment",{start:function(){var i,s,n=y(this).resizable("instance"),t=n.options,e=n.element,o=t.containment,h=o instanceof y?o.get(0):/parent/.test(o)?e.parent().get(0):o;h&&(n.containerElement=y(h),/document/.test(o)||o===document?(n.containerOffset={left:0,top:0},n.containerPosition={left:0,top:0},n.parentData={element:y(document),left:0,top:0,width:y(document).width(),height:y(document).height()||document.body.parentNode.scrollHeight}):(i=y(h),s=[],y(["Top","Right","Left","Bottom"]).each(function(t,e){s[t]=n._num(i.css("padding"+e))}),n.containerOffset=i.offset(),n.containerPosition=i.position(),n.containerSize={height:i.innerHeight()-s[3],width:i.innerWidth()-s[1]},t=n.containerOffset,e=n.containerSize.height,o=n.containerSize.width,o=n._hasScroll(h,"left")?h.scrollWidth:o,e=n._hasScroll(h)?h.scrollHeight:e,n.parentData={element:h,left:t.left,top:t.top,width:o,height:e}))},resize:function(t){var e=y(this).resizable("instance"),i=e.options,s=e.containerOffset,n=e.position,o=e._aspectRatio||t.shiftKey,h={top:0,left:0},a=e.containerElement,t=!0;a[0]!==document&&/static/.test(a.css("position"))&&(h=s),n.left<(e._helper?s.left:0)&&(e.size.width=e.size.width+(e._helper?e.position.left-s.left:e.position.left-h.left),o&&(e.size.height=e.size.width/e.aspectRatio,t=!1),e.position.left=i.helper?s.left:0),n.top<(e._helper?s.top:0)&&(e.size.height=e.size.height+(e._helper?e.position.top-s.top:e.position.top),o&&(e.size.width=e.size.height*e.aspectRatio,t=!1),e.position.top=e._helper?s.top:0),i=e.containerElement.get(0)===e.element.parent().get(0),n=/relative|absolute/.test(e.containerElement.css("position")),i&&n?(e.offset.left=e.parentData.left+e.position.left,e.offset.top=e.parentData.top+e.position.top):(e.offset.left=e.element.offset().left,e.offset.top=e.element.offset().top),n=Math.abs(e.sizeDiff.width+(e._helper?e.offset.left-h.left:e.offset.left-s.left)),s=Math.abs(e.sizeDiff.height+(e._helper?e.offset.top-h.top:e.offset.top-s.top)),n+e.size.width>=e.parentData.width&&(e.size.width=e.parentData.width-n,o&&(e.size.height=e.size.width/e.aspectRatio,t=!1)),s+e.size.height>=e.parentData.height&&(e.size.height=e.parentData.height-s,o&&(e.size.width=e.size.height*e.aspectRatio,t=!1)),t||(e.position.left=e.prevPosition.left,e.position.top=e.prevPosition.top,e.size.width=e.prevSize.width,e.size.height=e.prevSize.height)},stop:function(){var t=y(this).resizable("instance"),e=t.options,i=t.containerOffset,s=t.containerPosition,n=t.containerElement,o=y(t.helper),h=o.offset(),a=o.outerWidth()-t.sizeDiff.width,o=o.outerHeight()-t.sizeDiff.height;t._helper&&!e.animate&&/relative/.test(n.css("position"))&&y(this).css({left:h.left-s.left-i.left,width:a,height:o}),t._helper&&!e.animate&&/static/.test(n.css("position"))&&y(this).css({left:h.left-s.left-i.left,width:a,height:o})}}),y.ui.plugin.add("resizable","alsoResize",{start:function(){var t=y(this).resizable("instance").options;y(t.alsoResize).each(function(){var t=y(this);t.data("ui-resizable-alsoresize",{width:parseFloat(t.width()),height:parseFloat(t.height()),left:parseFloat(t.css("left")),top:parseFloat(t.css("top"))})})},resize:function(t,i){var e=y(this).resizable("instance"),s=e.options,n=e.originalSize,o=e.originalPosition,h={height:e.size.height-n.height||0,width:e.size.width-n.width||0,top:e.position.top-o.top||0,left:e.position.left-o.left||0};y(s.alsoResize).each(function(){var t=y(this),s=y(this).data("ui-resizable-alsoresize"),n={},e=t.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];y.each(e,function(t,e){var i=(s[e]||0)+(h[e]||0);i&&0<=i&&(n[e]=i||null)}),t.css(n)})},stop:function(){y(this).removeData("ui-resizable-alsoresize")}}),y.ui.plugin.add("resizable","ghost",{start:function(){var t=y(this).resizable("instance"),e=t.size;t.ghost=t.originalElement.clone(),t.ghost.css({opacity:.25,display:"block",position:"relative",height:e.height,width:e.width,margin:0,left:0,top:0}),t._addClass(t.ghost,"ui-resizable-ghost"),!1!==y.uiBackCompat&&"string"==typeof t.options.ghost&&t.ghost.addClass(this.options.ghost),t.ghost.appendTo(t.helper)},resize:function(){var t=y(this).resizable("instance");t.ghost&&t.ghost.css({position:"relative",height:t.size.height,width:t.size.width})},stop:function(){var t=y(this).resizable("instance");t.ghost&&t.helper&&t.helper.get(0).removeChild(t.ghost.get(0))}}),y.ui.plugin.add("resizable","grid",{resize:function(){var t,e=y(this).resizable("instance"),i=e.options,s=e.size,n=e.originalSize,o=e.originalPosition,h=e.axis,a="number"==typeof i.grid?[i.grid,i.grid]:i.grid,r=a[0]||1,l=a[1]||1,u=Math.round((s.width-n.width)/r)*r,p=Math.round((s.height-n.height)/l)*l,d=n.width+u,c=n.height+p,f=i.maxWidth&&i.maxWidthd,s=i.minHeight&&i.minHeight>c;i.grid=a,m&&(d+=r),s&&(c+=l),f&&(d-=r),g&&(c-=l),/^(se|s|e)$/.test(h)?(e.size.width=d,e.size.height=c):/^(ne)$/.test(h)?(e.size.width=d,e.size.height=c,e.position.top=o.top-p):/^(sw)$/.test(h)?(e.size.width=d,e.size.height=c,e.position.left=o.left-u):((c-l<=0||d-r<=0)&&(t=e._getPaddingPlusBorderDimensions(this)),0=f[g]?0:Math.min(f[g],n));!a&&1-1){targetElements.on(evt+EVENT_NAMESPACE,function elementToggle(event){$.powerTip.toggle(this,event)})}else{targetElements.on(evt+EVENT_NAMESPACE,function elementOpen(event){$.powerTip.show(this,event)})}});$.each(options.closeEvents,function(idx,evt){if($.inArray(evt,options.openEvents)<0){targetElements.on(evt+EVENT_NAMESPACE,function elementClose(event){$.powerTip.hide(this,!isMouseEvent(event))})}});targetElements.on("keydown"+EVENT_NAMESPACE,function elementKeyDown(event){if(event.keyCode===27){$.powerTip.hide(this,true)}})}return targetElements};$.fn.powerTip.defaults={fadeInTime:200,fadeOutTime:100,followMouse:false,popupId:"powerTip",popupClass:null,intentSensitivity:7,intentPollInterval:100,closeDelay:100,placement:"n",smartPlacement:false,offset:10,mouseOnToPopup:false,manual:false,openEvents:["mouseenter","focus"],closeEvents:["mouseleave","blur"]};$.fn.powerTip.smartPlacementLists={n:["n","ne","nw","s"],e:["e","ne","se","w","nw","sw","n","s","e"],s:["s","se","sw","n"],w:["w","nw","sw","e","ne","se","n","s","w"],nw:["nw","w","sw","n","s","se","nw"],ne:["ne","e","se","n","s","sw","ne"],sw:["sw","w","nw","s","n","ne","sw"],se:["se","e","ne","s","n","nw","se"],"nw-alt":["nw-alt","n","ne-alt","sw-alt","s","se-alt","w","e"],"ne-alt":["ne-alt","n","nw-alt","se-alt","s","sw-alt","e","w"],"sw-alt":["sw-alt","s","se-alt","nw-alt","n","ne-alt","w","e"],"se-alt":["se-alt","s","sw-alt","ne-alt","n","nw-alt","e","w"]};$.powerTip={show:function apiShowTip(element,event){if(isMouseEvent(event)){trackMouse(event);session.previousX=event.pageX;session.previousY=event.pageY;$(element).data(DATA_DISPLAYCONTROLLER).show()}else{$(element).first().data(DATA_DISPLAYCONTROLLER).show(true,true)}return element},reposition:function apiResetPosition(element){$(element).first().data(DATA_DISPLAYCONTROLLER).resetPosition();return element},hide:function apiCloseTip(element,immediate){var displayController;immediate=element?immediate:true;if(element){displayController=$(element).first().data(DATA_DISPLAYCONTROLLER)}else if(session.activeHover){displayController=session.activeHover.data(DATA_DISPLAYCONTROLLER)}if(displayController){displayController.hide(immediate)}return element},toggle:function apiToggle(element,event){if(session.activeHover&&session.activeHover.is(element)){$.powerTip.hide(element,!isMouseEvent(event))}else{$.powerTip.show(element,event)}return element}};$.powerTip.showTip=$.powerTip.show;$.powerTip.closeTip=$.powerTip.hide;function CSSCoordinates(){var me=this;me.top="auto";me.left="auto";me.right="auto";me.bottom="auto";me.set=function(property,value){if($.isNumeric(value)){me[property]=Math.round(value)}}}function DisplayController(element,options,tipController){var hoverTimer=null,myCloseDelay=null;function openTooltip(immediate,forceOpen){cancelTimer();if(!element.data(DATA_HASACTIVEHOVER)){if(!immediate){session.tipOpenImminent=true;hoverTimer=setTimeout(function intentDelay(){hoverTimer=null;checkForIntent()},options.intentPollInterval)}else{if(forceOpen){element.data(DATA_FORCEDOPEN,true)}closeAnyDelayed();tipController.showTip(element)}}else{cancelClose()}}function closeTooltip(disableDelay){if(myCloseDelay){myCloseDelay=session.closeDelayTimeout=clearTimeout(myCloseDelay);session.delayInProgress=false}cancelTimer();session.tipOpenImminent=false;if(element.data(DATA_HASACTIVEHOVER)){element.data(DATA_FORCEDOPEN,false);if(!disableDelay){session.delayInProgress=true;session.closeDelayTimeout=setTimeout(function closeDelay(){session.closeDelayTimeout=null;tipController.hideTip(element);session.delayInProgress=false;myCloseDelay=null},options.closeDelay);myCloseDelay=session.closeDelayTimeout}else{tipController.hideTip(element)}}}function checkForIntent(){var xDifference=Math.abs(session.previousX-session.currentX),yDifference=Math.abs(session.previousY-session.currentY),totalDifference=xDifference+yDifference;if(totalDifference",{id:options.popupId});if($body.length===0){$body=$("body")}$body.append(tipElement);session.tooltips=session.tooltips?session.tooltips.add(tipElement):tipElement}if(options.followMouse){if(!tipElement.data(DATA_HASMOUSEMOVE)){$document.on("mousemove"+EVENT_NAMESPACE,positionTipOnCursor);$window.on("scroll"+EVENT_NAMESPACE,positionTipOnCursor);tipElement.data(DATA_HASMOUSEMOVE,true)}}function beginShowTip(element){element.data(DATA_HASACTIVEHOVER,true);tipElement.queue(function queueTipInit(next){showTip(element);next()})}function showTip(element){var tipContent;if(!element.data(DATA_HASACTIVEHOVER)){return}if(session.isTipOpen){if(!session.isClosing){hideTip(session.activeHover)}tipElement.delay(100).queue(function queueTipAgain(next){showTip(element);next()});return}element.trigger("powerTipPreRender");tipContent=getTooltipContent(element);if(tipContent){tipElement.empty().append(tipContent)}else{return}element.trigger("powerTipRender");session.activeHover=element;session.isTipOpen=true;tipElement.data(DATA_MOUSEONTOTIP,options.mouseOnToPopup);tipElement.addClass(options.popupClass);if(!options.followMouse||element.data(DATA_FORCEDOPEN)){positionTipOnElement(element);session.isFixedTipOpen=true}else{positionTipOnCursor()}if(!element.data(DATA_FORCEDOPEN)&&!options.followMouse){$document.on("click"+EVENT_NAMESPACE,function documentClick(event){var target=event.target;if(target!==element[0]){if(options.mouseOnToPopup){if(target!==tipElement[0]&&!$.contains(tipElement[0],target)){$.powerTip.hide()}}else{$.powerTip.hide()}}})}if(options.mouseOnToPopup&&!options.manual){tipElement.on("mouseenter"+EVENT_NAMESPACE,function tipMouseEnter(){if(session.activeHover){session.activeHover.data(DATA_DISPLAYCONTROLLER).cancel()}});tipElement.on("mouseleave"+EVENT_NAMESPACE,function tipMouseLeave(){if(session.activeHover){session.activeHover.data(DATA_DISPLAYCONTROLLER).hide()}})}tipElement.fadeIn(options.fadeInTime,function fadeInCallback(){if(!session.desyncTimeout){session.desyncTimeout=setInterval(closeDesyncedTip,500)}element.trigger("powerTipOpen")})}function hideTip(element){session.isClosing=true;session.isTipOpen=false;session.desyncTimeout=clearInterval(session.desyncTimeout);element.data(DATA_HASACTIVEHOVER,false);element.data(DATA_FORCEDOPEN,false);$document.off("click"+EVENT_NAMESPACE);tipElement.off(EVENT_NAMESPACE);tipElement.fadeOut(options.fadeOutTime,function fadeOutCallback(){var coords=new CSSCoordinates;session.activeHover=null;session.isClosing=false;session.isFixedTipOpen=false;tipElement.removeClass();coords.set("top",session.currentY+options.offset);coords.set("left",session.currentX+options.offset);tipElement.css(coords);element.trigger("powerTipClose")})}function positionTipOnCursor(){var tipWidth,tipHeight,coords,collisions,collisionCount;if(!session.isFixedTipOpen&&(session.isTipOpen||session.tipOpenImminent&&tipElement.data(DATA_HASMOUSEMOVE))){tipWidth=tipElement.outerWidth();tipHeight=tipElement.outerHeight();coords=new CSSCoordinates;coords.set("top",session.currentY+options.offset);coords.set("left",session.currentX+options.offset);collisions=getViewportCollisions(coords,tipWidth,tipHeight);if(collisions!==Collision.none){collisionCount=countFlags(collisions);if(collisionCount===1){if(collisions===Collision.right){coords.set("left",session.scrollLeft+session.windowWidth-tipWidth)}else if(collisions===Collision.bottom){coords.set("top",session.scrollTop+session.windowHeight-tipHeight)}}else{coords.set("left",session.currentX-tipWidth-options.offset);coords.set("top",session.currentY-tipHeight-options.offset)}}tipElement.css(coords)}}function positionTipOnElement(element){var priorityList,finalPlacement;if(options.smartPlacement||options.followMouse&&element.data(DATA_FORCEDOPEN)){priorityList=$.fn.powerTip.smartPlacementLists[options.placement];$.each(priorityList,function(idx,pos){var collisions=getViewportCollisions(placeTooltip(element,pos),tipElement.outerWidth(),tipElement.outerHeight());finalPlacement=pos;return collisions!==Collision.none})}else{placeTooltip(element,options.placement);finalPlacement=options.placement}tipElement.removeClass("w nw sw e ne se n s w se-alt sw-alt ne-alt nw-alt");tipElement.addClass(finalPlacement)}function placeTooltip(element,placement){var iterationCount=0,tipWidth,tipHeight,coords=new CSSCoordinates;coords.set("top",0);coords.set("left",0);tipElement.css(coords);do{tipWidth=tipElement.outerWidth();tipHeight=tipElement.outerHeight();coords=placementCalculator.compute(element,placement,tipWidth,tipHeight,options.offset);tipElement.css(coords)}while(++iterationCount<=5&&(tipWidth!==tipElement.outerWidth()||tipHeight!==tipElement.outerHeight()));return coords}function closeDesyncedTip(){var isDesynced=false,hasDesyncableCloseEvent=$.grep(["mouseleave","mouseout","blur","focusout"],function(eventType){return $.inArray(eventType,options.closeEvents)!==-1}).length>0;if(session.isTipOpen&&!session.isClosing&&!session.delayInProgress&&hasDesyncableCloseEvent){if(session.activeHover.data(DATA_HASACTIVEHOVER)===false||session.activeHover.is(":disabled")){isDesynced=true}else if(!isMouseOver(session.activeHover)&&!session.activeHover.is(":focus")&&!session.activeHover.data(DATA_FORCEDOPEN)){if(tipElement.data(DATA_MOUSEONTOTIP)){if(!isMouseOver(tipElement)){isDesynced=true}}else{isDesynced=true}}if(isDesynced){hideTip(session.activeHover)}}}this.showTip=beginShowTip;this.hideTip=hideTip;this.resetPosition=positionTipOnElement}function isSvgElement(element){return Boolean(window.SVGElement&&element[0]instanceof SVGElement)}function isMouseEvent(event){return Boolean(event&&$.inArray(event.type,MOUSE_EVENTS)>-1&&typeof event.pageX==="number")}function initTracking(){if(!session.mouseTrackingActive){session.mouseTrackingActive=true;getViewportDimensions();$(getViewportDimensions);$document.on("mousemove"+EVENT_NAMESPACE,trackMouse);$window.on("resize"+EVENT_NAMESPACE,trackResize);$window.on("scroll"+EVENT_NAMESPACE,trackScroll)}}function getViewportDimensions(){session.scrollLeft=$window.scrollLeft();session.scrollTop=$window.scrollTop();session.windowWidth=$window.width();session.windowHeight=$window.height()}function trackResize(){session.windowWidth=$window.width();session.windowHeight=$window.height()}function trackScroll(){var x=$window.scrollLeft(),y=$window.scrollTop();if(x!==session.scrollLeft){session.currentX+=x-session.scrollLeft;session.scrollLeft=x}if(y!==session.scrollTop){session.currentY+=y-session.scrollTop;session.scrollTop=y}}function trackMouse(event){session.currentX=event.pageX;session.currentY=event.pageY}function isMouseOver(element){var elementPosition=element.offset(),elementBox=element[0].getBoundingClientRect(),elementWidth=elementBox.right-elementBox.left,elementHeight=elementBox.bottom-elementBox.top;return session.currentX>=elementPosition.left&&session.currentX<=elementPosition.left+elementWidth&&session.currentY>=elementPosition.top&&session.currentY<=elementPosition.top+elementHeight}function getTooltipContent(element){var tipText=element.data(DATA_POWERTIP),tipObject=element.data(DATA_POWERTIPJQ),tipTarget=element.data(DATA_POWERTIPTARGET),targetElement,content;if(tipText){if($.isFunction(tipText)){tipText=tipText.call(element[0])}content=tipText}else if(tipObject){if($.isFunction(tipObject)){tipObject=tipObject.call(element[0])}if(tipObject.length>0){content=tipObject.clone(true,true)}}else if(tipTarget){targetElement=$("#"+tipTarget);if(targetElement.length>0){content=targetElement.html()}}return content}function getViewportCollisions(coords,elementWidth,elementHeight){var viewportTop=session.scrollTop,viewportLeft=session.scrollLeft,viewportBottom=viewportTop+session.windowHeight,viewportRight=viewportLeft+session.windowWidth,collisions=Collision.none;if(coords.topviewportBottom||Math.abs(coords.bottom-session.windowHeight)>viewportBottom){collisions|=Collision.bottom}if(coords.leftviewportRight){collisions|=Collision.left}if(coords.left+elementWidth>viewportRight||coords.right1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery);/*! SmartMenus jQuery Plugin - v1.1.0 - September 17, 2017 + * http://www.smartmenus.org/ + * Copyright Vasil Dinkov, Vadikom Web Ltd. http://vadikom.com; Licensed MIT */(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):"object"==typeof module&&"object"==typeof module.exports?module.exports=t(require("jquery")):t(jQuery)})(function($){function initMouseDetection(t){var e=".smartmenus_mouse";if(mouseDetectionEnabled||t)mouseDetectionEnabled&&t&&($(document).off(e),mouseDetectionEnabled=!1);else{var i=!0,s=null,o={mousemove:function(t){var e={x:t.pageX,y:t.pageY,timeStamp:(new Date).getTime()};if(s){var o=Math.abs(s.x-e.x),a=Math.abs(s.y-e.y);if((o>0||a>0)&&2>=o&&2>=a&&300>=e.timeStamp-s.timeStamp&&(mouse=!0,i)){var n=$(t.target).closest("a");n.is("a")&&$.each(menuTrees,function(){return $.contains(this.$root[0],n[0])?(this.itemEnter({currentTarget:n[0]}),!1):void 0}),i=!1}}s=e}};o[touchEvents?"touchstart":"pointerover pointermove pointerout MSPointerOver MSPointerMove MSPointerOut"]=function(t){isTouchEvent(t.originalEvent)&&(mouse=!1)},$(document).on(getEventsNS(o,e)),mouseDetectionEnabled=!0}}function isTouchEvent(t){return!/^(4|mouse)$/.test(t.pointerType)}function getEventsNS(t,e){e||(e="");var i={};for(var s in t)i[s.split(" ").join(e+" ")+e]=t[s];return i}var menuTrees=[],mouse=!1,touchEvents="ontouchstart"in window,mouseDetectionEnabled=!1,requestAnimationFrame=window.requestAnimationFrame||function(t){return setTimeout(t,1e3/60)},cancelAnimationFrame=window.cancelAnimationFrame||function(t){clearTimeout(t)},canAnimate=!!$.fn.animate;return $.SmartMenus=function(t,e){this.$root=$(t),this.opts=e,this.rootId="",this.accessIdPrefix="",this.$subArrow=null,this.activatedItems=[],this.visibleSubMenus=[],this.showTimeout=0,this.hideTimeout=0,this.scrollTimeout=0,this.clickActivated=!1,this.focusActivated=!1,this.zIndexInc=0,this.idInc=0,this.$firstLink=null,this.$firstSub=null,this.disabled=!1,this.$disableOverlay=null,this.$touchScrollingSub=null,this.cssTransforms3d="perspective"in t.style||"webkitPerspective"in t.style,this.wasCollapsible=!1,this.init()},$.extend($.SmartMenus,{hideAll:function(){$.each(menuTrees,function(){this.menuHideAll()})},destroy:function(){for(;menuTrees.length;)menuTrees[0].destroy();initMouseDetection(!0)},prototype:{init:function(t){var e=this;if(!t){menuTrees.push(this),this.rootId=((new Date).getTime()+Math.random()+"").replace(/\D/g,""),this.accessIdPrefix="sm-"+this.rootId+"-",this.$root.hasClass("sm-rtl")&&(this.opts.rightToLeftSubMenus=!0);var i=".smartmenus";this.$root.data("smartmenus",this).attr("data-smartmenus-id",this.rootId).dataSM("level",1).on(getEventsNS({"mouseover focusin":$.proxy(this.rootOver,this),"mouseout focusout":$.proxy(this.rootOut,this),keydown:$.proxy(this.rootKeyDown,this)},i)).on(getEventsNS({mouseenter:$.proxy(this.itemEnter,this),mouseleave:$.proxy(this.itemLeave,this),mousedown:$.proxy(this.itemDown,this),focus:$.proxy(this.itemFocus,this),blur:$.proxy(this.itemBlur,this),click:$.proxy(this.itemClick,this)},i),"a"),i+=this.rootId,this.opts.hideOnClick&&$(document).on(getEventsNS({touchstart:$.proxy(this.docTouchStart,this),touchmove:$.proxy(this.docTouchMove,this),touchend:$.proxy(this.docTouchEnd,this),click:$.proxy(this.docClick,this)},i)),$(window).on(getEventsNS({"resize orientationchange":$.proxy(this.winResize,this)},i)),this.opts.subIndicators&&(this.$subArrow=$("").addClass("sub-arrow"),this.opts.subIndicatorsText&&this.$subArrow.html(this.opts.subIndicatorsText)),initMouseDetection()}if(this.$firstSub=this.$root.find("ul").each(function(){e.menuInit($(this))}).eq(0),this.$firstLink=this.$root.find("a").eq(0),this.opts.markCurrentItem){var s=/(index|default)\.[^#\?\/]*/i,o=/#.*/,a=window.location.href.replace(s,""),n=a.replace(o,"");this.$root.find("a").each(function(){var t=this.href.replace(s,""),i=$(this);(t==a||t==n)&&(i.addClass("current"),e.opts.markCurrentTree&&i.parentsUntil("[data-smartmenus-id]","ul").each(function(){$(this).dataSM("parent-a").addClass("current")}))})}this.wasCollapsible=this.isCollapsible()},destroy:function(t){if(!t){var e=".smartmenus";this.$root.removeData("smartmenus").removeAttr("data-smartmenus-id").removeDataSM("level").off(e),e+=this.rootId,$(document).off(e),$(window).off(e),this.opts.subIndicators&&(this.$subArrow=null)}this.menuHideAll();var i=this;this.$root.find("ul").each(function(){var t=$(this);t.dataSM("scroll-arrows")&&t.dataSM("scroll-arrows").remove(),t.dataSM("shown-before")&&((i.opts.subMenusMinWidth||i.opts.subMenusMaxWidth)&&t.css({width:"",minWidth:"",maxWidth:""}).removeClass("sm-nowrap"),t.dataSM("scroll-arrows")&&t.dataSM("scroll-arrows").remove(),t.css({zIndex:"",top:"",left:"",marginLeft:"",marginTop:"",display:""})),0==(t.attr("id")||"").indexOf(i.accessIdPrefix)&&t.removeAttr("id")}).removeDataSM("in-mega").removeDataSM("shown-before").removeDataSM("scroll-arrows").removeDataSM("parent-a").removeDataSM("level").removeDataSM("beforefirstshowfired").removeAttr("role").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeAttr("aria-expanded"),this.$root.find("a.has-submenu").each(function(){var t=$(this);0==t.attr("id").indexOf(i.accessIdPrefix)&&t.removeAttr("id")}).removeClass("has-submenu").removeDataSM("sub").removeAttr("aria-haspopup").removeAttr("aria-controls").removeAttr("aria-expanded").closest("li").removeDataSM("sub"),this.opts.subIndicators&&this.$root.find("span.sub-arrow").remove(),this.opts.markCurrentItem&&this.$root.find("a.current").removeClass("current"),t||(this.$root=null,this.$firstLink=null,this.$firstSub=null,this.$disableOverlay&&(this.$disableOverlay.remove(),this.$disableOverlay=null),menuTrees.splice($.inArray(this,menuTrees),1))},disable:function(t){if(!this.disabled){if(this.menuHideAll(),!t&&!this.opts.isPopup&&this.$root.is(":visible")){var e=this.$root.offset();this.$disableOverlay=$('
').css({position:"absolute",top:e.top,left:e.left,width:this.$root.outerWidth(),height:this.$root.outerHeight(),zIndex:this.getStartZIndex(!0),opacity:0}).appendTo(document.body)}this.disabled=!0}},docClick:function(t){return this.$touchScrollingSub?(this.$touchScrollingSub=null,void 0):((this.visibleSubMenus.length&&!$.contains(this.$root[0],t.target)||$(t.target).closest("a").length)&&this.menuHideAll(),void 0)},docTouchEnd:function(){if(this.lastTouch){if(!(!this.visibleSubMenus.length||void 0!==this.lastTouch.x2&&this.lastTouch.x1!=this.lastTouch.x2||void 0!==this.lastTouch.y2&&this.lastTouch.y1!=this.lastTouch.y2||this.lastTouch.target&&$.contains(this.$root[0],this.lastTouch.target))){this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0);var t=this;this.hideTimeout=setTimeout(function(){t.menuHideAll()},350)}this.lastTouch=null}},docTouchMove:function(t){if(this.lastTouch){var e=t.originalEvent.touches[0];this.lastTouch.x2=e.pageX,this.lastTouch.y2=e.pageY}},docTouchStart:function(t){var e=t.originalEvent.touches[0];this.lastTouch={x1:e.pageX,y1:e.pageY,target:e.target}},enable:function(){this.disabled&&(this.$disableOverlay&&(this.$disableOverlay.remove(),this.$disableOverlay=null),this.disabled=!1)},getClosestMenu:function(t){for(var e=$(t).closest("ul");e.dataSM("in-mega");)e=e.parent().closest("ul");return e[0]||null},getHeight:function(t){return this.getOffset(t,!0)},getOffset:function(t,e){var i;"none"==t.css("display")&&(i={position:t[0].style.position,visibility:t[0].style.visibility},t.css({position:"absolute",visibility:"hidden"}).show());var s=t[0].getBoundingClientRect&&t[0].getBoundingClientRect(),o=s&&(e?s.height||s.bottom-s.top:s.width||s.right-s.left);return o||0===o||(o=e?t[0].offsetHeight:t[0].offsetWidth),i&&t.hide().css(i),o},getStartZIndex:function(t){var e=parseInt(this[t?"$root":"$firstSub"].css("z-index"));return!t&&isNaN(e)&&(e=parseInt(this.$root.css("z-index"))),isNaN(e)?1:e},getTouchPoint:function(t){return t.touches&&t.touches[0]||t.changedTouches&&t.changedTouches[0]||t},getViewport:function(t){var e=t?"Height":"Width",i=document.documentElement["client"+e],s=window["inner"+e];return s&&(i=Math.min(i,s)),i},getViewportHeight:function(){return this.getViewport(!0)},getViewportWidth:function(){return this.getViewport()},getWidth:function(t){return this.getOffset(t)},handleEvents:function(){return!this.disabled&&this.isCSSOn()},handleItemEvents:function(t){return this.handleEvents()&&!this.isLinkInMegaMenu(t)},isCollapsible:function(){return"static"==this.$firstSub.css("position")},isCSSOn:function(){return"inline"!=this.$firstLink.css("display")},isFixed:function(){var t="fixed"==this.$root.css("position");return t||this.$root.parentsUntil("body").each(function(){return"fixed"==$(this).css("position")?(t=!0,!1):void 0}),t},isLinkInMegaMenu:function(t){return $(this.getClosestMenu(t[0])).hasClass("mega-menu")},isTouchMode:function(){return!mouse||this.opts.noMouseOver||this.isCollapsible()},itemActivate:function(t,e){var i=t.closest("ul"),s=i.dataSM("level");if(s>1&&(!this.activatedItems[s-2]||this.activatedItems[s-2][0]!=i.dataSM("parent-a")[0])){var o=this;$(i.parentsUntil("[data-smartmenus-id]","ul").get().reverse()).add(i).each(function(){o.itemActivate($(this).dataSM("parent-a"))})}if((!this.isCollapsible()||e)&&this.menuHideSubMenus(this.activatedItems[s-1]&&this.activatedItems[s-1][0]==t[0]?s:s-1),this.activatedItems[s-1]=t,this.$root.triggerHandler("activate.smapi",t[0])!==!1){var a=t.dataSM("sub");a&&(this.isTouchMode()||!this.opts.showOnClick||this.clickActivated)&&this.menuShow(a)}},itemBlur:function(t){var e=$(t.currentTarget);this.handleItemEvents(e)&&this.$root.triggerHandler("blur.smapi",e[0])},itemClick:function(t){var e=$(t.currentTarget);if(this.handleItemEvents(e)){if(this.$touchScrollingSub&&this.$touchScrollingSub[0]==e.closest("ul")[0])return this.$touchScrollingSub=null,t.stopPropagation(),!1;if(this.$root.triggerHandler("click.smapi",e[0])===!1)return!1;var i=$(t.target).is(".sub-arrow"),s=e.dataSM("sub"),o=s?2==s.dataSM("level"):!1,a=this.isCollapsible(),n=/toggle$/.test(this.opts.collapsibleBehavior),r=/link$/.test(this.opts.collapsibleBehavior),h=/^accordion/.test(this.opts.collapsibleBehavior);if(s&&!s.is(":visible")){if((!r||!a||i)&&(this.opts.showOnClick&&o&&(this.clickActivated=!0),this.itemActivate(e,h),s.is(":visible")))return this.focusActivated=!0,!1}else if(a&&(n||i))return this.itemActivate(e,h),this.menuHide(s),n&&(this.focusActivated=!1),!1;return this.opts.showOnClick&&o||e.hasClass("disabled")||this.$root.triggerHandler("select.smapi",e[0])===!1?!1:void 0}},itemDown:function(t){var e=$(t.currentTarget);this.handleItemEvents(e)&&e.dataSM("mousedown",!0)},itemEnter:function(t){var e=$(t.currentTarget);if(this.handleItemEvents(e)){if(!this.isTouchMode()){this.showTimeout&&(clearTimeout(this.showTimeout),this.showTimeout=0);var i=this;this.showTimeout=setTimeout(function(){i.itemActivate(e)},this.opts.showOnClick&&1==e.closest("ul").dataSM("level")?1:this.opts.showTimeout)}this.$root.triggerHandler("mouseenter.smapi",e[0])}},itemFocus:function(t){var e=$(t.currentTarget);this.handleItemEvents(e)&&(!this.focusActivated||this.isTouchMode()&&e.dataSM("mousedown")||this.activatedItems.length&&this.activatedItems[this.activatedItems.length-1][0]==e[0]||this.itemActivate(e,!0),this.$root.triggerHandler("focus.smapi",e[0]))},itemLeave:function(t){var e=$(t.currentTarget);this.handleItemEvents(e)&&(this.isTouchMode()||(e[0].blur(),this.showTimeout&&(clearTimeout(this.showTimeout),this.showTimeout=0)),e.removeDataSM("mousedown"),this.$root.triggerHandler("mouseleave.smapi",e[0]))},menuHide:function(t){if(this.$root.triggerHandler("beforehide.smapi",t[0])!==!1&&(canAnimate&&t.stop(!0,!0),"none"!=t.css("display"))){var e=function(){t.css("z-index","")};this.isCollapsible()?canAnimate&&this.opts.collapsibleHideFunction?this.opts.collapsibleHideFunction.call(this,t,e):t.hide(this.opts.collapsibleHideDuration,e):canAnimate&&this.opts.hideFunction?this.opts.hideFunction.call(this,t,e):t.hide(this.opts.hideDuration,e),t.dataSM("scroll")&&(this.menuScrollStop(t),t.css({"touch-action":"","-ms-touch-action":"","-webkit-transform":"",transform:""}).off(".smartmenus_scroll").removeDataSM("scroll").dataSM("scroll-arrows").hide()),t.dataSM("parent-a").removeClass("highlighted").attr("aria-expanded","false"),t.attr({"aria-expanded":"false","aria-hidden":"true"});var i=t.dataSM("level");this.activatedItems.splice(i-1,1),this.visibleSubMenus.splice($.inArray(t,this.visibleSubMenus),1),this.$root.triggerHandler("hide.smapi",t[0])}},menuHideAll:function(){this.showTimeout&&(clearTimeout(this.showTimeout),this.showTimeout=0);for(var t=this.opts.isPopup?1:0,e=this.visibleSubMenus.length-1;e>=t;e--)this.menuHide(this.visibleSubMenus[e]);this.opts.isPopup&&(canAnimate&&this.$root.stop(!0,!0),this.$root.is(":visible")&&(canAnimate&&this.opts.hideFunction?this.opts.hideFunction.call(this,this.$root):this.$root.hide(this.opts.hideDuration))),this.activatedItems=[],this.visibleSubMenus=[],this.clickActivated=!1,this.focusActivated=!1,this.zIndexInc=0,this.$root.triggerHandler("hideAll.smapi")},menuHideSubMenus:function(t){for(var e=this.activatedItems.length-1;e>=t;e--){var i=this.activatedItems[e].dataSM("sub");i&&this.menuHide(i)}},menuInit:function(t){if(!t.dataSM("in-mega")){t.hasClass("mega-menu")&&t.find("ul").dataSM("in-mega",!0);for(var e=2,i=t[0];(i=i.parentNode.parentNode)!=this.$root[0];)e++;var s=t.prevAll("a").eq(-1);s.length||(s=t.prevAll().find("a").eq(-1)),s.addClass("has-submenu").dataSM("sub",t),t.dataSM("parent-a",s).dataSM("level",e).parent().dataSM("sub",t);var o=s.attr("id")||this.accessIdPrefix+ ++this.idInc,a=t.attr("id")||this.accessIdPrefix+ ++this.idInc;s.attr({id:o,"aria-haspopup":"true","aria-controls":a,"aria-expanded":"false"}),t.attr({id:a,role:"group","aria-hidden":"true","aria-labelledby":o,"aria-expanded":"false"}),this.opts.subIndicators&&s[this.opts.subIndicatorsPos](this.$subArrow.clone())}},menuPosition:function(t){var e,i,s=t.dataSM("parent-a"),o=s.closest("li"),a=o.parent(),n=t.dataSM("level"),r=this.getWidth(t),h=this.getHeight(t),u=s.offset(),l=u.left,c=u.top,d=this.getWidth(s),m=this.getHeight(s),p=$(window),f=p.scrollLeft(),v=p.scrollTop(),b=this.getViewportWidth(),S=this.getViewportHeight(),g=a.parent().is("[data-sm-horizontal-sub]")||2==n&&!a.hasClass("sm-vertical"),M=this.opts.rightToLeftSubMenus&&!o.is("[data-sm-reverse]")||!this.opts.rightToLeftSubMenus&&o.is("[data-sm-reverse]"),w=2==n?this.opts.mainMenuSubOffsetX:this.opts.subMenusSubOffsetX,T=2==n?this.opts.mainMenuSubOffsetY:this.opts.subMenusSubOffsetY;if(g?(e=M?d-r-w:w,i=this.opts.bottomToTopSubMenus?-h-T:m+T):(e=M?w-r:d-w,i=this.opts.bottomToTopSubMenus?m-T-h:T),this.opts.keepInViewport){var y=l+e,I=c+i;if(M&&f>y?e=g?f-y+e:d-w:!M&&y+r>f+b&&(e=g?f+b-r-y+e:w-r),g||(S>h&&I+h>v+S?i+=v+S-h-I:(h>=S||v>I)&&(i+=v-I)),g&&(I+h>v+S+.49||v>I)||!g&&h>S+.49){var x=this;t.dataSM("scroll-arrows")||t.dataSM("scroll-arrows",$([$('')[0],$('')[0]]).on({mouseenter:function(){t.dataSM("scroll").up=$(this).hasClass("scroll-up"),x.menuScroll(t)},mouseleave:function(e){x.menuScrollStop(t),x.menuScrollOut(t,e)},"mousewheel DOMMouseScroll":function(t){t.preventDefault()}}).insertAfter(t));var A=".smartmenus_scroll";if(t.dataSM("scroll",{y:this.cssTransforms3d?0:i-m,step:1,itemH:m,subH:h,arrowDownH:this.getHeight(t.dataSM("scroll-arrows").eq(1))}).on(getEventsNS({mouseover:function(e){x.menuScrollOver(t,e)},mouseout:function(e){x.menuScrollOut(t,e)},"mousewheel DOMMouseScroll":function(e){x.menuScrollMousewheel(t,e)}},A)).dataSM("scroll-arrows").css({top:"auto",left:"0",marginLeft:e+(parseInt(t.css("border-left-width"))||0),width:r-(parseInt(t.css("border-left-width"))||0)-(parseInt(t.css("border-right-width"))||0),zIndex:t.css("z-index")}).eq(g&&this.opts.bottomToTopSubMenus?0:1).show(),this.isFixed()){var C={};C[touchEvents?"touchstart touchmove touchend":"pointerdown pointermove pointerup MSPointerDown MSPointerMove MSPointerUp"]=function(e){x.menuScrollTouch(t,e)},t.css({"touch-action":"none","-ms-touch-action":"none"}).on(getEventsNS(C,A))}}}t.css({top:"auto",left:"0",marginLeft:e,marginTop:i-m})},menuScroll:function(t,e,i){var s,o=t.dataSM("scroll"),a=t.dataSM("scroll-arrows"),n=o.up?o.upEnd:o.downEnd;if(!e&&o.momentum){if(o.momentum*=.92,s=o.momentum,.5>s)return this.menuScrollStop(t),void 0}else s=i||(e||!this.opts.scrollAccelerate?this.opts.scrollStep:Math.floor(o.step));var r=t.dataSM("level");if(this.activatedItems[r-1]&&this.activatedItems[r-1].dataSM("sub")&&this.activatedItems[r-1].dataSM("sub").is(":visible")&&this.menuHideSubMenus(r-1),o.y=o.up&&o.y>=n||!o.up&&n>=o.y?o.y:Math.abs(n-o.y)>s?o.y+(o.up?s:-s):n,t.css(this.cssTransforms3d?{"-webkit-transform":"translate3d(0, "+o.y+"px, 0)",transform:"translate3d(0, "+o.y+"px, 0)"}:{marginTop:o.y}),mouse&&(o.up&&o.y>o.downEnd||!o.up&&o.y0;t.dataSM("scroll-arrows").eq(i?0:1).is(":visible")&&(t.dataSM("scroll").up=i,this.menuScroll(t,!0))}e.preventDefault()},menuScrollOut:function(t,e){mouse&&(/^scroll-(up|down)/.test((e.relatedTarget||"").className)||(t[0]==e.relatedTarget||$.contains(t[0],e.relatedTarget))&&this.getClosestMenu(e.relatedTarget)==t[0]||t.dataSM("scroll-arrows").css("visibility","hidden"))},menuScrollOver:function(t,e){if(mouse&&!/^scroll-(up|down)/.test(e.target.className)&&this.getClosestMenu(e.target)==t[0]){this.menuScrollRefreshData(t);var i=t.dataSM("scroll"),s=$(window).scrollTop()-t.dataSM("parent-a").offset().top-i.itemH;t.dataSM("scroll-arrows").eq(0).css("margin-top",s).end().eq(1).css("margin-top",s+this.getViewportHeight()-i.arrowDownH).end().css("visibility","visible")}},menuScrollRefreshData:function(t){var e=t.dataSM("scroll"),i=$(window).scrollTop()-t.dataSM("parent-a").offset().top-e.itemH;this.cssTransforms3d&&(i=-(parseFloat(t.css("margin-top"))-i)),$.extend(e,{upEnd:i,downEnd:i+this.getViewportHeight()-e.subH})},menuScrollStop:function(t){return this.scrollTimeout?(cancelAnimationFrame(this.scrollTimeout),this.scrollTimeout=0,t.dataSM("scroll").step=1,!0):void 0},menuScrollTouch:function(t,e){if(e=e.originalEvent,isTouchEvent(e)){var i=this.getTouchPoint(e);if(this.getClosestMenu(i.target)==t[0]){var s=t.dataSM("scroll");if(/(start|down)$/i.test(e.type))this.menuScrollStop(t)?(e.preventDefault(),this.$touchScrollingSub=t):this.$touchScrollingSub=null,this.menuScrollRefreshData(t),$.extend(s,{touchStartY:i.pageY,touchStartTime:e.timeStamp});else if(/move$/i.test(e.type)){var o=void 0!==s.touchY?s.touchY:s.touchStartY;if(void 0!==o&&o!=i.pageY){this.$touchScrollingSub=t;var a=i.pageY>o;void 0!==s.up&&s.up!=a&&$.extend(s,{touchStartY:i.pageY,touchStartTime:e.timeStamp}),$.extend(s,{up:a,touchY:i.pageY}),this.menuScroll(t,!0,Math.abs(i.pageY-o))}e.preventDefault()}else void 0!==s.touchY&&((s.momentum=15*Math.pow(Math.abs(i.pageY-s.touchStartY)/(e.timeStamp-s.touchStartTime),2))&&(this.menuScrollStop(t),this.menuScroll(t),e.preventDefault()),delete s.touchY)}}},menuShow:function(t){if((t.dataSM("beforefirstshowfired")||(t.dataSM("beforefirstshowfired",!0),this.$root.triggerHandler("beforefirstshow.smapi",t[0])!==!1))&&this.$root.triggerHandler("beforeshow.smapi",t[0])!==!1&&(t.dataSM("shown-before",!0),canAnimate&&t.stop(!0,!0),!t.is(":visible"))){var e=t.dataSM("parent-a"),i=this.isCollapsible();if((this.opts.keepHighlighted||i)&&e.addClass("highlighted"),i)t.removeClass("sm-nowrap").css({zIndex:"",width:"auto",minWidth:"",maxWidth:"",top:"",left:"",marginLeft:"",marginTop:""});else{if(t.css("z-index",this.zIndexInc=(this.zIndexInc||this.getStartZIndex())+1),(this.opts.subMenusMinWidth||this.opts.subMenusMaxWidth)&&(t.css({width:"auto",minWidth:"",maxWidth:""}).addClass("sm-nowrap"),this.opts.subMenusMinWidth&&t.css("min-width",this.opts.subMenusMinWidth),this.opts.subMenusMaxWidth)){var s=this.getWidth(t);t.css("max-width",this.opts.subMenusMaxWidth),s>this.getWidth(t)&&t.removeClass("sm-nowrap").css("width",this.opts.subMenusMaxWidth)}this.menuPosition(t)}var o=function(){t.css("overflow","")};i?canAnimate&&this.opts.collapsibleShowFunction?this.opts.collapsibleShowFunction.call(this,t,o):t.show(this.opts.collapsibleShowDuration,o):canAnimate&&this.opts.showFunction?this.opts.showFunction.call(this,t,o):t.show(this.opts.showDuration,o),e.attr("aria-expanded","true"),t.attr({"aria-expanded":"true","aria-hidden":"false"}),this.visibleSubMenus.push(t),this.$root.triggerHandler("show.smapi",t[0])}},popupHide:function(t){this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0);var e=this;this.hideTimeout=setTimeout(function(){e.menuHideAll()},t?1:this.opts.hideTimeout)},popupShow:function(t,e){if(!this.opts.isPopup)return alert('SmartMenus jQuery Error:\n\nIf you want to show this menu via the "popupShow" method, set the isPopup:true option.'),void 0;if(this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0),this.$root.dataSM("shown-before",!0),canAnimate&&this.$root.stop(!0,!0),!this.$root.is(":visible")){this.$root.css({left:t,top:e});var i=this,s=function(){i.$root.css("overflow","")};canAnimate&&this.opts.showFunction?this.opts.showFunction.call(this,this.$root,s):this.$root.show(this.opts.showDuration,s),this.visibleSubMenus[0]=this.$root}},refresh:function(){this.destroy(!0),this.init(!0)},rootKeyDown:function(t){if(this.handleEvents())switch(t.keyCode){case 27:var e=this.activatedItems[0];if(e){this.menuHideAll(),e[0].focus();var i=e.dataSM("sub");i&&this.menuHide(i)}break;case 32:var s=$(t.target);if(s.is("a")&&this.handleItemEvents(s)){var i=s.dataSM("sub");i&&!i.is(":visible")&&(this.itemClick({currentTarget:t.target}),t.preventDefault())}}},rootOut:function(t){if(this.handleEvents()&&!this.isTouchMode()&&t.target!=this.$root[0]&&(this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0),!this.opts.showOnClick||!this.opts.hideOnClick)){var e=this;this.hideTimeout=setTimeout(function(){e.menuHideAll()},this.opts.hideTimeout)}},rootOver:function(t){this.handleEvents()&&!this.isTouchMode()&&t.target!=this.$root[0]&&this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0)},winResize:function(t){if(this.handleEvents()){if(!("onorientationchange"in window)||"orientationchange"==t.type){var e=this.isCollapsible();this.wasCollapsible&&e||(this.activatedItems.length&&this.activatedItems[this.activatedItems.length-1][0].blur(),this.menuHideAll()),this.wasCollapsible=e}}else if(this.$disableOverlay){var i=this.$root.offset();this.$disableOverlay.css({top:i.top,left:i.left,width:this.$root.outerWidth(),height:this.$root.outerHeight()})}}}}),$.fn.dataSM=function(t,e){return e?this.data(t+"_smartmenus",e):this.data(t+"_smartmenus")},$.fn.removeDataSM=function(t){return this.removeData(t+"_smartmenus")},$.fn.smartmenus=function(options){if("string"==typeof options){var args=arguments,method=options;return Array.prototype.shift.call(args),this.each(function(){var t=$(this).data("smartmenus");t&&t[method]&&t[method].apply(t,args)})}return this.each(function(){var dataOpts=$(this).data("sm-options")||null;if(dataOpts)try{dataOpts=eval("("+dataOpts+")")}catch(e){dataOpts=null,alert('ERROR\n\nSmartMenus jQuery init:\nInvalid "data-sm-options" attribute value syntax.')}new $.SmartMenus(this,$.extend({},$.fn.smartmenus.defaults,options,dataOpts))})},$.fn.smartmenus.defaults={isPopup:!1,mainMenuSubOffsetX:0,mainMenuSubOffsetY:0,subMenusSubOffsetX:0,subMenusSubOffsetY:0,subMenusMinWidth:"10em",subMenusMaxWidth:"20em",subIndicators:!0,subIndicatorsPos:"append",subIndicatorsText:"",scrollStep:30,scrollAccelerate:!0,showTimeout:250,hideTimeout:500,showDuration:0,showFunction:null,hideDuration:0,hideFunction:function(t,e){t.fadeOut(200,e)},collapsibleShowDuration:0,collapsibleShowFunction:function(t,e){t.slideDown(200,e)},collapsibleHideDuration:0,collapsibleHideFunction:function(t,e){t.slideUp(200,e)},showOnClick:!1,hideOnClick:!0,noMouseOver:!1,keepInViewport:!0,keepHighlighted:!0,markCurrentItem:!1,markCurrentTree:!0,rightToLeftSubMenus:!1,bottomToTopSubMenus:!1,collapsibleBehavior:"default"},$}); \ No newline at end of file diff --git a/doc/html/lib_2buffer_8c_source.html b/doc/html/lib_2buffer_8c_source.html new file mode 100644 index 0000000..2ac8180 --- /dev/null +++ b/doc/html/lib_2buffer_8c_source.html @@ -0,0 +1,406 @@ + + + + + + + +libfuse: lib/buffer.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
buffer.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2010 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Functions for dealing with `struct fuse_buf` and `struct
+
6 fuse_bufvec`.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file LGPL2.txt
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_lowlevel.h"
+
17#include <string.h>
+
18#include <unistd.h>
+
19#include <errno.h>
+
20#include <assert.h>
+
21
+
22size_t fuse_buf_size(const struct fuse_bufvec *bufv)
+
23{
+
24 size_t i;
+
25 size_t size = 0;
+
26
+
27 for (i = 0; i < bufv->count; i++) {
+
28 if (bufv->buf[i].size >= SIZE_MAX - size)
+
29 return SIZE_MAX;
+
30
+
31 size += bufv->buf[i].size;
+
32 }
+
33
+
34 return size;
+
35}
+
36
+
37static size_t min_size(size_t s1, size_t s2)
+
38{
+
39 return s1 < s2 ? s1 : s2;
+
40}
+
41
+
42static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off,
+
43 const struct fuse_buf *src, size_t src_off,
+
44 size_t len)
+
45{
+
46 ssize_t res = 0;
+
47 size_t copied = 0;
+
48
+
49 while (len) {
+
50 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
51 res = pwrite(dst->fd, (char *)src->mem + src_off, len,
+
52 dst->pos + dst_off);
+
53 } else {
+
54 res = write(dst->fd, (char *)src->mem + src_off, len);
+
55 }
+
56 if (res == -1) {
+
57 if (!copied)
+
58 return -errno;
+
59 break;
+
60 }
+
61 if (res == 0)
+
62 break;
+
63
+
64 copied += res;
+
65 if (!(dst->flags & FUSE_BUF_FD_RETRY))
+
66 break;
+
67
+
68 src_off += res;
+
69 dst_off += res;
+
70 len -= res;
+
71 }
+
72
+
73 return copied;
+
74}
+
75
+
76static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off,
+
77 const struct fuse_buf *src, size_t src_off,
+
78 size_t len)
+
79{
+
80 ssize_t res = 0;
+
81 size_t copied = 0;
+
82
+
83 while (len) {
+
84 if (src->flags & FUSE_BUF_FD_SEEK) {
+
85 res = pread(src->fd, (char *)dst->mem + dst_off, len,
+
86 src->pos + src_off);
+
87 } else {
+
88 res = read(src->fd, (char *)dst->mem + dst_off, len);
+
89 }
+
90 if (res == -1) {
+
91 if (!copied)
+
92 return -errno;
+
93 break;
+
94 }
+
95 if (res == 0)
+
96 break;
+
97
+
98 copied += res;
+
99 if (!(src->flags & FUSE_BUF_FD_RETRY))
+
100 break;
+
101
+
102 dst_off += res;
+
103 src_off += res;
+
104 len -= res;
+
105 }
+
106
+
107 return copied;
+
108}
+
109
+
110static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
+
111 const struct fuse_buf *src, size_t src_off,
+
112 size_t len)
+
113{
+
114 char buf[4096];
+
115 struct fuse_buf tmp = {
+
116 .size = sizeof(buf),
+
117 .flags = 0,
+
118 };
+
119 ssize_t res;
+
120 size_t copied = 0;
+
121
+
122 tmp.mem = buf;
+
123
+
124 while (len) {
+
125 size_t this_len = min_size(tmp.size, len);
+
126 size_t read_len;
+
127
+
128 res = fuse_buf_read(&tmp, 0, src, src_off, this_len);
+
129 if (res < 0) {
+
130 if (!copied)
+
131 return res;
+
132 break;
+
133 }
+
134 if (res == 0)
+
135 break;
+
136
+
137 read_len = res;
+
138 res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len);
+
139 if (res < 0) {
+
140 if (!copied)
+
141 return res;
+
142 break;
+
143 }
+
144 if (res == 0)
+
145 break;
+
146
+
147 copied += res;
+
148
+
149 if (res < this_len)
+
150 break;
+
151
+
152 dst_off += res;
+
153 src_off += res;
+
154 len -= res;
+
155 }
+
156
+
157 return copied;
+
158}
+
159
+
160#ifdef HAVE_SPLICE
+
161static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
162 const struct fuse_buf *src, size_t src_off,
+
163 size_t len, enum fuse_buf_copy_flags flags)
+
164{
+
165 int splice_flags = 0;
+
166 off_t *srcpos = NULL;
+
167 off_t *dstpos = NULL;
+
168 off_t srcpos_val;
+
169 off_t dstpos_val;
+
170 ssize_t res;
+
171 size_t copied = 0;
+
172
+ +
174 splice_flags |= SPLICE_F_MOVE;
+ +
176 splice_flags |= SPLICE_F_NONBLOCK;
+
177
+
178 if (src->flags & FUSE_BUF_FD_SEEK) {
+
179 srcpos_val = src->pos + src_off;
+
180 srcpos = &srcpos_val;
+
181 }
+
182 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
183 dstpos_val = dst->pos + dst_off;
+
184 dstpos = &dstpos_val;
+
185 }
+
186
+
187 while (len) {
+
188 res = splice(src->fd, srcpos, dst->fd, dstpos, len,
+
189 splice_flags);
+
190 if (res == -1) {
+
191 if (copied)
+
192 break;
+
193
+
194 if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE))
+
195 return -errno;
+
196
+
197 /* Maybe splice is not supported for this combination */
+
198 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off,
+
199 len);
+
200 }
+
201 if (res == 0)
+
202 break;
+
203
+
204 copied += res;
+
205 if (!(src->flags & FUSE_BUF_FD_RETRY) &&
+
206 !(dst->flags & FUSE_BUF_FD_RETRY)) {
+
207 break;
+
208 }
+
209
+
210 len -= res;
+
211 }
+
212
+
213 return copied;
+
214}
+
215#else
+
216static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
217 const struct fuse_buf *src, size_t src_off,
+
218 size_t len, enum fuse_buf_copy_flags flags)
+
219{
+
220 (void) flags;
+
221
+
222 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
223}
+
224#endif
+
225
+
226
+
227static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
+
228 const struct fuse_buf *src, size_t src_off,
+
229 size_t len, enum fuse_buf_copy_flags flags)
+
230{
+
231 int src_is_fd = src->flags & FUSE_BUF_IS_FD;
+
232 int dst_is_fd = dst->flags & FUSE_BUF_IS_FD;
+
233
+
234 if (!src_is_fd && !dst_is_fd) {
+
235 char *dstmem = (char *)dst->mem + dst_off;
+
236 char *srcmem = (char *)src->mem + src_off;
+
237
+
238 if (dstmem != srcmem) {
+
239 if (dstmem + len <= srcmem || srcmem + len <= dstmem)
+
240 memcpy(dstmem, srcmem, len);
+
241 else
+
242 memmove(dstmem, srcmem, len);
+
243 }
+
244
+
245 return len;
+
246 } else if (!src_is_fd) {
+
247 return fuse_buf_write(dst, dst_off, src, src_off, len);
+
248 } else if (!dst_is_fd) {
+
249 return fuse_buf_read(dst, dst_off, src, src_off, len);
+
250 } else if (flags & FUSE_BUF_NO_SPLICE) {
+
251 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
252 } else {
+
253 return fuse_buf_splice(dst, dst_off, src, src_off, len, flags);
+
254 }
+
255}
+
256
+
257static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv)
+
258{
+
259 if (bufv->idx < bufv->count)
+
260 return &bufv->buf[bufv->idx];
+
261 else
+
262 return NULL;
+
263}
+
264
+
265static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len)
+
266{
+
267 const struct fuse_buf *buf = fuse_bufvec_current(bufv);
+
268
+
269 if (!buf)
+
270 return 0;
+
271
+
272 bufv->off += len;
+
273 assert(bufv->off <= buf->size);
+
274 if (bufv->off == buf->size) {
+
275 assert(bufv->idx < bufv->count);
+
276 bufv->idx++;
+
277 if (bufv->idx == bufv->count)
+
278 return 0;
+
279 bufv->off = 0;
+
280 }
+
281 return 1;
+
282}
+
283
+
284ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
+ +
286{
+
287 size_t copied = 0;
+
288
+
289 if (dstv == srcv)
+
290 return fuse_buf_size(dstv);
+
291
+
292 for (;;) {
+
293 const struct fuse_buf *src = fuse_bufvec_current(srcv);
+
294 const struct fuse_buf *dst = fuse_bufvec_current(dstv);
+
295 size_t src_len;
+
296 size_t dst_len;
+
297 size_t len;
+
298 ssize_t res;
+
299
+
300 if (src == NULL || dst == NULL)
+
301 break;
+
302
+
303 src_len = src->size - srcv->off;
+
304 dst_len = dst->size - dstv->off;
+
305 len = min_size(src_len, dst_len);
+
306
+
307 res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
+
308 if (res < 0) {
+
309 if (!copied)
+
310 return res;
+
311 break;
+
312 }
+
313 copied += res;
+
314
+
315 if (!fuse_bufvec_advance(srcv, res) ||
+
316 !fuse_bufvec_advance(dstv, res))
+
317 break;
+
318
+
319 if (res < len)
+
320 break;
+
321 }
+
322
+
323 return copied;
+
324}
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+ +
enum fuse_buf_flags flags
+ +
off_t pos
+
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
+ + + + diff --git a/doc/html/lib_2compat_8c_source.html b/doc/html/lib_2compat_8c_source.html new file mode 100644 index 0000000..7019dd4 --- /dev/null +++ b/doc/html/lib_2compat_8c_source.html @@ -0,0 +1,148 @@ + + + + + + + +libfuse: lib/compat.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
compat.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file LGPL2.txt.
+
11*/
+
12
+
13/* Description:
+
14 This file has compatibility symbols for platforms that do not
+
15 support version symboling
+
16*/
+
17
+
18#include "libfuse_config.h"
+
19
+
20#include <stddef.h>
+
21#include <stdint.h>
+
22
+
23struct fuse_args;
+ + +
26struct fuse_session;
+
27struct fuse_custom_io;
+
28struct fuse_operations;
+ +
30
+
34#if (!defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
35/* With current libfuse fuse_parse_cmdline is a macro pointing to the
+
36 * versioned function. Here in this file we need to provide the ABI symbol
+
37 * and the redirecting macro is conflicting.
+
38 */
+
39#ifdef fuse_parse_cmdline
+
40#undef fuse_parse_cmdline
+
41#endif
+
42int fuse_parse_cmdline_30(struct fuse_args *args,
+
43 struct fuse_cmdline_opts *opts);
+
44int fuse_parse_cmdline(struct fuse_args *args,
+
45 struct fuse_cmdline_opts *opts);
+
46int fuse_parse_cmdline(struct fuse_args *args,
+
47 struct fuse_cmdline_opts *opts)
+
48{
+
49 return fuse_parse_cmdline_30(args, opts);
+
50}
+
51
+
52int fuse_session_custom_io_30(struct fuse_session *se,
+
53 const struct fuse_custom_io *io, int fd);
+
54int fuse_session_custom_io(struct fuse_session *se,
+
55 const struct fuse_custom_io *io, int fd);
+
56int fuse_session_custom_io(struct fuse_session *se,
+
57 const struct fuse_custom_io *io, int fd)
+
58
+
59{
+
60 return fuse_session_custom_io_30(se, io, fd);
+
61}
+
62
+
63#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
64
+
65int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
66 size_t op_size, void *user_data);
+
67int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
68 size_t op_size, void *user_data);
+
69int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
70 size_t op_size, void *user_data)
+
71{
+
72 return fuse_main_real_30(argc, argv, op, op_size, user_data);
+
73}
+
74
+
75struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
76 const struct fuse_lowlevel_ops *op,
+
77 size_t op_size, void *userdata);
+
78struct fuse_session *fuse_session_new(struct fuse_args *args,
+
79 const struct fuse_lowlevel_ops *op,
+
80 size_t op_size, void *userdata);
+
81struct fuse_session *fuse_session_new(struct fuse_args *args,
+
82 const struct fuse_lowlevel_ops *op,
+
83 size_t op_size, void *userdata)
+
84{
+
85 return fuse_session_new_30(args, op, op_size, userdata);
+
86}
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+ + + + +
+ + + + diff --git a/doc/html/lib_2cuse__lowlevel_8c_source.html b/doc/html/lib_2cuse__lowlevel_8c_source.html new file mode 100644 index 0000000..b234f40 --- /dev/null +++ b/doc/html/lib_2cuse__lowlevel_8c_source.html @@ -0,0 +1,458 @@ + + + + + + + +libfuse: lib/cuse_lowlevel.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_lowlevel.c
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file LGPL2.txt.
+
8*/
+
9
+
10#include "fuse_config.h"
+
11#include "cuse_lowlevel.h"
+
12#include "fuse_kernel.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15
+
16#include <stdio.h>
+
17#include <string.h>
+
18#include <stdlib.h>
+
19#include <stddef.h>
+
20#include <errno.h>
+
21#include <unistd.h>
+
22
+
23struct cuse_data {
+
24 struct cuse_lowlevel_ops clop;
+
25 unsigned max_read;
+
26 unsigned dev_major;
+
27 unsigned dev_minor;
+
28 unsigned flags;
+
29 unsigned dev_info_len;
+
30 char dev_info[];
+
31};
+
32
+
33static struct cuse_lowlevel_ops *req_clop(fuse_req_t req)
+
34{
+
35 return &req->se->cuse_data->clop;
+
36}
+
37
+
38static void cuse_fll_open(fuse_req_t req, fuse_ino_t ino,
+
39 struct fuse_file_info *fi)
+
40{
+
41 (void)ino;
+
42 req_clop(req)->open(req, fi);
+
43}
+
44
+
45static void cuse_fll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
46 off_t off, struct fuse_file_info *fi)
+
47{
+
48 (void)ino;
+
49 req_clop(req)->read(req, size, off, fi);
+
50}
+
51
+
52static void cuse_fll_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+
53 size_t size, off_t off, struct fuse_file_info *fi)
+
54{
+
55 (void)ino;
+
56 req_clop(req)->write(req, buf, size, off, fi);
+
57}
+
58
+
59static void cuse_fll_flush(fuse_req_t req, fuse_ino_t ino,
+
60 struct fuse_file_info *fi)
+
61{
+
62 (void)ino;
+
63 req_clop(req)->flush(req, fi);
+
64}
+
65
+
66static void cuse_fll_release(fuse_req_t req, fuse_ino_t ino,
+
67 struct fuse_file_info *fi)
+
68{
+
69 (void)ino;
+
70 req_clop(req)->release(req, fi);
+
71}
+
72
+
73static void cuse_fll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
74 struct fuse_file_info *fi)
+
75{
+
76 (void)ino;
+
77 req_clop(req)->fsync(req, datasync, fi);
+
78}
+
79
+
80static void cuse_fll_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg,
+
81 struct fuse_file_info *fi, unsigned int flags,
+
82 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
83{
+
84 (void)ino;
+
85 req_clop(req)->ioctl(req, cmd, arg, fi, flags, in_buf, in_bufsz,
+
86 out_bufsz);
+
87}
+
88
+
89static void cuse_fll_poll(fuse_req_t req, fuse_ino_t ino,
+
90 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
91{
+
92 (void)ino;
+
93 req_clop(req)->poll(req, fi, ph);
+
94}
+
95
+
96static size_t cuse_pack_info(int argc, const char **argv, char *buf)
+
97{
+
98 size_t size = 0;
+
99 int i;
+
100
+
101 for (i = 0; i < argc; i++) {
+
102 size_t len;
+
103
+
104 len = strlen(argv[i]) + 1;
+
105 size += len;
+
106 if (buf) {
+
107 memcpy(buf, argv[i], len);
+
108 buf += len;
+
109 }
+
110 }
+
111
+
112 return size;
+
113}
+
114
+
115static struct cuse_data *cuse_prep_data(const struct cuse_info *ci,
+
116 const struct cuse_lowlevel_ops *clop)
+
117{
+
118 struct cuse_data *cd;
+
119 size_t dev_info_len;
+
120
+
121 dev_info_len = cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv,
+
122 NULL);
+
123
+
124 if (dev_info_len > CUSE_INIT_INFO_MAX) {
+
125 fuse_log(FUSE_LOG_ERR, "cuse: dev_info (%zu) too large, limit=%u\n",
+
126 dev_info_len, CUSE_INIT_INFO_MAX);
+
127 return NULL;
+
128 }
+
129
+
130 cd = calloc(1, sizeof(*cd) + dev_info_len);
+
131 if (!cd) {
+
132 fuse_log(FUSE_LOG_ERR, "cuse: failed to allocate cuse_data\n");
+
133 return NULL;
+
134 }
+
135
+
136 memcpy(&cd->clop, clop, sizeof(cd->clop));
+
137 cd->max_read = 131072;
+
138 cd->dev_major = ci->dev_major;
+
139 cd->dev_minor = ci->dev_minor;
+
140 cd->dev_info_len = dev_info_len;
+
141 cd->flags = ci->flags;
+
142 cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info);
+
143
+
144 return cd;
+
145}
+
146
+
147struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
148 const struct cuse_info *ci,
+
149 const struct cuse_lowlevel_ops *clop,
+
150 void *userdata)
+
151{
+
152 struct fuse_lowlevel_ops lop;
+
153 struct cuse_data *cd;
+
154 struct fuse_session *se;
+
155
+
156 cd = cuse_prep_data(ci, clop);
+
157 if (!cd)
+
158 return NULL;
+
159
+
160 memset(&lop, 0, sizeof(lop));
+
161 lop.init = clop->init;
+
162 lop.destroy = clop->destroy;
+
163 lop.open = clop->open ? cuse_fll_open : NULL;
+
164 lop.read = clop->read ? cuse_fll_read : NULL;
+
165 lop.write = clop->write ? cuse_fll_write : NULL;
+
166 lop.flush = clop->flush ? cuse_fll_flush : NULL;
+
167 lop.release = clop->release ? cuse_fll_release : NULL;
+
168 lop.fsync = clop->fsync ? cuse_fll_fsync : NULL;
+
169 lop.ioctl = clop->ioctl ? cuse_fll_ioctl : NULL;
+
170 lop.poll = clop->poll ? cuse_fll_poll : NULL;
+
171
+
172 se = fuse_session_new(args, &lop, sizeof(lop), userdata);
+
173 if (!se) {
+
174 free(cd);
+
175 return NULL;
+
176 }
+
177 se->cuse_data = cd;
+
178
+
179 return se;
+
180}
+
181
+
182static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg,
+
183 char *dev_info, unsigned dev_info_len)
+
184{
+
185 struct iovec iov[3];
+
186
+
187 iov[1].iov_base = arg;
+
188 iov[1].iov_len = sizeof(struct cuse_init_out);
+
189 iov[2].iov_base = dev_info;
+
190 iov[2].iov_len = dev_info_len;
+
191
+
192 return fuse_send_reply_iov_nofree(req, 0, iov, 3);
+
193}
+
194
+
195void _cuse_lowlevel_init(fuse_req_t req, const fuse_ino_t nodeid,
+
196 const void *op_in, const void *req_payload)
+
197{
+
198 const struct fuse_init_in *arg = op_in;
+
199 (void)req_payload;
+
200 struct cuse_init_out outarg;
+
201 struct fuse_session *se = req->se;
+
202 struct cuse_data *cd = se->cuse_data;
+
203 size_t bufsize = se->bufsize;
+
204 struct cuse_lowlevel_ops *clop = req_clop(req);
+
205
+
206 (void) nodeid;
+
207 if (se->debug) {
+
208 fuse_log(FUSE_LOG_DEBUG, "CUSE_INIT: %u.%u\n", arg->major, arg->minor);
+
209 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
210 }
+
211 se->conn.proto_major = arg->major;
+
212 se->conn.proto_minor = arg->minor;
+
213
+
214 /* XXX This is not right.*/
+
215 se->conn.capable_ext = 0;
+
216 se->conn.want_ext = 0;
+
217
+
218 if (arg->major < 7) {
+
219 fuse_log(FUSE_LOG_ERR, "cuse: unsupported protocol version: %u.%u\n",
+
220 arg->major, arg->minor);
+
221 fuse_reply_err(req, EPROTO);
+
222 return;
+
223 }
+
224
+
225 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
226 fuse_log(FUSE_LOG_ERR, "cuse: warning: buffer size too small: %zu\n",
+
227 bufsize);
+
228 bufsize = FUSE_MIN_READ_BUFFER;
+
229 }
+
230
+
231 bufsize -= 4096;
+
232 if (bufsize < se->conn.max_write)
+
233 se->conn.max_write = bufsize;
+
234
+
235 se->got_init = 1;
+
236 if (se->op.init)
+
237 se->op.init(se->userdata, &se->conn);
+
238
+
239 memset(&outarg, 0, sizeof(outarg));
+
240 outarg.major = FUSE_KERNEL_VERSION;
+
241 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
242 outarg.flags = cd->flags;
+
243 outarg.max_read = cd->max_read;
+
244 outarg.max_write = se->conn.max_write;
+
245 outarg.dev_major = cd->dev_major;
+
246 outarg.dev_minor = cd->dev_minor;
+
247
+
248 if (se->debug) {
+
249 fuse_log(FUSE_LOG_DEBUG, " CUSE_INIT: %u.%u\n",
+
250 outarg.major, outarg.minor);
+
251 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
252 fuse_log(FUSE_LOG_DEBUG, " max_read=0x%08x\n", outarg.max_read);
+
253 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
254 fuse_log(FUSE_LOG_DEBUG, " dev_major=%u\n", outarg.dev_major);
+
255 fuse_log(FUSE_LOG_DEBUG, " dev_minor=%u\n", outarg.dev_minor);
+
256 fuse_log(FUSE_LOG_DEBUG, " dev_info: %.*s\n", cd->dev_info_len,
+
257 cd->dev_info);
+
258 }
+
259
+
260 cuse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len);
+
261
+
262 if (clop->init_done)
+
263 clop->init_done(se->userdata);
+
264
+
265 fuse_free_req(req);
+
266}
+
267
+
268void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
269{
+
270 _cuse_lowlevel_init(req, nodeid, inarg, NULL);
+
271}
+
272
+
273struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
274 const struct cuse_info *ci,
+
275 const struct cuse_lowlevel_ops *clop,
+
276 int *multithreaded, void *userdata)
+
277{
+
278 const char *devname = "/dev/cuse";
+
279 static const struct fuse_opt kill_subtype_opts[] = {
+ + +
282 };
+
283 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
284 struct fuse_session *se;
+
285 struct fuse_cmdline_opts opts;
+
286 int fd;
+
287 int res;
+
288
+
289 if (fuse_parse_cmdline(&args, &opts) == -1)
+
290 return NULL;
+
291 *multithreaded = !opts.singlethread;
+
292
+
293 /* Remove subtype= option */
+
294 res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL);
+
295 if (res == -1)
+
296 goto out1;
+
297
+
298 /*
+
299 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
300 * would ensue.
+
301 */
+
302 do {
+
303 fd = open("/dev/null", O_RDWR);
+
304 if (fd > 2)
+
305 close(fd);
+
306 } while (fd >= 0 && fd <= 2);
+
307
+
308 se = cuse_lowlevel_new(&args, ci, clop, userdata);
+
309 if (se == NULL)
+
310 goto out1;
+
311
+
312 fd = open(devname, O_RDWR);
+
313 if (fd == -1) {
+
314 if (errno == ENODEV || errno == ENOENT)
+
315 fuse_log(FUSE_LOG_ERR, "cuse: device not found, try 'modprobe cuse' first\n");
+
316 else
+
317 fuse_log(FUSE_LOG_ERR, "cuse: failed to open %s: %s\n",
+
318 devname, strerror(errno));
+
319 goto err_se;
+
320 }
+
321 se->fd = fd;
+
322
+
323 res = fuse_set_signal_handlers(se);
+
324 if (res == -1)
+
325 goto err_se;
+
326
+
327 res = fuse_daemonize(opts.foreground);
+
328 if (res == -1)
+
329 goto err_sig;
+
330
+
331 fuse_opt_free_args(&args);
+
332 return se;
+
333
+
334err_sig:
+ +
336err_se:
+ +
338out1:
+
339 free(opts.mountpoint);
+
340 fuse_opt_free_args(&args);
+
341 return NULL;
+
342}
+
343
+
344void cuse_lowlevel_teardown(struct fuse_session *se)
+
345{
+ + +
348}
+
349
+
350int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
351 const struct cuse_lowlevel_ops *clop, void *userdata)
+
352{
+
353 struct fuse_session *se;
+
354 int multithreaded;
+
355 int res;
+
356
+
357 se = cuse_lowlevel_setup(argc, argv, ci, clop, &multithreaded,
+
358 userdata);
+
359 if (se == NULL)
+
360 return 1;
+
361
+
362 if (multithreaded) {
+
363 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
364 res = fuse_session_loop_mt(se, config);
+
365 fuse_loop_cfg_destroy(config);
+
366 }
+
367 else
+
368 res = fuse_session_loop(se);
+
369
+
370 cuse_lowlevel_teardown(se);
+
371 if (res == -1)
+
372 return 1;
+
373
+
374 return 0;
+
375}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
uint64_t fuse_ino_t
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + + + +
+ + + + diff --git a/doc/html/lib_2fuse_8c_source.html b/doc/html/lib_2fuse_8c_source.html new file mode 100644 index 0000000..bd9edea --- /dev/null +++ b/doc/html/lib_2fuse_8c_source.html @@ -0,0 +1,5449 @@ + + + + + + + +libfuse: lib/fuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the high-level FUSE API on top of the low-level
+
6 API.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file LGPL2.txt
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13#include "fuse.h"
+
14#include <pthread.h>
+
15
+
16#include "fuse_config.h"
+
17#include "fuse_i.h"
+
18#include "fuse_lowlevel.h"
+
19#include "fuse_opt.h"
+
20#include "fuse_misc.h"
+
21#include "fuse_kernel.h"
+
22#include "util.h"
+
23
+
24#include <stdint.h>
+
25#include <stdio.h>
+
26#include <string.h>
+
27#include <stdlib.h>
+
28#include <stddef.h>
+
29#include <stdbool.h>
+
30#include <unistd.h>
+
31#include <time.h>
+
32#include <fcntl.h>
+
33#include <limits.h>
+
34#include <errno.h>
+
35#include <signal.h>
+
36#include <dlfcn.h>
+
37#include <assert.h>
+
38#include <poll.h>
+
39#include <sys/param.h>
+
40#include <sys/uio.h>
+
41#include <sys/time.h>
+
42#include <sys/mman.h>
+
43#include <sys/file.h>
+
44
+
45#define FUSE_NODE_SLAB 1
+
46
+
47#ifndef MAP_ANONYMOUS
+
48#undef FUSE_NODE_SLAB
+
49#endif
+
50
+
51#ifndef RENAME_EXCHANGE
+
52#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */
+
53#endif
+
54
+
55#define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1
+
56
+
57#define FUSE_UNKNOWN_INO 0xffffffff
+
58#define OFFSET_MAX 0x7fffffffffffffffLL
+
59
+
60#define NODE_TABLE_MIN_SIZE 8192
+
61
+
62struct fuse_fs {
+
63 struct fuse_operations op;
+
64 void *user_data;
+
65 int debug;
+
66};
+
67
+
68struct fusemod_so {
+
69 void *handle;
+
70 int ctr;
+
71};
+
72
+
73struct lock_queue_element {
+
74 struct lock_queue_element *next;
+
75 pthread_cond_t cond;
+
76 fuse_ino_t nodeid1;
+
77 const char *name1;
+
78 char **path1;
+
79 struct node **wnode1;
+
80 fuse_ino_t nodeid2;
+
81 const char *name2;
+
82 char **path2;
+
83 struct node **wnode2;
+
84 int err;
+
85 bool done : 1;
+
86};
+
87
+
88struct node_table {
+
89 struct node **array;
+
90 size_t use;
+
91 size_t size;
+
92 size_t split;
+
93};
+
94
+
95#define list_entry(ptr, type, member) \
+
96 container_of(ptr, type, member)
+
97
+
98struct list_head {
+
99 struct list_head *next;
+
100 struct list_head *prev;
+
101};
+
102
+
103struct node_slab {
+
104 struct list_head list; /* must be the first member */
+
105 struct list_head freelist;
+
106 int used;
+
107};
+
108
+
109struct fuse {
+
110 struct fuse_session *se;
+
111 struct node_table name_table;
+
112 struct node_table id_table;
+
113 struct list_head lru_table;
+
114 fuse_ino_t ctr;
+
115 unsigned int generation;
+
116 unsigned int hidectr;
+
117 pthread_mutex_t lock;
+
118 struct fuse_config conf;
+
119 int intr_installed;
+
120 struct fuse_fs *fs;
+
121 struct lock_queue_element *lockq;
+
122 int pagesize;
+
123 struct list_head partial_slabs;
+
124 struct list_head full_slabs;
+
125 pthread_t prune_thread;
+
126};
+
127
+
128struct lock {
+
129 int type;
+
130 off_t start;
+
131 off_t end;
+
132 pid_t pid;
+
133 uint64_t owner;
+
134 struct lock *next;
+
135};
+
136
+
137struct node {
+
138 struct node *name_next;
+
139 struct node *id_next;
+
140 fuse_ino_t nodeid;
+
141 unsigned int generation;
+
142 int refctr;
+
143 struct node *parent;
+
144 char *name;
+
145 uint64_t nlookup;
+
146 int open_count;
+
147 struct timespec stat_updated;
+
148 struct timespec mtime;
+
149 off_t size;
+
150 struct lock *locks;
+
151 unsigned int is_hidden : 1;
+
152 unsigned int cache_valid : 1;
+
153 int treelock;
+
154 char inline_name[32];
+
155};
+
156
+
157#define TREELOCK_WRITE -1
+
158#define TREELOCK_WAIT_OFFSET INT_MIN
+
159
+
160struct node_lru {
+
161 struct node node;
+
162 struct list_head lru;
+
163 struct timespec forget_time;
+
164};
+
165
+
166struct fuse_direntry {
+
167 struct stat stat;
+
168 enum fuse_fill_dir_flags flags;
+
169 char *name;
+
170 struct fuse_direntry *next;
+
171};
+
172
+
173struct fuse_dh {
+
174 pthread_mutex_t lock;
+
175 struct fuse *fuse;
+
176 fuse_req_t req;
+
177 char *contents;
+
178 struct fuse_direntry *first;
+
179 struct fuse_direntry **last;
+
180 unsigned len;
+
181 unsigned size;
+
182 unsigned needlen;
+
183 int filled;
+
184 uint64_t fh;
+
185 int error;
+
186 fuse_ino_t nodeid;
+
187};
+
188
+
189struct fuse_context_i {
+
190 struct fuse_context ctx;
+
191 fuse_req_t req;
+
192};
+
193
+
194/* Defined by FUSE_REGISTER_MODULE() in lib/modules/subdir.c and iconv.c. */
+
195extern fuse_module_factory_t fuse_module_subdir_factory;
+
196#ifdef HAVE_ICONV
+
197extern fuse_module_factory_t fuse_module_iconv_factory;
+
198#endif
+
199
+
200static pthread_key_t fuse_context_key;
+
201static pthread_mutex_t fuse_context_lock = PTHREAD_MUTEX_INITIALIZER;
+
202static int fuse_context_ref;
+
203static struct fuse_module *fuse_modules = NULL;
+
204
+
205static int fuse_register_module(const char *name,
+
206 fuse_module_factory_t factory,
+
207 struct fusemod_so *so)
+
208{
+
209 struct fuse_module *mod;
+
210
+
211 mod = calloc(1, sizeof(struct fuse_module));
+
212 if (!mod) {
+
213 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module\n");
+
214 return -1;
+
215 }
+
216 mod->name = strdup(name);
+
217 if (!mod->name) {
+
218 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module name\n");
+
219 free(mod);
+
220 return -1;
+
221 }
+
222 mod->factory = factory;
+
223 mod->ctr = 0;
+
224 mod->so = so;
+
225 if (mod->so)
+
226 mod->so->ctr++;
+
227 mod->next = fuse_modules;
+
228 fuse_modules = mod;
+
229
+
230 return 0;
+
231}
+
232
+
233static void fuse_unregister_module(struct fuse_module *m)
+
234{
+
235 struct fuse_module **mp;
+
236 for (mp = &fuse_modules; *mp; mp = &(*mp)->next) {
+
237 if (*mp == m) {
+
238 *mp = (*mp)->next;
+
239 break;
+
240 }
+
241 }
+
242 free(m->name);
+
243 free(m);
+
244}
+
245
+
246static int fuse_load_so_module(const char *module)
+
247{
+
248 int ret = -1;
+
249 char *tmp;
+
250 struct fusemod_so *so;
+
251 fuse_module_factory_t *factory;
+
252
+
253 tmp = malloc(strlen(module) + 64);
+
254 if (!tmp) {
+
255 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
256 return -1;
+
257 }
+
258 sprintf(tmp, "libfusemod_%s.so", module);
+
259 so = calloc(1, sizeof(struct fusemod_so));
+
260 if (!so) {
+
261 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module so\n");
+
262 goto out;
+
263 }
+
264
+
265 so->handle = dlopen(tmp, RTLD_NOW);
+
266 if (so->handle == NULL) {
+
267 fuse_log(FUSE_LOG_ERR, "fuse: dlopen(%s) failed: %s\n",
+
268 tmp, dlerror());
+
269 goto out_free_so;
+
270 }
+
271
+
272 sprintf(tmp, "fuse_module_%s_factory", module);
+
273 factory = (fuse_module_factory_t*)dlsym(so->handle, tmp);
+
274 if (factory == NULL) {
+
275 fuse_log(FUSE_LOG_ERR, "fuse: symbol <%s> not found in module: %s\n",
+
276 tmp, dlerror());
+
277 goto out_dlclose;
+
278 }
+
279 ret = fuse_register_module(module, *factory, so);
+
280 if (ret)
+
281 goto out_dlclose;
+
282
+
283out:
+
284 free(tmp);
+
285 return ret;
+
286
+
287out_dlclose:
+
288 dlclose(so->handle);
+
289out_free_so:
+
290 free(so);
+
291 goto out;
+
292}
+
293
+
294static struct fuse_module *fuse_find_module(const char *module)
+
295{
+
296 struct fuse_module *m;
+
297 for (m = fuse_modules; m; m = m->next) {
+
298 if (strcmp(module, m->name) == 0) {
+
299 m->ctr++;
+
300 break;
+
301 }
+
302 }
+
303 return m;
+
304}
+
305
+
306static struct fuse_module *fuse_get_module(const char *module)
+
307{
+
308 struct fuse_module *m;
+
309
+
310 pthread_mutex_lock(&fuse_context_lock);
+
311 m = fuse_find_module(module);
+
312 if (!m) {
+
313 int err = fuse_load_so_module(module);
+
314 if (!err)
+
315 m = fuse_find_module(module);
+
316 }
+
317 pthread_mutex_unlock(&fuse_context_lock);
+
318 return m;
+
319}
+
320
+
321static void fuse_put_module(struct fuse_module *m)
+
322{
+
323 pthread_mutex_lock(&fuse_context_lock);
+
324 if (m->so)
+
325 assert(m->ctr > 0);
+
326 /* Builtin modules may already have m->ctr == 0 */
+
327 if (m->ctr > 0)
+
328 m->ctr--;
+
329 if (!m->ctr && m->so) {
+
330 struct fusemod_so *so = m->so;
+
331 assert(so->ctr > 0);
+
332 so->ctr--;
+
333 if (!so->ctr) {
+
334 struct fuse_module **mp;
+
335 for (mp = &fuse_modules; *mp;) {
+
336 if ((*mp)->so == so)
+
337 fuse_unregister_module(*mp);
+
338 else
+
339 mp = &(*mp)->next;
+
340 }
+
341 dlclose(so->handle);
+
342 free(so);
+
343 }
+
344 } else if (!m->ctr) {
+
345 fuse_unregister_module(m);
+
346 }
+
347 pthread_mutex_unlock(&fuse_context_lock);
+
348}
+
349
+
350static void init_list_head(struct list_head *list)
+
351{
+
352 list->next = list;
+
353 list->prev = list;
+
354}
+
355
+
356static int list_empty(const struct list_head *head)
+
357{
+
358 return head->next == head;
+
359}
+
360
+
361static void list_add(struct list_head *new, struct list_head *prev,
+
362 struct list_head *next)
+
363{
+
364 next->prev = new;
+
365 new->next = next;
+
366 new->prev = prev;
+
367 prev->next = new;
+
368}
+
369
+
370static inline void list_add_head(struct list_head *new, struct list_head *head)
+
371{
+
372 list_add(new, head, head->next);
+
373}
+
374
+
375static inline void list_add_tail(struct list_head *new, struct list_head *head)
+
376{
+
377 list_add(new, head->prev, head);
+
378}
+
379
+
380static inline void list_del(struct list_head *entry)
+
381{
+
382 struct list_head *prev = entry->prev;
+
383 struct list_head *next = entry->next;
+
384
+
385 next->prev = prev;
+
386 prev->next = next;
+
387}
+
388
+
389static inline int lru_enabled(struct fuse *f)
+
390{
+
391 return f->conf.remember > 0;
+
392}
+
393
+
394static struct node_lru *node_lru(struct node *node)
+
395{
+
396 return (struct node_lru *) node;
+
397}
+
398
+
399static size_t get_node_size(struct fuse *f)
+
400{
+
401 if (lru_enabled(f))
+
402 return sizeof(struct node_lru);
+
403 else
+
404 return sizeof(struct node);
+
405}
+
406
+
407#ifdef FUSE_NODE_SLAB
+
408static struct node_slab *list_to_slab(struct list_head *head)
+
409{
+
410 return (struct node_slab *) head;
+
411}
+
412
+
413static struct node_slab *node_to_slab(struct fuse *f, struct node *node)
+
414{
+
415 return (struct node_slab *) (((uintptr_t) node) & ~((uintptr_t) f->pagesize - 1));
+
416}
+
417
+
418static int alloc_slab(struct fuse *f)
+
419{
+
420 void *mem;
+
421 struct node_slab *slab;
+
422 char *start;
+
423 size_t num;
+
424 size_t i;
+
425 size_t node_size = get_node_size(f);
+
426
+
427 mem = mmap(NULL, f->pagesize, PROT_READ | PROT_WRITE,
+
428 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
429
+
430 if (mem == MAP_FAILED)
+
431 return -1;
+
432
+
433 slab = mem;
+
434 init_list_head(&slab->freelist);
+
435 slab->used = 0;
+
436 num = (f->pagesize - sizeof(struct node_slab)) / node_size;
+
437
+
438 start = (char *) mem + f->pagesize - num * node_size;
+
439 for (i = 0; i < num; i++) {
+
440 struct list_head *n;
+
441
+
442 n = (struct list_head *) (start + i * node_size);
+
443 list_add_tail(n, &slab->freelist);
+
444 }
+
445 list_add_tail(&slab->list, &f->partial_slabs);
+
446
+
447 return 0;
+
448}
+
449
+
450static struct node *alloc_node(struct fuse *f)
+
451{
+
452 struct node_slab *slab;
+
453 struct list_head *node;
+
454
+
455 if (list_empty(&f->partial_slabs)) {
+
456 int res = alloc_slab(f);
+
457 if (res != 0)
+
458 return NULL;
+
459 }
+
460 slab = list_to_slab(f->partial_slabs.next);
+
461 slab->used++;
+
462 node = slab->freelist.next;
+
463 list_del(node);
+
464 if (list_empty(&slab->freelist)) {
+
465 list_del(&slab->list);
+
466 list_add_tail(&slab->list, &f->full_slabs);
+
467 }
+
468 memset(node, 0, sizeof(struct node));
+
469
+
470 return (struct node *) node;
+
471}
+
472
+
473static void free_slab(struct fuse *f, struct node_slab *slab)
+
474{
+
475 int res;
+
476
+
477 list_del(&slab->list);
+
478 res = munmap(slab, f->pagesize);
+
479 if (res == -1)
+
480 fuse_log(FUSE_LOG_WARNING, "fuse warning: munmap(%p) failed\n",
+
481 slab);
+
482}
+
483
+
484static void free_node_mem(struct fuse *f, struct node *node)
+
485{
+
486 struct node_slab *slab = node_to_slab(f, node);
+
487 struct list_head *n = (struct list_head *) node;
+
488
+
489 slab->used--;
+
490 if (slab->used) {
+
491 if (list_empty(&slab->freelist)) {
+
492 list_del(&slab->list);
+
493 list_add_tail(&slab->list, &f->partial_slabs);
+
494 }
+
495 list_add_head(n, &slab->freelist);
+
496 } else {
+
497 free_slab(f, slab);
+
498 }
+
499}
+
500#else
+
501static struct node *alloc_node(struct fuse *f)
+
502{
+
503 return (struct node *) calloc(1, get_node_size(f));
+
504}
+
505
+
506static void free_node_mem(struct fuse *f, struct node *node)
+
507{
+
508 (void) f;
+
509 free(node);
+
510}
+
511#endif
+
512
+
513static size_t id_hash(struct fuse *f, fuse_ino_t ino)
+
514{
+
515 uint64_t hash = ((uint32_t) ino * 2654435761U) % f->id_table.size;
+
516 uint64_t oldhash = hash % (f->id_table.size / 2);
+
517
+
518 if (oldhash >= f->id_table.split)
+
519 return oldhash;
+
520 else
+
521 return hash;
+
522}
+
523
+
524static struct node *get_node_nocheck(struct fuse *f, fuse_ino_t nodeid)
+
525{
+
526 size_t hash = id_hash(f, nodeid);
+
527 struct node *node;
+
528
+
529 for (node = f->id_table.array[hash]; node != NULL; node = node->id_next)
+
530 if (node->nodeid == nodeid)
+
531 return node;
+
532
+
533 return NULL;
+
534}
+
535
+
536static struct node *get_node(struct fuse *f, fuse_ino_t nodeid)
+
537{
+
538 struct node *node = get_node_nocheck(f, nodeid);
+
539 if (!node) {
+
540 fuse_log(FUSE_LOG_ERR, "fuse internal error: node %llu not found\n",
+
541 (unsigned long long) nodeid);
+
542 abort();
+
543 }
+
544 return node;
+
545}
+
546
+
547static void curr_time(struct timespec *now);
+
548static double diff_timespec(const struct timespec *t1,
+
549 const struct timespec *t2);
+
550
+
551static void remove_node_lru(struct node *node)
+
552{
+
553 struct node_lru *lnode = node_lru(node);
+
554 list_del(&lnode->lru);
+
555 init_list_head(&lnode->lru);
+
556}
+
557
+
558static void set_forget_time(struct fuse *f, struct node *node)
+
559{
+
560 struct node_lru *lnode = node_lru(node);
+
561
+
562 list_del(&lnode->lru);
+
563 list_add_tail(&lnode->lru, &f->lru_table);
+
564 curr_time(&lnode->forget_time);
+
565}
+
566
+
567static void free_node(struct fuse *f, struct node *node)
+
568{
+
569 if (node->name != node->inline_name)
+
570 free(node->name);
+
571 free_node_mem(f, node);
+
572}
+
573
+
574static void node_table_reduce(struct node_table *t)
+
575{
+
576 size_t newsize = t->size / 2;
+
577 void *newarray;
+
578
+
579 if (newsize < NODE_TABLE_MIN_SIZE)
+
580 return;
+
581
+
582 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
583 if (newarray != NULL)
+
584 t->array = newarray;
+
585
+
586 t->size = newsize;
+
587 t->split = t->size / 2;
+
588}
+
589
+
590static void remerge_id(struct fuse *f)
+
591{
+
592 struct node_table *t = &f->id_table;
+
593 int iter;
+
594
+
595 if (t->split == 0)
+
596 node_table_reduce(t);
+
597
+
598 for (iter = 8; t->split > 0 && iter; iter--) {
+
599 struct node **upper;
+
600
+
601 t->split--;
+
602 upper = &t->array[t->split + t->size / 2];
+
603 if (*upper) {
+
604 struct node **nodep;
+
605
+
606 for (nodep = &t->array[t->split]; *nodep;
+
607 nodep = &(*nodep)->id_next);
+
608
+
609 *nodep = *upper;
+
610 *upper = NULL;
+
611 break;
+
612 }
+
613 }
+
614}
+
615
+
616static void unhash_id(struct fuse *f, struct node *node)
+
617{
+
618 struct node **nodep = &f->id_table.array[id_hash(f, node->nodeid)];
+
619
+
620 for (; *nodep != NULL; nodep = &(*nodep)->id_next)
+
621 if (*nodep == node) {
+
622 *nodep = node->id_next;
+
623 f->id_table.use--;
+
624
+
625 if(f->id_table.use < f->id_table.size / 4)
+
626 remerge_id(f);
+
627 return;
+
628 }
+
629}
+
630
+
631static int node_table_resize(struct node_table *t)
+
632{
+
633 size_t newsize = t->size * 2;
+
634 void *newarray;
+
635
+
636 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
637 if (newarray == NULL)
+
638 return -1;
+
639
+
640 t->array = newarray;
+
641 memset(t->array + t->size, 0, t->size * sizeof(struct node *));
+
642 t->size = newsize;
+
643 t->split = 0;
+
644
+
645 return 0;
+
646}
+
647
+
648static void rehash_id(struct fuse *f)
+
649{
+
650 struct node_table *t = &f->id_table;
+
651 struct node **nodep;
+
652 struct node **next;
+
653 size_t hash;
+
654
+
655 if (t->split == t->size / 2)
+
656 return;
+
657
+
658 hash = t->split;
+
659 t->split++;
+
660 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
661 struct node *node = *nodep;
+
662 size_t newhash = id_hash(f, node->nodeid);
+
663
+
664 if (newhash != hash) {
+
665 next = nodep;
+
666 *nodep = node->id_next;
+
667 node->id_next = t->array[newhash];
+
668 t->array[newhash] = node;
+
669 } else {
+
670 next = &node->id_next;
+
671 }
+
672 }
+
673 if (t->split == t->size / 2)
+
674 node_table_resize(t);
+
675}
+
676
+
677static void hash_id(struct fuse *f, struct node *node)
+
678{
+
679 size_t hash = id_hash(f, node->nodeid);
+
680 node->id_next = f->id_table.array[hash];
+
681 f->id_table.array[hash] = node;
+
682 f->id_table.use++;
+
683
+
684 if (f->id_table.use >= f->id_table.size / 2)
+
685 rehash_id(f);
+
686}
+
687
+
688static size_t name_hash(struct fuse *f, fuse_ino_t parent,
+
689 const char *name)
+
690{
+
691 uint64_t hash = parent;
+
692 uint64_t oldhash;
+
693
+
694 for (; *name; name++)
+
695 hash = hash * 31 + (unsigned char) *name;
+
696
+
697 hash %= f->name_table.size;
+
698 oldhash = hash % (f->name_table.size / 2);
+
699 if (oldhash >= f->name_table.split)
+
700 return oldhash;
+
701 else
+
702 return hash;
+
703}
+
704
+
705static void unref_node(struct fuse *f, struct node *node);
+
706
+
707static void remerge_name(struct fuse *f)
+
708{
+
709 struct node_table *t = &f->name_table;
+
710 int iter;
+
711
+
712 if (t->split == 0)
+
713 node_table_reduce(t);
+
714
+
715 for (iter = 8; t->split > 0 && iter; iter--) {
+
716 struct node **upper;
+
717
+
718 t->split--;
+
719 upper = &t->array[t->split + t->size / 2];
+
720 if (*upper) {
+
721 struct node **nodep;
+
722
+
723 for (nodep = &t->array[t->split]; *nodep;
+
724 nodep = &(*nodep)->name_next);
+
725
+
726 *nodep = *upper;
+
727 *upper = NULL;
+
728 break;
+
729 }
+
730 }
+
731}
+
732
+
733static void unhash_name(struct fuse *f, struct node *node)
+
734{
+
735 if (node->name) {
+
736 size_t hash = name_hash(f, node->parent->nodeid, node->name);
+
737 struct node **nodep = &f->name_table.array[hash];
+
738
+
739 for (; *nodep != NULL; nodep = &(*nodep)->name_next)
+
740 if (*nodep == node) {
+
741 *nodep = node->name_next;
+
742 node->name_next = NULL;
+
743 unref_node(f, node->parent);
+
744 if (node->name != node->inline_name)
+
745 free(node->name);
+
746 node->name = NULL;
+
747 node->parent = NULL;
+
748 f->name_table.use--;
+
749
+
750 if (f->name_table.use < f->name_table.size / 4)
+
751 remerge_name(f);
+
752 return;
+
753 }
+
754 fuse_log(FUSE_LOG_ERR,
+
755 "fuse internal error: unable to unhash node: %llu\n",
+
756 (unsigned long long) node->nodeid);
+
757 abort();
+
758 }
+
759}
+
760
+
761static void rehash_name(struct fuse *f)
+
762{
+
763 struct node_table *t = &f->name_table;
+
764 struct node **nodep;
+
765 struct node **next;
+
766 size_t hash;
+
767
+
768 if (t->split == t->size / 2)
+
769 return;
+
770
+
771 hash = t->split;
+
772 t->split++;
+
773 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
774 struct node *node = *nodep;
+
775 size_t newhash = name_hash(f, node->parent->nodeid, node->name);
+
776
+
777 if (newhash != hash) {
+
778 next = nodep;
+
779 *nodep = node->name_next;
+
780 node->name_next = t->array[newhash];
+
781 t->array[newhash] = node;
+
782 } else {
+
783 next = &node->name_next;
+
784 }
+
785 }
+
786 if (t->split == t->size / 2)
+
787 node_table_resize(t);
+
788}
+
789
+
790static int hash_name(struct fuse *f, struct node *node, fuse_ino_t parentid,
+
791 const char *name)
+
792{
+
793 size_t hash = name_hash(f, parentid, name);
+
794 struct node *parent = get_node(f, parentid);
+
795 if (strlen(name) < sizeof(node->inline_name)) {
+
796 strcpy(node->inline_name, name);
+
797 node->name = node->inline_name;
+
798 } else {
+
799 node->name = strdup(name);
+
800 if (node->name == NULL)
+
801 return -1;
+
802 }
+
803
+
804 parent->refctr ++;
+
805 node->parent = parent;
+
806 node->name_next = f->name_table.array[hash];
+
807 f->name_table.array[hash] = node;
+
808 f->name_table.use++;
+
809
+
810 if (f->name_table.use >= f->name_table.size / 2)
+
811 rehash_name(f);
+
812
+
813 return 0;
+
814}
+
815
+
816static void delete_node(struct fuse *f, struct node *node)
+
817{
+
818 if (f->conf.debug)
+
819 fuse_log(FUSE_LOG_DEBUG, "DELETE: %llu\n",
+
820 (unsigned long long) node->nodeid);
+
821
+
822 assert(node->treelock == 0);
+
823 unhash_name(f, node);
+
824 if (lru_enabled(f))
+
825 remove_node_lru(node);
+
826 unhash_id(f, node);
+
827 free_node(f, node);
+
828}
+
829
+
830static void unref_node(struct fuse *f, struct node *node)
+
831{
+
832 assert(node->refctr > 0);
+
833 node->refctr --;
+
834 if (!node->refctr)
+
835 delete_node(f, node);
+
836}
+
837
+
838static fuse_ino_t next_id(struct fuse *f)
+
839{
+
840 do {
+
841 f->ctr = (f->ctr + 1) & 0xffffffff;
+
842 if (!f->ctr)
+
843 f->generation ++;
+
844 } while (f->ctr == 0 || f->ctr == FUSE_UNKNOWN_INO ||
+
845 get_node_nocheck(f, f->ctr) != NULL);
+
846 return f->ctr;
+
847}
+
848
+
849static struct node *lookup_node(struct fuse *f, fuse_ino_t parent,
+
850 const char *name)
+
851{
+
852 size_t hash = name_hash(f, parent, name);
+
853 struct node *node;
+
854
+
855 for (node = f->name_table.array[hash]; node != NULL; node = node->name_next)
+
856 if (node->parent->nodeid == parent &&
+
857 strcmp(node->name, name) == 0)
+
858 return node;
+
859
+
860 return NULL;
+
861}
+
862
+
863static void inc_nlookup(struct node *node)
+
864{
+
865 if (!node->nlookup)
+
866 node->refctr++;
+
867 node->nlookup++;
+
868}
+
869
+
870static struct node *find_node(struct fuse *f, fuse_ino_t parent,
+
871 const char *name)
+
872{
+
873 struct node *node;
+
874
+
875 pthread_mutex_lock(&f->lock);
+
876 if (!name)
+
877 node = get_node(f, parent);
+
878 else
+
879 node = lookup_node(f, parent, name);
+
880 if (node == NULL) {
+
881 node = alloc_node(f);
+
882 if (node == NULL)
+
883 goto out_err;
+
884
+
885 node->nodeid = next_id(f);
+
886 node->generation = f->generation;
+
887 if (f->conf.remember)
+
888 inc_nlookup(node);
+
889
+
890 if (hash_name(f, node, parent, name) == -1) {
+
891 free_node(f, node);
+
892 node = NULL;
+
893 goto out_err;
+
894 }
+
895 hash_id(f, node);
+
896 if (lru_enabled(f)) {
+
897 struct node_lru *lnode = node_lru(node);
+
898 init_list_head(&lnode->lru);
+
899 }
+
900 } else if (lru_enabled(f) && node->nlookup == 1) {
+
901 remove_node_lru(node);
+
902 }
+
903 inc_nlookup(node);
+
904out_err:
+
905 pthread_mutex_unlock(&f->lock);
+
906 return node;
+
907}
+
908
+
909static int lookup_path_in_cache(struct fuse *f,
+
910 const char *path, fuse_ino_t *inop)
+
911{
+
912 char *tmp = strdup(path);
+
913 if (!tmp)
+
914 return -ENOMEM;
+
915
+
916 pthread_mutex_lock(&f->lock);
+
917 fuse_ino_t ino = FUSE_ROOT_ID;
+
918
+
919 int err = 0;
+
920 char *save_ptr;
+
921 char *path_element = strtok_r(tmp, "/", &save_ptr);
+
922 while (path_element != NULL) {
+
923 struct node *node = lookup_node(f, ino, path_element);
+
924 if (node == NULL) {
+
925 err = -ENOENT;
+
926 break;
+
927 }
+
928 ino = node->nodeid;
+
929 path_element = strtok_r(NULL, "/", &save_ptr);
+
930 }
+
931 pthread_mutex_unlock(&f->lock);
+
932 free(tmp);
+
933
+
934 if (!err)
+
935 *inop = ino;
+
936 return err;
+
937}
+
938
+
939static char *add_name(char **buf, unsigned *bufsize, char *s, const char *name)
+
940{
+
941 size_t len = strlen(name);
+
942
+
943 if (s - len <= *buf) {
+
944 unsigned pathlen = *bufsize - (s - *buf);
+
945 unsigned newbufsize = *bufsize;
+
946 char *newbuf;
+
947
+
948 while (newbufsize < pathlen + len + 1) {
+
949 if (newbufsize >= 0x80000000)
+
950 newbufsize = 0xffffffff;
+
951 else
+
952 newbufsize *= 2;
+
953 }
+
954
+
955 newbuf = realloc(*buf, newbufsize);
+
956 if (newbuf == NULL)
+
957 return NULL;
+
958
+
959 *buf = newbuf;
+
960 s = newbuf + newbufsize - pathlen;
+
961 memmove(s, newbuf + *bufsize - pathlen, pathlen);
+
962 *bufsize = newbufsize;
+
963 }
+
964 s -= len;
+
965 memcpy(s, name, len);
+
966 s--;
+
967 *s = '/';
+
968
+
969 return s;
+
970}
+
971
+
972static void unlock_path(struct fuse *f, fuse_ino_t nodeid, struct node *wnode,
+
973 struct node *end)
+
974{
+
975 struct node *node;
+
976
+
977 if (wnode) {
+
978 assert(wnode->treelock == TREELOCK_WRITE);
+
979 wnode->treelock = 0;
+
980 }
+
981
+
982 for (node = get_node(f, nodeid);
+
983 node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) {
+
984 assert(node->treelock != 0);
+
985 assert(node->treelock != TREELOCK_WAIT_OFFSET);
+
986 assert(node->treelock != TREELOCK_WRITE);
+
987 node->treelock--;
+
988 if (node->treelock == TREELOCK_WAIT_OFFSET)
+
989 node->treelock = 0;
+
990 }
+
991}
+
992
+
993static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
994 char **path, struct node **wnodep, bool need_lock)
+
995{
+
996 unsigned bufsize = 256;
+
997 char *buf;
+
998 char *s;
+
999 struct node *node;
+
1000 struct node *wnode = NULL;
+
1001 int err;
+
1002
+
1003 *path = NULL;
+
1004
+
1005 err = -ENOMEM;
+
1006 buf = malloc(bufsize);
+
1007 if (buf == NULL)
+
1008 goto out_err;
+
1009
+
1010 s = buf + bufsize - 1;
+
1011 *s = '\0';
+
1012
+
1013 if (name != NULL) {
+
1014 s = add_name(&buf, &bufsize, s, name);
+
1015 err = -ENOMEM;
+
1016 if (s == NULL)
+
1017 goto out_free;
+
1018 }
+
1019
+
1020 if (wnodep) {
+
1021 assert(need_lock);
+
1022 wnode = lookup_node(f, nodeid, name);
+
1023 if (wnode) {
+
1024 if (wnode->treelock != 0) {
+
1025 if (wnode->treelock > 0)
+
1026 wnode->treelock += TREELOCK_WAIT_OFFSET;
+
1027 err = -EAGAIN;
+
1028 goto out_free;
+
1029 }
+
1030 wnode->treelock = TREELOCK_WRITE;
+
1031 }
+
1032 }
+
1033
+
1034 for (node = get_node(f, nodeid); node->nodeid != FUSE_ROOT_ID;
+
1035 node = node->parent) {
+
1036 err = -ESTALE;
+
1037 if (node->name == NULL || node->parent == NULL)
+
1038 goto out_unlock;
+
1039
+
1040 err = -ENOMEM;
+
1041 s = add_name(&buf, &bufsize, s, node->name);
+
1042 if (s == NULL)
+
1043 goto out_unlock;
+
1044
+
1045 if (need_lock) {
+
1046 err = -EAGAIN;
+
1047 if (node->treelock < 0)
+
1048 goto out_unlock;
+
1049
+
1050 node->treelock++;
+
1051 }
+
1052 }
+
1053
+
1054 if (s[0])
+
1055 memmove(buf, s, bufsize - (s - buf));
+
1056 else
+
1057 strcpy(buf, "/");
+
1058
+
1059 *path = buf;
+
1060 if (wnodep)
+
1061 *wnodep = wnode;
+
1062
+
1063 return 0;
+
1064
+
1065 out_unlock:
+
1066 if (need_lock)
+
1067 unlock_path(f, nodeid, wnode, node);
+
1068 out_free:
+
1069 free(buf);
+
1070
+
1071 out_err:
+
1072 return err;
+
1073}
+
1074
+
1075static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1076 fuse_ino_t nodeid2, const char *name2,
+
1077 char **path1, char **path2,
+
1078 struct node **wnode1, struct node **wnode2)
+
1079{
+
1080 int err;
+
1081
+
1082 /* FIXME: locking two paths needs deadlock checking */
+
1083 err = try_get_path(f, nodeid1, name1, path1, wnode1, true);
+
1084 if (!err) {
+
1085 err = try_get_path(f, nodeid2, name2, path2, wnode2, true);
+
1086 if (err) {
+
1087 struct node *wn1 = wnode1 ? *wnode1 : NULL;
+
1088
+
1089 unlock_path(f, nodeid1, wn1, NULL);
+
1090 free(*path1);
+
1091 }
+
1092 }
+
1093 return err;
+
1094}
+
1095
+
1096static void queue_element_wakeup(struct fuse *f, struct lock_queue_element *qe)
+
1097{
+
1098 int err;
+
1099
+
1100 if (!qe->path1) {
+
1101 /* Just waiting for it to be unlocked */
+
1102 if (get_node(f, qe->nodeid1)->treelock == 0)
+
1103 pthread_cond_signal(&qe->cond);
+
1104
+
1105 return;
+
1106 }
+
1107
+
1108 if (qe->done)
+
1109 return; // Don't try to double-lock the element
+
1110
+
1111 if (!qe->path2) {
+
1112 err = try_get_path(f, qe->nodeid1, qe->name1, qe->path1,
+
1113 qe->wnode1, true);
+
1114 } else {
+
1115 err = try_get_path2(f, qe->nodeid1, qe->name1, qe->nodeid2,
+
1116 qe->name2, qe->path1, qe->path2, qe->wnode1,
+
1117 qe->wnode2);
+
1118 }
+
1119
+
1120 if (err == -EAGAIN)
+
1121 return; /* keep trying */
+
1122
+
1123 qe->err = err;
+
1124 qe->done = true;
+
1125 pthread_cond_signal(&qe->cond);
+
1126}
+
1127
+
1128static void wake_up_queued(struct fuse *f)
+
1129{
+
1130 struct lock_queue_element *qe;
+
1131
+
1132 for (qe = f->lockq; qe != NULL; qe = qe->next)
+
1133 queue_element_wakeup(f, qe);
+
1134}
+
1135
+
1136static void debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid,
+
1137 const char *name, bool wr)
+
1138{
+
1139 if (f->conf.debug) {
+
1140 struct node *wnode = NULL;
+
1141
+
1142 if (wr)
+
1143 wnode = lookup_node(f, nodeid, name);
+
1144
+
1145 if (wnode) {
+
1146 fuse_log(FUSE_LOG_DEBUG, "%s %llu (w)\n",
+
1147 msg, (unsigned long long) wnode->nodeid);
+
1148 } else {
+
1149 fuse_log(FUSE_LOG_DEBUG, "%s %llu\n",
+
1150 msg, (unsigned long long) nodeid);
+
1151 }
+
1152 }
+
1153}
+
1154
+
1155static void queue_path(struct fuse *f, struct lock_queue_element *qe)
+
1156{
+
1157 struct lock_queue_element **qp;
+
1158
+
1159 qe->done = false;
+
1160 pthread_cond_init(&qe->cond, NULL);
+
1161 qe->next = NULL;
+
1162 for (qp = &f->lockq; *qp != NULL; qp = &(*qp)->next);
+
1163 *qp = qe;
+
1164}
+
1165
+
1166static void dequeue_path(struct fuse *f, struct lock_queue_element *qe)
+
1167{
+
1168 struct lock_queue_element **qp;
+
1169
+
1170 pthread_cond_destroy(&qe->cond);
+
1171 for (qp = &f->lockq; *qp != qe; qp = &(*qp)->next);
+
1172 *qp = qe->next;
+
1173}
+
1174
+
1175static int wait_path(struct fuse *f, struct lock_queue_element *qe)
+
1176{
+
1177 queue_path(f, qe);
+
1178
+
1179 do {
+
1180 pthread_cond_wait(&qe->cond, &f->lock);
+
1181 } while (!qe->done);
+
1182
+
1183 dequeue_path(f, qe);
+
1184
+
1185 return qe->err;
+
1186}
+
1187
+
1188static int get_path_common(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1189 char **path, struct node **wnode)
+
1190{
+
1191 int err;
+
1192
+
1193 pthread_mutex_lock(&f->lock);
+
1194 err = try_get_path(f, nodeid, name, path, wnode, true);
+
1195 if (err == -EAGAIN) {
+
1196 struct lock_queue_element qe = {
+
1197 .nodeid1 = nodeid,
+
1198 .name1 = name,
+
1199 .path1 = path,
+
1200 .wnode1 = wnode,
+
1201 };
+
1202 debug_path(f, "QUEUE PATH", nodeid, name, !!wnode);
+
1203 err = wait_path(f, &qe);
+
1204 debug_path(f, "DEQUEUE PATH", nodeid, name, !!wnode);
+
1205 }
+
1206 pthread_mutex_unlock(&f->lock);
+
1207
+
1208 return err;
+
1209}
+
1210
+
1211static int get_path(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1212{
+
1213 return get_path_common(f, nodeid, NULL, path, NULL);
+
1214}
+
1215
+
1216static int get_path_nullok(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1217{
+
1218 int err = 0;
+
1219
+
1220 if (f->conf.nullpath_ok) {
+
1221 *path = NULL;
+
1222 } else {
+
1223 err = get_path_common(f, nodeid, NULL, path, NULL);
+
1224 if (err == -ESTALE)
+
1225 err = 0;
+
1226 }
+
1227
+
1228 return err;
+
1229}
+
1230
+
1231static int get_path_name(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1232 char **path)
+
1233{
+
1234 return get_path_common(f, nodeid, name, path, NULL);
+
1235}
+
1236
+
1237static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1238 char **path, struct node **wnode)
+
1239{
+
1240 return get_path_common(f, nodeid, name, path, wnode);
+
1241}
+
1242
+
1243#if defined(__FreeBSD__)
+
1244#define CHECK_DIR_LOOP
+
1245#endif
+
1246
+
1247#if defined(CHECK_DIR_LOOP)
+
1248static int check_dir_loop(struct fuse *f,
+
1249 fuse_ino_t nodeid1, const char *name1,
+
1250 fuse_ino_t nodeid2, const char *name2)
+
1251{
+
1252 struct node *node, *node1, *node2;
+
1253 fuse_ino_t id1, id2;
+
1254
+
1255 node1 = lookup_node(f, nodeid1, name1);
+
1256 id1 = node1 ? node1->nodeid : nodeid1;
+
1257
+
1258 node2 = lookup_node(f, nodeid2, name2);
+
1259 id2 = node2 ? node2->nodeid : nodeid2;
+
1260
+
1261 for (node = get_node(f, id2); node->nodeid != FUSE_ROOT_ID;
+
1262 node = node->parent) {
+
1263 if (node->name == NULL || node->parent == NULL)
+
1264 break;
+
1265
+
1266 if (node->nodeid != id2 && node->nodeid == id1)
+
1267 return -EINVAL;
+
1268 }
+
1269
+
1270 if (node2)
+
1271 {
+
1272 for (node = get_node(f, id1); node->nodeid != FUSE_ROOT_ID;
+
1273 node = node->parent) {
+
1274 if (node->name == NULL || node->parent == NULL)
+
1275 break;
+
1276
+
1277 if (node->nodeid != id1 && node->nodeid == id2)
+
1278 return -ENOTEMPTY;
+
1279 }
+
1280 }
+
1281
+
1282 return 0;
+
1283}
+
1284#endif
+
1285
+
1286static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1287 fuse_ino_t nodeid2, const char *name2,
+
1288 char **path1, char **path2,
+
1289 struct node **wnode1, struct node **wnode2)
+
1290{
+
1291 int err;
+
1292
+
1293 pthread_mutex_lock(&f->lock);
+
1294
+
1295#if defined(CHECK_DIR_LOOP)
+
1296 if (name1)
+
1297 {
+
1298 // called during rename; perform dir loop check
+
1299 err = check_dir_loop(f, nodeid1, name1, nodeid2, name2);
+
1300 if (err)
+
1301 goto out_unlock;
+
1302 }
+
1303#endif
+
1304
+
1305 err = try_get_path2(f, nodeid1, name1, nodeid2, name2,
+
1306 path1, path2, wnode1, wnode2);
+
1307 if (err == -EAGAIN) {
+
1308 struct lock_queue_element qe = {
+
1309 .nodeid1 = nodeid1,
+
1310 .name1 = name1,
+
1311 .path1 = path1,
+
1312 .wnode1 = wnode1,
+
1313 .nodeid2 = nodeid2,
+
1314 .name2 = name2,
+
1315 .path2 = path2,
+
1316 .wnode2 = wnode2,
+
1317 };
+
1318
+
1319 debug_path(f, "QUEUE PATH1", nodeid1, name1, !!wnode1);
+
1320 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1321 err = wait_path(f, &qe);
+
1322 debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1);
+
1323 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1324 }
+
1325
+
1326#if defined(CHECK_DIR_LOOP)
+
1327out_unlock:
+
1328#endif
+
1329 pthread_mutex_unlock(&f->lock);
+
1330
+
1331 return err;
+
1332}
+
1333
+
1334static void free_path_wrlock(struct fuse *f, fuse_ino_t nodeid,
+
1335 struct node *wnode, char *path)
+
1336{
+
1337 pthread_mutex_lock(&f->lock);
+
1338 unlock_path(f, nodeid, wnode, NULL);
+
1339 if (f->lockq)
+
1340 wake_up_queued(f);
+
1341 pthread_mutex_unlock(&f->lock);
+
1342 free(path);
+
1343}
+
1344
+
1345static void free_path(struct fuse *f, fuse_ino_t nodeid, char *path)
+
1346{
+
1347 if (path)
+
1348 free_path_wrlock(f, nodeid, NULL, path);
+
1349}
+
1350
+
1351static void free_path2(struct fuse *f, fuse_ino_t nodeid1, fuse_ino_t nodeid2,
+
1352 struct node *wnode1, struct node *wnode2,
+
1353 char *path1, char *path2)
+
1354{
+
1355 pthread_mutex_lock(&f->lock);
+
1356 unlock_path(f, nodeid1, wnode1, NULL);
+
1357 unlock_path(f, nodeid2, wnode2, NULL);
+
1358 wake_up_queued(f);
+
1359 pthread_mutex_unlock(&f->lock);
+
1360 free(path1);
+
1361 free(path2);
+
1362}
+
1363
+
1364static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup)
+
1365{
+
1366 struct node *node;
+
1367 if (nodeid == FUSE_ROOT_ID)
+
1368 return;
+
1369 pthread_mutex_lock(&f->lock);
+
1370 node = get_node(f, nodeid);
+
1371
+
1372 /*
+
1373 * Node may still be locked due to interrupt idiocy in open,
+
1374 * create and opendir
+
1375 */
+
1376 while (node->nlookup == nlookup && node->treelock) {
+
1377 struct lock_queue_element qe = {
+
1378 .nodeid1 = nodeid,
+
1379 };
+
1380
+
1381 debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false);
+
1382 queue_path(f, &qe);
+
1383
+
1384 do {
+
1385 pthread_cond_wait(&qe.cond, &f->lock);
+
1386 } while (node->nlookup == nlookup && node->treelock);
+
1387
+
1388 dequeue_path(f, &qe);
+
1389 debug_path(f, "DEQUEUE_PATH (forget)", nodeid, NULL, false);
+
1390 }
+
1391
+
1392 assert(node->nlookup >= nlookup);
+
1393 node->nlookup -= nlookup;
+
1394 if (!node->nlookup) {
+
1395 unref_node(f, node);
+
1396 } else if (lru_enabled(f) && node->nlookup == 1) {
+
1397 set_forget_time(f, node);
+
1398 }
+
1399 pthread_mutex_unlock(&f->lock);
+
1400}
+
1401
+
1402static void unlink_node(struct fuse *f, struct node *node)
+
1403{
+
1404 if (f->conf.remember) {
+
1405 assert(node->nlookup > 1);
+
1406 node->nlookup--;
+
1407 }
+
1408 unhash_name(f, node);
+
1409}
+
1410
+
1411static void remove_node(struct fuse *f, fuse_ino_t dir, const char *name)
+
1412{
+
1413 struct node *node;
+
1414
+
1415 pthread_mutex_lock(&f->lock);
+
1416 node = lookup_node(f, dir, name);
+
1417 if (node != NULL)
+
1418 unlink_node(f, node);
+
1419 pthread_mutex_unlock(&f->lock);
+
1420}
+
1421
+
1422static int rename_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1423 fuse_ino_t newdir, const char *newname, int hide)
+
1424{
+
1425 struct node *node;
+
1426 struct node *newnode;
+
1427 int err = 0;
+
1428
+
1429 pthread_mutex_lock(&f->lock);
+
1430 node = lookup_node(f, olddir, oldname);
+
1431 newnode = lookup_node(f, newdir, newname);
+
1432 if (node == NULL)
+
1433 goto out;
+
1434
+
1435 if (newnode != NULL) {
+
1436 if (hide) {
+
1437 fuse_log(FUSE_LOG_ERR, "fuse: hidden file got created during hiding\n");
+
1438 err = -EBUSY;
+
1439 goto out;
+
1440 }
+
1441 unlink_node(f, newnode);
+
1442 }
+
1443
+
1444 unhash_name(f, node);
+
1445 if (hash_name(f, node, newdir, newname) == -1) {
+
1446 err = -ENOMEM;
+
1447 goto out;
+
1448 }
+
1449
+
1450 if (hide)
+
1451 node->is_hidden = 1;
+
1452
+
1453out:
+
1454 pthread_mutex_unlock(&f->lock);
+
1455 return err;
+
1456}
+
1457
+
1458static int exchange_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1459 fuse_ino_t newdir, const char *newname)
+
1460{
+
1461 struct node *oldnode;
+
1462 struct node *newnode;
+
1463 int err;
+
1464
+
1465 pthread_mutex_lock(&f->lock);
+
1466 oldnode = lookup_node(f, olddir, oldname);
+
1467 newnode = lookup_node(f, newdir, newname);
+
1468
+
1469 if (oldnode)
+
1470 unhash_name(f, oldnode);
+
1471 if (newnode)
+
1472 unhash_name(f, newnode);
+
1473
+
1474 err = -ENOMEM;
+
1475 if (oldnode) {
+
1476 if (hash_name(f, oldnode, newdir, newname) == -1)
+
1477 goto out;
+
1478 }
+
1479 if (newnode) {
+
1480 if (hash_name(f, newnode, olddir, oldname) == -1)
+
1481 goto out;
+
1482 }
+
1483 err = 0;
+
1484out:
+
1485 pthread_mutex_unlock(&f->lock);
+
1486 return err;
+
1487}
+
1488
+
1489static void set_stat(struct fuse *f, fuse_ino_t nodeid, struct stat *stbuf)
+
1490{
+
1491 if (!f->conf.use_ino)
+
1492 stbuf->st_ino = nodeid;
+
1493 if (f->conf.set_mode) {
+
1494 if (f->conf.dmask && S_ISDIR(stbuf->st_mode))
+
1495 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1496 (0777 & ~f->conf.dmask);
+
1497 else if (f->conf.fmask)
+
1498 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1499 (0777 & ~f->conf.fmask);
+
1500 else
+
1501 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1502 (0777 & ~f->conf.umask);
+
1503 }
+
1504 if (f->conf.set_uid)
+
1505 stbuf->st_uid = f->conf.uid;
+
1506 if (f->conf.set_gid)
+
1507 stbuf->st_gid = f->conf.gid;
+
1508}
+
1509
+
1510#ifdef HAVE_STATX
+
1511static void set_statx(struct fuse *f, fuse_ino_t nodeid, struct statx *stxbuf)
+
1512{
+
1513 if (!f->conf.use_ino)
+
1514 stxbuf->stx_ino = nodeid;
+
1515 if (f->conf.set_mode) {
+
1516 if (f->conf.dmask && S_ISDIR(stxbuf->stx_mode))
+
1517 stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) |
+
1518 (0777 & ~f->conf.dmask);
+
1519 else if (f->conf.fmask)
+
1520 stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) |
+
1521 (0777 & ~f->conf.fmask);
+
1522 else
+
1523 stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) |
+
1524 (0777 & ~f->conf.umask);
+
1525 }
+
1526 if (f->conf.set_uid)
+
1527 stxbuf->stx_uid = f->conf.uid;
+
1528 if (f->conf.set_gid)
+
1529 stxbuf->stx_gid = f->conf.gid;
+
1530}
+
1531#endif
+
1532
+
1533static struct fuse *req_fuse(fuse_req_t req)
+
1534{
+
1535 return (struct fuse *) fuse_req_userdata(req);
+
1536}
+
1537
+
1538static void fuse_intr_sighandler(int sig)
+
1539{
+
1540 (void) sig;
+
1541 /* Nothing to do */
+
1542}
+
1543
+
1544struct fuse_intr_data {
+
1545 pthread_t id;
+
1546 pthread_cond_t cond;
+
1547 int finished;
+
1548};
+
1549
+
1550static void fuse_interrupt(fuse_req_t req, void *d_)
+
1551{
+
1552 struct fuse_intr_data *d = d_;
+
1553 struct fuse *f = req_fuse(req);
+
1554
+
1555 if (d->id == pthread_self())
+
1556 return;
+
1557
+
1558 pthread_mutex_lock(&f->lock);
+
1559 while (!d->finished) {
+
1560 struct timeval now;
+
1561 struct timespec timeout;
+
1562
+
1563 pthread_kill(d->id, f->conf.intr_signal);
+
1564 gettimeofday(&now, NULL);
+
1565 timeout.tv_sec = now.tv_sec + 1;
+
1566 timeout.tv_nsec = now.tv_usec * 1000;
+
1567 pthread_cond_timedwait(&d->cond, &f->lock, &timeout);
+
1568 }
+
1569 pthread_mutex_unlock(&f->lock);
+
1570}
+
1571
+
1572static void fuse_do_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1573 struct fuse_intr_data *d)
+
1574{
+
1575 pthread_mutex_lock(&f->lock);
+
1576 d->finished = 1;
+
1577 pthread_cond_broadcast(&d->cond);
+
1578 pthread_mutex_unlock(&f->lock);
+
1579 fuse_req_interrupt_func(req, NULL, NULL);
+
1580 pthread_cond_destroy(&d->cond);
+
1581}
+
1582
+
1583static void fuse_do_prepare_interrupt(fuse_req_t req, struct fuse_intr_data *d)
+
1584{
+
1585 d->id = pthread_self();
+
1586 pthread_cond_init(&d->cond, NULL);
+
1587 d->finished = 0;
+
1588 fuse_req_interrupt_func(req, fuse_interrupt, d);
+
1589}
+
1590
+
1591static inline void fuse_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1592 struct fuse_intr_data *d)
+
1593{
+
1594 if (f->conf.intr)
+
1595 fuse_do_finish_interrupt(f, req, d);
+
1596}
+
1597
+
1598static inline void fuse_prepare_interrupt(struct fuse *f, fuse_req_t req,
+
1599 struct fuse_intr_data *d)
+
1600{
+
1601 if (f->conf.intr)
+
1602 fuse_do_prepare_interrupt(req, d);
+
1603}
+
1604
+
1605static const char* file_info_string(struct fuse_file_info *fi,
+
1606 char* buf, size_t len)
+
1607{
+
1608 if(fi == NULL)
+
1609 return "NULL";
+
1610 snprintf(buf, len, "%llu", (unsigned long long) fi->fh);
+
1611 return buf;
+
1612}
+
1613
+
1614int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1615 struct fuse_file_info *fi)
+
1616{
+
1617 fuse_get_context()->private_data = fs->user_data;
+
1618 if (!fs->op.getattr)
+
1619 return -ENOSYS;
+
1620
+
1621 if (fs->debug) {
+
1622 char buf[10];
+
1623
+
1624 fuse_log(FUSE_LOG_DEBUG, "getattr[%s] %s\n",
+
1625 file_info_string(fi, buf, sizeof(buf)),
+
1626 path);
+
1627 }
+
1628 return fs->op.getattr(path, buf, fi);
+
1629}
+
1630
+
1631int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1632 const char *newpath, unsigned int flags)
+
1633{
+
1634 fuse_get_context()->private_data = fs->user_data;
+
1635 if (!fs->op.rename)
+
1636 return -ENOSYS;
+
1637 if (fs->debug)
+
1638 fuse_log(FUSE_LOG_DEBUG, "rename %s %s 0x%x\n", oldpath, newpath,
+
1639 flags);
+
1640
+
1641 return fs->op.rename(oldpath, newpath, flags);
+
1642}
+
1643
+
1644int fuse_fs_unlink(struct fuse_fs *fs, const char *path)
+
1645{
+
1646 fuse_get_context()->private_data = fs->user_data;
+
1647 if (!fs->op.unlink)
+
1648 return -ENOSYS;
+
1649 if (fs->debug)
+
1650 fuse_log(FUSE_LOG_DEBUG, "unlink %s\n", path);
+
1651
+
1652 return fs->op.unlink(path);
+
1653}
+
1654
+
1655int fuse_fs_rmdir(struct fuse_fs *fs, const char *path)
+
1656{
+
1657 fuse_get_context()->private_data = fs->user_data;
+
1658 if (!fs->op.rmdir)
+
1659 return -ENOSYS;
+
1660 if (fs->debug)
+
1661 fuse_log(FUSE_LOG_DEBUG, "rmdir %s\n", path);
+
1662
+
1663 return fs->op.rmdir(path);
+
1664}
+
1665
+
1666int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path)
+
1667{
+
1668 fuse_get_context()->private_data = fs->user_data;
+
1669 if (!fs->op.symlink)
+
1670 return -ENOSYS;
+
1671 if (fs->debug)
+
1672 fuse_log(FUSE_LOG_DEBUG, "symlink %s %s\n", linkname, path);
+
1673
+
1674 return fs->op.symlink(linkname, path);
+
1675}
+
1676
+
1677int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath)
+
1678{
+
1679 fuse_get_context()->private_data = fs->user_data;
+
1680 if (!fs->op.link)
+
1681 return -ENOSYS;
+
1682 if (fs->debug)
+
1683 fuse_log(FUSE_LOG_DEBUG, "link %s %s\n", oldpath, newpath);
+
1684
+
1685 return fs->op.link(oldpath, newpath);
+
1686}
+
1687
+
1688int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1689 struct fuse_file_info *fi)
+
1690{
+
1691 fuse_get_context()->private_data = fs->user_data;
+
1692 if (!fs->op.release)
+
1693 return 0;
+
1694
+
1695 if (fs->debug)
+
1696 fuse_log(FUSE_LOG_DEBUG, "release%s[%llu] flags: 0x%x\n",
+
1697 fi->flush ? "+flush" : "",
+
1698 (unsigned long long) fi->fh, fi->flags);
+
1699
+
1700 return fs->op.release(path, fi);
+
1701}
+
1702
+
1703int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1704 struct fuse_file_info *fi)
+
1705{
+
1706 int err;
+
1707
+
1708 fuse_get_context()->private_data = fs->user_data;
+
1709 if (!fs->op.opendir)
+
1710 return 0;
+
1711
+
1712 if (fs->debug)
+
1713 fuse_log(FUSE_LOG_DEBUG, "opendir flags: 0x%x %s\n", fi->flags,
+
1714 path);
+
1715
+
1716 err = fs->op.opendir(path, fi);
+
1717
+
1718 if (fs->debug && !err)
+
1719 fuse_log(FUSE_LOG_DEBUG, " opendir[%llu] flags: 0x%x %s\n",
+
1720 (unsigned long long) fi->fh, fi->flags, path);
+
1721
+
1722 return err;
+
1723}
+
1724
+
1725int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1726 struct fuse_file_info *fi)
+
1727{
+
1728 int err;
+
1729
+
1730 fuse_get_context()->private_data = fs->user_data;
+
1731 if (!fs->op.open)
+
1732 return 0;
+
1733
+
1734 if (fs->debug)
+
1735 fuse_log(FUSE_LOG_DEBUG, "open flags: 0x%x %s\n", fi->flags,
+
1736 path);
+
1737
+
1738 err = fs->op.open(path, fi);
+
1739
+
1740 if (fs->debug && !err)
+
1741 fuse_log(FUSE_LOG_DEBUG, " open[%llu] flags: 0x%x %s\n",
+
1742 (unsigned long long) fi->fh, fi->flags, path);
+
1743
+
1744 return err;
+
1745}
+
1746
+
1747static void fuse_free_buf(struct fuse_bufvec *buf)
+
1748{
+
1749 if (buf != NULL) {
+
1750 size_t i;
+
1751
+
1752 for (i = 0; i < buf->count; i++)
+
1753 if (!(buf->buf[i].flags & FUSE_BUF_IS_FD))
+
1754 free(buf->buf[i].mem);
+
1755 free(buf);
+
1756 }
+
1757}
+
1758
+
1759int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1760 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1761 struct fuse_file_info *fi)
+
1762{
+
1763 int res;
+
1764
+
1765 fuse_get_context()->private_data = fs->user_data;
+
1766 if (!fs->op.read && !fs->op.read_buf)
+
1767 return -ENOSYS;
+
1768
+
1769 if (fs->debug)
+
1770 fuse_log(FUSE_LOG_DEBUG,
+
1771 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1772 (unsigned long long) fi->fh,
+
1773 size, (unsigned long long) off, fi->flags);
+
1774
+
1775 if (fs->op.read_buf) {
+
1776 res = fs->op.read_buf(path, bufp, size, off, fi);
+
1777 } else {
+
1778 struct fuse_bufvec *buf;
+
1779 void *mem;
+
1780
+
1781 buf = malloc(sizeof(struct fuse_bufvec));
+
1782 if (buf == NULL)
+
1783 return -ENOMEM;
+
1784
+
1785 mem = malloc(size);
+
1786 if (mem == NULL) {
+
1787 free(buf);
+
1788 return -ENOMEM;
+
1789 }
+
1790 *buf = FUSE_BUFVEC_INIT(size);
+
1791 buf->buf[0].mem = mem;
+
1792 *bufp = buf;
+
1793
+
1794 res = fs->op.read(path, mem, size, off, fi);
+
1795 if (res >= 0)
+
1796 buf->buf[0].size = res;
+
1797 }
+
1798
+
1799 if (fs->debug && res >= 0)
+
1800 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %zu bytes from %llu\n",
+
1801 (unsigned long long) fi->fh,
+
1802 fuse_buf_size(*bufp),
+
1803 (unsigned long long) off);
+
1804 if (res >= 0 && fuse_buf_size(*bufp) > size)
+
1805 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1806
+
1807 if (res < 0)
+
1808 return res;
+
1809
+
1810 return 0;
+
1811}
+
1812
+
1813int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size,
+
1814 off_t off, struct fuse_file_info *fi)
+
1815{
+
1816 int res;
+
1817
+
1818 fuse_get_context()->private_data = fs->user_data;
+
1819 if (!fs->op.read && !fs->op.read_buf)
+
1820 return -ENOSYS;
+
1821
+
1822 if (fs->debug)
+
1823 fuse_log(FUSE_LOG_DEBUG,
+
1824 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1825 (unsigned long long) fi->fh,
+
1826 size, (unsigned long long) off, fi->flags);
+
1827
+
1828 if (fs->op.read_buf) {
+
1829 struct fuse_bufvec *buf = NULL;
+
1830
+
1831 res = fs->op.read_buf(path, &buf, size, off, fi);
+
1832 if (res == 0) {
+
1833 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size);
+
1834
+
1835 dst.buf[0].mem = mem;
+
1836 res = fuse_buf_copy(&dst, buf, 0);
+
1837 }
+
1838 fuse_free_buf(buf);
+
1839 } else {
+
1840 res = fs->op.read(path, mem, size, off, fi);
+
1841 }
+
1842
+
1843 if (fs->debug && res >= 0)
+
1844 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %u bytes from %llu\n",
+
1845 (unsigned long long) fi->fh,
+
1846 res,
+
1847 (unsigned long long) off);
+
1848 if (res >= 0 && res > (int) size)
+
1849 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1850
+
1851 return res;
+
1852}
+
1853
+
1854int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1855 struct fuse_bufvec *buf, off_t off,
+
1856 struct fuse_file_info *fi)
+
1857{
+
1858 int res;
+
1859 size_t size;
+
1860
+
1861 fuse_get_context()->private_data = fs->user_data;
+
1862 if (!fs->op.write_buf && !fs->op.write)
+
1863 return -ENOSYS;
+
1864
+
1865 size = fuse_buf_size(buf);
+
1866 assert(buf->idx == 0 && buf->off == 0);
+
1867 if (fs->debug)
+
1868 fuse_log(FUSE_LOG_DEBUG,
+
1869 "write%s[%llu] %zu bytes to %llu flags: 0x%x\n",
+
1870 fi->writepage ? "page" : "",
+
1871 (unsigned long long) fi->fh,
+
1872 size,
+
1873 (unsigned long long) off,
+
1874 fi->flags);
+
1875
+
1876 if (fs->op.write_buf) {
+
1877 res = fs->op.write_buf(path, buf, off, fi);
+
1878 } else {
+
1879 void *mem = NULL;
+
1880 struct fuse_buf *flatbuf;
+
1881 struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size);
+
1882
+
1883 if (buf->count == 1 &&
+
1884 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
1885 flatbuf = &buf->buf[0];
+
1886 } else {
+
1887 res = -ENOMEM;
+
1888 mem = malloc(size);
+
1889 if (mem == NULL)
+
1890 goto out;
+
1891
+
1892 tmp.buf[0].mem = mem;
+
1893 res = fuse_buf_copy(&tmp, buf, 0);
+
1894 if (res <= 0)
+
1895 goto out_free;
+
1896
+
1897 tmp.buf[0].size = res;
+
1898 flatbuf = &tmp.buf[0];
+
1899 }
+
1900
+
1901 res = fs->op.write(path, flatbuf->mem, flatbuf->size,
+
1902 off, fi);
+
1903out_free:
+
1904 free(mem);
+
1905 }
+
1906out:
+
1907 if (fs->debug && res >= 0)
+
1908 fuse_log(FUSE_LOG_DEBUG, " write%s[%llu] %u bytes to %llu\n",
+
1909 fi->writepage ? "page" : "",
+
1910 (unsigned long long) fi->fh, res,
+
1911 (unsigned long long) off);
+
1912 if (res > (int) size)
+
1913 fuse_log(FUSE_LOG_ERR, "fuse: wrote too many bytes\n");
+
1914
+
1915 return res;
+
1916}
+
1917
+
1918int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem,
+
1919 size_t size, off_t off, struct fuse_file_info *fi)
+
1920{
+
1921 struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(size);
+
1922
+
1923 bufv.buf[0].mem = (void *) mem;
+
1924
+
1925 return fuse_fs_write_buf(fs, path, &bufv, off, fi);
+
1926}
+
1927
+
1928int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1929 struct fuse_file_info *fi)
+
1930{
+
1931 fuse_get_context()->private_data = fs->user_data;
+
1932 if (!fs->op.fsync)
+
1933 return -ENOSYS;
+
1934
+
1935 if (fs->debug)
+
1936 fuse_log(FUSE_LOG_DEBUG, "fsync[%llu] datasync: %i\n",
+
1937 (unsigned long long) fi->fh, datasync);
+
1938
+
1939 return fs->op.fsync(path, datasync, fi);
+
1940}
+
1941
+
1942int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1943 struct fuse_file_info *fi)
+
1944{
+
1945 fuse_get_context()->private_data = fs->user_data;
+
1946 if (!fs->op.fsyncdir)
+
1947 return -ENOSYS;
+
1948
+
1949 if (fs->debug)
+
1950 fuse_log(FUSE_LOG_DEBUG, "fsyncdir[%llu] datasync: %i\n",
+
1951 (unsigned long long) fi->fh, datasync);
+
1952
+
1953 return fs->op.fsyncdir(path, datasync, fi);
+
1954}
+
1955
+
1956int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1957 struct fuse_file_info *fi)
+
1958{
+
1959 fuse_get_context()->private_data = fs->user_data;
+
1960 if (!fs->op.flush)
+
1961 return -ENOSYS;
+
1962 if (fs->debug)
+
1963 fuse_log(FUSE_LOG_DEBUG, "flush[%llu]\n",
+
1964 (unsigned long long) fi->fh);
+
1965
+
1966 return fs->op.flush(path, fi);
+
1967}
+
1968
+
1969int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf)
+
1970{
+
1971 fuse_get_context()->private_data = fs->user_data;
+
1972 if (fs->op.statfs) {
+
1973 if (fs->debug)
+
1974 fuse_log(FUSE_LOG_DEBUG, "statfs %s\n", path);
+
1975
+
1976 return fs->op.statfs(path, buf);
+
1977 } else {
+
1978 buf->f_namemax = 255;
+
1979 buf->f_bsize = 512;
+
1980 return 0;
+
1981 }
+
1982}
+
1983
+
1984int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1985 struct fuse_file_info *fi)
+
1986{
+
1987 fuse_get_context()->private_data = fs->user_data;
+
1988 if (!fs->op.releasedir)
+
1989 return 0;
+
1990
+
1991 if (fs->debug)
+
1992 fuse_log(FUSE_LOG_DEBUG, "releasedir[%llu] flags: 0x%x\n",
+
1993 (unsigned long long) fi->fh, fi->flags);
+
1994
+
1995 return fs->op.releasedir(path, fi);
+
1996}
+
1997
+
1998int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
1999 fuse_fill_dir_t filler, off_t off,
+
2000 struct fuse_file_info *fi,
+
2001 enum fuse_readdir_flags flags)
+
2002{
+
2003 fuse_get_context()->private_data = fs->user_data;
+
2004 if (!fs->op.readdir)
+
2005 return -ENOSYS;
+
2006 if (fs->debug) {
+
2007 fuse_log(FUSE_LOG_DEBUG, "readdir%s[%llu] from %llu\n",
+
2008 (flags & FUSE_READDIR_PLUS) ? "plus" : "",
+
2009 (unsigned long long) fi->fh,
+
2010 (unsigned long long) off);
+
2011 }
+
2012
+
2013 return fs->op.readdir(path, buf, filler, off, fi, flags);
+
2014}
+
2015
+
2016int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
2017 struct fuse_file_info *fi)
+
2018{
+
2019 int err;
+
2020
+
2021 fuse_get_context()->private_data = fs->user_data;
+
2022 if (!fs->op.create)
+
2023 return -ENOSYS;
+
2024
+
2025 if (fs->debug)
+
2026 fuse_log(FUSE_LOG_DEBUG,
+
2027 "create flags: 0x%x %s 0%o umask=0%03o\n",
+
2028 fi->flags, path, mode,
+
2029 fuse_get_context()->umask);
+
2030
+
2031 err = fs->op.create(path, mode, fi);
+
2032
+
2033 if (fs->debug && !err)
+
2034 fuse_log(FUSE_LOG_DEBUG, " create[%llu] flags: 0x%x %s\n",
+
2035 (unsigned long long) fi->fh, fi->flags, path);
+
2036
+
2037 return err;
+
2038}
+
2039
+
2040int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
2041 struct fuse_file_info *fi, int cmd, struct flock *lock)
+
2042{
+
2043 fuse_get_context()->private_data = fs->user_data;
+
2044 if (!fs->op.lock)
+
2045 return -ENOSYS;
+
2046
+
2047 if (fs->debug)
+
2048 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n",
+
2049 (unsigned long long) fi->fh,
+
2050 (cmd == F_GETLK ? "F_GETLK" :
+
2051 (cmd == F_SETLK ? "F_SETLK" :
+
2052 (cmd == F_SETLKW ? "F_SETLKW" : "???"))),
+
2053 (lock->l_type == F_RDLCK ? "F_RDLCK" :
+
2054 (lock->l_type == F_WRLCK ? "F_WRLCK" :
+
2055 (lock->l_type == F_UNLCK ? "F_UNLCK" :
+
2056 "???"))),
+
2057 (unsigned long long) lock->l_start,
+
2058 (unsigned long long) lock->l_len,
+
2059 (unsigned long long) lock->l_pid);
+
2060
+
2061 return fs->op.lock(path, fi, cmd, lock);
+
2062}
+
2063
+
2064int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
2065 struct fuse_file_info *fi, int op)
+
2066{
+
2067 fuse_get_context()->private_data = fs->user_data;
+
2068 if (!fs->op.flock)
+
2069 return -ENOSYS;
+
2070
+
2071 if (fs->debug) {
+
2072 int xop = op & ~LOCK_NB;
+
2073
+
2074 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s%s\n",
+
2075 (unsigned long long) fi->fh,
+
2076 xop == LOCK_SH ? "LOCK_SH" :
+
2077 (xop == LOCK_EX ? "LOCK_EX" :
+
2078 (xop == LOCK_UN ? "LOCK_UN" : "???")),
+
2079 (op & LOCK_NB) ? "|LOCK_NB" : "");
+
2080 }
+
2081 return fs->op.flock(path, fi, op);
+
2082}
+
2083
+
2084int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid,
+
2085 gid_t gid, struct fuse_file_info *fi)
+
2086{
+
2087 fuse_get_context()->private_data = fs->user_data;
+
2088 if (!fs->op.chown)
+
2089 return -ENOSYS;
+
2090 if (fs->debug) {
+
2091 char buf[10];
+
2092
+
2093 fuse_log(FUSE_LOG_DEBUG, "chown[%s] %s %lu %lu\n",
+
2094 file_info_string(fi, buf, sizeof(buf)),
+
2095 path, (unsigned long) uid, (unsigned long) gid);
+
2096 }
+
2097 return fs->op.chown(path, uid, gid, fi);
+
2098}
+
2099
+
2100int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
2101 struct fuse_file_info *fi)
+
2102{
+
2103 fuse_get_context()->private_data = fs->user_data;
+
2104 if (!fs->op.truncate)
+
2105 return -ENOSYS;
+
2106 if (fs->debug) {
+
2107 char buf[10];
+
2108
+
2109 fuse_log(FUSE_LOG_DEBUG, "truncate[%s] %llu\n",
+
2110 file_info_string(fi, buf, sizeof(buf)),
+
2111 (unsigned long long) size);
+
2112 }
+
2113 return fs->op.truncate(path, size, fi);
+
2114}
+
2115
+
2116int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
2117 const struct timespec tv[2], struct fuse_file_info *fi)
+
2118{
+
2119 fuse_get_context()->private_data = fs->user_data;
+
2120 if (!fs->op.utimens)
+
2121 return -ENOSYS;
+
2122 if (fs->debug) {
+
2123 char buf[10];
+
2124
+
2125 fuse_log(FUSE_LOG_DEBUG, "utimens[%s] %s %jd.%09ld %jd.%09ld\n",
+
2126 file_info_string(fi, buf, sizeof(buf)),
+
2127 path, (intmax_t)tv[0].tv_sec, tv[0].tv_nsec,
+
2128 (intmax_t)tv[1].tv_sec, tv[1].tv_nsec);
+
2129 }
+
2130 return fs->op.utimens(path, tv, fi);
+
2131}
+
2132
+
2133int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask)
+
2134{
+
2135 fuse_get_context()->private_data = fs->user_data;
+
2136 if (!fs->op.access)
+
2137 return -ENOSYS;
+
2138 if (fs->debug)
+
2139 fuse_log(FUSE_LOG_DEBUG, "access %s 0%o\n", path, mask);
+
2140
+
2141 return fs->op.access(path, mask);
+
2142}
+
2143
+
2144int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
2145 size_t len)
+
2146{
+
2147 fuse_get_context()->private_data = fs->user_data;
+
2148 if (!fs->op.readlink)
+
2149 return -ENOSYS;
+
2150 if (fs->debug)
+
2151 fuse_log(FUSE_LOG_DEBUG, "readlink %s %lu\n", path,
+
2152 (unsigned long) len);
+
2153
+
2154 return fs->op.readlink(path, buf, len);
+
2155}
+
2156
+
2157int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2158 dev_t rdev)
+
2159{
+
2160 fuse_get_context()->private_data = fs->user_data;
+
2161 if (!fs->op.mknod)
+
2162 return -ENOSYS;
+
2163 if (fs->debug)
+
2164 fuse_log(FUSE_LOG_DEBUG, "mknod %s 0%o 0x%llx umask=0%03o\n",
+
2165 path, mode, (unsigned long long) rdev,
+
2166 fuse_get_context()->umask);
+
2167
+
2168 return fs->op.mknod(path, mode, rdev);
+
2169}
+
2170
+
2171int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode)
+
2172{
+
2173 fuse_get_context()->private_data = fs->user_data;
+
2174 if (!fs->op.mkdir)
+
2175 return -ENOSYS;
+
2176 if (fs->debug)
+
2177 fuse_log(FUSE_LOG_DEBUG, "mkdir %s 0%o umask=0%03o\n",
+
2178 path, mode, fuse_get_context()->umask);
+
2179
+
2180 return fs->op.mkdir(path, mode);
+
2181}
+
2182
+
2183int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2184 const char *value, size_t size, int flags)
+
2185{
+
2186 fuse_get_context()->private_data = fs->user_data;
+
2187 if (!fs->op.setxattr)
+
2188 return -ENOSYS;
+
2189 if (fs->debug)
+
2190 fuse_log(FUSE_LOG_DEBUG, "setxattr %s %s %lu 0x%x\n",
+
2191 path, name, (unsigned long) size, flags);
+
2192
+
2193 return fs->op.setxattr(path, name, value, size, flags);
+
2194}
+
2195
+
2196int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2197 char *value, size_t size)
+
2198{
+
2199 fuse_get_context()->private_data = fs->user_data;
+
2200 if (!fs->op.getxattr)
+
2201 return -ENOSYS;
+
2202 if (fs->debug)
+
2203 fuse_log(FUSE_LOG_DEBUG, "getxattr %s %s %lu\n",
+
2204 path, name, (unsigned long) size);
+
2205
+
2206 return fs->op.getxattr(path, name, value, size);
+
2207}
+
2208
+
2209int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
2210 size_t size)
+
2211{
+
2212 fuse_get_context()->private_data = fs->user_data;
+
2213 if (!fs->op.listxattr)
+
2214 return -ENOSYS;
+
2215 if (fs->debug)
+
2216 fuse_log(FUSE_LOG_DEBUG, "listxattr %s %lu\n",
+
2217 path, (unsigned long) size);
+
2218
+
2219 return fs->op.listxattr(path, list, size);
+
2220}
+
2221
+
2222int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
2223 uint64_t *idx)
+
2224{
+
2225 fuse_get_context()->private_data = fs->user_data;
+
2226 if (!fs->op.bmap)
+
2227 return -ENOSYS;
+
2228 if (fs->debug)
+
2229 fuse_log(FUSE_LOG_DEBUG, "bmap %s blocksize: %lu index: %llu\n",
+
2230 path, (unsigned long) blocksize,
+
2231 (unsigned long long) *idx);
+
2232
+
2233 return fs->op.bmap(path, blocksize, idx);
+
2234}
+
2235
+
2236int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name)
+
2237{
+
2238 fuse_get_context()->private_data = fs->user_data;
+
2239 if (!fs->op.removexattr)
+
2240 return -ENOSYS;
+
2241 if (fs->debug)
+
2242 fuse_log(FUSE_LOG_DEBUG, "removexattr %s %s\n", path, name);
+
2243
+
2244 return fs->op.removexattr(path, name);
+
2245}
+
2246
+
2247int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
2248 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
2249 void *data)
+
2250{
+
2251 fuse_get_context()->private_data = fs->user_data;
+
2252 if (!fs->op.ioctl)
+
2253 return -ENOSYS;
+
2254 if (fs->debug)
+
2255 fuse_log(FUSE_LOG_DEBUG, "ioctl[%llu] 0x%x flags: 0x%x\n",
+
2256 (unsigned long long) fi->fh, cmd, flags);
+
2257
+
2258 return fs->op.ioctl(path, cmd, arg, fi, flags, data);
+
2259}
+
2260
+
2261int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
2262 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
2263 unsigned *reventsp)
+
2264{
+
2265 int res;
+
2266
+
2267 fuse_get_context()->private_data = fs->user_data;
+
2268 if (!fs->op.poll)
+
2269 return -ENOSYS;
+
2270 if (fs->debug)
+
2271 fuse_log(FUSE_LOG_DEBUG, "poll[%llu] ph: %p, events 0x%x\n",
+
2272 (unsigned long long) fi->fh, ph,
+
2273 fi->poll_events);
+
2274
+
2275 res = fs->op.poll(path, fi, ph, reventsp);
+
2276
+
2277 if (fs->debug && !res)
+
2278 fuse_log(FUSE_LOG_DEBUG, " poll[%llu] revents: 0x%x\n",
+
2279 (unsigned long long) fi->fh, *reventsp);
+
2280
+
2281 return res;
+
2282}
+
2283
+
2284int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
2285 off_t offset, off_t length, struct fuse_file_info *fi)
+
2286{
+
2287 fuse_get_context()->private_data = fs->user_data;
+
2288 if (!fs->op.fallocate)
+
2289 return -ENOSYS;
+
2290 if (fs->debug)
+
2291 fuse_log(FUSE_LOG_DEBUG, "fallocate %s mode %x, offset: %llu, length: %llu\n",
+
2292 path,
+
2293 mode,
+
2294 (unsigned long long) offset,
+
2295 (unsigned long long) length);
+
2296
+
2297 return fs->op.fallocate(path, mode, offset, length, fi);
+
2298}
+
2299
+
2300ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
2301 struct fuse_file_info *fi_in, off_t off_in,
+
2302 const char *path_out,
+
2303 struct fuse_file_info *fi_out, off_t off_out,
+
2304 size_t len, int flags)
+
2305{
+
2306 fuse_get_context()->private_data = fs->user_data;
+
2307 if (!fs->op.copy_file_range)
+
2308 return -ENOSYS;
+
2309 if (fs->debug)
+
2310 fuse_log(FUSE_LOG_DEBUG, "copy_file_range from %s:%llu to "
+
2311 "%s:%llu, length: %llu\n",
+
2312 path_in,
+
2313 (unsigned long long) off_in,
+
2314 path_out,
+
2315 (unsigned long long) off_out,
+
2316 (unsigned long long) len);
+
2317
+
2318 return fs->op.copy_file_range(path_in, fi_in, off_in, path_out,
+
2319 fi_out, off_out, len, flags);
+
2320}
+
2321
+
2322off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
2323 struct fuse_file_info *fi)
+
2324{
+
2325 fuse_get_context()->private_data = fs->user_data;
+
2326 if (!fs->op.lseek)
+
2327 return -ENOSYS;
+
2328 if (fs->debug) {
+
2329 char buf[10];
+
2330
+
2331 fuse_log(FUSE_LOG_DEBUG, "lseek[%s] %llu %d\n",
+
2332 file_info_string(fi, buf, sizeof(buf)),
+
2333 (unsigned long long) off, whence);
+
2334 }
+
2335 return fs->op.lseek(path, off, whence, fi);
+
2336}
+
2337
+
2338#ifdef HAVE_STATX
+
2339int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask,
+
2340 struct statx *stxbuf, struct fuse_file_info *fi)
+
2341{
+
2342 fuse_get_context()->private_data = fs->user_data;
+
2343 if (fs->op.statx) {
+
2344 if (fs->debug) {
+
2345 char buf[10];
+
2346
+
2347 fuse_log(FUSE_LOG_DEBUG, "statx[%s] %s %d %d\n",
+
2348 file_info_string(fi, buf, sizeof(buf)), path,
+
2349 flags, mask);
+
2350 }
+
2351 return fs->op.statx(path, flags, mask, stxbuf, fi);
+
2352 }
+
2353
+
2354 return -ENOSYS;
+
2355}
+
2356#else
+
2357int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask,
+
2358 struct statx *stxbuf, struct fuse_file_info *fi)
+
2359{
+
2360 (void)fs;
+
2361 (void)path;
+
2362 (void)flags;
+
2363 (void)mask;
+
2364 (void)stxbuf;
+
2365 (void)fi;
+
2366
+
2367 return -ENOSYS;
+
2368}
+
2369#endif
+
2370
+
2371static int is_open(struct fuse *f, fuse_ino_t dir, const char *name)
+
2372{
+
2373 struct node *node;
+
2374 int isopen = 0;
+
2375 pthread_mutex_lock(&f->lock);
+
2376 node = lookup_node(f, dir, name);
+
2377 if (node && node->open_count > 0)
+
2378 isopen = 1;
+
2379 pthread_mutex_unlock(&f->lock);
+
2380 return isopen;
+
2381}
+
2382
+
2383static char *hidden_name(struct fuse *f, fuse_ino_t dir, const char *oldname,
+
2384 char *newname, size_t bufsize)
+
2385{
+
2386 struct stat buf;
+
2387 struct node *node;
+
2388 struct node *newnode;
+
2389 char *newpath;
+
2390 int res;
+
2391 int failctr = 10;
+
2392
+
2393 do {
+
2394 pthread_mutex_lock(&f->lock);
+
2395 node = lookup_node(f, dir, oldname);
+
2396 if (node == NULL) {
+
2397 pthread_mutex_unlock(&f->lock);
+
2398 return NULL;
+
2399 }
+
2400 do {
+
2401 f->hidectr ++;
+
2402 snprintf(newname, bufsize, ".fuse_hidden%08x%08x",
+
2403 (unsigned int) node->nodeid, f->hidectr);
+
2404 newnode = lookup_node(f, dir, newname);
+
2405 } while(newnode);
+
2406
+
2407 res = try_get_path(f, dir, newname, &newpath, NULL, false);
+
2408 pthread_mutex_unlock(&f->lock);
+
2409 if (res)
+
2410 break;
+
2411
+
2412 memset(&buf, 0, sizeof(buf));
+
2413 res = fuse_fs_getattr(f->fs, newpath, &buf, NULL);
+
2414 if (res == -ENOENT)
+
2415 break;
+
2416 free(newpath);
+
2417 newpath = NULL;
+
2418 } while(res == 0 && --failctr);
+
2419
+
2420 return newpath;
+
2421}
+
2422
+
2423static int hide_node(struct fuse *f, const char *oldpath,
+
2424 fuse_ino_t dir, const char *oldname)
+
2425{
+
2426 char newname[64];
+
2427 char *newpath;
+
2428 int err = -EBUSY;
+
2429
+
2430 newpath = hidden_name(f, dir, oldname, newname, sizeof(newname));
+
2431 if (newpath) {
+
2432 err = fuse_fs_rename(f->fs, oldpath, newpath, 0);
+
2433 if (!err)
+
2434 err = rename_node(f, dir, oldname, dir, newname, 1);
+
2435 free(newpath);
+
2436 }
+
2437 return err;
+
2438}
+
2439
+
2440static int mtime_eq(const struct stat *stbuf, const struct timespec *ts)
+
2441{
+
2442 return stbuf->st_mtime == ts->tv_sec &&
+
2443 ST_MTIM_NSEC(stbuf) == ts->tv_nsec;
+
2444}
+
2445
+
2446#ifndef CLOCK_MONOTONIC
+
2447#define CLOCK_MONOTONIC CLOCK_REALTIME
+
2448#endif
+
2449
+
2450static void curr_time(struct timespec *now)
+
2451{
+
2452 static clockid_t clockid = CLOCK_MONOTONIC;
+
2453 int res = clock_gettime(clockid, now);
+
2454 if (res == -1 && errno == EINVAL) {
+
2455 clockid = CLOCK_REALTIME;
+
2456 res = clock_gettime(clockid, now);
+
2457 }
+
2458 if (res == -1) {
+
2459 perror("fuse: clock_gettime");
+
2460 abort();
+
2461 }
+
2462}
+
2463
+
2464static void update_stat(struct node *node, const struct stat *stbuf)
+
2465{
+
2466 if (node->cache_valid && (!mtime_eq(stbuf, &node->mtime) ||
+
2467 stbuf->st_size != node->size))
+
2468 node->cache_valid = 0;
+
2469 node->mtime.tv_sec = stbuf->st_mtime;
+
2470 node->mtime.tv_nsec = ST_MTIM_NSEC(stbuf);
+
2471 node->size = stbuf->st_size;
+
2472 curr_time(&node->stat_updated);
+
2473}
+
2474
+
2475static int do_lookup(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
2476 struct fuse_entry_param *e)
+
2477{
+
2478 struct node *node;
+
2479
+
2480 node = find_node(f, nodeid, name);
+
2481 if (node == NULL)
+
2482 return -ENOMEM;
+
2483
+
2484 e->ino = node->nodeid;
+
2485 e->generation = node->generation;
+
2486 e->entry_timeout = f->conf.entry_timeout;
+
2487 e->attr_timeout = f->conf.attr_timeout;
+
2488 if (f->conf.auto_cache) {
+
2489 pthread_mutex_lock(&f->lock);
+
2490 update_stat(node, &e->attr);
+
2491 pthread_mutex_unlock(&f->lock);
+
2492 }
+
2493 set_stat(f, e->ino, &e->attr);
+
2494 return 0;
+
2495}
+
2496
+
2497static int lookup_path(struct fuse *f, fuse_ino_t nodeid,
+
2498 const char *name, const char *path,
+
2499 struct fuse_entry_param *e, struct fuse_file_info *fi)
+
2500{
+
2501 int res;
+
2502
+
2503 memset(e, 0, sizeof(struct fuse_entry_param));
+
2504 res = fuse_fs_getattr(f->fs, path, &e->attr, fi);
+
2505 if (res == 0) {
+
2506 res = do_lookup(f, nodeid, name, e);
+
2507 if (res == 0 && f->conf.debug) {
+
2508 fuse_log(FUSE_LOG_DEBUG, " NODEID: %llu\n",
+
2509 (unsigned long long) e->ino);
+
2510 }
+
2511 }
+
2512 return res;
+
2513}
+
2514
+
2515static struct fuse_context_i *fuse_get_context_internal(void)
+
2516{
+
2517 return (struct fuse_context_i *) pthread_getspecific(fuse_context_key);
+
2518}
+
2519
+
2520static struct fuse_context_i *fuse_create_context(struct fuse *f)
+
2521{
+
2522 struct fuse_context_i *c = fuse_get_context_internal();
+
2523 if (c == NULL) {
+
2524 c = (struct fuse_context_i *)
+
2525 calloc(1, sizeof(struct fuse_context_i));
+
2526 if (c == NULL) {
+
2527 /* This is hard to deal with properly, so just
+
2528 abort. If memory is so low that the
+
2529 context cannot be allocated, there's not
+
2530 much hope for the filesystem anyway */
+
2531 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate thread specific data\n");
+
2532 abort();
+
2533 }
+
2534 pthread_setspecific(fuse_context_key, c);
+
2535 } else {
+
2536 memset(c, 0, sizeof(*c));
+
2537 }
+
2538 c->ctx.fuse = f;
+
2539
+
2540 return c;
+
2541}
+
2542
+
2543static void fuse_freecontext(void *data)
+
2544{
+
2545 free(data);
+
2546}
+
2547
+
2548static int fuse_create_context_key(void)
+
2549{
+
2550 int err = 0;
+
2551 pthread_mutex_lock(&fuse_context_lock);
+
2552 if (!fuse_context_ref) {
+
2553 err = pthread_key_create(&fuse_context_key, fuse_freecontext);
+
2554 if (err) {
+
2555 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
2556 strerror(err));
+
2557 pthread_mutex_unlock(&fuse_context_lock);
+
2558 return -1;
+
2559 }
+
2560 }
+
2561 fuse_context_ref++;
+
2562 pthread_mutex_unlock(&fuse_context_lock);
+
2563 return 0;
+
2564}
+
2565
+
2566static void fuse_delete_context_key(void)
+
2567{
+
2568 pthread_mutex_lock(&fuse_context_lock);
+
2569 fuse_context_ref--;
+
2570 if (!fuse_context_ref) {
+
2571 free(pthread_getspecific(fuse_context_key));
+
2572 pthread_key_delete(fuse_context_key);
+
2573 }
+
2574 pthread_mutex_unlock(&fuse_context_lock);
+
2575}
+
2576
+
2577static struct fuse *req_fuse_prepare(fuse_req_t req)
+
2578{
+
2579 struct fuse_context_i *c = fuse_create_context(req_fuse(req));
+
2580 const struct fuse_ctx *ctx = fuse_req_ctx(req);
+
2581 c->req = req;
+
2582 c->ctx.uid = ctx->uid;
+
2583 c->ctx.gid = ctx->gid;
+
2584 c->ctx.pid = ctx->pid;
+
2585 c->ctx.umask = ctx->umask;
+
2586 return c->ctx.fuse;
+
2587}
+
2588
+
2589static inline void reply_err(fuse_req_t req, int err)
+
2590{
+
2591 /* fuse_reply_err() uses non-negated errno values */
+
2592 fuse_reply_err(req, -err);
+
2593}
+
2594
+
2595static void reply_entry(fuse_req_t req, const struct fuse_entry_param *e,
+
2596 int err)
+
2597{
+
2598 if (!err) {
+
2599 struct fuse *f = req_fuse(req);
+
2600 if (fuse_reply_entry(req, e) == -ENOENT) {
+
2601 /* Skip forget for negative result */
+
2602 if (e->ino != 0)
+
2603 forget_node(f, e->ino, 1);
+
2604 }
+
2605 } else
+
2606 reply_err(req, err);
+
2607}
+
2608
+
2609void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
2610 struct fuse_config *cfg)
+
2611{
+
2612 fuse_get_context()->private_data = fs->user_data;
+
2613 if (!fs->op.write_buf)
+ +
2615 if (!fs->op.lock)
+ +
2617 if (!fs->op.flock)
+ +
2619 if (fs->op.init)
+
2620 fs->user_data = fs->op.init(conn, cfg);
+
2621}
+
2622
+
2623static int fuse_init_intr_signal(int signum, int *installed);
+
2624
+
2625static void fuse_lib_init(void *data, struct fuse_conn_info *conn)
+
2626{
+
2627 struct fuse *f = (struct fuse *) data;
+
2628
+
2629 fuse_create_context(f);
+ +
2631 fuse_fs_init(f->fs, conn, &f->conf);
+
2632
+
2633 if (f->conf.intr) {
+
2634 if (fuse_init_intr_signal(f->conf.intr_signal,
+
2635 &f->intr_installed) == -1)
+
2636 fuse_log(FUSE_LOG_ERR, "fuse: failed to init interrupt signal\n");
+
2637 } else {
+
2638 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
2639 conn->no_interrupt = 1;
+
2640 }
+
2641}
+
2642
+
2643void fuse_fs_destroy(struct fuse_fs *fs)
+
2644{
+
2645 fuse_get_context()->private_data = fs->user_data;
+
2646 if (fs->op.destroy)
+
2647 fs->op.destroy(fs->user_data);
+
2648}
+
2649
+
2650static void fuse_lib_destroy(void *data)
+
2651{
+
2652 struct fuse *f = (struct fuse *) data;
+
2653
+
2654 fuse_create_context(f);
+
2655 fuse_fs_destroy(f->fs);
+
2656}
+
2657
+
2658static void fuse_lib_lookup(fuse_req_t req, fuse_ino_t parent,
+
2659 const char *name)
+
2660{
+
2661 struct fuse *f = req_fuse_prepare(req);
+
2662 struct fuse_entry_param e = { .ino = 0 }; /* invalid ino */
+
2663 char *path;
+
2664 int err;
+
2665 struct node *dot = NULL;
+
2666
+
2667 if (name[0] == '.') {
+
2668 int len = strlen(name);
+
2669
+
2670 if (len == 1 || (name[1] == '.' && len == 2)) {
+
2671 pthread_mutex_lock(&f->lock);
+
2672 if (len == 1) {
+
2673 if (f->conf.debug)
+
2674 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOT\n");
+
2675 dot = get_node_nocheck(f, parent);
+
2676 if (dot == NULL) {
+
2677 pthread_mutex_unlock(&f->lock);
+
2678 reply_entry(req, &e, -ESTALE);
+
2679 return;
+
2680 }
+
2681 dot->refctr++;
+
2682 } else {
+
2683 if (f->conf.debug)
+
2684 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOTDOT\n");
+
2685 parent = get_node(f, parent)->parent->nodeid;
+
2686 }
+
2687 pthread_mutex_unlock(&f->lock);
+
2688 name = NULL;
+
2689 }
+
2690 }
+
2691
+
2692 err = get_path_name(f, parent, name, &path);
+
2693 if (!err) {
+
2694 struct fuse_intr_data d;
+
2695 if (f->conf.debug)
+
2696 fuse_log(FUSE_LOG_DEBUG, "LOOKUP %s\n", path);
+
2697 fuse_prepare_interrupt(f, req, &d);
+
2698 err = lookup_path(f, parent, name, path, &e, NULL);
+
2699 if (err == -ENOENT && f->conf.negative_timeout != 0.0) {
+
2700 e.ino = 0;
+
2701 e.entry_timeout = f->conf.negative_timeout;
+
2702 err = 0;
+
2703 }
+
2704 fuse_finish_interrupt(f, req, &d);
+
2705 free_path(f, parent, path);
+
2706 }
+
2707 if (dot) {
+
2708 pthread_mutex_lock(&f->lock);
+
2709 unref_node(f, dot);
+
2710 pthread_mutex_unlock(&f->lock);
+
2711 }
+
2712 reply_entry(req, &e, err);
+
2713}
+
2714
+
2715static void do_forget(struct fuse *f, fuse_ino_t ino, uint64_t nlookup)
+
2716{
+
2717 if (f->conf.debug)
+
2718 fuse_log(FUSE_LOG_DEBUG, "FORGET %llu/%llu\n", (unsigned long long)ino,
+
2719 (unsigned long long) nlookup);
+
2720 forget_node(f, ino, nlookup);
+
2721}
+
2722
+
2723static void fuse_lib_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
2724{
+
2725 do_forget(req_fuse(req), ino, nlookup);
+
2726 fuse_reply_none(req);
+
2727}
+
2728
+
2729static void fuse_lib_forget_multi(fuse_req_t req, size_t count,
+
2730 struct fuse_forget_data *forgets)
+
2731{
+
2732 struct fuse *f = req_fuse(req);
+
2733 size_t i;
+
2734
+
2735 for (i = 0; i < count; i++)
+
2736 do_forget(f, forgets[i].ino, forgets[i].nlookup);
+
2737
+
2738 fuse_reply_none(req);
+
2739}
+
2740
+
2741
+
2742static void fuse_lib_getattr(fuse_req_t req, fuse_ino_t ino,
+
2743 struct fuse_file_info *fi)
+
2744{
+
2745 struct fuse *f = req_fuse_prepare(req);
+
2746 struct stat buf;
+
2747 char *path;
+
2748 int err;
+
2749
+
2750 memset(&buf, 0, sizeof(buf));
+
2751
+
2752 if (fi != NULL)
+
2753 err = get_path_nullok(f, ino, &path);
+
2754 else
+
2755 err = get_path(f, ino, &path);
+
2756 if (!err) {
+
2757 struct fuse_intr_data d;
+
2758 fuse_prepare_interrupt(f, req, &d);
+
2759 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2760 fuse_finish_interrupt(f, req, &d);
+
2761 free_path(f, ino, path);
+
2762 }
+
2763 if (!err) {
+
2764 struct node *node;
+
2765
+
2766 pthread_mutex_lock(&f->lock);
+
2767 node = get_node(f, ino);
+
2768 if (node->is_hidden && buf.st_nlink > 0)
+
2769 buf.st_nlink--;
+
2770 if (f->conf.auto_cache)
+
2771 update_stat(node, &buf);
+
2772 pthread_mutex_unlock(&f->lock);
+
2773 set_stat(f, ino, &buf);
+
2774 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2775 } else
+
2776 reply_err(req, err);
+
2777}
+
2778
+
2779int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2780 struct fuse_file_info *fi)
+
2781{
+
2782 fuse_get_context()->private_data = fs->user_data;
+
2783 if (!fs->op.chmod)
+
2784 return -ENOSYS;
+
2785
+
2786 if (fs->debug) {
+
2787 char buf[10];
+
2788
+
2789 fuse_log(FUSE_LOG_DEBUG, "chmod[%s] %s %llo\n",
+
2790 file_info_string(fi, buf, sizeof(buf)),
+
2791 path, (unsigned long long) mode);
+
2792 }
+
2793 return fs->op.chmod(path, mode, fi);
+
2794}
+
2795
+
2796static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
2797 int valid, struct fuse_file_info *fi)
+
2798{
+
2799 struct fuse *f = req_fuse_prepare(req);
+
2800 struct stat buf;
+
2801 char *path;
+
2802 int err;
+
2803
+
2804 memset(&buf, 0, sizeof(buf));
+
2805 if (fi != NULL)
+
2806 err = get_path_nullok(f, ino, &path);
+
2807 else
+
2808 err = get_path(f, ino, &path);
+
2809 if (!err) {
+
2810 struct fuse_intr_data d;
+
2811 fuse_prepare_interrupt(f, req, &d);
+
2812 err = 0;
+
2813 if (!err && (valid & FUSE_SET_ATTR_MODE))
+
2814 err = fuse_fs_chmod(f->fs, path, attr->st_mode, fi);
+
2815 if (!err && (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))) {
+
2816 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
2817 attr->st_uid : (uid_t) -1;
+
2818 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
2819 attr->st_gid : (gid_t) -1;
+
2820 err = fuse_fs_chown(f->fs, path, uid, gid, fi);
+
2821 }
+
2822 if (!err && (valid & FUSE_SET_ATTR_SIZE)) {
+
2823 err = fuse_fs_truncate(f->fs, path,
+
2824 attr->st_size, fi);
+
2825 }
+
2826#ifdef HAVE_UTIMENSAT
+
2827 if (!err &&
+
2828 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) {
+
2829 struct timespec tv[2];
+
2830
+
2831 tv[0].tv_sec = 0;
+
2832 tv[1].tv_sec = 0;
+
2833 tv[0].tv_nsec = UTIME_OMIT;
+
2834 tv[1].tv_nsec = UTIME_OMIT;
+
2835
+
2836 if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
2837 tv[0].tv_nsec = UTIME_NOW;
+
2838 else if (valid & FUSE_SET_ATTR_ATIME)
+
2839 tv[0] = attr->st_atim;
+
2840
+
2841 if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
2842 tv[1].tv_nsec = UTIME_NOW;
+
2843 else if (valid & FUSE_SET_ATTR_MTIME)
+
2844 tv[1] = attr->st_mtim;
+
2845
+
2846 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2847 } else
+
2848#endif
+
2849 if (!err &&
+
2850 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) ==
+
2851 (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
2852 struct timespec tv[2];
+
2853 tv[0].tv_sec = attr->st_atime;
+
2854 tv[0].tv_nsec = ST_ATIM_NSEC(attr);
+
2855 tv[1].tv_sec = attr->st_mtime;
+
2856 tv[1].tv_nsec = ST_MTIM_NSEC(attr);
+
2857 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2858 }
+
2859 if (!err) {
+
2860 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2861 }
+
2862 fuse_finish_interrupt(f, req, &d);
+
2863 free_path(f, ino, path);
+
2864 }
+
2865 if (!err) {
+
2866 if (f->conf.auto_cache) {
+
2867 pthread_mutex_lock(&f->lock);
+
2868 update_stat(get_node(f, ino), &buf);
+
2869 pthread_mutex_unlock(&f->lock);
+
2870 }
+
2871 set_stat(f, ino, &buf);
+
2872 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2873 } else
+
2874 reply_err(req, err);
+
2875}
+
2876
+
2877static void fuse_lib_access(fuse_req_t req, fuse_ino_t ino, int mask)
+
2878{
+
2879 struct fuse *f = req_fuse_prepare(req);
+
2880 char *path;
+
2881 int err;
+
2882
+
2883 err = get_path(f, ino, &path);
+
2884 if (!err) {
+
2885 struct fuse_intr_data d;
+
2886
+
2887 fuse_prepare_interrupt(f, req, &d);
+
2888 err = fuse_fs_access(f->fs, path, mask);
+
2889 fuse_finish_interrupt(f, req, &d);
+
2890 free_path(f, ino, path);
+
2891 }
+
2892 reply_err(req, err);
+
2893}
+
2894
+
2895static void fuse_lib_readlink(fuse_req_t req, fuse_ino_t ino)
+
2896{
+
2897 struct fuse *f = req_fuse_prepare(req);
+
2898 char linkname[PATH_MAX + 1];
+
2899 char *path;
+
2900 int err;
+
2901
+
2902 err = get_path(f, ino, &path);
+
2903 if (!err) {
+
2904 struct fuse_intr_data d;
+
2905 fuse_prepare_interrupt(f, req, &d);
+
2906 err = fuse_fs_readlink(f->fs, path, linkname, sizeof(linkname));
+
2907 fuse_finish_interrupt(f, req, &d);
+
2908 free_path(f, ino, path);
+
2909 }
+
2910 if (!err) {
+
2911 linkname[PATH_MAX] = '\0';
+
2912 fuse_reply_readlink(req, linkname);
+
2913 } else
+
2914 reply_err(req, err);
+
2915}
+
2916
+
2917static void fuse_lib_mknod(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2918 mode_t mode, dev_t rdev)
+
2919{
+
2920 struct fuse *f = req_fuse_prepare(req);
+
2921 struct fuse_entry_param e;
+
2922 char *path;
+
2923 int err;
+
2924
+
2925 err = get_path_name(f, parent, name, &path);
+
2926 if (!err) {
+
2927 struct fuse_intr_data d;
+
2928
+
2929 fuse_prepare_interrupt(f, req, &d);
+
2930 err = -ENOSYS;
+
2931 if (S_ISREG(mode)) {
+
2932 struct fuse_file_info fi;
+
2933
+
2934 memset(&fi, 0, sizeof(fi));
+
2935 fi.flags = O_CREAT | O_EXCL | O_WRONLY;
+
2936 err = fuse_fs_create(f->fs, path, mode, &fi);
+
2937 if (!err) {
+
2938 err = lookup_path(f, parent, name, path, &e,
+
2939 &fi);
+
2940 fuse_fs_release(f->fs, path, &fi);
+
2941 }
+
2942 }
+
2943 if (err == -ENOSYS) {
+
2944 err = fuse_fs_mknod(f->fs, path, mode, rdev);
+
2945 if (!err)
+
2946 err = lookup_path(f, parent, name, path, &e,
+
2947 NULL);
+
2948 }
+
2949 fuse_finish_interrupt(f, req, &d);
+
2950 free_path(f, parent, path);
+
2951 }
+
2952 reply_entry(req, &e, err);
+
2953}
+
2954
+
2955static void fuse_lib_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2956 mode_t mode)
+
2957{
+
2958 struct fuse *f = req_fuse_prepare(req);
+
2959 struct fuse_entry_param e;
+
2960 char *path;
+
2961 int err;
+
2962
+
2963 err = get_path_name(f, parent, name, &path);
+
2964 if (!err) {
+
2965 struct fuse_intr_data d;
+
2966
+
2967 fuse_prepare_interrupt(f, req, &d);
+
2968 err = fuse_fs_mkdir(f->fs, path, mode);
+
2969 if (!err)
+
2970 err = lookup_path(f, parent, name, path, &e, NULL);
+
2971 fuse_finish_interrupt(f, req, &d);
+
2972 free_path(f, parent, path);
+
2973 }
+
2974 reply_entry(req, &e, err);
+
2975}
+
2976
+
2977static void fuse_lib_unlink(fuse_req_t req, fuse_ino_t parent,
+
2978 const char *name)
+
2979{
+
2980 struct fuse *f = req_fuse_prepare(req);
+
2981 struct node *wnode;
+
2982 char *path;
+
2983 int err;
+
2984
+
2985 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
2986 if (!err) {
+
2987 struct fuse_intr_data d;
+
2988
+
2989 fuse_prepare_interrupt(f, req, &d);
+
2990 if (!f->conf.hard_remove && is_open(f, parent, name)) {
+
2991 err = hide_node(f, path, parent, name);
+
2992 if (!err) {
+
2993 /* we have hidden the node so now check again under a lock in case it is not used any more */
+
2994 if (!is_open(f, parent, wnode->name)) {
+
2995 char *unlinkpath;
+
2996
+
2997 /* get the hidden file path, to unlink it */
+
2998 if (try_get_path(f, wnode->nodeid, NULL, &unlinkpath, NULL, false) == 0) {
+
2999 err = fuse_fs_unlink(f->fs, unlinkpath);
+
3000 if (!err)
+
3001 remove_node(f, parent, wnode->name);
+
3002 free(unlinkpath);
+
3003 }
+
3004 }
+
3005 }
+
3006 } else {
+
3007 err = fuse_fs_unlink(f->fs, path);
+
3008 if (!err)
+
3009 remove_node(f, parent, name);
+
3010 }
+
3011 fuse_finish_interrupt(f, req, &d);
+
3012 free_path_wrlock(f, parent, wnode, path);
+
3013 }
+
3014 reply_err(req, err);
+
3015}
+
3016
+
3017static void fuse_lib_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
3018{
+
3019 struct fuse *f = req_fuse_prepare(req);
+
3020 struct node *wnode;
+
3021 char *path;
+
3022 int err;
+
3023
+
3024 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
3025 if (!err) {
+
3026 struct fuse_intr_data d;
+
3027
+
3028 fuse_prepare_interrupt(f, req, &d);
+
3029 err = fuse_fs_rmdir(f->fs, path);
+
3030 fuse_finish_interrupt(f, req, &d);
+
3031 if (!err)
+
3032 remove_node(f, parent, name);
+
3033 free_path_wrlock(f, parent, wnode, path);
+
3034 }
+
3035 reply_err(req, err);
+
3036}
+
3037
+
3038static void fuse_lib_symlink(fuse_req_t req, const char *linkname,
+
3039 fuse_ino_t parent, const char *name)
+
3040{
+
3041 struct fuse *f = req_fuse_prepare(req);
+
3042 struct fuse_entry_param e;
+
3043 char *path;
+
3044 int err;
+
3045
+
3046 err = get_path_name(f, parent, name, &path);
+
3047 if (!err) {
+
3048 struct fuse_intr_data d;
+
3049
+
3050 fuse_prepare_interrupt(f, req, &d);
+
3051 err = fuse_fs_symlink(f->fs, linkname, path);
+
3052 if (!err)
+
3053 err = lookup_path(f, parent, name, path, &e, NULL);
+
3054 fuse_finish_interrupt(f, req, &d);
+
3055 free_path(f, parent, path);
+
3056 }
+
3057 reply_entry(req, &e, err);
+
3058}
+
3059
+
3060static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir,
+
3061 const char *oldname, fuse_ino_t newdir,
+
3062 const char *newname, unsigned int flags)
+
3063{
+
3064 struct fuse *f = req_fuse_prepare(req);
+
3065 char *oldpath;
+
3066 char *newpath;
+
3067 struct node *wnode1;
+
3068 struct node *wnode2;
+
3069 int err;
+
3070
+
3071 err = get_path2(f, olddir, oldname, newdir, newname,
+
3072 &oldpath, &newpath, &wnode1, &wnode2);
+
3073 if (!err) {
+
3074 struct fuse_intr_data d;
+
3075 err = 0;
+
3076 fuse_prepare_interrupt(f, req, &d);
+
3077 if (!f->conf.hard_remove && !(flags & RENAME_EXCHANGE) &&
+
3078 is_open(f, newdir, newname))
+
3079 err = hide_node(f, newpath, newdir, newname);
+
3080 if (!err) {
+
3081 err = fuse_fs_rename(f->fs, oldpath, newpath, flags);
+
3082 if (!err) {
+
3083 if (flags & RENAME_EXCHANGE) {
+
3084 err = exchange_node(f, olddir, oldname,
+
3085 newdir, newname);
+
3086 } else {
+
3087 err = rename_node(f, olddir, oldname,
+
3088 newdir, newname, 0);
+
3089 }
+
3090 }
+
3091 }
+
3092 fuse_finish_interrupt(f, req, &d);
+
3093 free_path2(f, olddir, newdir, wnode1, wnode2, oldpath, newpath);
+
3094 }
+
3095 reply_err(req, err);
+
3096}
+
3097
+
3098static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
3099 const char *newname)
+
3100{
+
3101 struct fuse *f = req_fuse_prepare(req);
+
3102 struct fuse_entry_param e;
+
3103 char *oldpath;
+
3104 char *newpath;
+
3105 int err;
+
3106
+
3107 err = get_path2(f, ino, NULL, newparent, newname,
+
3108 &oldpath, &newpath, NULL, NULL);
+
3109 if (!err) {
+
3110 struct fuse_intr_data d;
+
3111
+
3112 fuse_prepare_interrupt(f, req, &d);
+
3113 err = fuse_fs_link(f->fs, oldpath, newpath);
+
3114 if (!err)
+
3115 err = lookup_path(f, newparent, newname, newpath,
+
3116 &e, NULL);
+
3117 fuse_finish_interrupt(f, req, &d);
+
3118 free_path2(f, ino, newparent, NULL, NULL, oldpath, newpath);
+
3119 }
+
3120 reply_entry(req, &e, err);
+
3121}
+
3122
+
3123static void fuse_do_release(struct fuse *f, fuse_ino_t ino, const char *path,
+
3124 struct fuse_file_info *fi)
+
3125{
+
3126 struct node *node;
+
3127 int unlink_hidden = 0;
+
3128
+
3129 fuse_fs_release(f->fs, path, fi);
+
3130
+
3131 pthread_mutex_lock(&f->lock);
+
3132 node = get_node(f, ino);
+
3133 assert(node->open_count > 0);
+
3134 --node->open_count;
+
3135 if (node->is_hidden && !node->open_count) {
+
3136 unlink_hidden = 1;
+
3137 node->is_hidden = 0;
+
3138 }
+
3139 pthread_mutex_unlock(&f->lock);
+
3140
+
3141 if(unlink_hidden) {
+
3142 if (path) {
+
3143 fuse_fs_unlink(f->fs, path);
+
3144 } else if (f->conf.nullpath_ok) {
+
3145 char *unlinkpath;
+
3146
+
3147 if (get_path(f, ino, &unlinkpath) == 0)
+
3148 fuse_fs_unlink(f->fs, unlinkpath);
+
3149
+
3150 free_path(f, ino, unlinkpath);
+
3151 }
+
3152 }
+
3153}
+
3154
+
3155static void fuse_lib_create(fuse_req_t req, fuse_ino_t parent,
+
3156 const char *name, mode_t mode,
+
3157 struct fuse_file_info *fi)
+
3158{
+
3159 struct fuse *f = req_fuse_prepare(req);
+
3160 struct fuse_intr_data d;
+
3161 struct fuse_entry_param e;
+
3162 char *path;
+
3163 int err;
+
3164
+
3165 err = get_path_name(f, parent, name, &path);
+
3166 if (!err) {
+
3167 fuse_prepare_interrupt(f, req, &d);
+
3168 err = fuse_fs_create(f->fs, path, mode, fi);
+
3169 if (!err) {
+
3170 err = lookup_path(f, parent, name, path, &e, fi);
+
3171 if (err)
+
3172 fuse_fs_release(f->fs, path, fi);
+
3173 else if (!S_ISREG(e.attr.st_mode)) {
+
3174 err = -EIO;
+
3175 fuse_fs_release(f->fs, path, fi);
+
3176 forget_node(f, e.ino, 1);
+
3177 } else {
+
3178 if (f->conf.direct_io)
+
3179 fi->direct_io = 1;
+
3180 if (f->conf.kernel_cache)
+
3181 fi->keep_cache = 1;
+
3182 if (fi->direct_io &&
+
3183 f->conf.parallel_direct_writes)
+
3184 fi->parallel_direct_writes = 1;
+
3185 }
+
3186 }
+
3187 fuse_finish_interrupt(f, req, &d);
+
3188 }
+
3189 if (!err) {
+
3190 pthread_mutex_lock(&f->lock);
+
3191 get_node(f, e.ino)->open_count++;
+
3192 pthread_mutex_unlock(&f->lock);
+
3193 if (fuse_reply_create(req, &e, fi) == -ENOENT) {
+
3194 /* The open syscall was interrupted, so it
+
3195 must be cancelled */
+
3196 fuse_do_release(f, e.ino, path, fi);
+
3197 forget_node(f, e.ino, 1);
+
3198 }
+
3199 } else {
+
3200 reply_err(req, err);
+
3201 }
+
3202
+
3203 free_path(f, parent, path);
+
3204}
+
3205
+
3206static double diff_timespec(const struct timespec *t1,
+
3207 const struct timespec *t2)
+
3208{
+
3209 return (t1->tv_sec - t2->tv_sec) +
+
3210 ((double) t1->tv_nsec - (double) t2->tv_nsec) / 1000000000.0;
+
3211}
+
3212
+
3213static void open_auto_cache(struct fuse *f, fuse_ino_t ino, const char *path,
+
3214 struct fuse_file_info *fi)
+
3215{
+
3216 struct node *node;
+
3217
+
3218 pthread_mutex_lock(&f->lock);
+
3219 node = get_node(f, ino);
+
3220 if (node->cache_valid) {
+
3221 struct timespec now;
+
3222
+
3223 curr_time(&now);
+
3224 if (diff_timespec(&now, &node->stat_updated) >
+
3225 f->conf.ac_attr_timeout) {
+
3226 struct stat stbuf;
+
3227 int err;
+
3228 pthread_mutex_unlock(&f->lock);
+
3229 err = fuse_fs_getattr(f->fs, path, &stbuf, fi);
+
3230 pthread_mutex_lock(&f->lock);
+
3231 if (!err)
+
3232 update_stat(node, &stbuf);
+
3233 else
+
3234 node->cache_valid = 0;
+
3235 }
+
3236 }
+
3237 if (node->cache_valid)
+
3238 fi->keep_cache = 1;
+
3239
+
3240 node->cache_valid = 1;
+
3241 pthread_mutex_unlock(&f->lock);
+
3242}
+
3243
+
3244static void fuse_lib_open(fuse_req_t req, fuse_ino_t ino,
+
3245 struct fuse_file_info *fi)
+
3246{
+
3247 struct fuse *f = req_fuse_prepare(req);
+
3248 struct fuse_intr_data d;
+
3249 char *path;
+
3250 int err;
+
3251
+
3252 err = get_path(f, ino, &path);
+
3253 if (!err) {
+
3254 fuse_prepare_interrupt(f, req, &d);
+
3255 err = fuse_fs_open(f->fs, path, fi);
+
3256 if (!err) {
+
3257 if (f->conf.direct_io)
+
3258 fi->direct_io = 1;
+
3259 if (f->conf.kernel_cache)
+
3260 fi->keep_cache = 1;
+
3261
+
3262 if (f->conf.auto_cache)
+
3263 open_auto_cache(f, ino, path, fi);
+
3264
+
3265 if (f->conf.no_rofd_flush &&
+
3266 (fi->flags & O_ACCMODE) == O_RDONLY)
+
3267 fi->noflush = 1;
+
3268
+
3269 if (fi->direct_io && f->conf.parallel_direct_writes)
+
3270 fi->parallel_direct_writes = 1;
+
3271
+
3272 }
+
3273 fuse_finish_interrupt(f, req, &d);
+
3274 }
+
3275 if (!err) {
+
3276 pthread_mutex_lock(&f->lock);
+
3277 get_node(f, ino)->open_count++;
+
3278 pthread_mutex_unlock(&f->lock);
+
3279 if (fuse_reply_open(req, fi) == -ENOENT) {
+
3280 /* The open syscall was interrupted, so it
+
3281 must be cancelled */
+
3282 fuse_do_release(f, ino, path, fi);
+
3283 }
+
3284 } else
+
3285 reply_err(req, err);
+
3286
+
3287 free_path(f, ino, path);
+
3288}
+
3289
+
3290static void fuse_lib_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3291 off_t off, struct fuse_file_info *fi)
+
3292{
+
3293 struct fuse *f = req_fuse_prepare(req);
+
3294 struct fuse_bufvec *buf = NULL;
+
3295 char *path;
+
3296 int res;
+
3297
+
3298 res = get_path_nullok(f, ino, &path);
+
3299 if (res == 0) {
+
3300 struct fuse_intr_data d;
+
3301
+
3302 fuse_prepare_interrupt(f, req, &d);
+
3303 res = fuse_fs_read_buf(f->fs, path, &buf, size, off, fi);
+
3304 fuse_finish_interrupt(f, req, &d);
+
3305 free_path(f, ino, path);
+
3306 }
+
3307
+
3308 if (res == 0)
+ +
3310 else
+
3311 reply_err(req, res);
+
3312
+
3313 fuse_free_buf(buf);
+
3314}
+
3315
+
3316static void fuse_lib_write_buf(fuse_req_t req, fuse_ino_t ino,
+
3317 struct fuse_bufvec *buf, off_t off,
+
3318 struct fuse_file_info *fi)
+
3319{
+
3320 struct fuse *f = req_fuse_prepare(req);
+
3321 char *path;
+
3322 int res;
+
3323
+
3324 res = get_path_nullok(f, ino, &path);
+
3325 if (res == 0) {
+
3326 struct fuse_intr_data d;
+
3327
+
3328 fuse_prepare_interrupt(f, req, &d);
+
3329 res = fuse_fs_write_buf(f->fs, path, buf, off, fi);
+
3330 fuse_finish_interrupt(f, req, &d);
+
3331 free_path(f, ino, path);
+
3332 }
+
3333
+
3334 if (res >= 0)
+
3335 fuse_reply_write(req, res);
+
3336 else
+
3337 reply_err(req, res);
+
3338}
+
3339
+
3340static void fuse_lib_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3341 struct fuse_file_info *fi)
+
3342{
+
3343 struct fuse *f = req_fuse_prepare(req);
+
3344 char *path;
+
3345 int err;
+
3346
+
3347 err = get_path_nullok(f, ino, &path);
+
3348 if (!err) {
+
3349 struct fuse_intr_data d;
+
3350
+
3351 fuse_prepare_interrupt(f, req, &d);
+
3352 err = fuse_fs_fsync(f->fs, path, datasync, fi);
+
3353 fuse_finish_interrupt(f, req, &d);
+
3354 free_path(f, ino, path);
+
3355 }
+
3356 reply_err(req, err);
+
3357}
+
3358
+
3359static struct fuse_dh *get_dirhandle(const struct fuse_file_info *llfi,
+
3360 struct fuse_file_info *fi)
+
3361{
+
3362 struct fuse_dh *dh = (struct fuse_dh *) (uintptr_t) llfi->fh;
+
3363 memset(fi, 0, sizeof(struct fuse_file_info));
+
3364 fi->fh = dh->fh;
+
3365 return dh;
+
3366}
+
3367
+
3368static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino,
+
3369 struct fuse_file_info *llfi)
+
3370{
+
3371 struct fuse *f = req_fuse_prepare(req);
+
3372 struct fuse_intr_data d;
+
3373 struct fuse_dh *dh;
+
3374 struct fuse_file_info fi;
+
3375 char *path;
+
3376 int err;
+
3377
+
3378 dh = (struct fuse_dh *) malloc(sizeof(struct fuse_dh));
+
3379 if (dh == NULL) {
+
3380 reply_err(req, -ENOMEM);
+
3381 return;
+
3382 }
+
3383 memset(dh, 0, sizeof(struct fuse_dh));
+
3384 dh->fuse = f;
+
3385 dh->contents = NULL;
+
3386 dh->first = NULL;
+
3387 dh->len = 0;
+
3388 dh->filled = 0;
+
3389 dh->nodeid = ino;
+
3390 pthread_mutex_init(&dh->lock, NULL);
+
3391
+
3392 llfi->fh = (uintptr_t) dh;
+
3393
+
3394 memset(&fi, 0, sizeof(fi));
+
3395 fi.flags = llfi->flags;
+
3396
+
3397 err = get_path(f, ino, &path);
+
3398 if (!err) {
+
3399 fuse_prepare_interrupt(f, req, &d);
+
3400 err = fuse_fs_opendir(f->fs, path, &fi);
+
3401 fuse_finish_interrupt(f, req, &d);
+
3402 dh->fh = fi.fh;
+
3403 llfi->cache_readdir = fi.cache_readdir;
+
3404 llfi->keep_cache = fi.keep_cache;
+
3405 }
+
3406 if (!err) {
+
3407 if (fuse_reply_open(req, llfi) == -ENOENT) {
+
3408 /* The opendir syscall was interrupted, so it
+
3409 must be cancelled */
+
3410 fuse_fs_releasedir(f->fs, path, &fi);
+
3411 pthread_mutex_destroy(&dh->lock);
+
3412 free(dh);
+
3413 }
+
3414 } else {
+
3415 reply_err(req, err);
+
3416 pthread_mutex_destroy(&dh->lock);
+
3417 free(dh);
+
3418 }
+
3419 free_path(f, ino, path);
+
3420}
+
3421
+
3422static int extend_contents(struct fuse_dh *dh, unsigned minsize)
+
3423{
+
3424 if (minsize > dh->size) {
+
3425 char *newptr;
+
3426 unsigned newsize = dh->size;
+
3427 if (!newsize)
+
3428 newsize = 1024;
+
3429 while (newsize < minsize) {
+
3430 if (newsize >= 0x80000000)
+
3431 newsize = 0xffffffff;
+
3432 else
+
3433 newsize *= 2;
+
3434 }
+
3435
+
3436 newptr = (char *) realloc(dh->contents, newsize);
+
3437 if (!newptr) {
+
3438 dh->error = -ENOMEM;
+
3439 return -1;
+
3440 }
+
3441 dh->contents = newptr;
+
3442 dh->size = newsize;
+
3443 }
+
3444 return 0;
+
3445}
+
3446
+
3447static int fuse_add_direntry_to_dh(struct fuse_dh *dh, const char *name,
+
3448 struct stat *st, enum fuse_fill_dir_flags flags)
+
3449{
+
3450 struct fuse_direntry *de;
+
3451
+
3452 de = malloc(sizeof(struct fuse_direntry));
+
3453 if (!de) {
+
3454 dh->error = -ENOMEM;
+
3455 return -1;
+
3456 }
+
3457 de->name = strdup(name);
+
3458 if (!de->name) {
+
3459 dh->error = -ENOMEM;
+
3460 free(de);
+
3461 return -1;
+
3462 }
+
3463 de->flags = flags;
+
3464 de->stat = *st;
+
3465 de->next = NULL;
+
3466
+
3467 *dh->last = de;
+
3468 dh->last = &de->next;
+
3469
+
3470 return 0;
+
3471}
+
3472
+
3473static fuse_ino_t lookup_nodeid(struct fuse *f, fuse_ino_t parent,
+
3474 const char *name)
+
3475{
+
3476 struct node *node;
+
3477 fuse_ino_t res = FUSE_UNKNOWN_INO;
+
3478
+
3479 pthread_mutex_lock(&f->lock);
+
3480 node = lookup_node(f, parent, name);
+
3481 if (node)
+
3482 res = node->nodeid;
+
3483 pthread_mutex_unlock(&f->lock);
+
3484
+
3485 return res;
+
3486}
+
3487
+
3488static int fill_dir(void *dh_, const char *name, const struct stat *statp,
+
3489 off_t off, enum fuse_fill_dir_flags flags)
+
3490{
+
3491 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3492 struct stat stbuf;
+
3493
+
3494 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3495 dh->error = -EIO;
+
3496 return 1;
+
3497 }
+
3498
+
3499 if (statp)
+
3500 stbuf = *statp;
+
3501 else {
+
3502 memset(&stbuf, 0, sizeof(stbuf));
+
3503 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3504 }
+
3505
+
3506 if (!dh->fuse->conf.use_ino) {
+
3507 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3508 if (dh->fuse->conf.readdir_ino) {
+
3509 stbuf.st_ino = (ino_t)
+
3510 lookup_nodeid(dh->fuse, dh->nodeid, name);
+
3511 }
+
3512 }
+
3513
+
3514 if (off) {
+
3515 size_t newlen;
+
3516
+
3517 if (dh->filled) {
+
3518 dh->error = -EIO;
+
3519 return 1;
+
3520 }
+
3521
+
3522 if (dh->first) {
+
3523 dh->error = -EIO;
+
3524 return 1;
+
3525 }
+
3526
+
3527 if (extend_contents(dh, dh->needlen) == -1)
+
3528 return 1;
+
3529
+
3530 newlen = dh->len +
+
3531 fuse_add_direntry(dh->req, dh->contents + dh->len,
+
3532 dh->needlen - dh->len, name,
+
3533 &stbuf, off);
+
3534 if (newlen > dh->needlen)
+
3535 return 1;
+
3536
+
3537 dh->len = newlen;
+
3538 } else {
+
3539 dh->filled = 1;
+
3540
+
3541 if (fuse_add_direntry_to_dh(dh, name, &stbuf, flags) == -1)
+
3542 return 1;
+
3543 }
+
3544 return 0;
+
3545}
+
3546
+
3547static int is_dot_or_dotdot(const char *name)
+
3548{
+
3549 return name[0] == '.' && (name[1] == '\0' ||
+
3550 (name[1] == '.' && name[2] == '\0'));
+
3551}
+
3552
+
3553static int fill_dir_plus(void *dh_, const char *name, const struct stat *statp,
+
3554 off_t off, enum fuse_fill_dir_flags flags)
+
3555{
+
3556 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3557 struct fuse_entry_param e = {
+
3558 /* ino=0 tells the kernel to ignore readdirplus stat info */
+
3559 .ino = 0,
+
3560 };
+
3561 struct fuse *f = dh->fuse;
+
3562 int res;
+
3563
+
3564 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3565 dh->error = -EIO;
+
3566 return 1;
+
3567 }
+
3568
+
3569 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3570 e.attr = *statp;
+
3571 }
+
3572
+
3573 e.attr.st_ino = FUSE_UNKNOWN_INO;
+
3574 if (statp) {
+
3575 e.attr.st_mode = statp->st_mode;
+
3576 if (f->conf.use_ino)
+
3577 e.attr.st_ino = statp->st_ino;
+
3578 }
+
3579 if (!f->conf.use_ino && f->conf.readdir_ino) {
+
3580 e.attr.st_ino = (ino_t)
+
3581 lookup_nodeid(f, dh->nodeid, name);
+
3582 }
+
3583
+
3584 if (off) {
+
3585 size_t newlen;
+
3586
+
3587 if (dh->filled) {
+
3588 dh->error = -EIO;
+
3589 return 1;
+
3590 }
+
3591
+
3592 if (dh->first) {
+
3593 dh->error = -EIO;
+
3594 return 1;
+
3595 }
+
3596 if (extend_contents(dh, dh->needlen) == -1)
+
3597 return 1;
+
3598
+
3599 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3600 if (!is_dot_or_dotdot(name)) {
+
3601 res = do_lookup(f, dh->nodeid, name, &e);
+
3602 if (res) {
+
3603 dh->error = res;
+
3604 return 1;
+
3605 }
+
3606 }
+
3607 }
+
3608
+
3609 newlen = dh->len +
+
3610 fuse_add_direntry_plus(dh->req, dh->contents + dh->len,
+
3611 dh->needlen - dh->len, name,
+
3612 &e, off);
+
3613 if (newlen > dh->needlen)
+
3614 return 1;
+
3615 dh->len = newlen;
+
3616 } else {
+
3617 dh->filled = 1;
+
3618
+
3619 if (fuse_add_direntry_to_dh(dh, name, &e.attr, flags) == -1)
+
3620 return 1;
+
3621 }
+
3622
+
3623 return 0;
+
3624}
+
3625
+
3626static void free_direntries(struct fuse_direntry *de)
+
3627{
+
3628 while (de) {
+
3629 struct fuse_direntry *next = de->next;
+
3630 free(de->name);
+
3631 free(de);
+
3632 de = next;
+
3633 }
+
3634}
+
3635
+
3636static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3637 size_t size, off_t off, struct fuse_dh *dh,
+
3638 struct fuse_file_info *fi,
+
3639 enum fuse_readdir_flags flags)
+
3640{
+
3641 char *path;
+
3642 int err;
+
3643
+
3644 if (f->fs->op.readdir)
+
3645 err = get_path_nullok(f, ino, &path);
+
3646 else
+
3647 err = get_path(f, ino, &path);
+
3648 if (!err) {
+
3649 struct fuse_intr_data d;
+
3650 fuse_fill_dir_t filler = fill_dir;
+
3651
+
3652 if (flags & FUSE_READDIR_PLUS)
+
3653 filler = fill_dir_plus;
+
3654
+
3655 free_direntries(dh->first);
+
3656 dh->first = NULL;
+
3657 dh->last = &dh->first;
+
3658 dh->len = 0;
+
3659 dh->error = 0;
+
3660 dh->needlen = size;
+
3661 dh->filled = 0;
+
3662 dh->req = req;
+
3663 fuse_prepare_interrupt(f, req, &d);
+
3664 err = fuse_fs_readdir(f->fs, path, dh, filler, off, fi, flags);
+
3665 fuse_finish_interrupt(f, req, &d);
+
3666 dh->req = NULL;
+
3667 if (!err)
+
3668 err = dh->error;
+
3669 if (err)
+
3670 dh->filled = 0;
+
3671 free_path(f, ino, path);
+
3672 }
+
3673 return err;
+
3674}
+
3675
+
3676static int readdir_fill_from_list(fuse_req_t req, struct fuse_dh *dh,
+
3677 off_t off, enum fuse_readdir_flags flags)
+
3678{
+
3679 off_t pos;
+
3680 struct fuse_direntry *de = dh->first;
+
3681 int res;
+
3682
+
3683 dh->len = 0;
+
3684
+
3685 if (extend_contents(dh, dh->needlen) == -1)
+
3686 return dh->error;
+
3687
+
3688 for (pos = 0; pos < off; pos++) {
+
3689 if (!de)
+
3690 break;
+
3691
+
3692 de = de->next;
+
3693 }
+
3694 while (de) {
+
3695 char *p = dh->contents + dh->len;
+
3696 unsigned rem = dh->needlen - dh->len;
+
3697 unsigned thislen;
+
3698 unsigned newlen;
+
3699 pos++;
+
3700
+
3701 if (flags & FUSE_READDIR_PLUS) {
+
3702 struct fuse_entry_param e = {
+
3703 .ino = 0,
+
3704 .attr = de->stat,
+
3705 };
+
3706
+
3707 if (de->flags & FUSE_FILL_DIR_PLUS &&
+
3708 !is_dot_or_dotdot(de->name)) {
+
3709 res = do_lookup(dh->fuse, dh->nodeid,
+
3710 de->name, &e);
+
3711 if (res) {
+
3712 dh->error = res;
+
3713 return 1;
+
3714 }
+
3715 }
+
3716
+
3717 thislen = fuse_add_direntry_plus(req, p, rem,
+
3718 de->name, &e, pos);
+
3719 } else {
+
3720 thislen = fuse_add_direntry(req, p, rem,
+
3721 de->name, &de->stat, pos);
+
3722 }
+
3723 newlen = dh->len + thislen;
+
3724 if (newlen > dh->needlen)
+
3725 break;
+
3726 dh->len = newlen;
+
3727 de = de->next;
+
3728 }
+
3729 return 0;
+
3730}
+
3731
+
3732static void fuse_readdir_common(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3733 off_t off, struct fuse_file_info *llfi,
+
3734 enum fuse_readdir_flags flags)
+
3735{
+
3736 struct fuse *f = req_fuse_prepare(req);
+
3737 struct fuse_file_info fi;
+
3738 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3739 int err;
+
3740
+
3741 pthread_mutex_lock(&dh->lock);
+
3742 /* According to SUS, directory contents need to be refreshed on
+
3743 rewinddir() */
+
3744 if (!off)
+
3745 dh->filled = 0;
+
3746
+
3747 if (!dh->filled) {
+
3748 err = readdir_fill(f, req, ino, size, off, dh, &fi, flags);
+
3749 if (err) {
+
3750 reply_err(req, err);
+
3751 goto out;
+
3752 }
+
3753 }
+
3754 if (dh->filled) {
+
3755 dh->needlen = size;
+
3756 err = readdir_fill_from_list(req, dh, off, flags);
+
3757 if (err) {
+
3758 reply_err(req, err);
+
3759 goto out;
+
3760 }
+
3761 }
+
3762 fuse_reply_buf(req, dh->contents, dh->len);
+
3763out:
+
3764 pthread_mutex_unlock(&dh->lock);
+
3765}
+
3766
+
3767static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3768 off_t off, struct fuse_file_info *llfi)
+
3769{
+
3770 fuse_readdir_common(req, ino, size, off, llfi, 0);
+
3771}
+
3772
+
3773static void fuse_lib_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3774 off_t off, struct fuse_file_info *llfi)
+
3775{
+
3776 fuse_readdir_common(req, ino, size, off, llfi, FUSE_READDIR_PLUS);
+
3777}
+
3778
+
3779static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino,
+
3780 struct fuse_file_info *llfi)
+
3781{
+
3782 struct fuse *f = req_fuse_prepare(req);
+
3783 struct fuse_intr_data d;
+
3784 struct fuse_file_info fi;
+
3785 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3786 char *path;
+
3787
+
3788 get_path_nullok(f, ino, &path);
+
3789
+
3790 fuse_prepare_interrupt(f, req, &d);
+
3791 fuse_fs_releasedir(f->fs, path, &fi);
+
3792 fuse_finish_interrupt(f, req, &d);
+
3793 free_path(f, ino, path);
+
3794
+
3795 pthread_mutex_lock(&dh->lock);
+
3796 pthread_mutex_unlock(&dh->lock);
+
3797 pthread_mutex_destroy(&dh->lock);
+
3798 free_direntries(dh->first);
+
3799 free(dh->contents);
+
3800 free(dh);
+
3801 reply_err(req, 0);
+
3802}
+
3803
+
3804static void fuse_lib_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3805 struct fuse_file_info *llfi)
+
3806{
+
3807 struct fuse *f = req_fuse_prepare(req);
+
3808 struct fuse_file_info fi;
+
3809 char *path;
+
3810 int err;
+
3811
+
3812 get_dirhandle(llfi, &fi);
+
3813
+
3814 err = get_path_nullok(f, ino, &path);
+
3815 if (!err) {
+
3816 struct fuse_intr_data d;
+
3817 fuse_prepare_interrupt(f, req, &d);
+
3818 err = fuse_fs_fsyncdir(f->fs, path, datasync, &fi);
+
3819 fuse_finish_interrupt(f, req, &d);
+
3820 free_path(f, ino, path);
+
3821 }
+
3822 reply_err(req, err);
+
3823}
+
3824
+
3825static void fuse_lib_statfs(fuse_req_t req, fuse_ino_t ino)
+
3826{
+
3827 struct fuse *f = req_fuse_prepare(req);
+
3828 struct statvfs buf;
+
3829 char *path = NULL;
+
3830 int err = 0;
+
3831
+
3832 memset(&buf, 0, sizeof(buf));
+
3833 if (ino)
+
3834 err = get_path(f, ino, &path);
+
3835
+
3836 if (!err) {
+
3837 struct fuse_intr_data d;
+
3838 fuse_prepare_interrupt(f, req, &d);
+
3839 err = fuse_fs_statfs(f->fs, path ? path : "/", &buf);
+
3840 fuse_finish_interrupt(f, req, &d);
+
3841 free_path(f, ino, path);
+
3842 }
+
3843
+
3844 if (!err)
+
3845 fuse_reply_statfs(req, &buf);
+
3846 else
+
3847 reply_err(req, err);
+
3848}
+
3849
+
3850static void fuse_lib_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3851 const char *value, size_t size, int flags)
+
3852{
+
3853 struct fuse *f = req_fuse_prepare(req);
+
3854 char *path;
+
3855 int err;
+
3856
+
3857 err = get_path(f, ino, &path);
+
3858 if (!err) {
+
3859 struct fuse_intr_data d;
+
3860 fuse_prepare_interrupt(f, req, &d);
+
3861 err = fuse_fs_setxattr(f->fs, path, name, value, size, flags);
+
3862 fuse_finish_interrupt(f, req, &d);
+
3863 free_path(f, ino, path);
+
3864 }
+
3865 reply_err(req, err);
+
3866}
+
3867
+
3868static int common_getxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3869 const char *name, char *value, size_t size)
+
3870{
+
3871 int err;
+
3872 char *path;
+
3873
+
3874 err = get_path(f, ino, &path);
+
3875 if (!err) {
+
3876 struct fuse_intr_data d;
+
3877 fuse_prepare_interrupt(f, req, &d);
+
3878 err = fuse_fs_getxattr(f->fs, path, name, value, size);
+
3879 fuse_finish_interrupt(f, req, &d);
+
3880 free_path(f, ino, path);
+
3881 }
+
3882 return err;
+
3883}
+
3884
+
3885static void fuse_lib_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3886 size_t size)
+
3887{
+
3888 struct fuse *f = req_fuse_prepare(req);
+
3889 int res;
+
3890
+
3891 if (size) {
+
3892 char *value = (char *) malloc(size);
+
3893 if (value == NULL) {
+
3894 reply_err(req, -ENOMEM);
+
3895 return;
+
3896 }
+
3897 res = common_getxattr(f, req, ino, name, value, size);
+
3898 if (res > 0)
+
3899 fuse_reply_buf(req, value, res);
+
3900 else
+
3901 reply_err(req, res);
+
3902 free(value);
+
3903 } else {
+
3904 res = common_getxattr(f, req, ino, name, NULL, 0);
+
3905 if (res >= 0)
+
3906 fuse_reply_xattr(req, res);
+
3907 else
+
3908 reply_err(req, res);
+
3909 }
+
3910}
+
3911
+
3912static int common_listxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3913 char *list, size_t size)
+
3914{
+
3915 char *path;
+
3916 int err;
+
3917
+
3918 err = get_path(f, ino, &path);
+
3919 if (!err) {
+
3920 struct fuse_intr_data d;
+
3921 fuse_prepare_interrupt(f, req, &d);
+
3922 err = fuse_fs_listxattr(f->fs, path, list, size);
+
3923 fuse_finish_interrupt(f, req, &d);
+
3924 free_path(f, ino, path);
+
3925 }
+
3926 return err;
+
3927}
+
3928
+
3929static void fuse_lib_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
3930{
+
3931 struct fuse *f = req_fuse_prepare(req);
+
3932 int res;
+
3933
+
3934 if (size) {
+
3935 char *list = (char *) malloc(size);
+
3936 if (list == NULL) {
+
3937 reply_err(req, -ENOMEM);
+
3938 return;
+
3939 }
+
3940 res = common_listxattr(f, req, ino, list, size);
+
3941 if (res > 0)
+
3942 fuse_reply_buf(req, list, res);
+
3943 else
+
3944 reply_err(req, res);
+
3945 free(list);
+
3946 } else {
+
3947 res = common_listxattr(f, req, ino, NULL, 0);
+
3948 if (res >= 0)
+
3949 fuse_reply_xattr(req, res);
+
3950 else
+
3951 reply_err(req, res);
+
3952 }
+
3953}
+
3954
+
3955static void fuse_lib_removexattr(fuse_req_t req, fuse_ino_t ino,
+
3956 const char *name)
+
3957{
+
3958 struct fuse *f = req_fuse_prepare(req);
+
3959 char *path;
+
3960 int err;
+
3961
+
3962 err = get_path(f, ino, &path);
+
3963 if (!err) {
+
3964 struct fuse_intr_data d;
+
3965 fuse_prepare_interrupt(f, req, &d);
+
3966 err = fuse_fs_removexattr(f->fs, path, name);
+
3967 fuse_finish_interrupt(f, req, &d);
+
3968 free_path(f, ino, path);
+
3969 }
+
3970 reply_err(req, err);
+
3971}
+
3972
+
3973static struct lock *locks_conflict(struct node *node, const struct lock *lock)
+
3974{
+
3975 struct lock *l;
+
3976
+
3977 for (l = node->locks; l; l = l->next)
+
3978 if (l->owner != lock->owner &&
+
3979 lock->start <= l->end && l->start <= lock->end &&
+
3980 (l->type == F_WRLCK || lock->type == F_WRLCK))
+
3981 break;
+
3982
+
3983 return l;
+
3984}
+
3985
+
3986static void delete_lock(struct lock **lockp)
+
3987{
+
3988 struct lock *l = *lockp;
+
3989 *lockp = l->next;
+
3990 free(l);
+
3991}
+
3992
+
3993static void insert_lock(struct lock **pos, struct lock *lock)
+
3994{
+
3995 lock->next = *pos;
+
3996 *pos = lock;
+
3997}
+
3998
+
3999static int locks_insert(struct node *node, struct lock *lock)
+
4000{
+
4001 struct lock **lp;
+
4002 struct lock *newl1 = NULL;
+
4003 struct lock *newl2 = NULL;
+
4004
+
4005 if (lock->type != F_UNLCK || lock->start != 0 ||
+
4006 lock->end != OFFSET_MAX) {
+
4007 newl1 = malloc(sizeof(struct lock));
+
4008 newl2 = malloc(sizeof(struct lock));
+
4009
+
4010 if (!newl1 || !newl2) {
+
4011 free(newl1);
+
4012 free(newl2);
+
4013 return -ENOLCK;
+
4014 }
+
4015 }
+
4016
+
4017 for (lp = &node->locks; *lp;) {
+
4018 struct lock *l = *lp;
+
4019 if (l->owner != lock->owner)
+
4020 goto skip;
+
4021
+
4022 if (lock->type == l->type) {
+
4023 if (l->end < lock->start - 1)
+
4024 goto skip;
+
4025 if (lock->end < l->start - 1)
+
4026 break;
+
4027 if (l->start <= lock->start && lock->end <= l->end)
+
4028 goto out;
+
4029 if (l->start < lock->start)
+
4030 lock->start = l->start;
+
4031 if (lock->end < l->end)
+
4032 lock->end = l->end;
+
4033 goto delete;
+
4034 } else {
+
4035 if (l->end < lock->start)
+
4036 goto skip;
+
4037 if (lock->end < l->start)
+
4038 break;
+
4039 if (lock->start <= l->start && l->end <= lock->end)
+
4040 goto delete;
+
4041 if (l->end <= lock->end) {
+
4042 l->end = lock->start - 1;
+
4043 goto skip;
+
4044 }
+
4045 if (lock->start <= l->start) {
+
4046 l->start = lock->end + 1;
+
4047 break;
+
4048 }
+
4049 *newl2 = *l;
+
4050 newl2->start = lock->end + 1;
+
4051 l->end = lock->start - 1;
+
4052 insert_lock(&l->next, newl2);
+
4053 newl2 = NULL;
+
4054 }
+
4055 skip:
+
4056 lp = &l->next;
+
4057 continue;
+
4058
+
4059 delete:
+
4060 delete_lock(lp);
+
4061 }
+
4062 if (lock->type != F_UNLCK) {
+
4063 *newl1 = *lock;
+
4064 insert_lock(lp, newl1);
+
4065 newl1 = NULL;
+
4066 }
+
4067out:
+
4068 free(newl1);
+
4069 free(newl2);
+
4070 return 0;
+
4071}
+
4072
+
4073static void flock_to_lock(struct flock *flock, struct lock *lock)
+
4074{
+
4075 memset(lock, 0, sizeof(struct lock));
+
4076 lock->type = flock->l_type;
+
4077 lock->start = flock->l_start;
+
4078 lock->end =
+
4079 flock->l_len ? flock->l_start + flock->l_len - 1 : OFFSET_MAX;
+
4080 lock->pid = flock->l_pid;
+
4081}
+
4082
+
4083static void lock_to_flock(struct lock *lock, struct flock *flock)
+
4084{
+
4085 flock->l_type = lock->type;
+
4086 flock->l_start = lock->start;
+
4087 flock->l_len =
+
4088 (lock->end == OFFSET_MAX) ? 0 : lock->end - lock->start + 1;
+
4089 flock->l_pid = lock->pid;
+
4090}
+
4091
+
4092static int fuse_flush_common(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
4093 const char *path, struct fuse_file_info *fi)
+
4094{
+
4095 struct fuse_intr_data d;
+
4096 struct flock lock;
+
4097 struct lock l;
+
4098 int err;
+
4099 int errlock;
+
4100
+
4101 fuse_prepare_interrupt(f, req, &d);
+
4102 memset(&lock, 0, sizeof(lock));
+
4103 lock.l_type = F_UNLCK;
+
4104 lock.l_whence = SEEK_SET;
+
4105 err = fuse_fs_flush(f->fs, path, fi);
+
4106 errlock = fuse_fs_lock(f->fs, path, fi, F_SETLK, &lock);
+
4107 fuse_finish_interrupt(f, req, &d);
+
4108
+
4109 if (errlock != -ENOSYS) {
+
4110 flock_to_lock(&lock, &l);
+
4111 l.owner = fi->lock_owner;
+
4112 pthread_mutex_lock(&f->lock);
+
4113 locks_insert(get_node(f, ino), &l);
+
4114 pthread_mutex_unlock(&f->lock);
+
4115
+
4116 /* if op.lock() is defined FLUSH is needed regardless
+
4117 of op.flush() */
+
4118 if (err == -ENOSYS)
+
4119 err = 0;
+
4120 }
+
4121 return err;
+
4122}
+
4123
+
4124static void fuse_lib_release(fuse_req_t req, fuse_ino_t ino,
+
4125 struct fuse_file_info *fi)
+
4126{
+
4127 struct fuse *f = req_fuse_prepare(req);
+
4128 struct fuse_intr_data d;
+
4129 char *path;
+
4130 int err = 0;
+
4131
+
4132 get_path_nullok(f, ino, &path);
+
4133 if (fi->flush) {
+
4134 err = fuse_flush_common(f, req, ino, path, fi);
+
4135 if (err == -ENOSYS)
+
4136 err = 0;
+
4137 }
+
4138
+
4139 fuse_prepare_interrupt(f, req, &d);
+
4140 fuse_do_release(f, ino, path, fi);
+
4141 fuse_finish_interrupt(f, req, &d);
+
4142 free_path(f, ino, path);
+
4143
+
4144 reply_err(req, err);
+
4145}
+
4146
+
4147static void fuse_lib_flush(fuse_req_t req, fuse_ino_t ino,
+
4148 struct fuse_file_info *fi)
+
4149{
+
4150 struct fuse *f = req_fuse_prepare(req);
+
4151 char *path;
+
4152 int err;
+
4153
+
4154 get_path_nullok(f, ino, &path);
+
4155 err = fuse_flush_common(f, req, ino, path, fi);
+
4156 free_path(f, ino, path);
+
4157
+
4158 reply_err(req, err);
+
4159}
+
4160
+
4161static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino,
+
4162 struct fuse_file_info *fi, struct flock *lock,
+
4163 int cmd)
+
4164{
+
4165 struct fuse *f = req_fuse_prepare(req);
+
4166 char *path;
+
4167 int err;
+
4168
+
4169 err = get_path_nullok(f, ino, &path);
+
4170 if (!err) {
+
4171 struct fuse_intr_data d;
+
4172 fuse_prepare_interrupt(f, req, &d);
+
4173 err = fuse_fs_lock(f->fs, path, fi, cmd, lock);
+
4174 fuse_finish_interrupt(f, req, &d);
+
4175 free_path(f, ino, path);
+
4176 }
+
4177 return err;
+
4178}
+
4179
+
4180static void fuse_lib_getlk(fuse_req_t req, fuse_ino_t ino,
+
4181 struct fuse_file_info *fi, struct flock *lock)
+
4182{
+
4183 int err;
+
4184 struct lock l;
+
4185 struct lock *conflict;
+
4186 struct fuse *f = req_fuse(req);
+
4187
+
4188 flock_to_lock(lock, &l);
+
4189 l.owner = fi->lock_owner;
+
4190 pthread_mutex_lock(&f->lock);
+
4191 conflict = locks_conflict(get_node(f, ino), &l);
+
4192 if (conflict)
+
4193 lock_to_flock(conflict, lock);
+
4194 pthread_mutex_unlock(&f->lock);
+
4195 if (!conflict)
+
4196 err = fuse_lock_common(req, ino, fi, lock, F_GETLK);
+
4197 else
+
4198 err = 0;
+
4199
+
4200 if (!err)
+
4201 fuse_reply_lock(req, lock);
+
4202 else
+
4203 reply_err(req, err);
+
4204}
+
4205
+
4206static void fuse_lib_setlk(fuse_req_t req, fuse_ino_t ino,
+
4207 struct fuse_file_info *fi, struct flock *lock,
+
4208 int sleep)
+
4209{
+
4210 int err = fuse_lock_common(req, ino, fi, lock,
+
4211 sleep ? F_SETLKW : F_SETLK);
+
4212 if (!err) {
+
4213 struct fuse *f = req_fuse(req);
+
4214 struct lock l;
+
4215 flock_to_lock(lock, &l);
+
4216 l.owner = fi->lock_owner;
+
4217 pthread_mutex_lock(&f->lock);
+
4218 locks_insert(get_node(f, ino), &l);
+
4219 pthread_mutex_unlock(&f->lock);
+
4220 }
+
4221 reply_err(req, err);
+
4222}
+
4223
+
4224static void fuse_lib_flock(fuse_req_t req, fuse_ino_t ino,
+
4225 struct fuse_file_info *fi, int op)
+
4226{
+
4227 struct fuse *f = req_fuse_prepare(req);
+
4228 char *path;
+
4229 int err;
+
4230
+
4231 err = get_path_nullok(f, ino, &path);
+
4232 if (err == 0) {
+
4233 struct fuse_intr_data d;
+
4234 fuse_prepare_interrupt(f, req, &d);
+
4235 err = fuse_fs_flock(f->fs, path, fi, op);
+
4236 fuse_finish_interrupt(f, req, &d);
+
4237 free_path(f, ino, path);
+
4238 }
+
4239 reply_err(req, err);
+
4240}
+
4241
+
4242static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
4243 uint64_t idx)
+
4244{
+
4245 struct fuse *f = req_fuse_prepare(req);
+
4246 struct fuse_intr_data d;
+
4247 char *path;
+
4248 int err;
+
4249
+
4250 err = get_path(f, ino, &path);
+
4251 if (!err) {
+
4252 fuse_prepare_interrupt(f, req, &d);
+
4253 err = fuse_fs_bmap(f->fs, path, blocksize, &idx);
+
4254 fuse_finish_interrupt(f, req, &d);
+
4255 free_path(f, ino, path);
+
4256 }
+
4257 if (!err)
+
4258 fuse_reply_bmap(req, idx);
+
4259 else
+
4260 reply_err(req, err);
+
4261}
+
4262
+
4263static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
4264 void *arg, struct fuse_file_info *llfi,
+
4265 unsigned int flags, const void *in_buf,
+
4266 size_t in_bufsz, size_t out_bufsz)
+
4267{
+
4268 struct fuse *f = req_fuse_prepare(req);
+
4269 struct fuse_intr_data d;
+
4270 struct fuse_file_info fi;
+
4271 char *path, *out_buf = NULL;
+
4272 int err;
+
4273
+
4274 err = -EPERM;
+
4275 if (flags & FUSE_IOCTL_UNRESTRICTED)
+
4276 goto err;
+
4277
+
4278 if (flags & FUSE_IOCTL_DIR)
+
4279 get_dirhandle(llfi, &fi);
+
4280 else
+
4281 fi = *llfi;
+
4282
+
4283 if (out_bufsz) {
+
4284 err = -ENOMEM;
+
4285 out_buf = malloc(out_bufsz);
+
4286 if (!out_buf)
+
4287 goto err;
+
4288 }
+
4289
+
4290 assert(!in_bufsz || !out_bufsz || in_bufsz == out_bufsz);
+
4291 if (out_buf && in_bufsz)
+
4292 memcpy(out_buf, in_buf, in_bufsz);
+
4293
+
4294 err = get_path_nullok(f, ino, &path);
+
4295 if (err)
+
4296 goto err;
+
4297
+
4298 fuse_prepare_interrupt(f, req, &d);
+
4299
+
4300 err = fuse_fs_ioctl(f->fs, path, cmd, arg, &fi, flags,
+
4301 out_buf ? out_buf : (void *)in_buf);
+
4302
+
4303 fuse_finish_interrupt(f, req, &d);
+
4304 free_path(f, ino, path);
+
4305
+
4306 if (err < 0)
+
4307 goto err;
+
4308 fuse_reply_ioctl(req, err, out_buf, out_bufsz);
+
4309 goto out;
+
4310err:
+
4311 reply_err(req, err);
+
4312out:
+
4313 free(out_buf);
+
4314}
+
4315
+
4316static void fuse_lib_poll(fuse_req_t req, fuse_ino_t ino,
+
4317 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
4318{
+
4319 struct fuse *f = req_fuse_prepare(req);
+
4320 struct fuse_intr_data d;
+
4321 char *path;
+
4322 int err;
+
4323 unsigned revents = 0;
+
4324
+
4325 err = get_path_nullok(f, ino, &path);
+
4326 if (!err) {
+
4327 fuse_prepare_interrupt(f, req, &d);
+
4328 err = fuse_fs_poll(f->fs, path, fi, ph, &revents);
+
4329 fuse_finish_interrupt(f, req, &d);
+
4330 free_path(f, ino, path);
+
4331 }
+
4332 if (!err)
+
4333 fuse_reply_poll(req, revents);
+
4334 else
+
4335 reply_err(req, err);
+
4336}
+
4337
+
4338static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
4339 off_t offset, off_t length, struct fuse_file_info *fi)
+
4340{
+
4341 struct fuse *f = req_fuse_prepare(req);
+
4342 struct fuse_intr_data d;
+
4343 char *path;
+
4344 int err;
+
4345
+
4346 err = get_path_nullok(f, ino, &path);
+
4347 if (!err) {
+
4348 fuse_prepare_interrupt(f, req, &d);
+
4349 err = fuse_fs_fallocate(f->fs, path, mode, offset, length, fi);
+
4350 fuse_finish_interrupt(f, req, &d);
+
4351 free_path(f, ino, path);
+
4352 }
+
4353 reply_err(req, err);
+
4354}
+
4355
+
4356static void fuse_lib_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in,
+
4357 off_t off_in, struct fuse_file_info *fi_in,
+
4358 fuse_ino_t nodeid_out, off_t off_out,
+
4359 struct fuse_file_info *fi_out, size_t len,
+
4360 int flags)
+
4361{
+
4362 struct fuse *f = req_fuse_prepare(req);
+
4363 struct fuse_intr_data d;
+
4364 char *path_in, *path_out;
+
4365 int err;
+
4366 ssize_t res;
+
4367
+
4368 err = get_path_nullok(f, nodeid_in, &path_in);
+
4369 if (err) {
+
4370 reply_err(req, err);
+
4371 return;
+
4372 }
+
4373
+
4374 err = get_path_nullok(f, nodeid_out, &path_out);
+
4375 if (err) {
+
4376 free_path(f, nodeid_in, path_in);
+
4377 reply_err(req, err);
+
4378 return;
+
4379 }
+
4380
+
4381 fuse_prepare_interrupt(f, req, &d);
+
4382 res = fuse_fs_copy_file_range(f->fs, path_in, fi_in, off_in, path_out,
+
4383 fi_out, off_out, len, flags);
+
4384 fuse_finish_interrupt(f, req, &d);
+
4385
+
4386 if (res >= 0)
+
4387 fuse_reply_write(req, res);
+
4388 else
+
4389 reply_err(req, res);
+
4390
+
4391 free_path(f, nodeid_in, path_in);
+
4392 free_path(f, nodeid_out, path_out);
+
4393}
+
4394
+
4395static void fuse_lib_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
4396 struct fuse_file_info *fi)
+
4397{
+
4398 struct fuse *f = req_fuse_prepare(req);
+
4399 struct fuse_intr_data d;
+
4400 char *path;
+
4401 int err;
+
4402 off_t res;
+
4403
+
4404 err = get_path(f, ino, &path);
+
4405 if (err) {
+
4406 reply_err(req, err);
+
4407 return;
+
4408 }
+
4409
+
4410 fuse_prepare_interrupt(f, req, &d);
+
4411 res = fuse_fs_lseek(f->fs, path, off, whence, fi);
+
4412 fuse_finish_interrupt(f, req, &d);
+
4413 free_path(f, ino, path);
+
4414 if (res >= 0)
+
4415 fuse_reply_lseek(req, res);
+
4416 else
+
4417 reply_err(req, res);
+
4418}
+
4419
+
4420#ifdef HAVE_STATX
+
4421static void fuse_lib_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
+
4422 struct fuse_file_info *fi)
+
4423{
+
4424 struct fuse *f = req_fuse_prepare(req);
+
4425 struct statx stxbuf;
+
4426 char *path;
+
4427 int err;
+
4428
+
4429 memset(&stxbuf, 0, sizeof(stxbuf));
+
4430
+
4431 if (fi != NULL)
+
4432 err = get_path_nullok(f, ino, &path);
+
4433 else
+
4434 err = get_path(f, ino, &path);
+
4435
+
4436 if (!err) {
+
4437 struct fuse_intr_data d;
+
4438
+
4439 if (!path)
+
4440 flags |= AT_EMPTY_PATH;
+
4441 fuse_prepare_interrupt(f, req, &d);
+
4442 err = fuse_fs_statx(f->fs, path, flags, mask, &stxbuf, fi);
+
4443 fuse_finish_interrupt(f, req, &d);
+
4444 free_path(f, ino, path);
+
4445 }
+
4446 if (!err) {
+
4447 struct node *node;
+
4448
+
4449 pthread_mutex_lock(&f->lock);
+
4450 node = get_node(f, ino);
+
4451 if (node->is_hidden && stxbuf.stx_nlink > 0)
+
4452 stxbuf.stx_nlink--;
+
4453 if (f->conf.auto_cache) {
+
4454 struct stat stbuf;
+
4455
+
4456 stbuf.st_mtime = stxbuf.stx_mtime.tv_nsec;
+
4457 ST_MTIM_NSEC(&stbuf) = stxbuf.stx_mtime.tv_nsec;
+
4458 stbuf.st_size = stxbuf.stx_size;
+
4459 update_stat(node, &stbuf);
+
4460 }
+
4461 pthread_mutex_unlock(&f->lock);
+
4462 set_statx(f, ino, &stxbuf);
+
4463 fuse_reply_statx(req, 0, &stxbuf, f->conf.attr_timeout);
+
4464 } else
+
4465 reply_err(req, err);
+
4466}
+
4467#endif
+
4468
+
4469static int clean_delay(struct fuse *f)
+
4470{
+
4471 /*
+
4472 * This is calculating the delay between clean runs. To
+
4473 * reduce the number of cleans we are doing them 10 times
+
4474 * within the remember window.
+
4475 */
+
4476 int min_sleep = 60;
+
4477 int max_sleep = 3600;
+
4478 int sleep_time = f->conf.remember / 10;
+
4479
+
4480 if (sleep_time > max_sleep)
+
4481 return max_sleep;
+
4482 if (sleep_time < min_sleep)
+
4483 return min_sleep;
+
4484 return sleep_time;
+
4485}
+
4486
+
4487int fuse_clean_cache(struct fuse *f)
+
4488{
+
4489 struct node_lru *lnode;
+
4490 struct list_head *curr, *next;
+
4491 struct node *node;
+
4492 struct timespec now;
+
4493
+
4494 pthread_mutex_lock(&f->lock);
+
4495
+
4496 curr_time(&now);
+
4497
+
4498 for (curr = f->lru_table.next; curr != &f->lru_table; curr = next) {
+
4499 double age;
+
4500
+
4501 next = curr->next;
+
4502 lnode = list_entry(curr, struct node_lru, lru);
+
4503 node = &lnode->node;
+
4504
+
4505 age = diff_timespec(&now, &lnode->forget_time);
+
4506 if (age <= f->conf.remember)
+
4507 break;
+
4508
+
4509 assert(node->nlookup == 1);
+
4510
+
4511 /* Don't forget active directories */
+
4512 if (node->refctr > 1)
+
4513 continue;
+
4514
+
4515 node->nlookup = 0;
+
4516 unhash_name(f, node);
+
4517 unref_node(f, node);
+
4518 }
+
4519 pthread_mutex_unlock(&f->lock);
+
4520
+
4521 return clean_delay(f);
+
4522}
+
4523
+
4524static struct fuse_lowlevel_ops fuse_path_ops = {
+
4525 .init = fuse_lib_init,
+
4526 .destroy = fuse_lib_destroy,
+
4527 .lookup = fuse_lib_lookup,
+
4528 .forget = fuse_lib_forget,
+
4529 .forget_multi = fuse_lib_forget_multi,
+
4530 .getattr = fuse_lib_getattr,
+
4531 .setattr = fuse_lib_setattr,
+
4532 .access = fuse_lib_access,
+
4533 .readlink = fuse_lib_readlink,
+
4534 .mknod = fuse_lib_mknod,
+
4535 .mkdir = fuse_lib_mkdir,
+
4536 .unlink = fuse_lib_unlink,
+
4537 .rmdir = fuse_lib_rmdir,
+
4538 .symlink = fuse_lib_symlink,
+
4539 .rename = fuse_lib_rename,
+
4540 .link = fuse_lib_link,
+
4541 .create = fuse_lib_create,
+
4542 .open = fuse_lib_open,
+
4543 .read = fuse_lib_read,
+
4544 .write_buf = fuse_lib_write_buf,
+
4545 .flush = fuse_lib_flush,
+
4546 .release = fuse_lib_release,
+
4547 .fsync = fuse_lib_fsync,
+
4548 .opendir = fuse_lib_opendir,
+
4549 .readdir = fuse_lib_readdir,
+
4550 .readdirplus = fuse_lib_readdirplus,
+
4551 .releasedir = fuse_lib_releasedir,
+
4552 .fsyncdir = fuse_lib_fsyncdir,
+
4553 .statfs = fuse_lib_statfs,
+
4554 .setxattr = fuse_lib_setxattr,
+
4555 .getxattr = fuse_lib_getxattr,
+
4556 .listxattr = fuse_lib_listxattr,
+
4557 .removexattr = fuse_lib_removexattr,
+
4558 .getlk = fuse_lib_getlk,
+
4559 .setlk = fuse_lib_setlk,
+
4560 .flock = fuse_lib_flock,
+
4561 .bmap = fuse_lib_bmap,
+
4562 .ioctl = fuse_lib_ioctl,
+
4563 .poll = fuse_lib_poll,
+
4564 .fallocate = fuse_lib_fallocate,
+
4565 .copy_file_range = fuse_lib_copy_file_range,
+
4566 .lseek = fuse_lib_lseek,
+
4567#ifdef HAVE_STATX
+
4568 .statx = fuse_lib_statx,
+
4569#endif
+
4570};
+
4571
+
4572int fuse_notify_poll(struct fuse_pollhandle *ph)
+
4573{
+
4574 return fuse_lowlevel_notify_poll(ph);
+
4575}
+
4576
+
4577struct fuse_session *fuse_get_session(struct fuse *f)
+
4578{
+
4579 return f->se;
+
4580}
+
4581
+
4582static int fuse_session_loop_remember(struct fuse *f)
+
4583{
+
4584 struct fuse_session *se = f->se;
+
4585 int res = 0;
+
4586 struct timespec now;
+
4587 time_t next_clean;
+
4588 struct pollfd fds = {
+
4589 .fd = se->fd,
+
4590 .events = POLLIN
+
4591 };
+
4592 struct fuse_buf fbuf = {
+
4593 .mem = NULL,
+
4594 };
+
4595
+
4596 curr_time(&now);
+
4597 next_clean = now.tv_sec;
+
4598 while (!fuse_session_exited(se)) {
+
4599 unsigned timeout;
+
4600
+
4601 curr_time(&now);
+
4602 if (now.tv_sec < next_clean)
+
4603 timeout = next_clean - now.tv_sec;
+
4604 else
+
4605 timeout = 0;
+
4606
+
4607 res = poll(&fds, 1, timeout * 1000);
+
4608 if (res == -1) {
+
4609 if (errno == EINTR)
+
4610 continue;
+
4611 else
+
4612 break;
+
4613 } else if (res > 0) {
+
4614 res = fuse_session_receive_buf_internal(se, &fbuf,
+
4615 NULL);
+
4616 if (res == -EINTR)
+
4617 continue;
+
4618 if (res <= 0)
+
4619 break;
+
4620
+
4621 fuse_session_process_buf_internal(se, &fbuf, NULL);
+
4622 } else {
+
4623 timeout = fuse_clean_cache(f);
+
4624 curr_time(&now);
+
4625 next_clean = now.tv_sec + timeout;
+
4626 }
+
4627 }
+
4628
+
4629 fuse_buf_free(&fbuf);
+
4630 return res < 0 ? -1 : 0;
+
4631}
+
4632
+
4633int fuse_loop(struct fuse *f)
+
4634{
+
4635 if (!f)
+
4636 return -1;
+
4637
+
4638 if (lru_enabled(f))
+
4639 return fuse_session_loop_remember(f);
+
4640
+
4641 return fuse_session_loop(f->se);
+
4642}
+
4643
+
4644FUSE_SYMVER("fuse_loop_mt_312", "fuse_loop_mt@@FUSE_3.12")
+
4645int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config)
+
4646{
+
4647 if (f == NULL)
+
4648 return -1;
+
4649
+
4650 int res = fuse_start_cleanup_thread(f);
+
4651 if (res)
+
4652 return -1;
+
4653
+
4654 res = fuse_session_loop_mt_312(fuse_get_session(f), config);
+ +
4656 return res;
+
4657}
+
4658
+
4659int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1);
+
4660FUSE_SYMVER("fuse_loop_mt_32", "fuse_loop_mt@FUSE_3.2")
+
4661int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1)
+
4662{
+
4663 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4664 if (config == NULL)
+
4665 return ENOMEM;
+
4666
+
4667 fuse_loop_cfg_convert(config, config_v1);
+
4668
+
4669 int res = fuse_loop_mt_312(f, config);
+
4670
+
4671 fuse_loop_cfg_destroy(config);
+
4672
+
4673 return res;
+
4674}
+
4675
+
4676int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
4677FUSE_SYMVER("fuse_loop_mt_31", "fuse_loop_mt@FUSE_3.0")
+
4678int fuse_loop_mt_31(struct fuse *f, int clone_fd)
+
4679{
+
4680 int err;
+
4681 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4682
+
4683 if (config == NULL)
+
4684 return ENOMEM;
+
4685
+
4686 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
4687
+
4688 err = fuse_loop_mt_312(f, config);
+
4689
+
4690 fuse_loop_cfg_destroy(config);
+
4691
+
4692 return err;
+
4693}
+
4694
+
4695void fuse_exit(struct fuse *f)
+
4696{
+
4697 fuse_session_exit(f->se);
+
4698}
+
4699
+
4700struct fuse_context *fuse_get_context(void)
+
4701{
+
4702 struct fuse_context_i *c = fuse_get_context_internal();
+
4703
+
4704 if (c)
+
4705 return &c->ctx;
+
4706 else
+
4707 return NULL;
+
4708}
+
4709
+
4710int fuse_getgroups(int size, gid_t list[])
+
4711{
+
4712 struct fuse_context_i *c = fuse_get_context_internal();
+
4713 if (!c)
+
4714 return -EINVAL;
+
4715
+
4716 return fuse_req_getgroups(c->req, size, list);
+
4717}
+
4718
+
4719int fuse_interrupted(void)
+
4720{
+
4721 struct fuse_context_i *c = fuse_get_context_internal();
+
4722
+
4723 if (c)
+
4724 return fuse_req_interrupted(c->req);
+
4725 else
+
4726 return 0;
+
4727}
+
4728
+
4729int fuse_invalidate_path(struct fuse *f, const char *path) {
+
4730 fuse_ino_t ino;
+
4731 int err = lookup_path_in_cache(f, path, &ino);
+
4732 if (err) {
+
4733 return err;
+
4734 }
+
4735
+
4736 return fuse_lowlevel_notify_inval_inode(f->se, ino, 0, 0);
+
4737}
+
4738
+
4739#define FUSE_LIB_OPT(t, p, v) { t, offsetof(struct fuse_config, p), v }
+
4740
+
4741static const struct fuse_opt fuse_lib_opts[] = {
+ + +
4744 FUSE_LIB_OPT("debug", debug, 1),
+
4745 FUSE_LIB_OPT("-d", debug, 1),
+
4746 FUSE_LIB_OPT("kernel_cache", kernel_cache, 1),
+
4747 FUSE_LIB_OPT("auto_cache", auto_cache, 1),
+
4748 FUSE_LIB_OPT("noauto_cache", auto_cache, 0),
+
4749 FUSE_LIB_OPT("no_rofd_flush", no_rofd_flush, 1),
+
4750 FUSE_LIB_OPT("umask=", set_mode, 1),
+
4751 FUSE_LIB_OPT("umask=%o", umask, 0),
+
4752 FUSE_LIB_OPT("fmask=", set_mode, 1),
+
4753 FUSE_LIB_OPT("fmask=%o", fmask, 0),
+
4754 FUSE_LIB_OPT("dmask=", set_mode, 1),
+
4755 FUSE_LIB_OPT("dmask=%o", dmask, 0),
+
4756 FUSE_LIB_OPT("uid=", set_uid, 1),
+
4757 FUSE_LIB_OPT("uid=%d", uid, 0),
+
4758 FUSE_LIB_OPT("gid=", set_gid, 1),
+
4759 FUSE_LIB_OPT("gid=%d", gid, 0),
+
4760 FUSE_LIB_OPT("entry_timeout=%lf", entry_timeout, 0),
+
4761 FUSE_LIB_OPT("attr_timeout=%lf", attr_timeout, 0),
+
4762 FUSE_LIB_OPT("ac_attr_timeout=%lf", ac_attr_timeout, 0),
+
4763 FUSE_LIB_OPT("ac_attr_timeout=", ac_attr_timeout_set, 1),
+
4764 FUSE_LIB_OPT("negative_timeout=%lf", negative_timeout, 0),
+
4765 FUSE_LIB_OPT("noforget", remember, -1),
+
4766 FUSE_LIB_OPT("remember=%u", remember, 0),
+
4767 FUSE_LIB_OPT("modules=%s", modules, 0),
+
4768 FUSE_LIB_OPT("parallel_direct_write=%d", parallel_direct_writes, 0),
+ +
4770};
+
4771
+
4772static int fuse_lib_opt_proc(void *data, const char *arg, int key,
+
4773 struct fuse_args *outargs)
+
4774{
+
4775 (void) arg; (void) outargs; (void) data; (void) key;
+
4776
+
4777 /* Pass through unknown options */
+
4778 return 1;
+
4779}
+
4780
+
4781
+
4782static const struct fuse_opt fuse_help_opts[] = {
+
4783 FUSE_LIB_OPT("modules=%s", modules, 1),
+
4784 FUSE_OPT_KEY("modules=%s", FUSE_OPT_KEY_KEEP),
+ +
4786};
+
4787
+
4788static void print_module_help(const char *name,
+ +
4790{
+
4791 struct fuse_args a = FUSE_ARGS_INIT(0, NULL);
+
4792 if (fuse_opt_add_arg(&a, "") == -1 ||
+
4793 fuse_opt_add_arg(&a, "-h") == -1)
+
4794 return;
+
4795 printf("\nOptions for %s module:\n", name);
+
4796 (*fac)(&a, NULL);
+ +
4798}
+
4799
+
4800void fuse_lib_help(struct fuse_args *args)
+
4801{
+
4802 /* These are not all options, but only the ones that
+
4803 may be of interest to an end-user */
+
4804 printf(
+
4805" -o kernel_cache cache files in kernel\n"
+
4806" -o [no]auto_cache enable caching based on modification times (off)\n"
+
4807" -o no_rofd_flush disable flushing of read-only fd on close (off)\n"
+
4808" -o umask=M set file permissions (octal)\n"
+
4809" -o fmask=M set file permissions (octal)\n"
+
4810" -o dmask=M set dir permissions (octal)\n"
+
4811" -o uid=N set file owner\n"
+
4812" -o gid=N set file group\n"
+
4813" -o entry_timeout=T cache timeout for names (1.0s)\n"
+
4814" -o negative_timeout=T cache timeout for deleted names (0.0s)\n"
+
4815" -o attr_timeout=T cache timeout for attributes (1.0s)\n"
+
4816" -o ac_attr_timeout=T auto cache timeout for attributes (attr_timeout)\n"
+
4817" -o noforget never forget cached inodes\n"
+
4818" -o remember=T remember cached inodes for T seconds (0s)\n"
+
4819" -o modules=M1[:M2...] names of modules to push onto filesystem stack\n");
+
4820
+
4821
+
4822 /* Print low-level help */
+ +
4824
+
4825 /* Print help for builtin modules */
+
4826 print_module_help("subdir", &fuse_module_subdir_factory);
+
4827#ifdef HAVE_ICONV
+
4828 print_module_help("iconv", &fuse_module_iconv_factory);
+
4829#endif
+
4830
+
4831 /* Parse command line options in case we need to
+
4832 activate more modules */
+
4833 struct fuse_config conf = { .modules = NULL };
+
4834 if (fuse_opt_parse(args, &conf, fuse_help_opts,
+
4835 fuse_lib_opt_proc) == -1
+
4836 || !conf.modules)
+
4837 return;
+
4838
+
4839 char *module;
+
4840 char *next;
+
4841 struct fuse_module *m;
+
4842
+
4843 // Iterate over all modules
+
4844 for (module = conf.modules; module; module = next) {
+
4845 char *p;
+
4846 for (p = module; *p && *p != ':'; p++);
+
4847 next = *p ? p + 1 : NULL;
+
4848 *p = '\0';
+
4849
+
4850 m = fuse_get_module(module);
+
4851 if (m)
+
4852 print_module_help(module, &m->factory);
+
4853 }
+
4854}
+
4855
+
4856static int fuse_init_intr_signal(int signum, int *installed)
+
4857{
+
4858 struct sigaction old_sa;
+
4859
+
4860 if (sigaction(signum, NULL, &old_sa) == -1) {
+
4861 perror("fuse: cannot get old signal handler");
+
4862 return -1;
+
4863 }
+
4864
+
4865 if (old_sa.sa_handler == SIG_DFL) {
+
4866 struct sigaction sa;
+
4867
+
4868 memset(&sa, 0, sizeof(struct sigaction));
+
4869 sa.sa_handler = fuse_intr_sighandler;
+
4870 sigemptyset(&sa.sa_mask);
+
4871
+
4872 if (sigaction(signum, &sa, NULL) == -1) {
+
4873 perror("fuse: cannot set interrupt signal handler");
+
4874 return -1;
+
4875 }
+
4876 *installed = 1;
+
4877 }
+
4878 return 0;
+
4879}
+
4880
+
4881static void fuse_restore_intr_signal(int signum)
+
4882{
+
4883 struct sigaction sa;
+
4884
+
4885 memset(&sa, 0, sizeof(struct sigaction));
+
4886 sa.sa_handler = SIG_DFL;
+
4887 sigaction(signum, &sa, NULL);
+
4888}
+
4889
+
4890
+
4891static int fuse_push_module(struct fuse *f, const char *module,
+
4892 struct fuse_args *args)
+
4893{
+
4894 struct fuse_fs *fs[2] = { f->fs, NULL };
+
4895 struct fuse_fs *newfs;
+
4896 struct fuse_module *m = fuse_get_module(module);
+
4897
+
4898 if (!m)
+
4899 return -1;
+
4900
+
4901 newfs = m->factory(args, fs);
+
4902 if (!newfs) {
+
4903 fuse_put_module(m);
+
4904 return -1;
+
4905 }
+
4906 f->fs = newfs;
+
4907 return 0;
+
4908}
+
4909
+
4910struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
4911 void *user_data)
+
4912{
+
4913 struct fuse_fs *fs;
+
4914
+
4915 if (sizeof(struct fuse_operations) < op_size) {
+
4916 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not not work\n");
+
4917 op_size = sizeof(struct fuse_operations);
+
4918 }
+
4919
+
4920 fs = (struct fuse_fs *) calloc(1, sizeof(struct fuse_fs));
+
4921 if (!fs) {
+
4922 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse_fs object\n");
+
4923 return NULL;
+
4924 }
+
4925
+
4926 fs->user_data = user_data;
+
4927 if (op)
+
4928 memcpy(&fs->op, op, op_size);
+
4929 return fs;
+
4930}
+
4931
+
4932static int node_table_init(struct node_table *t)
+
4933{
+
4934 t->size = NODE_TABLE_MIN_SIZE;
+
4935 t->array = (struct node **) calloc(1, sizeof(struct node *) * t->size);
+
4936 if (t->array == NULL) {
+
4937 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
4938 return -1;
+
4939 }
+
4940 t->use = 0;
+
4941 t->split = 0;
+
4942
+
4943 return 0;
+
4944}
+
4945
+
4946static void *fuse_prune_nodes(void *fuse)
+
4947{
+
4948 struct fuse *f = fuse;
+
4949 int sleep_time;
+
4950
+
4951 fuse_set_thread_name("fuse_prune_nodes");
+
4952
+
4953 while(1) {
+
4954 sleep_time = fuse_clean_cache(f);
+
4955 sleep(sleep_time);
+
4956 }
+
4957 return NULL;
+
4958}
+
4959
+
4960int fuse_start_cleanup_thread(struct fuse *f)
+
4961{
+
4962 if (lru_enabled(f))
+
4963 return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f);
+
4964
+
4965 return 0;
+
4966}
+
4967
+
4968void fuse_stop_cleanup_thread(struct fuse *f)
+
4969{
+
4970 if (lru_enabled(f)) {
+
4971 pthread_mutex_lock(&f->lock);
+
4972 pthread_cancel(f->prune_thread);
+
4973 pthread_mutex_unlock(&f->lock);
+
4974 pthread_join(f->prune_thread, NULL);
+
4975 }
+
4976}
+
4977
+
4978/*
+
4979 * Not supposed to be called directly, but supposed to be called
+
4980 * through the fuse_new macro
+
4981 */
+
4982struct fuse *_fuse_new_31(struct fuse_args *args,
+
4983 const struct fuse_operations *op, size_t op_size,
+
4984 struct libfuse_version *version, void *user_data)
+
4985{
+
4986 struct fuse *f;
+
4987 struct node *root;
+
4988 struct fuse_fs *fs;
+
4989 struct fuse_lowlevel_ops llop = fuse_path_ops;
+
4990
+
4991 f = (struct fuse *) calloc(1, sizeof(struct fuse));
+
4992 if (f == NULL) {
+
4993 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
4994 goto out;
+
4995 }
+
4996
+
4997 f->conf.entry_timeout = 1.0;
+
4998 f->conf.attr_timeout = 1.0;
+
4999 f->conf.negative_timeout = 0.0;
+
5000 f->conf.intr_signal = FUSE_DEFAULT_INTR_SIGNAL;
+
5001
+
5002 /* Parse options */
+
5003 if (fuse_opt_parse(args, &f->conf, fuse_lib_opts,
+
5004 fuse_lib_opt_proc) == -1)
+
5005 goto out_free;
+
5006
+
5007 pthread_mutex_lock(&fuse_context_lock);
+
5008 static int builtin_modules_registered = 0;
+
5009 /* Have the builtin modules already been registered? */
+
5010 if (builtin_modules_registered == 0) {
+
5011 /* If not, register them. */
+
5012 fuse_register_module("subdir", fuse_module_subdir_factory, NULL);
+
5013#ifdef HAVE_ICONV
+
5014 fuse_register_module("iconv", fuse_module_iconv_factory, NULL);
+
5015#endif
+
5016 builtin_modules_registered= 1;
+
5017 }
+
5018 pthread_mutex_unlock(&fuse_context_lock);
+
5019
+
5020 if (fuse_create_context_key() == -1)
+
5021 goto out_free;
+
5022
+
5023 fs = fuse_fs_new(op, op_size, user_data);
+
5024 if (!fs)
+
5025 goto out_delete_context_key;
+
5026
+
5027 f->fs = fs;
+
5028
+
5029 /* Oh f**k, this is ugly! */
+
5030 if (!fs->op.lock) {
+
5031 llop.getlk = NULL;
+
5032 llop.setlk = NULL;
+
5033 }
+
5034
+
5035 f->pagesize = getpagesize();
+
5036 init_list_head(&f->partial_slabs);
+
5037 init_list_head(&f->full_slabs);
+
5038 init_list_head(&f->lru_table);
+
5039
+
5040 if (f->conf.modules) {
+
5041 char *module;
+
5042 char *next;
+
5043
+
5044 for (module = f->conf.modules; module; module = next) {
+
5045 char *p;
+
5046 for (p = module; *p && *p != ':'; p++);
+
5047 next = *p ? p + 1 : NULL;
+
5048 *p = '\0';
+
5049 if (module[0] &&
+
5050 fuse_push_module(f, module, args) == -1)
+
5051 goto out_free_fs;
+
5052 }
+
5053 }
+
5054
+
5055 if (!f->conf.ac_attr_timeout_set)
+
5056 f->conf.ac_attr_timeout = f->conf.attr_timeout;
+
5057
+
5058#if defined(__FreeBSD__) || defined(__NetBSD__)
+
5059 /*
+
5060 * In FreeBSD, we always use these settings as inode numbers
+
5061 * are needed to make getcwd(3) work.
+
5062 */
+
5063 f->conf.readdir_ino = 1;
+
5064#endif
+
5065
+
5066 /* not declared globally, to restrict usage of this function */
+
5067 struct fuse_session *fuse_session_new_versioned(
+
5068 struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
5069 size_t op_size, struct libfuse_version *version,
+
5070 void *userdata);
+
5071 f->se = fuse_session_new_versioned(args, &llop, sizeof(llop), version,
+
5072 f);
+
5073 if (f->se == NULL)
+
5074 goto out_free_fs;
+
5075
+
5076 /* Trace topmost layer by default */
+
5077 f->fs->debug = f->conf.debug;
+
5078 f->ctr = 0;
+
5079 f->generation = 0;
+
5080 if (node_table_init(&f->name_table) == -1)
+
5081 goto out_free_session;
+
5082
+
5083 if (node_table_init(&f->id_table) == -1)
+
5084 goto out_free_name_table;
+
5085
+
5086 pthread_mutex_init(&f->lock, NULL);
+
5087
+
5088 root = alloc_node(f);
+
5089 if (root == NULL) {
+
5090 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
5091 goto out_free_id_table;
+
5092 }
+
5093 if (lru_enabled(f)) {
+
5094 struct node_lru *lnode = node_lru(root);
+
5095 init_list_head(&lnode->lru);
+
5096 }
+
5097
+
5098 strcpy(root->inline_name, "/");
+
5099 root->name = root->inline_name;
+
5100 root->parent = NULL;
+
5101 root->nodeid = FUSE_ROOT_ID;
+
5102 inc_nlookup(root);
+
5103 hash_id(f, root);
+
5104
+
5105 return f;
+
5106
+
5107out_free_id_table:
+
5108 free(f->id_table.array);
+
5109out_free_name_table:
+
5110 free(f->name_table.array);
+
5111out_free_session:
+
5112 fuse_session_destroy(f->se);
+
5113out_free_fs:
+
5114 free(f->fs);
+
5115 free(f->conf.modules);
+
5116out_delete_context_key:
+
5117 fuse_delete_context_key();
+
5118out_free:
+
5119 free(f);
+
5120out:
+
5121 return NULL;
+
5122}
+
5123
+
5124/* Emulates 3.0-style fuse_new(), which processes --help */
+
5125FUSE_SYMVER("_fuse_new_30", "_fuse_new@FUSE_3.0")
+
5126struct fuse *_fuse_new_30(struct fuse_args *args,
+
5127 const struct fuse_operations *op,
+
5128 size_t op_size,
+
5129 struct libfuse_version *version,
+
5130 void *user_data)
+
5131{
+
5132 struct fuse_config conf = {0};
+
5133
+
5134 const struct fuse_opt opts[] = {
+
5135 FUSE_LIB_OPT("-h", show_help, 1),
+
5136 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5138 };
+
5139
+
5140 if (fuse_opt_parse(args, &conf, opts,
+
5141 fuse_lib_opt_proc) == -1)
+
5142 return NULL;
+
5143
+
5144 if (conf.show_help) {
+
5145 fuse_lib_help(args);
+
5146 return NULL;
+
5147 } else
+
5148 return _fuse_new_31(args, op, op_size, version, user_data);
+
5149}
+
5150
+
5151/* ABI compat version */
+
5152struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
5153 size_t op_size, void *user_data);
+
5154FUSE_SYMVER("fuse_new_31", "fuse_new@FUSE_3.1")
+
5155struct fuse *fuse_new_31(struct fuse_args *args,
+
5156 const struct fuse_operations *op,
+
5157 size_t op_size, void *user_data)
+
5158{
+
5159 /* unknown version */
+
5160 struct libfuse_version version = { 0 };
+
5161
+
5162 return _fuse_new_31(args, op, op_size, &version, user_data);
+
5163}
+
5164
+
5165/*
+
5166 * ABI compat version
+
5167 * Emulates 3.0-style fuse_new(), which processes --help
+
5168 */
+
5169struct fuse *fuse_new_30(struct fuse_args *args, const struct fuse_operations *op,
+
5170 size_t op_size, void *user_data);
+
5171FUSE_SYMVER("fuse_new_30", "fuse_new@FUSE_3.0")
+
5172struct fuse *fuse_new_30(struct fuse_args *args,
+
5173 const struct fuse_operations *op,
+
5174 size_t op_size, void *user_data)
+
5175{
+
5176 struct fuse_config conf = {0};
+
5177
+
5178 const struct fuse_opt opts[] = {
+
5179 FUSE_LIB_OPT("-h", show_help, 1),
+
5180 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5182 };
+
5183
+
5184 if (fuse_opt_parse(args, &conf, opts,
+
5185 fuse_lib_opt_proc) == -1)
+
5186 return NULL;
+
5187
+
5188 if (conf.show_help) {
+
5189 fuse_lib_help(args);
+
5190 return NULL;
+
5191 } else
+
5192 return fuse_new_31(args, op, op_size, user_data);
+
5193}
+
5194
+
5195
+
5196void fuse_destroy(struct fuse *f)
+
5197{
+
5198 size_t i;
+
5199
+
5200 if (f->conf.intr && f->intr_installed)
+
5201 fuse_restore_intr_signal(f->conf.intr_signal);
+
5202
+
5203 if (f->fs) {
+
5204 fuse_create_context(f);
+
5205
+
5206 for (i = 0; i < f->id_table.size; i++) {
+
5207 struct node *node;
+
5208
+
5209 for (node = f->id_table.array[i]; node != NULL;
+
5210 node = node->id_next) {
+
5211 if (node->is_hidden) {
+
5212 char *path;
+
5213 if (try_get_path(f, node->nodeid, NULL, &path, NULL, false) == 0) {
+
5214 fuse_fs_unlink(f->fs, path);
+
5215 free(path);
+
5216 }
+
5217 }
+
5218 }
+
5219 }
+
5220 }
+
5221 for (i = 0; i < f->id_table.size; i++) {
+
5222 struct node *node;
+
5223 struct node *next;
+
5224
+
5225 for (node = f->id_table.array[i]; node != NULL; node = next) {
+
5226 next = node->id_next;
+
5227 free_node(f, node);
+
5228 f->id_table.use--;
+
5229 }
+
5230 }
+
5231 assert(list_empty(&f->partial_slabs));
+
5232 assert(list_empty(&f->full_slabs));
+
5233
+
5234 while (fuse_modules) {
+
5235 fuse_put_module(fuse_modules);
+
5236 }
+
5237 free(f->id_table.array);
+
5238 free(f->name_table.array);
+
5239 pthread_mutex_destroy(&f->lock);
+
5240 fuse_session_destroy(f->se);
+
5241 free(f->fs);
+
5242 free(f->conf.modules);
+
5243 free(f);
+
5244 fuse_delete_context_key();
+
5245}
+
5246
+
5247int fuse_mount(struct fuse *f, const char *mountpoint) {
+
5248 return fuse_session_mount(fuse_get_session(f), mountpoint);
+
5249}
+
5250
+
5251
+
5252void fuse_unmount(struct fuse *f) {
+ +
5254}
+
5255
+
5256int fuse_version(void)
+
5257{
+
5258 return FUSE_VERSION;
+
5259}
+
5260
+
5261const char *fuse_pkgversion(void)
+
5262{
+
5263 return PACKAGE_VERSION;
+
5264}
+
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4654
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5197
+
int fuse_interrupted(void)
Definition fuse.c:4663
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5146
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4906
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1383
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4639
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4433
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5202
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4914
+
fuse_fill_dir_flags
Definition fuse.h:58
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_SPLICE_READ
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_IS_FD
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5211
+
int fuse_version(void)
Definition fuse.c:5206
+
@ FUSE_BUF_SPLICE_MOVE
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+ + +
enum fuse_buf_flags flags
+
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + + +
int32_t show_help
Definition fuse.h:279
+ +
uint32_t no_interrupt
+ +
void * private_data
Definition fuse.h:874
+ + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:60
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t noflush
Definition fuse_common.h:93
+
uint32_t flush
Definition fuse_common.h:74
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + + +
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+ + + + +
+ + + + diff --git a/doc/html/lib_2fuse__i_8h_source.html b/doc/html/lib_2fuse__i_8h_source.html new file mode 100644 index 0000000..4792549 --- /dev/null +++ b/doc/html/lib_2fuse__i_8h_source.html @@ -0,0 +1,292 @@ + + + + + + + +libfuse: lib/fuse_i.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_i.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt
+
7*/
+
8
+
9#ifndef LIB_FUSE_I_H_
+
10#define LIB_FUSE_I_H_
+
11
+
12#include "fuse.h"
+
13#include "fuse_lowlevel.h"
+
14#include "util.h"
+
15
+
16#include <pthread.h>
+
17#include <semaphore.h>
+
18#include <stdint.h>
+
19#include <stdbool.h>
+
20#include <errno.h>
+
21#include <stdatomic.h>
+
22
+
23#define MIN(a, b) \
+
24({ \
+
25 typeof(a) _a = (a); \
+
26 typeof(b) _b = (b); \
+
27 _a < _b ? _a : _b; \
+
28})
+
29
+
30struct mount_opts;
+
31struct fuse_ring_pool;
+
32
+
33struct fuse_req {
+
34 struct fuse_session *se;
+
35 uint64_t unique;
+
36 _Atomic int ref_cnt;
+
37 pthread_mutex_t lock;
+
38 struct fuse_ctx ctx;
+
39 struct fuse_chan *ch;
+
40 int interrupted;
+
41 struct {
+
42 unsigned int ioctl_64bit : 1;
+
43 unsigned int is_uring : 1;
+
44 unsigned int is_copy_file_range_64 : 1;
+
45 } flags;
+
46 union {
+
47 struct {
+
48 uint64_t unique;
+
49 } i;
+
50 struct {
+ +
52 void *data;
+
53 } ni;
+
54 } u;
+
55 struct fuse_req *next;
+
56 struct fuse_req *prev;
+
57};
+
58
+
59struct fuse_notify_req {
+
60 uint64_t unique;
+
61 void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t,
+
62 const void *, const struct fuse_buf *);
+
63 struct fuse_notify_req *next;
+
64 struct fuse_notify_req *prev;
+
65};
+
66
+
67struct fuse_session_uring {
+
68 bool enable;
+
69 unsigned int q_depth;
+
70 struct fuse_ring_pool *pool;
+
71};
+
72
+
73struct fuse_session {
+
74 _Atomic(char *)mountpoint;
+
75 int fd;
+
76 struct fuse_custom_io *io;
+
77 struct mount_opts *mo;
+
78 int debug;
+
79 int deny_others;
+
80 struct fuse_lowlevel_ops op;
+
81 int got_init;
+
82 struct cuse_data *cuse_data;
+
83 void *userdata;
+
84 uid_t owner;
+
85 struct fuse_conn_info conn;
+
86 struct fuse_req list;
+
87 struct fuse_req interrupts;
+
88 pthread_mutex_t lock;
+
89 int got_destroy;
+
90 pthread_key_t pipe_key;
+
91 int broken_splice_nonblock;
+
92 uint64_t notify_ctr;
+
93 struct fuse_notify_req notify_list;
+
94 _Atomic size_t bufsize;
+
95 int error;
+
96
+
97 /*
+
98 * This is useful if any kind of ABI incompatibility is found at
+
99 * a later version, to 'fix' it at run time.
+
100 */
+
101 struct libfuse_version version;
+
102
+
103 /* thread synchronization */
+
104 _Atomic bool mt_exited;
+
105 pthread_mutex_t mt_lock;
+
106 sem_t mt_finish;
+
107
+
108 /* true if reading requests from /dev/fuse are handled internally */
+
109 bool buf_reallocable;
+
110
+
111 /* io_uring */
+
112 struct fuse_session_uring uring;
+
113
+
114 /*
+
115 * conn->want and conn_want_ext options set by libfuse , needed
+
116 * to correctly convert want to want_ext
+
117 */
+
118 uint32_t conn_want;
+
119 uint64_t conn_want_ext;
+
120};
+
121
+
122struct fuse_chan {
+
123 pthread_mutex_t lock;
+
124 int ctr;
+
125 int fd;
+
126};
+
127
+
135struct fuse_module {
+
136 char *name;
+
137 fuse_module_factory_t factory;
+
138 struct fuse_module *next;
+
139 struct fusemod_so *so;
+
140 int ctr;
+
141};
+
142
+
151#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
152struct fuse_loop_config
+
153{
+
154 /* verififier that a correct struct was was passed. This is especially
+
155 * needed, as versions below (3, 12) were using a public struct
+
156 * (now called fuse_loop_config_v1), which was hard to extend with
+
157 * additional parameters, without risking that file system implementations
+
158 * would not have noticed and might either pass uninitialized members
+
159 * or even too small structs.
+
160 * fuse_loop_config_v1 has clone_fd at this offset, which should be either 0
+
161 * or 1. v2 or even higher version just need to set a value here
+
162 * which not conflicting and very unlikely as having been set by
+
163 * file system implementation.
+
164 */
+
165 int version_id;
+
166
+
171 int clone_fd;
+ +
184
+
190 unsigned int max_threads;
+
191};
+
192#endif
+
193
+
194/* ----------------------------------------------------------- *
+
195 * Channel interface (when using -o clone_fd) *
+
196 * ----------------------------------------------------------- */
+
197
+
204struct fuse_chan *fuse_chan_get(struct fuse_chan *ch);
+
205
+
211void fuse_chan_put(struct fuse_chan *ch);
+
212
+
213struct mount_opts *parse_mount_opts(struct fuse_args *args);
+
214void destroy_mount_opts(struct mount_opts *mo);
+
215void fuse_mount_version(void);
+
216unsigned get_max_read(struct mount_opts *o);
+
217void fuse_kern_unmount(const char *mountpoint, int fd);
+
218int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo);
+
219
+
220int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
221 int count);
+
222void fuse_free_req(fuse_req_t req);
+
223void list_init_req(struct fuse_req *req);
+
224
+
225void _cuse_lowlevel_init(fuse_req_t req, const fuse_ino_t nodeid,
+
226 const void *req_header, const void *req_payload);
+
227void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg);
+
228
+
229int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg);
+
230
+
231void fuse_buf_free(struct fuse_buf *buf);
+
232
+
233int fuse_session_receive_buf_internal(struct fuse_session *se,
+
234 struct fuse_buf *buf,
+
235 struct fuse_chan *ch);
+
236void fuse_session_process_buf_internal(struct fuse_session *se,
+
237 const struct fuse_buf *buf,
+
238 struct fuse_chan *ch);
+
239
+
240struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
241 size_t op_size, void *private_data);
+
242int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config);
+
243int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
244
+
250int fuse_loop_cfg_verify(struct fuse_loop_config *config);
+
251
+
252
+
253/*
+
254 * This can be changed dynamically on recent kernels through the
+
255 * /proc/sys/fs/fuse/max_pages_limit interface.
+
256 *
+
257 * Older kernels will always use the default value.
+
258 */
+
259#define FUSE_DEFAULT_MAX_PAGES_LIMIT 256
+
260#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32
+
261
+
262/* room needed in buffer to accommodate header */
+
263#define FUSE_BUFFER_HEADER_SIZE 0x1000
+
264
+
265
+
266#endif /* LIB_FUSE_I_H_*/
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1383
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
struct fuse_req * fuse_req_t
+
uint64_t fuse_ino_t
+ + + + + + +
unsigned int max_threads
Definition fuse_i.h:166
+
unsigned int max_idle_threads
+ + + + + +
+ + + + diff --git a/doc/html/lib_2fuse__log_8c_source.html b/doc/html/lib_2fuse__log_8c_source.html new file mode 100644 index 0000000..4ddcce8 --- /dev/null +++ b/doc/html/lib_2fuse__log_8c_source.html @@ -0,0 +1,125 @@ + + + + + + + +libfuse: lib/fuse_log.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_log.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt
+
9*/
+
10
+
11#include "fuse_log.h"
+
12
+
13#include <stdio.h>
+
14#include <stdbool.h>
+
15#include <syslog.h>
+
16#include <stdarg.h>
+
17
+
18#define MAX_SYSLOG_LINE_LEN 512
+
19
+
20static bool to_syslog = false;
+
21
+
22static void default_log_func(enum fuse_log_level level, const char *fmt, va_list ap)
+
23{
+
24 if (to_syslog)
+
25 vsyslog(level, fmt, ap);
+
26 else
+
27 vfprintf(stderr, fmt, ap);
+
28}
+
29
+
30static fuse_log_func_t log_func = default_log_func;
+
31
+ +
33{
+
34 if (!func)
+
35 func = default_log_func;
+
36
+
37 log_func = func;
+
38}
+
39
+
40void fuse_log(enum fuse_log_level level, const char *fmt, ...)
+
41{
+
42 va_list ap;
+
43
+
44 va_start(ap, fmt);
+
45 log_func(level, fmt, ap);
+
46 va_end(ap);
+
47}
+
48
+
49void fuse_log_enable_syslog(const char *ident, int option, int facility)
+
50{
+
51 to_syslog = true;
+
52
+
53 openlog(ident, option, facility);
+
54}
+
55
+
56void fuse_log_close_syslog(void)
+
57{
+
58 closelog();
+
59}
+
void fuse_log_close_syslog(void)
Definition fuse_log.c:93
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:86
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:69
+
+ + + + diff --git a/doc/html/lib_2fuse__loop_8c_source.html b/doc/html/lib_2fuse__loop_8c_source.html new file mode 100644 index 0000000..b27b738 --- /dev/null +++ b/doc/html/lib_2fuse__loop_8c_source.html @@ -0,0 +1,113 @@ + + + + + + + +libfuse: lib/fuse_loop.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_loop.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the single-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14#include "fuse_uring_i.h"
+
15#include <stdio.h>
+
16#include <stdlib.h>
+
17#include <errno.h>
+
18
+
19int fuse_session_loop(struct fuse_session *se)
+
20{
+
21 int res = 0;
+
22 struct fuse_buf fbuf = {
+
23 .mem = NULL,
+
24 };
+
25
+
26 while (!fuse_session_exited(se)) {
+
27 res = fuse_session_receive_buf_internal(se, &fbuf, NULL);
+
28
+
29 if (res == -EINTR)
+
30 continue;
+
31 if (res <= 0)
+
32 break;
+
33
+
34 fuse_session_process_buf(se, &fbuf);
+
35 }
+
36
+
37 fuse_buf_free(&fbuf);
+
38 if(res > 0)
+
39 /* No error, just the length of the most recently read
+
40 request */
+
41 res = 0;
+
42 if(se->error != 0)
+
43 res = se->error;
+
44
+
45 if (se->uring.pool)
+
46 fuse_uring_stop(se);
+
47 return res;
+
48}
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+ +
void * mem
+
+ + + + diff --git a/doc/html/lib_2fuse__loop__mt_8c_source.html b/doc/html/lib_2fuse__loop__mt_8c_source.html new file mode 100644 index 0000000..119ac38 --- /dev/null +++ b/doc/html/lib_2fuse__loop__mt_8c_source.html @@ -0,0 +1,604 @@ + + + + + + + +libfuse: lib/fuse_loop_mt.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_loop_mt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the multi-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt.
+
9*/
+
10
+
11#define _GNU_SOURCE
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_lowlevel.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_kernel.h"
+
17#include "fuse_i.h"
+
18#include "fuse_uring_i.h"
+
19#include "util.h"
+
20
+
21#include <stdio.h>
+
22#include <stdlib.h>
+
23#include <string.h>
+
24#include <unistd.h>
+
25#include <signal.h>
+
26#include <semaphore.h>
+
27#include <errno.h>
+
28#include <sys/time.h>
+
29#include <sys/ioctl.h>
+
30#include <assert.h>
+
31#include <limits.h>
+
32
+
33/* Environment var controlling the thread stack size */
+
34#define ENVNAME_THREAD_STACK "FUSE_THREAD_STACK"
+
35
+
36#define FUSE_LOOP_MT_V2_IDENTIFIER INT_MAX - 2
+
37#define FUSE_LOOP_MT_DEF_CLONE_FD 0
+
38#define FUSE_LOOP_MT_DEF_MAX_THREADS 10
+
39#define FUSE_LOOP_MT_DEF_IDLE_THREADS -1 /* thread destruction is disabled
+
40 * by default */
+
41
+
42/* an arbitrary large value that cannot be valid */
+
43#define FUSE_LOOP_MT_MAX_THREADS (100U * 1000)
+
44
+
45struct fuse_worker {
+
46 struct fuse_worker *prev;
+
47 struct fuse_worker *next;
+
48 pthread_t thread_id;
+
49
+
50 // We need to include fuse_buf so that we can properly free
+
51 // it when a thread is terminated by pthread_cancel().
+
52 struct fuse_buf fbuf;
+
53 struct fuse_chan *ch;
+
54 struct fuse_mt *mt;
+
55};
+
56
+
57/* synchronization via se->mt_lock */
+
58struct fuse_mt {
+
59 int numworker;
+
60 int numavail;
+
61 struct fuse_session *se;
+
62 struct fuse_worker main;
+
63 int error;
+
64 int clone_fd;
+
65 int max_idle;
+
66 int max_threads;
+
67};
+
68
+
69static struct fuse_chan *fuse_chan_new(int fd)
+
70{
+
71 struct fuse_chan *ch = (struct fuse_chan *) malloc(sizeof(*ch));
+
72 if (ch == NULL) {
+
73 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate channel\n");
+
74 return NULL;
+
75 }
+
76
+
77 memset(ch, 0, sizeof(*ch));
+
78 ch->fd = fd;
+
79 ch->ctr = 1;
+
80 pthread_mutex_init(&ch->lock, NULL);
+
81
+
82 return ch;
+
83}
+
84
+
85struct fuse_chan *fuse_chan_get(struct fuse_chan *ch)
+
86{
+
87 assert(ch->ctr > 0);
+
88 pthread_mutex_lock(&ch->lock);
+
89 ch->ctr++;
+
90 pthread_mutex_unlock(&ch->lock);
+
91
+
92 return ch;
+
93}
+
94
+
95void fuse_chan_put(struct fuse_chan *ch)
+
96{
+
97 if (ch == NULL)
+
98 return;
+
99 pthread_mutex_lock(&ch->lock);
+
100 ch->ctr--;
+
101 if (!ch->ctr) {
+
102 pthread_mutex_unlock(&ch->lock);
+
103 close(ch->fd);
+
104 pthread_mutex_destroy(&ch->lock);
+
105 free(ch);
+
106 } else
+
107 pthread_mutex_unlock(&ch->lock);
+
108}
+
109
+
110static void list_add_worker(struct fuse_worker *w, struct fuse_worker *next)
+
111{
+
112 struct fuse_worker *prev = next->prev;
+
113 w->next = next;
+
114 w->prev = prev;
+
115 prev->next = w;
+
116 next->prev = w;
+
117}
+
118
+
119static void list_del_worker(struct fuse_worker *w)
+
120{
+
121 struct fuse_worker *prev = w->prev;
+
122 struct fuse_worker *next = w->next;
+
123 prev->next = next;
+
124 next->prev = prev;
+
125}
+
126
+
127static int fuse_loop_start_thread(struct fuse_mt *mt);
+
128
+
129static void *fuse_do_work(void *data)
+
130{
+
131 struct fuse_worker *w = (struct fuse_worker *) data;
+
132 struct fuse_mt *mt = w->mt;
+
133 struct fuse_session *se = mt->se;
+
134
+
135 fuse_set_thread_name("fuse_worker");
+
136
+
137 while (!fuse_session_exited(se)) {
+
138 int isforget = 0;
+
139 int res;
+
140
+
141 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
142 res = fuse_session_receive_buf_internal(se, &w->fbuf, w->ch);
+
143 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+
144 if (res == -EINTR)
+
145 continue;
+
146 if (res <= 0) {
+
147 if (res < 0) {
+ +
149 mt->error = res;
+
150 }
+
151 break;
+
152 }
+
153
+
154 pthread_mutex_lock(&se->mt_lock);
+
155 if (fuse_session_exited(se)) {
+
156 pthread_mutex_unlock(&se->mt_lock);
+
157 return NULL;
+
158 }
+
159
+
160 /*
+
161 * This disgusting hack is needed so that zillions of threads
+
162 * are not created on a burst of FORGET messages
+
163 */
+
164 if (!(w->fbuf.flags & FUSE_BUF_IS_FD)) {
+
165 struct fuse_in_header *in = w->fbuf.mem;
+
166
+
167 if (in->opcode == FUSE_FORGET ||
+
168 in->opcode == FUSE_BATCH_FORGET)
+
169 isforget = 1;
+
170 }
+
171
+
172 if (!isforget)
+
173 mt->numavail--;
+
174 if (mt->numavail == 0 && mt->numworker < mt->max_threads &&
+
175 likely(se->got_init))
+
176 fuse_loop_start_thread(mt);
+
177 pthread_mutex_unlock(&se->mt_lock);
+
178
+
179 fuse_session_process_buf_internal(se, &w->fbuf, w->ch);
+
180
+
181 pthread_mutex_lock(&se->mt_lock);
+
182 if (!isforget)
+
183 mt->numavail++;
+
184
+
185 /* creating and destroying threads is rather expensive - and there is
+
186 * not much gain from destroying existing threads. It is therefore
+
187 * discouraged to set max_idle to anything else than -1. If there
+
188 * is indeed a good reason to destruct threads it should be done
+
189 * delayed, a moving average might be useful for that.
+
190 */
+
191 if (mt->max_idle != -1 && mt->numavail > mt->max_idle && mt->numworker > 1) {
+
192 if (fuse_session_exited(se)) {
+
193 pthread_mutex_unlock(&se->mt_lock);
+
194 return NULL;
+
195 }
+
196 list_del_worker(w);
+
197 mt->numavail--;
+
198 mt->numworker--;
+
199 pthread_mutex_unlock(&se->mt_lock);
+
200
+
201 pthread_detach(w->thread_id);
+
202 fuse_buf_free(&w->fbuf);
+
203 fuse_chan_put(w->ch);
+
204 free(w);
+
205 return NULL;
+
206 }
+
207 pthread_mutex_unlock(&se->mt_lock);
+
208 }
+
209
+
210 sem_post(&se->mt_finish);
+
211 return NULL;
+
212}
+
213
+
214int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg)
+
215{
+
216 sigset_t oldset;
+
217 sigset_t newset;
+
218 int res;
+
219 pthread_attr_t attr;
+
220 char *stack_size;
+
221
+
222 /* Override default stack size
+
223 * XXX: This should ideally be a parameter option. It is rather
+
224 * well hidden here.
+
225 */
+
226 pthread_attr_init(&attr);
+
227 stack_size = getenv(ENVNAME_THREAD_STACK);
+
228 if (stack_size) {
+
229 long size;
+
230
+
231 res = libfuse_strtol(stack_size, &size);
+
232 if (res)
+
233 fuse_log(FUSE_LOG_ERR, "fuse: invalid stack size: %s\n",
+
234 stack_size);
+
235 else if (pthread_attr_setstacksize(&attr, size))
+
236 fuse_log(FUSE_LOG_ERR, "fuse: could not set stack size: %ld\n",
+
237 size);
+
238 }
+
239
+
240 /* Disallow signal reception in worker threads */
+
241 sigemptyset(&newset);
+
242 sigaddset(&newset, SIGTERM);
+
243 sigaddset(&newset, SIGINT);
+
244 sigaddset(&newset, SIGHUP);
+
245 sigaddset(&newset, SIGQUIT);
+
246 pthread_sigmask(SIG_BLOCK, &newset, &oldset);
+
247 res = pthread_create(thread_id, &attr, func, arg);
+
248 pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+
249 pthread_attr_destroy(&attr);
+
250 if (res != 0) {
+
251 fuse_log(FUSE_LOG_ERR, "fuse: error creating thread: %s\n",
+
252 strerror(res));
+
253 return -1;
+
254 }
+
255
+
256 return 0;
+
257}
+
258
+
259static int fuse_clone_chan_fd_default(struct fuse_session *se)
+
260{
+
261 int res;
+
262 int clonefd;
+
263 uint32_t masterfd;
+
264 const char *devname = "/dev/fuse";
+
265
+
266#ifndef O_CLOEXEC
+
267#define O_CLOEXEC 0
+
268#endif
+
269 clonefd = open(devname, O_RDWR | O_CLOEXEC);
+
270 if (clonefd == -1) {
+
271 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n", devname,
+
272 strerror(errno));
+
273 return -1;
+
274 }
+
275 if (!O_CLOEXEC) {
+
276 res = fcntl(clonefd, F_SETFD, FD_CLOEXEC);
+
277 if (res == -1) {
+
278 fuse_log(FUSE_LOG_ERR, "fuse: failed to set CLOEXEC: %s\n",
+
279 strerror(errno));
+
280 close(clonefd);
+
281 return -1;
+
282 }
+
283 }
+
284
+
285 masterfd = se->fd;
+
286 res = ioctl(clonefd, FUSE_DEV_IOC_CLONE, &masterfd);
+
287 if (res == -1) {
+
288 fuse_log(FUSE_LOG_ERR, "fuse: failed to clone device fd: %s\n",
+
289 strerror(errno));
+
290 close(clonefd);
+
291 return -1;
+
292 }
+
293 return clonefd;
+
294}
+
295
+
296static struct fuse_chan *fuse_clone_chan(struct fuse_mt *mt)
+
297{
+
298 int clonefd;
+
299 struct fuse_session *se = mt->se;
+
300 struct fuse_chan *newch;
+
301
+
302 if (se->io != NULL) {
+
303 if (se->io->clone_fd != NULL)
+
304 clonefd = se->io->clone_fd(se->fd);
+
305 else
+
306 return NULL;
+
307 } else {
+
308 clonefd = fuse_clone_chan_fd_default(se);
+
309 }
+
310 if (clonefd < 0)
+
311 return NULL;
+
312
+
313 newch = fuse_chan_new(clonefd);
+
314 if (newch == NULL)
+
315 close(clonefd);
+
316
+
317 return newch;
+
318}
+
319
+
320static int fuse_loop_start_thread(struct fuse_mt *mt)
+
321{
+
322 int res;
+
323
+
324 struct fuse_worker *w = malloc(sizeof(struct fuse_worker));
+
325 if (!w) {
+
326 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate worker structure\n");
+
327 return -1;
+
328 }
+
329 memset(w, 0, sizeof(struct fuse_worker));
+
330 w->fbuf.mem = NULL;
+
331 w->mt = mt;
+
332
+
333 w->ch = NULL;
+
334 if (mt->clone_fd) {
+
335 w->ch = fuse_clone_chan(mt);
+
336 if(!w->ch) {
+
337 /* Don't attempt this again */
+
338 fuse_log(FUSE_LOG_ERR, "fuse: trying to continue "
+
339 "without -o clone_fd.\n");
+
340 mt->clone_fd = 0;
+
341 }
+
342 }
+
343
+
344 res = fuse_start_thread(&w->thread_id, fuse_do_work, w);
+
345 if (res == -1) {
+
346 fuse_chan_put(w->ch);
+
347 free(w);
+
348 return -1;
+
349 }
+
350 list_add_worker(w, &mt->main);
+
351 mt->numavail ++;
+
352 mt->numworker ++;
+
353
+
354 return 0;
+
355}
+
356
+
357static void fuse_join_worker(struct fuse_mt *mt, struct fuse_worker *w)
+
358{
+
359 pthread_join(w->thread_id, NULL);
+
360 pthread_mutex_lock(&mt->se->mt_lock);
+
361 list_del_worker(w);
+
362 pthread_mutex_unlock(&mt->se->mt_lock);
+
363 fuse_buf_free(&w->fbuf);
+
364 fuse_chan_put(w->ch);
+
365 free(w);
+
366}
+
367
+
368int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
369FUSE_SYMVER("fuse_session_loop_mt_312", "fuse_session_loop_mt@@FUSE_3.12")
+
370int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config)
+
371{
+
372int err;
+
373 struct fuse_mt mt;
+
374 struct fuse_worker *w;
+
375 int created_config = 0;
+
376
+
377 if (config) {
+
378 err = fuse_loop_cfg_verify(config);
+
379 if (err)
+
380 return err;
+
381 } else {
+
382 /* The caller does not care about parameters - use the default */
+
383 config = fuse_loop_cfg_create();
+
384 created_config = 1;
+
385 }
+
386
+
387
+
388 memset(&mt, 0, sizeof(struct fuse_mt));
+
389 mt.se = se;
+
390 mt.clone_fd = config->clone_fd;
+
391 mt.error = 0;
+
392 mt.numworker = 0;
+
393 mt.numavail = 0;
+
394 mt.max_idle = config->max_idle_threads;
+
395 mt.max_threads = config->max_threads;
+
396 mt.main.thread_id = pthread_self();
+
397 mt.main.prev = mt.main.next = &mt.main;
+
398
+
399 pthread_mutex_lock(&se->mt_lock);
+
400 err = fuse_loop_start_thread(&mt);
+
401 pthread_mutex_unlock(&se->mt_lock);
+
402 if (!err) {
+
403 while (!fuse_session_exited(se))
+
404 sem_wait(&se->mt_finish);
+
405 if (se->debug)
+
406 fuse_log(FUSE_LOG_DEBUG,
+
407 "fuse: session exited, terminating workers\n");
+
408
+
409 pthread_mutex_lock(&se->mt_lock);
+
410 for (w = mt.main.next; w != &mt.main; w = w->next)
+
411 pthread_cancel(w->thread_id);
+
412 pthread_mutex_unlock(&se->mt_lock);
+
413
+
414 while (mt.main.next != &mt.main)
+
415 fuse_join_worker(&mt, mt.main.next);
+
416
+
417 err = mt.error;
+
418
+
419 if (se->uring.pool)
+
420 fuse_uring_stop(se);
+
421 }
+
422
+
423 pthread_mutex_destroy(&se->mt_lock);
+
424 if(se->error != 0)
+
425 err = se->error;
+
426
+
427
+
428 if (created_config) {
+
429 fuse_loop_cfg_destroy(config);
+
430 config = NULL;
+
431 }
+
432
+
433 return err;
+
434}
+
435
+
436int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1);
+
437FUSE_SYMVER("fuse_session_loop_mt_32", "fuse_session_loop_mt@FUSE_3.2")
+
438int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1)
+
439{
+
440 int err;
+
441 struct fuse_loop_config *config = NULL;
+
442
+
443 if (config_v1 != NULL) {
+
444 /* convert the given v1 config */
+
445 config = fuse_loop_cfg_create();
+
446 if (config == NULL)
+
447 return ENOMEM;
+
448
+
449 fuse_loop_cfg_convert(config, config_v1);
+
450 }
+
451
+
452 err = fuse_session_loop_mt_312(se, config);
+
453
+
454 fuse_loop_cfg_destroy(config);
+
455
+
456 return err;
+
457}
+
458
+
459
+
460int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
461FUSE_SYMVER("fuse_session_loop_mt_31", "fuse_session_loop_mt@FUSE_3.0")
+
462int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd)
+
463{
+
464 int err;
+
465 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
466 if (clone_fd > 0)
+
467 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
468 err = fuse_session_loop_mt_312(se, config);
+
469
+
470 fuse_loop_cfg_destroy(config);
+
471
+
472 return err;
+
473}
+
474
+
475struct fuse_loop_config *fuse_loop_cfg_create(void)
+
476{
+
477 struct fuse_loop_config *config = calloc(1, sizeof(*config));
+
478 if (config == NULL)
+
479 return NULL;
+
480
+
481 config->version_id = FUSE_LOOP_MT_V2_IDENTIFIER;
+
482 config->max_idle_threads = FUSE_LOOP_MT_DEF_IDLE_THREADS;
+
483 config->max_threads = FUSE_LOOP_MT_DEF_MAX_THREADS;
+
484 config->clone_fd = FUSE_LOOP_MT_DEF_CLONE_FD;
+
485
+
486 return config;
+
487}
+
488
+
489void fuse_loop_cfg_destroy(struct fuse_loop_config *config)
+
490{
+
491 free(config);
+
492}
+
493
+
494int fuse_loop_cfg_verify(struct fuse_loop_config *config)
+
495{
+
496 if (config->version_id != FUSE_LOOP_MT_V2_IDENTIFIER)
+
497 return -EINVAL;
+
498
+
499 return 0;
+
500}
+
501
+
502void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
503 struct fuse_loop_config_v1 *v1_conf)
+
504{
+
505 fuse_loop_cfg_set_idle_threads(config, v1_conf->max_idle_threads);
+
506
+
507 fuse_loop_cfg_set_clone_fd(config, v1_conf->clone_fd);
+
508}
+
509
+
510void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
511 unsigned int value)
+
512{
+
513 if (value > FUSE_LOOP_MT_MAX_THREADS) {
+
514 if (value != UINT_MAX)
+
515 fuse_log(FUSE_LOG_ERR,
+
516 "Ignoring invalid max threads value "
+
517 "%u > max (%u).\n", value,
+
518 FUSE_LOOP_MT_MAX_THREADS);
+
519 return;
+
520 }
+
521 config->max_idle_threads = value;
+
522}
+
523
+
524void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
525 unsigned int value)
+
526{
+
527 config->max_threads = value;
+
528}
+
529
+
530void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
531 unsigned int value)
+
532{
+
533 config->clone_fd = value;
+
534}
+
535
+
@ FUSE_BUF_IS_FD
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_exited(struct fuse_session *se)
+ + + +
unsigned int max_threads
Definition fuse_i.h:166
+
unsigned int max_idle_threads
+
+ + + + diff --git a/doc/html/lib_2fuse__lowlevel_8c_source.html b/doc/html/lib_2fuse__lowlevel_8c_source.html new file mode 100644 index 0000000..04d4681 --- /dev/null +++ b/doc/html/lib_2fuse__lowlevel_8c_source.html @@ -0,0 +1,4667 @@ + + + + + + + +libfuse: lib/fuse_lowlevel.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_lowlevel.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of (most of) the low-level FUSE API. The session loop
+
6 functions are implemented in separate files.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file LGPL2.txt
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_kernel.h"
+
17#include "fuse_opt.h"
+
18#include "fuse_misc.h"
+
19#include "mount_util.h"
+
20#include "util.h"
+
21#include "fuse_uring_i.h"
+
22
+
23#include <pthread.h>
+
24#include <stdatomic.h>
+
25#include <stdint.h>
+
26#include <inttypes.h>
+
27#include <stdbool.h>
+
28#include <stdio.h>
+
29#include <stdlib.h>
+
30#include <stddef.h>
+
31#include <stdalign.h>
+
32#include <string.h>
+
33#include <unistd.h>
+
34#include <limits.h>
+
35#include <errno.h>
+
36#include <assert.h>
+
37#include <sys/file.h>
+
38#include <sys/ioctl.h>
+
39#include <stdalign.h>
+
40
+
41#ifdef USDT_ENABLED
+
42#include "usdt.h"
+
43#endif
+
44
+
45#ifndef F_LINUX_SPECIFIC_BASE
+
46#define F_LINUX_SPECIFIC_BASE 1024
+
47#endif
+
48#ifndef F_SETPIPE_SZ
+
49#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7)
+
50#endif
+
51
+
52#define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg)))
+
53#define OFFSET_MAX 0x7fffffffffffffffLL
+
54
+
55struct fuse_pollhandle {
+
56 uint64_t kh;
+
57 struct fuse_session *se;
+
58};
+
59
+
60static size_t pagesize;
+
61
+
62static __attribute__((constructor)) void fuse_ll_init_pagesize(void)
+
63{
+
64 pagesize = getpagesize();
+
65}
+
66
+
67#ifdef USDT_ENABLED
+
68/* tracepoints */
+
69static void trace_request_receive(int err)
+
70{
+
71 USDT(libfuse, request_receive, err);
+
72}
+
73
+
74static void trace_request_process(unsigned int opcode, unsigned int unique)
+
75{
+
76 USDT(libfuse, request_process, opcode, unique);
+
77}
+
78
+
79static void trace_request_reply(uint64_t unique, unsigned int len,
+
80 int error, int reply_err)
+
81{
+
82 USDT(libfuse, request_reply, unique, len, error, reply_err);
+
83}
+
84#else
+
85static void trace_request_receive(int err)
+
86{
+
87 (void)err;
+
88}
+
89
+
90static void trace_request_process(unsigned int opcode, unsigned int unique)
+
91{
+
92 (void)opcode;
+
93 (void)unique;
+
94}
+
95
+
96static void trace_request_reply(uint64_t unique, unsigned int len,
+
97 int error, int reply_err)
+
98{
+
99 (void)unique;
+
100 (void)len;
+
101 (void)error;
+
102 (void)reply_err;
+
103}
+
104#endif
+
105
+
106static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr)
+
107{
+
108 attr->ino = stbuf->st_ino;
+
109 attr->mode = stbuf->st_mode;
+
110 attr->nlink = stbuf->st_nlink;
+
111 attr->uid = stbuf->st_uid;
+
112 attr->gid = stbuf->st_gid;
+
113 attr->rdev = stbuf->st_rdev;
+
114 attr->size = stbuf->st_size;
+
115 attr->blksize = stbuf->st_blksize;
+
116 attr->blocks = stbuf->st_blocks;
+
117 attr->atime = stbuf->st_atime;
+
118 attr->mtime = stbuf->st_mtime;
+
119 attr->ctime = stbuf->st_ctime;
+
120 attr->atimensec = ST_ATIM_NSEC(stbuf);
+
121 attr->mtimensec = ST_MTIM_NSEC(stbuf);
+
122 attr->ctimensec = ST_CTIM_NSEC(stbuf);
+
123}
+
124
+
125static void convert_attr(const struct fuse_setattr_in *attr, struct stat *stbuf)
+
126{
+
127 stbuf->st_mode = attr->mode;
+
128 stbuf->st_uid = attr->uid;
+
129 stbuf->st_gid = attr->gid;
+
130 stbuf->st_size = attr->size;
+
131 stbuf->st_atime = attr->atime;
+
132 stbuf->st_mtime = attr->mtime;
+
133 stbuf->st_ctime = attr->ctime;
+
134 ST_ATIM_NSEC_SET(stbuf, attr->atimensec);
+
135 ST_MTIM_NSEC_SET(stbuf, attr->mtimensec);
+
136 ST_CTIM_NSEC_SET(stbuf, attr->ctimensec);
+
137}
+
138
+
139static size_t iov_length(const struct iovec *iov, size_t count)
+
140{
+
141 size_t seg;
+
142 size_t ret = 0;
+
143
+
144 for (seg = 0; seg < count; seg++)
+
145 ret += iov[seg].iov_len;
+
146 return ret;
+
147}
+
148
+
149void list_init_req(struct fuse_req *req)
+
150{
+
151 req->next = req;
+
152 req->prev = req;
+
153}
+
154
+
155static void list_del_req(struct fuse_req *req)
+
156{
+
157 struct fuse_req *prev = req->prev;
+
158 struct fuse_req *next = req->next;
+
159 prev->next = next;
+
160 next->prev = prev;
+
161}
+
162
+
163static void list_add_req(struct fuse_req *req, struct fuse_req *next)
+
164{
+
165 struct fuse_req *prev = next->prev;
+
166 req->next = next;
+
167 req->prev = prev;
+
168 prev->next = req;
+
169 next->prev = req;
+
170}
+
171
+
172static void destroy_req(fuse_req_t req)
+
173{
+
174 if (req->flags.is_uring) {
+
175 fuse_log(FUSE_LOG_ERR, "Refusing to destruct uring req\n");
+
176 return;
+
177 }
+
178 assert(req->ch == NULL);
+
179 pthread_mutex_destroy(&req->lock);
+
180 free(req);
+
181}
+
182
+
183void fuse_free_req(fuse_req_t req)
+
184{
+
185 int ctr;
+
186 struct fuse_session *se = req->se;
+
187
+
188 /* XXX: for now no support for interrupts with io-uring
+
189 * It actually might work already, though. But then would add
+
190 * a lock across ring queues.
+
191 */
+
192 if (se->conn.no_interrupt || req->flags.is_uring) {
+
193 ctr = --req->ref_cnt;
+
194 fuse_chan_put(req->ch);
+
195 req->ch = NULL;
+
196 } else {
+
197 pthread_mutex_lock(&se->lock);
+
198 req->u.ni.func = NULL;
+
199 req->u.ni.data = NULL;
+
200 list_del_req(req);
+
201 ctr = --req->ref_cnt;
+
202 fuse_chan_put(req->ch);
+
203 req->ch = NULL;
+
204 pthread_mutex_unlock(&se->lock);
+
205 }
+
206 if (!ctr)
+
207 destroy_req(req);
+
208}
+
209
+
210static struct fuse_req *fuse_ll_alloc_req(struct fuse_session *se)
+
211{
+
212 struct fuse_req *req;
+
213
+
214 req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req));
+
215 if (req == NULL) {
+
216 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate request\n");
+
217 } else {
+
218 req->se = se;
+
219 req->ref_cnt = 1;
+
220 list_init_req(req);
+
221 pthread_mutex_init(&req->lock, NULL);
+
222 }
+
223
+
224 return req;
+
225}
+
226
+
227/*
+
228 * Send data to fuse-kernel using an fd of the fuse device.
+
229 */
+
230static int fuse_write_msg_dev(struct fuse_session *se, struct fuse_chan *ch,
+
231 struct iovec *iov, int count)
+
232{
+
233 ssize_t res;
+
234 int err;
+
235
+
236 if (se->io != NULL)
+
237
+
238 /* se->io->writev is never NULL if se->io is not NULL as
+
239 * specified by fuse_session_custom_io()
+
240 */
+
241 res = se->io->writev(ch ? ch->fd : se->fd, iov, count,
+
242 se->userdata);
+
243 else
+
244 res = writev(ch ? ch->fd : se->fd, iov, count);
+
245
+
246 if (res == -1) {
+
247 /* ENOENT means the operation was interrupted */
+
248 err = errno;
+
249 if (!fuse_session_exited(se) && err != ENOENT)
+
250 perror("fuse: writing device");
+
251 return -err;
+
252 }
+
253
+
254 return 0;
+
255}
+
256
+
257static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch,
+
258 struct iovec *iov, int count, fuse_req_t req)
+
259{
+
260 struct fuse_out_header *out = iov[0].iov_base;
+
261 int err;
+
262 bool is_uring = req && req->flags.is_uring ? true : false;
+
263
+
264 if (!is_uring)
+
265 assert(se != NULL);
+
266 out->len = iov_length(iov, count);
+
267
+
268 if (se->debug) {
+
269 if (out->unique == 0) {
+
270 fuse_log(FUSE_LOG_DEBUG, "NOTIFY: code=%d length=%u\n",
+
271 out->error, out->len);
+
272 } else if (out->error) {
+
273 fuse_log(FUSE_LOG_DEBUG,
+
274 " unique: %llu, error: %i (%s), outsize: %i\n",
+
275 (unsigned long long) out->unique, out->error,
+
276 strerror(-out->error), out->len);
+
277 } else {
+
278 fuse_log(FUSE_LOG_DEBUG,
+
279 " unique: %llu, success, outsize: %i\n",
+
280 (unsigned long long) out->unique, out->len);
+
281 }
+
282 }
+
283
+
284 if (is_uring)
+
285 err = fuse_send_msg_uring(req, iov, count);
+
286 else
+
287 err = fuse_write_msg_dev(se, ch, iov, count);
+
288
+
289 trace_request_reply(out->unique, out->len, out->error, err);
+
290 return err;
+
291}
+
292
+
293int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
294 int count)
+
295{
+
296 struct fuse_out_header out;
+
297
+
298#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
+
299 const char *str = strerrordesc_np(error * -1);
+
300 if ((str == NULL && error != 0) || error > 0) {
+
301#else
+
302 if (error <= -1000 || error > 0) {
+
303#endif
+
304 fuse_log(FUSE_LOG_ERR, "fuse: bad error value: %i\n", error);
+
305 error = -ERANGE;
+
306 }
+
307
+
308 out.unique = req->unique;
+
309 out.error = error;
+
310
+
311 iov[0].iov_base = &out;
+
312 iov[0].iov_len = sizeof(struct fuse_out_header);
+
313
+
314 return fuse_send_msg(req->se, req->ch, iov, count, req);
+
315}
+
316
+
317static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov,
+
318 int count)
+
319{
+
320 int res;
+
321
+
322 res = fuse_send_reply_iov_nofree(req, error, iov, count);
+
323 fuse_free_req(req);
+
324 return res;
+
325}
+
326
+
327static int send_reply(fuse_req_t req, int error, const void *arg,
+
328 size_t argsize)
+
329{
+
330 if (req->flags.is_uring)
+
331 return send_reply_uring(req, error, arg, argsize);
+
332
+
333 struct iovec iov[2];
+
334 int count = 1;
+
335 if (argsize) {
+
336 iov[1].iov_base = (void *) arg;
+
337 iov[1].iov_len = argsize;
+
338 count++;
+
339 }
+
340 return send_reply_iov(req, error, iov, count);
+
341}
+
342
+
343int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
344{
+
345 int res;
+
346 struct iovec *padded_iov;
+
347
+
348 padded_iov = malloc((count + 1) * sizeof(struct iovec));
+
349 if (padded_iov == NULL)
+
350 return fuse_reply_err(req, ENOMEM);
+
351
+
352 memcpy(padded_iov + 1, iov, count * sizeof(struct iovec));
+
353 count++;
+
354
+
355 res = send_reply_iov(req, 0, padded_iov, count);
+
356 free(padded_iov);
+
357
+
358 return res;
+
359}
+
360
+
361
+
362/* `buf` is allowed to be empty so that the proper size may be
+
363 allocated by the caller */
+
364size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
365 const char *name, const struct stat *stbuf, off_t off)
+
366{
+
367 (void)req;
+
368 size_t namelen;
+
369 size_t entlen;
+
370 size_t entlen_padded;
+
371 struct fuse_dirent *dirent;
+
372
+
373 namelen = strlen(name);
+
374 entlen = FUSE_NAME_OFFSET + namelen;
+
375 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
376
+
377 if ((buf == NULL) || (entlen_padded > bufsize))
+
378 return entlen_padded;
+
379
+
380 dirent = (struct fuse_dirent*) buf;
+
381 dirent->ino = stbuf->st_ino;
+
382 dirent->off = off;
+
383 dirent->namelen = namelen;
+
384 dirent->type = (stbuf->st_mode & S_IFMT) >> 12;
+
385 memcpy(dirent->name, name, namelen);
+
386 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
387
+
388 return entlen_padded;
+
389}
+
390
+
391static void convert_statfs(const struct statvfs *stbuf,
+
392 struct fuse_kstatfs *kstatfs)
+
393{
+
394 kstatfs->bsize = stbuf->f_bsize;
+
395 kstatfs->frsize = stbuf->f_frsize;
+
396 kstatfs->blocks = stbuf->f_blocks;
+
397 kstatfs->bfree = stbuf->f_bfree;
+
398 kstatfs->bavail = stbuf->f_bavail;
+
399 kstatfs->files = stbuf->f_files;
+
400 kstatfs->ffree = stbuf->f_ffree;
+
401 kstatfs->namelen = stbuf->f_namemax;
+
402}
+
403
+
404static int send_reply_ok(fuse_req_t req, const void *arg, size_t argsize)
+
405{
+
406 return send_reply(req, 0, arg, argsize);
+
407}
+
408
+
409int fuse_reply_err(fuse_req_t req, int err)
+
410{
+
411 return send_reply(req, -err, NULL, 0);
+
412}
+
413
+ +
415{
+
416 fuse_free_req(req);
+
417}
+
418
+
419static unsigned long calc_timeout_sec(double t)
+
420{
+
421 if (t > (double) ULONG_MAX)
+
422 return ULONG_MAX;
+
423 else if (t < 0.0)
+
424 return 0;
+
425 else
+
426 return (unsigned long) t;
+
427}
+
428
+
429static unsigned int calc_timeout_nsec(double t)
+
430{
+
431 double f = t - (double) calc_timeout_sec(t);
+
432 if (f < 0.0)
+
433 return 0;
+
434 else if (f >= 0.999999999)
+
435 return 999999999;
+
436 else
+
437 return (unsigned int) (f * 1.0e9);
+
438}
+
439
+
440static void fill_entry(struct fuse_entry_out *arg,
+
441 const struct fuse_entry_param *e)
+
442{
+
443 arg->nodeid = e->ino;
+
444 arg->generation = e->generation;
+
445 arg->entry_valid = calc_timeout_sec(e->entry_timeout);
+
446 arg->entry_valid_nsec = calc_timeout_nsec(e->entry_timeout);
+
447 arg->attr_valid = calc_timeout_sec(e->attr_timeout);
+
448 arg->attr_valid_nsec = calc_timeout_nsec(e->attr_timeout);
+
449 convert_stat(&e->attr, &arg->attr);
+
450}
+
451
+
452/* `buf` is allowed to be empty so that the proper size may be
+
453 allocated by the caller */
+
454size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
455 const char *name,
+
456 const struct fuse_entry_param *e, off_t off)
+
457{
+
458 (void)req;
+
459 size_t namelen;
+
460 size_t entlen;
+
461 size_t entlen_padded;
+
462
+
463 namelen = strlen(name);
+
464 entlen = FUSE_NAME_OFFSET_DIRENTPLUS + namelen;
+
465 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
466 if ((buf == NULL) || (entlen_padded > bufsize))
+
467 return entlen_padded;
+
468
+
469 struct fuse_direntplus *dp = (struct fuse_direntplus *) buf;
+
470 memset(&dp->entry_out, 0, sizeof(dp->entry_out));
+
471 fill_entry(&dp->entry_out, e);
+
472
+
473 struct fuse_dirent *dirent = &dp->dirent;
+
474 dirent->ino = e->attr.st_ino;
+
475 dirent->off = off;
+
476 dirent->namelen = namelen;
+
477 dirent->type = (e->attr.st_mode & S_IFMT) >> 12;
+
478 memcpy(dirent->name, name, namelen);
+
479 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
480
+
481 return entlen_padded;
+
482}
+
483
+
484static void fill_open(struct fuse_open_out *arg,
+
485 const struct fuse_file_info *f)
+
486{
+
487 arg->fh = f->fh;
+
488 if (f->backing_id > 0) {
+
489 arg->backing_id = f->backing_id;
+
490 arg->open_flags |= FOPEN_PASSTHROUGH;
+
491 }
+
492 if (f->direct_io)
+
493 arg->open_flags |= FOPEN_DIRECT_IO;
+
494 if (f->keep_cache)
+
495 arg->open_flags |= FOPEN_KEEP_CACHE;
+
496 if (f->cache_readdir)
+
497 arg->open_flags |= FOPEN_CACHE_DIR;
+
498 if (f->nonseekable)
+
499 arg->open_flags |= FOPEN_NONSEEKABLE;
+
500 if (f->noflush)
+
501 arg->open_flags |= FOPEN_NOFLUSH;
+ +
503 arg->open_flags |= FOPEN_PARALLEL_DIRECT_WRITES;
+
504}
+
505
+
506int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
507{
+
508 struct fuse_entry_out arg;
+
509 size_t size = req->se->conn.proto_minor < 9 ?
+
510 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg);
+
511
+
512 /* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant
+
513 negative entry */
+
514 if (!e->ino && req->se->conn.proto_minor < 4)
+
515 return fuse_reply_err(req, ENOENT);
+
516
+
517 memset(&arg, 0, sizeof(arg));
+
518 fill_entry(&arg, e);
+
519 return send_reply_ok(req, &arg, size);
+
520}
+
521
+
522int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+
523 const struct fuse_file_info *f)
+
524{
+
525 alignas(uint64_t) char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)];
+
526 size_t entrysize = req->se->conn.proto_minor < 9 ?
+
527 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(struct fuse_entry_out);
+
528 struct fuse_entry_out *earg = (struct fuse_entry_out *) buf;
+
529 struct fuse_open_out *oarg = (struct fuse_open_out *) (buf + entrysize);
+
530
+
531 memset(buf, 0, sizeof(buf));
+
532 fill_entry(earg, e);
+
533 fill_open(oarg, f);
+
534 return send_reply_ok(req, buf,
+
535 entrysize + sizeof(struct fuse_open_out));
+
536}
+
537
+
538int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
539 double attr_timeout)
+
540{
+
541 struct fuse_attr_out arg;
+
542 size_t size = req->se->conn.proto_minor < 9 ?
+
543 FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg);
+
544
+
545 memset(&arg, 0, sizeof(arg));
+
546 arg.attr_valid = calc_timeout_sec(attr_timeout);
+
547 arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
+
548 convert_stat(attr, &arg.attr);
+
549
+
550 return send_reply_ok(req, &arg, size);
+
551}
+
552
+
553int fuse_reply_readlink(fuse_req_t req, const char *linkname)
+
554{
+
555 return send_reply_ok(req, linkname, strlen(linkname));
+
556}
+
557
+
558int fuse_passthrough_open(fuse_req_t req, int fd)
+
559{
+
560 struct fuse_backing_map map = { .fd = fd };
+
561 int ret;
+
562
+
563 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_OPEN, &map);
+
564 if (ret <= 0) {
+
565 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_open: %s\n", strerror(errno));
+
566 return 0;
+
567 }
+
568
+
569 return ret;
+
570}
+
571
+
572int fuse_passthrough_close(fuse_req_t req, int backing_id)
+
573{
+
574 int ret;
+
575
+
576 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_CLOSE, &backing_id);
+
577 if (ret < 0)
+
578 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_close: %s\n", strerror(errno));
+
579
+
580 return ret;
+
581}
+
582
+
583int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *f)
+
584{
+
585 struct fuse_open_out arg;
+
586
+
587 memset(&arg, 0, sizeof(arg));
+
588 fill_open(&arg, f);
+
589 return send_reply_ok(req, &arg, sizeof(arg));
+
590}
+
591
+
592static int do_fuse_reply_write(fuse_req_t req, size_t count)
+
593{
+
594 struct fuse_write_out arg;
+
595
+
596 memset(&arg, 0, sizeof(arg));
+
597 arg.size = count;
+
598
+
599 return send_reply_ok(req, &arg, sizeof(arg));
+
600}
+
601
+
602static int do_fuse_reply_copy(fuse_req_t req, size_t count)
+
603{
+
604 struct fuse_copy_file_range_out arg;
+
605
+
606 memset(&arg, 0, sizeof(arg));
+
607 arg.bytes_copied = count;
+
608
+
609 return send_reply_ok(req, &arg, sizeof(arg));
+
610}
+
611
+
612int fuse_reply_write(fuse_req_t req, size_t count)
+
613{
+
614 /*
+
615 * This function is also used by FUSE_COPY_FILE_RANGE and its 64-bit
+
616 * variant.
+
617 */
+
618 if (req->flags.is_copy_file_range_64)
+
619 return do_fuse_reply_copy(req, count);
+
620 else
+
621 return do_fuse_reply_write(req, count);
+
622}
+
623
+
624int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
625{
+
626 return send_reply_ok(req, buf, size);
+
627}
+
628
+
629static int fuse_send_data_iov_fallback(struct fuse_session *se,
+
630 struct fuse_chan *ch,
+
631 struct iovec *iov, int iov_count,
+
632 struct fuse_bufvec *buf,
+
633 size_t len, fuse_req_t req)
+
634{
+
635 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
636 void *mbuf;
+
637 int res;
+
638
+
639 /* Optimize common case */
+
640 if (buf->count == 1 && buf->idx == 0 && buf->off == 0 &&
+
641 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
642 /* FIXME: also avoid memory copy if there are multiple buffers
+
643 but none of them contain an fd */
+
644
+
645 iov[iov_count].iov_base = buf->buf[0].mem;
+
646 iov[iov_count].iov_len = len;
+
647 iov_count++;
+
648 return fuse_send_msg(se, ch, iov, iov_count, req);
+
649 }
+
650
+
651 res = posix_memalign(&mbuf, pagesize, len);
+
652 if (res != 0)
+
653 return res;
+
654
+
655 mem_buf.buf[0].mem = mbuf;
+
656 res = fuse_buf_copy(&mem_buf, buf, 0);
+
657 if (res < 0) {
+
658 free(mbuf);
+
659 return -res;
+
660 }
+
661 len = res;
+
662
+
663 iov[iov_count].iov_base = mbuf;
+
664 iov[iov_count].iov_len = len;
+
665 iov_count++;
+
666 res = fuse_send_msg(se, ch, iov, iov_count, req);
+
667 free(mbuf);
+
668
+
669 return res;
+
670}
+
671
+
672struct fuse_ll_pipe {
+
673 size_t size;
+
674 int can_grow;
+
675 int pipe[2];
+
676};
+
677
+
678static void fuse_ll_pipe_free(struct fuse_ll_pipe *llp)
+
679{
+
680 close(llp->pipe[0]);
+
681 close(llp->pipe[1]);
+
682 free(llp);
+
683}
+
684
+
685#ifdef HAVE_SPLICE
+
686#if !defined(HAVE_PIPE2) || !defined(O_CLOEXEC)
+
687static int fuse_pipe(int fds[2])
+
688{
+
689 int rv = pipe(fds);
+
690
+
691 if (rv == -1)
+
692 return rv;
+
693
+
694 if (fcntl(fds[0], F_SETFL, O_NONBLOCK) == -1 ||
+
695 fcntl(fds[1], F_SETFL, O_NONBLOCK) == -1 ||
+
696 fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 ||
+
697 fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) {
+
698 close(fds[0]);
+
699 close(fds[1]);
+
700 rv = -1;
+
701 }
+
702 return rv;
+
703}
+
704#else
+
705static int fuse_pipe(int fds[2])
+
706{
+
707 return pipe2(fds, O_CLOEXEC | O_NONBLOCK);
+
708}
+
709#endif
+
710
+
711static struct fuse_ll_pipe *fuse_ll_get_pipe(struct fuse_session *se)
+
712{
+
713 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
714 if (llp == NULL) {
+
715 int res;
+
716
+
717 llp = malloc(sizeof(struct fuse_ll_pipe));
+
718 if (llp == NULL)
+
719 return NULL;
+
720
+
721 res = fuse_pipe(llp->pipe);
+
722 if (res == -1) {
+
723 free(llp);
+
724 return NULL;
+
725 }
+
726
+
727 /*
+
728 *the default size is 16 pages on linux
+
729 */
+
730 llp->size = pagesize * 16;
+
731 llp->can_grow = 1;
+
732
+
733 pthread_setspecific(se->pipe_key, llp);
+
734 }
+
735
+
736 return llp;
+
737}
+
738#endif
+
739
+
740static void fuse_ll_clear_pipe(struct fuse_session *se)
+
741{
+
742 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
743 if (llp) {
+
744 pthread_setspecific(se->pipe_key, NULL);
+
745 fuse_ll_pipe_free(llp);
+
746 }
+
747}
+
748
+
749#if defined(HAVE_SPLICE) && defined(HAVE_VMSPLICE)
+
750static int read_back(int fd, char *buf, size_t len)
+
751{
+
752 int res;
+
753
+
754 res = read(fd, buf, len);
+
755 if (res == -1) {
+
756 fuse_log(FUSE_LOG_ERR,
+
757 "fuse: internal error: failed to read back from pipe: %s\n",
+
758 strerror(errno));
+
759 return -EIO;
+
760 }
+
761 if (res != len) {
+
762 fuse_log(FUSE_LOG_ERR,
+
763 "fuse: internal error: short read back from pipe: %i from %zd\n",
+
764 res, len);
+
765 return -EIO;
+
766 }
+
767 return 0;
+
768}
+
769
+
770static int grow_pipe_to_max(int pipefd)
+
771{
+
772 int res;
+
773 long max;
+
774 long maxfd;
+
775 char buf[32];
+
776
+
777 maxfd = open("/proc/sys/fs/pipe-max-size", O_RDONLY);
+
778 if (maxfd < 0)
+
779 return -errno;
+
780
+
781 res = read(maxfd, buf, sizeof(buf) - 1);
+
782 if (res < 0) {
+
783 int saved_errno;
+
784
+
785 saved_errno = errno;
+
786 close(maxfd);
+
787 return -saved_errno;
+
788 }
+
789 close(maxfd);
+
790 buf[res] = '\0';
+
791
+
792 res = libfuse_strtol(buf, &max);
+
793 if (res)
+
794 return res;
+
795 res = fcntl(pipefd, F_SETPIPE_SZ, max);
+
796 if (res < 0)
+
797 return -errno;
+
798 return max;
+
799}
+
800
+
801static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
802 struct iovec *iov, int iov_count,
+
803 struct fuse_bufvec *buf, unsigned int flags,
+
804 fuse_req_t req)
+
805{
+
806 int res;
+
807 size_t len = fuse_buf_size(buf);
+
808 struct fuse_out_header *out = iov[0].iov_base;
+
809 struct fuse_ll_pipe *llp;
+
810 int splice_flags;
+
811 size_t pipesize;
+
812 size_t total_buf_size;
+
813 size_t idx;
+
814 size_t headerlen;
+
815 struct fuse_bufvec pipe_buf = FUSE_BUFVEC_INIT(len);
+
816
+
817 if (se->broken_splice_nonblock)
+
818 goto fallback;
+
819
+
820 if (flags & FUSE_BUF_NO_SPLICE)
+
821 goto fallback;
+
822
+
823 total_buf_size = 0;
+
824 for (idx = buf->idx; idx < buf->count; idx++) {
+
825 total_buf_size += buf->buf[idx].size;
+
826 if (idx == buf->idx)
+
827 total_buf_size -= buf->off;
+
828 }
+
829 if (total_buf_size < 2 * pagesize)
+
830 goto fallback;
+
831
+
832 if (se->conn.proto_minor < 14 ||
+
833 !(se->conn.want_ext & FUSE_CAP_SPLICE_WRITE))
+
834 goto fallback;
+
835
+
836 llp = fuse_ll_get_pipe(se);
+
837 if (llp == NULL)
+
838 goto fallback;
+
839
+
840
+
841 headerlen = iov_length(iov, iov_count);
+
842
+
843 out->len = headerlen + len;
+
844
+
845 /*
+
846 * Heuristic for the required pipe size, does not work if the
+
847 * source contains less than page size fragments
+
848 */
+
849 pipesize = pagesize * (iov_count + buf->count + 1) + out->len;
+
850
+
851 if (llp->size < pipesize) {
+
852 if (llp->can_grow) {
+
853 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, pipesize);
+
854 if (res == -1) {
+
855 res = grow_pipe_to_max(llp->pipe[0]);
+
856 if (res > 0)
+
857 llp->size = res;
+
858 llp->can_grow = 0;
+
859 goto fallback;
+
860 }
+
861 llp->size = res;
+
862 }
+
863 if (llp->size < pipesize)
+
864 goto fallback;
+
865 }
+
866
+
867
+
868 res = vmsplice(llp->pipe[1], iov, iov_count, SPLICE_F_NONBLOCK);
+
869 if (res == -1)
+
870 goto fallback;
+
871
+
872 if (res != headerlen) {
+
873 res = -EIO;
+
874 fuse_log(FUSE_LOG_ERR, "fuse: short vmsplice to pipe: %u/%zu\n", res,
+
875 headerlen);
+
876 goto clear_pipe;
+
877 }
+
878
+
879 pipe_buf.buf[0].flags = FUSE_BUF_IS_FD;
+
880 pipe_buf.buf[0].fd = llp->pipe[1];
+
881
+
882 res = fuse_buf_copy(&pipe_buf, buf,
+ +
884 if (res < 0) {
+
885 if (res == -EAGAIN || res == -EINVAL) {
+
886 /*
+
887 * Should only get EAGAIN on kernels with
+
888 * broken SPLICE_F_NONBLOCK support (<=
+
889 * 2.6.35) where this error or a short read is
+
890 * returned even if the pipe itself is not
+
891 * full
+
892 *
+
893 * EINVAL might mean that splice can't handle
+
894 * this combination of input and output.
+
895 */
+
896 if (res == -EAGAIN)
+
897 se->broken_splice_nonblock = 1;
+
898
+
899 pthread_setspecific(se->pipe_key, NULL);
+
900 fuse_ll_pipe_free(llp);
+
901 goto fallback;
+
902 }
+
903 res = -res;
+
904 goto clear_pipe;
+
905 }
+
906
+
907 if (res != 0 && res < len) {
+
908 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
909 void *mbuf;
+
910 size_t now_len = res;
+
911 /*
+
912 * For regular files a short count is either
+
913 * 1) due to EOF, or
+
914 * 2) because of broken SPLICE_F_NONBLOCK (see above)
+
915 *
+
916 * For other inputs it's possible that we overflowed
+
917 * the pipe because of small buffer fragments.
+
918 */
+
919
+
920 res = posix_memalign(&mbuf, pagesize, len);
+
921 if (res != 0)
+
922 goto clear_pipe;
+
923
+
924 mem_buf.buf[0].mem = mbuf;
+
925 mem_buf.off = now_len;
+
926 res = fuse_buf_copy(&mem_buf, buf, 0);
+
927 if (res > 0) {
+
928 char *tmpbuf;
+
929 size_t extra_len = res;
+
930 /*
+
931 * Trickiest case: got more data. Need to get
+
932 * back the data from the pipe and then fall
+
933 * back to regular write.
+
934 */
+
935 tmpbuf = malloc(headerlen);
+
936 if (tmpbuf == NULL) {
+
937 free(mbuf);
+
938 res = ENOMEM;
+
939 goto clear_pipe;
+
940 }
+
941 res = read_back(llp->pipe[0], tmpbuf, headerlen);
+
942 free(tmpbuf);
+
943 if (res != 0) {
+
944 free(mbuf);
+
945 goto clear_pipe;
+
946 }
+
947 res = read_back(llp->pipe[0], mbuf, now_len);
+
948 if (res != 0) {
+
949 free(mbuf);
+
950 goto clear_pipe;
+
951 }
+
952 len = now_len + extra_len;
+
953 iov[iov_count].iov_base = mbuf;
+
954 iov[iov_count].iov_len = len;
+
955 iov_count++;
+
956 res = fuse_send_msg(se, ch, iov, iov_count, req);
+
957 free(mbuf);
+
958 return res;
+
959 }
+
960 free(mbuf);
+
961 res = now_len;
+
962 }
+
963 len = res;
+
964 out->len = headerlen + len;
+
965
+
966 if (se->debug) {
+
967 fuse_log(FUSE_LOG_DEBUG,
+
968 " unique: %llu, success, outsize: %i (splice)\n",
+
969 (unsigned long long) out->unique, out->len);
+
970 }
+
971
+
972 splice_flags = 0;
+
973 if ((flags & FUSE_BUF_SPLICE_MOVE) &&
+
974 (se->conn.want_ext & FUSE_CAP_SPLICE_MOVE))
+
975 splice_flags |= SPLICE_F_MOVE;
+
976
+
977 if (se->io != NULL && se->io->splice_send != NULL) {
+
978 res = se->io->splice_send(llp->pipe[0], NULL,
+
979 ch ? ch->fd : se->fd, NULL, out->len,
+
980 splice_flags, se->userdata);
+
981 } else {
+
982 res = splice(llp->pipe[0], NULL, ch ? ch->fd : se->fd, NULL,
+
983 out->len, splice_flags);
+
984 }
+
985 if (res == -1) {
+
986 res = -errno;
+
987 perror("fuse: splice from pipe");
+
988 goto clear_pipe;
+
989 }
+
990 if (res != out->len) {
+
991 res = -EIO;
+
992 fuse_log(FUSE_LOG_ERR, "fuse: short splice from pipe: %u/%u\n",
+
993 res, out->len);
+
994 goto clear_pipe;
+
995 }
+
996 return 0;
+
997
+
998clear_pipe:
+
999 fuse_ll_clear_pipe(se);
+
1000 return res;
+
1001
+
1002fallback:
+
1003 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len, req);
+
1004}
+
1005#else
+
1006static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
1007 struct iovec *iov, int iov_count,
+
1008 struct fuse_bufvec *req_data, unsigned int flags,
+
1009 fuse_req_t req)
+
1010{
+
1011 size_t len = fuse_buf_size(req_data);
+
1012 (void) flags;
+
1013
+
1014 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, req_data, len, req);
+
1015}
+
1016#endif
+
1017
+
1018int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+
1019 enum fuse_buf_copy_flags flags)
+
1020{
+
1021 struct iovec iov[2];
+
1022 struct fuse_out_header out;
+
1023 int res;
+
1024
+
1025 if (req->flags.is_uring)
+
1026 return fuse_reply_data_uring(req, bufv, flags);
+
1027
+
1028 iov[0].iov_base = &out;
+
1029 iov[0].iov_len = sizeof(struct fuse_out_header);
+
1030
+
1031 out.unique = req->unique;
+
1032 out.error = 0;
+
1033
+
1034 res = fuse_send_data_iov(req->se, req->ch, iov, 1, bufv, flags, req);
+
1035 if (res <= 0) {
+
1036 fuse_free_req(req);
+
1037 return res;
+
1038 } else {
+
1039 return fuse_reply_err(req, res);
+
1040 }
+
1041}
+
1042
+
1043int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
1044{
+
1045 struct fuse_statfs_out arg;
+
1046 size_t size = req->se->conn.proto_minor < 4 ?
+
1047 FUSE_COMPAT_STATFS_SIZE : sizeof(arg);
+
1048
+
1049 memset(&arg, 0, sizeof(arg));
+
1050 convert_statfs(stbuf, &arg.st);
+
1051
+
1052 return send_reply_ok(req, &arg, size);
+
1053}
+
1054
+
1055int fuse_reply_xattr(fuse_req_t req, size_t count)
+
1056{
+
1057 struct fuse_getxattr_out arg;
+
1058
+
1059 memset(&arg, 0, sizeof(arg));
+
1060 arg.size = count;
+
1061
+
1062 return send_reply_ok(req, &arg, sizeof(arg));
+
1063}
+
1064
+
1065int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
1066{
+
1067 struct fuse_lk_out arg;
+
1068
+
1069 memset(&arg, 0, sizeof(arg));
+
1070 arg.lk.type = lock->l_type;
+
1071 if (lock->l_type != F_UNLCK) {
+
1072 arg.lk.start = lock->l_start;
+
1073 if (lock->l_len == 0)
+
1074 arg.lk.end = OFFSET_MAX;
+
1075 else
+
1076 arg.lk.end = lock->l_start + lock->l_len - 1;
+
1077 }
+
1078 arg.lk.pid = lock->l_pid;
+
1079 return send_reply_ok(req, &arg, sizeof(arg));
+
1080}
+
1081
+
1082int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
1083{
+
1084 struct fuse_bmap_out arg;
+
1085
+
1086 memset(&arg, 0, sizeof(arg));
+
1087 arg.block = idx;
+
1088
+
1089 return send_reply_ok(req, &arg, sizeof(arg));
+
1090}
+
1091
+
1092static struct fuse_ioctl_iovec *fuse_ioctl_iovec_copy(const struct iovec *iov,
+
1093 size_t count)
+
1094{
+
1095 struct fuse_ioctl_iovec *fiov;
+
1096 size_t i;
+
1097
+
1098 fiov = malloc(sizeof(fiov[0]) * count);
+
1099 if (!fiov)
+
1100 return NULL;
+
1101
+
1102 for (i = 0; i < count; i++) {
+
1103 fiov[i].base = (uintptr_t) iov[i].iov_base;
+
1104 fiov[i].len = iov[i].iov_len;
+
1105 }
+
1106
+
1107 return fiov;
+
1108}
+
1109
+ +
1111 const struct iovec *in_iov, size_t in_count,
+
1112 const struct iovec *out_iov, size_t out_count)
+
1113{
+
1114 struct fuse_ioctl_out arg;
+
1115 struct fuse_ioctl_iovec *in_fiov = NULL;
+
1116 struct fuse_ioctl_iovec *out_fiov = NULL;
+
1117 struct iovec iov[4];
+
1118 size_t count = 1;
+
1119 int res;
+
1120
+
1121 memset(&arg, 0, sizeof(arg));
+
1122 arg.flags |= FUSE_IOCTL_RETRY;
+
1123 arg.in_iovs = in_count;
+
1124 arg.out_iovs = out_count;
+
1125 iov[count].iov_base = &arg;
+
1126 iov[count].iov_len = sizeof(arg);
+
1127 count++;
+
1128
+
1129 if (req->se->conn.proto_minor < 16) {
+
1130 if (in_count) {
+
1131 iov[count].iov_base = (void *)in_iov;
+
1132 iov[count].iov_len = sizeof(in_iov[0]) * in_count;
+
1133 count++;
+
1134 }
+
1135
+
1136 if (out_count) {
+
1137 iov[count].iov_base = (void *)out_iov;
+
1138 iov[count].iov_len = sizeof(out_iov[0]) * out_count;
+
1139 count++;
+
1140 }
+
1141 } else {
+
1142 /* Can't handle non-compat 64bit ioctls on 32bit */
+
1143 if (sizeof(void *) == 4 && req->flags.ioctl_64bit) {
+
1144 res = fuse_reply_err(req, EINVAL);
+
1145 goto out;
+
1146 }
+
1147
+
1148 if (in_count) {
+
1149 in_fiov = fuse_ioctl_iovec_copy(in_iov, in_count);
+
1150 if (!in_fiov)
+
1151 goto enomem;
+
1152
+
1153 iov[count].iov_base = (void *)in_fiov;
+
1154 iov[count].iov_len = sizeof(in_fiov[0]) * in_count;
+
1155 count++;
+
1156 }
+
1157 if (out_count) {
+
1158 out_fiov = fuse_ioctl_iovec_copy(out_iov, out_count);
+
1159 if (!out_fiov)
+
1160 goto enomem;
+
1161
+
1162 iov[count].iov_base = (void *)out_fiov;
+
1163 iov[count].iov_len = sizeof(out_fiov[0]) * out_count;
+
1164 count++;
+
1165 }
+
1166 }
+
1167
+
1168 res = send_reply_iov(req, 0, iov, count);
+
1169out:
+
1170 free(in_fiov);
+
1171 free(out_fiov);
+
1172
+
1173 return res;
+
1174
+
1175enomem:
+
1176 res = fuse_reply_err(req, ENOMEM);
+
1177 goto out;
+
1178}
+
1179
+
1180int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
1181{
+
1182 struct fuse_ioctl_out arg;
+
1183 struct iovec iov[3];
+
1184 size_t count = 1;
+
1185
+
1186 memset(&arg, 0, sizeof(arg));
+
1187 arg.result = result;
+
1188 iov[count].iov_base = &arg;
+
1189 iov[count].iov_len = sizeof(arg);
+
1190 count++;
+
1191
+
1192 if (size) {
+
1193 iov[count].iov_base = (char *) buf;
+
1194 iov[count].iov_len = size;
+
1195 count++;
+
1196 }
+
1197
+
1198 return send_reply_iov(req, 0, iov, count);
+
1199}
+
1200
+
1201int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1202 int count)
+
1203{
+
1204 struct iovec *padded_iov;
+
1205 struct fuse_ioctl_out arg;
+
1206 int res;
+
1207
+
1208 padded_iov = malloc((count + 2) * sizeof(struct iovec));
+
1209 if (padded_iov == NULL)
+
1210 return fuse_reply_err(req, ENOMEM);
+
1211
+
1212 memset(&arg, 0, sizeof(arg));
+
1213 arg.result = result;
+
1214 padded_iov[1].iov_base = &arg;
+
1215 padded_iov[1].iov_len = sizeof(arg);
+
1216
+
1217 memcpy(&padded_iov[2], iov, count * sizeof(struct iovec));
+
1218
+
1219 res = send_reply_iov(req, 0, padded_iov, count + 2);
+
1220 free(padded_iov);
+
1221
+
1222 return res;
+
1223}
+
1224
+
1225int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
1226{
+
1227 struct fuse_poll_out arg;
+
1228
+
1229 memset(&arg, 0, sizeof(arg));
+
1230 arg.revents = revents;
+
1231
+
1232 return send_reply_ok(req, &arg, sizeof(arg));
+
1233}
+
1234
+
1235int fuse_reply_lseek(fuse_req_t req, off_t off)
+
1236{
+
1237 struct fuse_lseek_out arg;
+
1238
+
1239 memset(&arg, 0, sizeof(arg));
+
1240 arg.offset = off;
+
1241
+
1242 return send_reply_ok(req, &arg, sizeof(arg));
+
1243}
+
1244
+
1245#ifdef HAVE_STATX
+
1246int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx,
+
1247 double attr_timeout)
+
1248{
+
1249 struct fuse_statx_out arg;
+
1250
+
1251 memset(&arg, 0, sizeof(arg));
+
1252 arg.flags = flags;
+
1253 arg.attr_valid = calc_timeout_sec(attr_timeout);
+
1254 arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
+
1255 memcpy(&arg.stat, statx, sizeof(arg.stat));
+
1256
+
1257 return send_reply_ok(req, &arg, sizeof(arg));
+
1258}
+
1259#else
+
1260int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx,
+
1261 double attr_timeout)
+
1262{
+
1263 (void)req;
+
1264 (void)flags;
+
1265 (void)statx;
+
1266 (void)attr_timeout;
+
1267
+
1268 return -ENOSYS;
+
1269}
+
1270#endif
+
1271
+
1272static void _do_lookup(fuse_req_t req, const fuse_ino_t nodeid,
+
1273 const void *op_in, const void *in_payload)
+
1274{
+
1275 (void)op_in;
+
1276
+
1277 char *name = (char *)in_payload;
+
1278
+
1279 if (req->se->op.lookup)
+
1280 req->se->op.lookup(req, nodeid, name);
+
1281 else
+
1282 fuse_reply_err(req, ENOSYS);
+
1283}
+
1284
+
1285static void do_lookup(fuse_req_t req, const fuse_ino_t nodeid,
+
1286 const void *inarg)
+
1287{
+
1288 _do_lookup(req, nodeid, NULL, inarg);
+
1289}
+
1290
+
1291static void _do_forget(fuse_req_t req, const fuse_ino_t nodeid,
+
1292 const void *op_in, const void *in_payload)
+
1293{
+
1294 (void)in_payload;
+
1295
+
1296 struct fuse_forget_in *arg = (struct fuse_forget_in *)op_in;
+
1297
+
1298 if (req->se->op.forget)
+
1299 req->se->op.forget(req, nodeid, arg->nlookup);
+
1300 else
+
1301 fuse_reply_none(req);
+
1302}
+
1303
+
1304static void do_forget(fuse_req_t req, const fuse_ino_t nodeid,
+
1305 const void *inarg)
+
1306{
+
1307 _do_forget(req, nodeid, inarg, NULL);
+
1308}
+
1309
+
1310static void _do_batch_forget(fuse_req_t req, const fuse_ino_t nodeid,
+
1311 const void *op_in, const void *in_payload)
+
1312{
+
1313 (void)nodeid;
+
1314 unsigned int i;
+
1315
+
1316 const struct fuse_batch_forget_in *arg = op_in;
+
1317 const struct fuse_forget_one *forgets = in_payload;
+
1318
+
1319 if (req->se->op.forget_multi) {
+
1320 req->se->op.forget_multi(req, arg->count,
+
1321 (struct fuse_forget_data *)in_payload);
+
1322 } else if (req->se->op.forget) {
+
1323 for (i = 0; i < arg->count; i++) {
+
1324 const struct fuse_forget_one *forget = &forgets[i];
+
1325 struct fuse_req *dummy_req;
+
1326
+
1327 dummy_req = fuse_ll_alloc_req(req->se);
+
1328 if (dummy_req == NULL)
+
1329 break;
+
1330
+
1331 dummy_req->unique = req->unique;
+
1332 dummy_req->ctx = req->ctx;
+
1333 dummy_req->ch = NULL;
+
1334
+
1335 req->se->op.forget(dummy_req, forget->nodeid,
+
1336 forget->nlookup);
+
1337 }
+
1338 fuse_reply_none(req);
+
1339 } else {
+
1340 fuse_reply_none(req);
+
1341 }
+
1342}
+
1343
+
1344static void do_batch_forget(fuse_req_t req, const fuse_ino_t nodeid,
+
1345 const void *inarg)
+
1346{
+
1347 struct fuse_batch_forget_in *arg = (void *)inarg;
+
1348 struct fuse_forget_one *param = (void *)PARAM(arg);
+
1349
+
1350 _do_batch_forget(req, nodeid, inarg, param);
+
1351}
+
1352
+
1353static void _do_getattr(fuse_req_t req, const fuse_ino_t nodeid,
+
1354 const void *op_in, const void *in_payload)
+
1355{
+
1356 struct fuse_getattr_in *arg = (struct fuse_getattr_in *)op_in;
+
1357 (void)in_payload;
+
1358
+
1359 struct fuse_file_info *fip = NULL;
+
1360 struct fuse_file_info fi;
+
1361
+
1362 if (req->se->conn.proto_minor >= 9) {
+
1363 if (arg->getattr_flags & FUSE_GETATTR_FH) {
+
1364 memset(&fi, 0, sizeof(fi));
+
1365 fi.fh = arg->fh;
+
1366 fip = &fi;
+
1367 }
+
1368 }
+
1369
+
1370 if (req->se->op.getattr)
+
1371 req->se->op.getattr(req, nodeid, fip);
+
1372 else
+
1373 fuse_reply_err(req, ENOSYS);
+
1374}
+
1375
+
1376static void do_getattr(fuse_req_t req, const fuse_ino_t nodeid,
+
1377 const void *inarg)
+
1378{
+
1379 _do_getattr(req, nodeid, inarg, NULL);
+
1380}
+
1381
+
1382static void _do_setattr(fuse_req_t req, const fuse_ino_t nodeid,
+
1383 const void *op_in, const void *in_payload)
+
1384{
+
1385 (void)in_payload;
+
1386 const struct fuse_setattr_in *arg = op_in;
+
1387 uint32_t valid = arg->valid;
+
1388
+
1389 if (req->se->op.setattr) {
+
1390 struct fuse_file_info *fi = NULL;
+
1391 struct fuse_file_info fi_store;
+
1392 struct stat stbuf;
+
1393 memset(&stbuf, 0, sizeof(stbuf));
+
1394 convert_attr(arg, &stbuf);
+
1395 if (arg->valid & FATTR_FH) {
+
1396 valid &= ~FATTR_FH;
+
1397 memset(&fi_store, 0, sizeof(fi_store));
+
1398 fi = &fi_store;
+
1399 fi->fh = arg->fh;
+
1400 }
+
1401 valid &= FUSE_SET_ATTR_MODE | FUSE_SET_ATTR_UID |
+
1402 FUSE_SET_ATTR_GID | FUSE_SET_ATTR_SIZE |
+
1403 FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME |
+
1404 FUSE_SET_ATTR_KILL_SUID | FUSE_SET_ATTR_KILL_SGID |
+
1405 FUSE_SET_ATTR_ATIME_NOW | FUSE_SET_ATTR_MTIME_NOW |
+
1406 FUSE_SET_ATTR_CTIME;
+
1407
+
1408 req->se->op.setattr(req, nodeid, &stbuf, valid, fi);
+
1409 } else
+
1410 fuse_reply_err(req, ENOSYS);
+
1411}
+
1412
+
1413static void do_setattr(fuse_req_t req, const fuse_ino_t nodeid,
+
1414 const void *inarg)
+
1415{
+
1416 _do_setattr(req, nodeid, inarg, NULL);
+
1417}
+
1418
+
1419static void _do_access(fuse_req_t req, const fuse_ino_t nodeid,
+
1420 const void *op_in, const void *in_payload)
+
1421{
+
1422 (void)in_payload;
+
1423 const struct fuse_access_in *arg = op_in;
+
1424
+
1425 if (req->se->op.access)
+
1426 req->se->op.access(req, nodeid, arg->mask);
+
1427 else
+
1428 fuse_reply_err(req, ENOSYS);
+
1429}
+
1430
+
1431static void do_access(fuse_req_t req, const fuse_ino_t nodeid,
+
1432 const void *inarg)
+
1433{
+
1434 _do_access(req, nodeid, inarg, NULL);
+
1435}
+
1436
+
1437static void _do_readlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1438 const void *op_in, const void *in_payload)
+
1439{
+
1440 (void)op_in;
+
1441 (void)in_payload;
+
1442
+
1443 if (req->se->op.readlink)
+
1444 req->se->op.readlink(req, nodeid);
+
1445 else
+
1446 fuse_reply_err(req, ENOSYS);
+
1447}
+
1448
+
1449static void do_readlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1450 const void *inarg)
+
1451{
+
1452 _do_readlink(req, nodeid, inarg, NULL);
+
1453}
+
1454
+
1455static void _do_mknod(fuse_req_t req, const fuse_ino_t nodeid,
+
1456 const void *op_in, const void *in_payload)
+
1457{
+
1458 const struct fuse_mknod_in *arg = (struct fuse_mknod_in *)op_in;
+
1459 const char *name = in_payload;
+
1460
+
1461 if (req->se->conn.proto_minor >= 12)
+
1462 req->ctx.umask = arg->umask;
+
1463
+
1464 if (req->se->op.mknod)
+
1465 req->se->op.mknod(req, nodeid, name, arg->mode, arg->rdev);
+
1466 else
+
1467 fuse_reply_err(req, ENOSYS);
+
1468}
+
1469
+
1470static void do_mknod(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1471{
+
1472 struct fuse_mknod_in *arg = (struct fuse_mknod_in *)inarg;
+
1473 char *name = PARAM(arg);
+
1474
+
1475 if (req->se->conn.proto_minor < 12)
+
1476 name = (char *)inarg + FUSE_COMPAT_MKNOD_IN_SIZE;
+
1477
+
1478 _do_mknod(req, nodeid, inarg, name);
+
1479}
+
1480
+
1481static void _do_mkdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1482 const void *op_in, const void *in_payload)
+
1483{
+
1484 const char *name = in_payload;
+
1485 const struct fuse_mkdir_in *arg = op_in;
+
1486
+
1487 if (req->se->conn.proto_minor >= 12)
+
1488 req->ctx.umask = arg->umask;
+
1489
+
1490 if (req->se->op.mkdir)
+
1491 req->se->op.mkdir(req, nodeid, name, arg->mode);
+
1492 else
+
1493 fuse_reply_err(req, ENOSYS);
+
1494}
+
1495
+
1496static void do_mkdir(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1497{
+
1498 const struct fuse_mkdir_in *arg = inarg;
+
1499 const char *name = PARAM(arg);
+
1500
+
1501 _do_mkdir(req, nodeid, inarg, name);
+
1502}
+
1503
+
1504static void _do_unlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1505 const void *op_in, const void *in_payload)
+
1506{
+
1507 (void)op_in;
+
1508 const char *name = in_payload;
+
1509
+
1510 if (req->se->op.unlink)
+
1511 req->se->op.unlink(req, nodeid, name);
+
1512 else
+
1513 fuse_reply_err(req, ENOSYS);
+
1514}
+
1515
+
1516static void do_unlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1517 const void *inarg)
+
1518{
+
1519 _do_unlink(req, nodeid, NULL, inarg);
+
1520}
+
1521
+
1522static void _do_rmdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1523 const void *op_in, const void *in_payload)
+
1524{
+
1525 (void)op_in;
+
1526 const char *name = in_payload;
+
1527
+
1528 if (req->se->op.rmdir)
+
1529 req->se->op.rmdir(req, nodeid, name);
+
1530 else
+
1531 fuse_reply_err(req, ENOSYS);
+
1532}
+
1533
+
1534static void do_rmdir(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1535{
+
1536 _do_rmdir(req, nodeid, NULL, inarg);
+
1537}
+
1538
+
1539static void _do_symlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1540 const void *op_in, const void *in_payload)
+
1541{
+
1542 (void)op_in;
+
1543 const char *name = (char *)in_payload;
+
1544 const char *linkname = name + strlen(name) + 1;
+
1545
+
1546 if (req->se->op.symlink)
+
1547 req->se->op.symlink(req, linkname, nodeid, name);
+
1548 else
+
1549 fuse_reply_err(req, ENOSYS);
+
1550}
+
1551
+
1552static void do_symlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1553 const void *inarg)
+
1554{
+
1555 _do_symlink(req, nodeid, NULL, inarg);
+
1556}
+
1557
+
1558static void _do_rename(fuse_req_t req, const fuse_ino_t nodeid,
+
1559 const void *op_in, const void *in_payload)
+
1560{
+
1561 const struct fuse_rename_in *arg = (struct fuse_rename_in *)op_in;
+
1562 const char *oldname = in_payload;
+
1563 const char *newname = oldname + strlen(oldname) + 1;
+
1564
+
1565 if (req->se->op.rename)
+
1566 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1567 0);
+
1568 else
+
1569 fuse_reply_err(req, ENOSYS);
+
1570}
+
1571
+
1572static void do_rename(fuse_req_t req, const fuse_ino_t nodeid,
+
1573 const void *inarg)
+
1574{
+
1575 const struct fuse_rename_in *arg = inarg;
+
1576 const void *payload = PARAM(arg);
+
1577
+
1578 _do_rename(req, nodeid, arg, payload);
+
1579}
+
1580
+
1581static void _do_rename2(fuse_req_t req, const fuse_ino_t nodeid,
+
1582 const void *op_in, const void *in_payload)
+
1583{
+
1584 const struct fuse_rename2_in *arg = op_in;
+
1585 const char *oldname = in_payload;
+
1586 const char *newname = oldname + strlen(oldname) + 1;
+
1587
+
1588 if (req->se->op.rename)
+
1589 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1590 arg->flags);
+
1591 else
+
1592 fuse_reply_err(req, ENOSYS);
+
1593}
+
1594
+
1595static void do_rename2(fuse_req_t req, const fuse_ino_t nodeid,
+
1596 const void *inarg)
+
1597{
+
1598 const struct fuse_rename2_in *arg = inarg;
+
1599 const void *payload = PARAM(arg);
+
1600
+
1601 _do_rename2(req, nodeid, arg, payload);
+
1602}
+
1603
+
1604static void _do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *op_in,
+
1605 const void *in_payload)
+
1606{
+
1607 (void)in_payload;
+
1608 const struct fuse_create_in *arg = op_in;
+
1609
+
1610 if (req->se->op.tmpfile) {
+
1611 struct fuse_file_info fi;
+
1612
+
1613 memset(&fi, 0, sizeof(fi));
+
1614 fi.flags = arg->flags;
+
1615
+
1616 if (req->se->conn.proto_minor >= 12)
+
1617 req->ctx.umask = arg->umask;
+
1618
+
1619 req->se->op.tmpfile(req, nodeid, arg->mode, &fi);
+
1620 } else
+
1621 fuse_reply_err(req, ENOSYS);
+
1622}
+
1623
+
1624static void do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1625{
+
1626 struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
+
1627
+
1628 _do_tmpfile(req, nodeid, arg, NULL);
+
1629}
+
1630
+
1631static void _do_link(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
1632 const void *in_payload)
+
1633{
+
1634 struct fuse_link_in *arg = (struct fuse_link_in *)op_in;
+
1635
+
1636 if (req->se->op.link)
+
1637 req->se->op.link(req, arg->oldnodeid, nodeid, in_payload);
+
1638 else
+
1639 fuse_reply_err(req, ENOSYS);
+
1640}
+
1641
+
1642static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1643{
+
1644 const struct fuse_link_in *arg = inarg;
+
1645 const void *name = PARAM(arg);
+
1646
+
1647 _do_link(req, nodeid, inarg, name);
+
1648}
+
1649
+
1650static void _do_create(fuse_req_t req, const fuse_ino_t nodeid,
+
1651 const void *op_in, const void *in_payload)
+
1652{
+
1653 const struct fuse_create_in *arg = op_in;
+
1654 const char *name = in_payload;
+
1655
+
1656 if (req->se->op.create) {
+
1657 struct fuse_file_info fi;
+
1658
+
1659 memset(&fi, 0, sizeof(fi));
+
1660 fi.flags = arg->flags;
+
1661
+
1662 if (req->se->conn.proto_minor >= 12)
+
1663 req->ctx.umask = arg->umask;
+
1664
+
1665 /* XXX: fuse_create_in::open_flags */
+
1666
+
1667 req->se->op.create(req, nodeid, name, arg->mode, &fi);
+
1668 } else {
+
1669 fuse_reply_err(req, ENOSYS);
+
1670 }
+
1671}
+
1672
+
1673static void do_create(fuse_req_t req, const fuse_ino_t nodeid,
+
1674 const void *inarg)
+
1675{
+
1676 const struct fuse_create_in *arg = (struct fuse_create_in *)inarg;
+
1677 void *payload = PARAM(arg);
+
1678
+
1679 if (req->se->conn.proto_minor < 12)
+
1680 payload = (char *)inarg + sizeof(struct fuse_open_in);
+
1681
+
1682 _do_create(req, nodeid, arg, payload);
+
1683}
+
1684
+
1685static void _do_open(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
1686 const void *in_payload)
+
1687{
+
1688 (void)in_payload;
+
1689 struct fuse_open_in *arg = (struct fuse_open_in *)op_in;
+
1690 struct fuse_file_info fi;
+
1691
+
1692 memset(&fi, 0, sizeof(fi));
+
1693 fi.flags = arg->flags;
+
1694
+
1695 /* XXX: fuse_open_in::open_flags */
+
1696
+
1697 if (req->se->op.open)
+
1698 req->se->op.open(req, nodeid, &fi);
+
1699 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPEN_SUPPORT)
+
1700 fuse_reply_err(req, ENOSYS);
+
1701 else
+
1702 fuse_reply_open(req, &fi);
+
1703}
+
1704
+
1705static void do_open(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1706{
+
1707 _do_open(req, nodeid, inarg, NULL);
+
1708}
+
1709
+
1710static void _do_read(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
1711 const void *in_payload)
+
1712{
+
1713 (void)in_payload;
+
1714 struct fuse_read_in *arg = (struct fuse_read_in *)op_in;
+
1715
+
1716 if (req->se->op.read) {
+
1717 struct fuse_file_info fi;
+
1718
+
1719 memset(&fi, 0, sizeof(fi));
+
1720 fi.fh = arg->fh;
+
1721 if (req->se->conn.proto_minor >= 9) {
+
1722 fi.lock_owner = arg->lock_owner;
+
1723 fi.flags = arg->flags;
+
1724 }
+
1725 req->se->op.read(req, nodeid, arg->size, arg->offset, &fi);
+
1726 } else
+
1727 fuse_reply_err(req, ENOSYS);
+
1728}
+
1729
+
1730static void do_read(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1731{
+
1732 _do_read(req, nodeid, inarg, NULL);
+
1733}
+
1734
+
1735static void _do_write(fuse_req_t req, const fuse_ino_t nodeid,
+
1736 const void *op_in, const void *in_payload)
+
1737{
+
1738 struct fuse_write_in *arg = (struct fuse_write_in *)op_in;
+
1739 const char *buf = in_payload;
+
1740 struct fuse_file_info fi;
+
1741
+
1742 memset(&fi, 0, sizeof(fi));
+
1743 fi.fh = arg->fh;
+
1744 fi.writepage = (arg->write_flags & FUSE_WRITE_CACHE) != 0;
+
1745
+
1746 if (req->se->conn.proto_minor >= 9) {
+
1747 fi.lock_owner = arg->lock_owner;
+
1748 fi.flags = arg->flags;
+
1749 }
+
1750
+
1751 if (req->se->op.write)
+
1752 req->se->op.write(req, nodeid, buf, arg->size, arg->offset,
+
1753 &fi);
+
1754 else
+
1755 fuse_reply_err(req, ENOSYS);
+
1756}
+
1757
+
1758static void do_write(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1759{
+
1760 struct fuse_write_in *arg = (struct fuse_write_in *)inarg;
+
1761 const void *payload;
+
1762
+
1763 if (req->se->conn.proto_minor < 9)
+
1764 payload = ((char *)arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1765 else
+
1766 payload = PARAM(arg);
+
1767
+
1768 _do_write(req, nodeid, arg, payload);
+
1769}
+
1770
+
1771static void _do_write_buf(fuse_req_t req, const fuse_ino_t nodeid,
+
1772 const void *op_in, struct fuse_bufvec *bufv)
+
1773{
+
1774 struct fuse_session *se = req->se;
+
1775 struct fuse_write_in *arg = (struct fuse_write_in *)op_in;
+
1776 struct fuse_file_info fi;
+
1777
+
1778 memset(&fi, 0, sizeof(fi));
+
1779 fi.fh = arg->fh;
+
1780 fi.writepage = arg->write_flags & FUSE_WRITE_CACHE;
+
1781
+
1782 if (se->conn.proto_minor >= 9) {
+
1783 fi.lock_owner = arg->lock_owner;
+
1784 fi.flags = arg->flags;
+
1785 }
+
1786
+
1787 se->op.write_buf(req, nodeid, bufv, arg->offset, &fi);
+
1788}
+
1789
+
1790static void do_write_buf(fuse_req_t req, const fuse_ino_t nodeid,
+
1791 const void *inarg, const struct fuse_buf *ibuf)
+
1792{
+
1793 struct fuse_session *se = req->se;
+
1794 struct fuse_bufvec bufv = {
+
1795 .buf[0] = *ibuf,
+
1796 .count = 1,
+
1797 };
+
1798 struct fuse_write_in *arg = (struct fuse_write_in *)inarg;
+
1799
+
1800 if (se->conn.proto_minor < 9) {
+
1801 bufv.buf[0].mem = ((char *)arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1802 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1803 FUSE_COMPAT_WRITE_IN_SIZE;
+
1804 assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD));
+
1805 } else {
+
1806 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
1807 bufv.buf[0].mem = PARAM(arg);
+
1808
+
1809 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1810 sizeof(struct fuse_write_in);
+
1811 }
+
1812 if (bufv.buf[0].size < arg->size) {
+
1813 fuse_log(FUSE_LOG_ERR,
+
1814 "fuse: %s: buffer size too small\n", __func__);
+
1815 fuse_reply_err(req, EIO);
+
1816 goto out;
+
1817 }
+
1818 bufv.buf[0].size = arg->size;
+
1819
+
1820 _do_write_buf(req, nodeid, inarg, &bufv);
+
1821
+
1822out:
+
1823 /* Need to reset the pipe if ->write_buf() didn't consume all data */
+
1824 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
1825 fuse_ll_clear_pipe(se);
+
1826}
+
1827
+
1828static void _do_flush(fuse_req_t req, const fuse_ino_t nodeid,
+
1829 const void *op_in, const void *in_payload)
+
1830{
+
1831 (void)in_payload;
+
1832 struct fuse_flush_in *arg = (struct fuse_flush_in *)op_in;
+
1833 struct fuse_file_info fi;
+
1834
+
1835 memset(&fi, 0, sizeof(fi));
+
1836 fi.fh = arg->fh;
+
1837 fi.flush = 1;
+
1838 if (req->se->conn.proto_minor >= 7)
+
1839 fi.lock_owner = arg->lock_owner;
+
1840
+
1841 if (req->se->op.flush)
+
1842 req->se->op.flush(req, nodeid, &fi);
+
1843 else
+
1844 fuse_reply_err(req, ENOSYS);
+
1845}
+
1846
+
1847static void do_flush(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1848{
+
1849 _do_flush(req, nodeid, inarg, NULL);
+
1850}
+
1851
+
1852static void _do_release(fuse_req_t req, const fuse_ino_t nodeid,
+
1853 const void *op_in, const void *in_payload)
+
1854{
+
1855 (void)in_payload;
+
1856 const struct fuse_release_in *arg = op_in;
+
1857 struct fuse_file_info fi;
+
1858
+
1859 memset(&fi, 0, sizeof(fi));
+
1860 fi.flags = arg->flags;
+
1861 fi.fh = arg->fh;
+
1862 if (req->se->conn.proto_minor >= 8) {
+
1863 fi.flush = (arg->release_flags & FUSE_RELEASE_FLUSH) ? 1 : 0;
+
1864 fi.lock_owner = arg->lock_owner;
+
1865 }
+
1866 if (arg->release_flags & FUSE_RELEASE_FLOCK_UNLOCK) {
+
1867 fi.flock_release = 1;
+
1868 fi.lock_owner = arg->lock_owner;
+
1869 }
+
1870
+
1871 if (req->se->op.release)
+
1872 req->se->op.release(req, nodeid, &fi);
+
1873 else
+
1874 fuse_reply_err(req, 0);
+
1875}
+
1876
+
1877static void do_release(fuse_req_t req, const fuse_ino_t nodeid,
+
1878 const void *inarg)
+
1879{
+
1880 _do_release(req, nodeid, inarg, NULL);
+
1881}
+
1882
+
1883static void _do_fsync(fuse_req_t req, const fuse_ino_t nodeid,
+
1884 const void *op_in, const void *in_payload)
+
1885{
+
1886 (void)in_payload;
+
1887 const struct fuse_fsync_in *arg = op_in;
+
1888 struct fuse_file_info fi;
+
1889 int datasync = arg->fsync_flags & 1;
+
1890
+
1891 memset(&fi, 0, sizeof(fi));
+
1892 fi.fh = arg->fh;
+
1893
+
1894 if (req->se->op.fsync)
+
1895 req->se->op.fsync(req, nodeid, datasync, &fi);
+
1896 else
+
1897 fuse_reply_err(req, ENOSYS);
+
1898}
+
1899
+
1900static void do_fsync(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1901{
+
1902 _do_fsync(req, nodeid, inarg, NULL);
+
1903}
+
1904
+
1905static void _do_opendir(fuse_req_t req, const fuse_ino_t nodeid,
+
1906 const void *op_in, const void *in_payload)
+
1907{
+
1908 (void)in_payload;
+
1909 const struct fuse_open_in *arg = op_in;
+
1910 struct fuse_file_info fi;
+
1911
+
1912 memset(&fi, 0, sizeof(fi));
+
1913 fi.flags = arg->flags;
+
1914 /* XXX: fuse_open_in::open_flags */
+
1915
+
1916 if (req->se->op.opendir)
+
1917 req->se->op.opendir(req, nodeid, &fi);
+
1918 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPENDIR_SUPPORT)
+
1919 fuse_reply_err(req, ENOSYS);
+
1920 else
+
1921 fuse_reply_open(req, &fi);
+
1922}
+
1923
+
1924static void do_opendir(fuse_req_t req, const fuse_ino_t nodeid,
+
1925 const void *inarg)
+
1926{
+
1927 _do_opendir(req, nodeid, inarg, NULL);
+
1928}
+
1929
+
1930static void _do_readdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1931 const void *op_in, const void *in_payload)
+
1932{
+
1933 (void)in_payload;
+
1934 struct fuse_read_in *arg = (struct fuse_read_in *)op_in;
+
1935 struct fuse_file_info fi;
+
1936
+
1937 memset(&fi, 0, sizeof(fi));
+
1938 fi.fh = arg->fh;
+
1939
+
1940 if (req->se->op.readdir)
+
1941 req->se->op.readdir(req, nodeid, arg->size, arg->offset, &fi);
+
1942 else
+
1943 fuse_reply_err(req, ENOSYS);
+
1944}
+
1945
+
1946static void do_readdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1947 const void *inarg)
+
1948{
+
1949 _do_readdir(req, nodeid, inarg, NULL);
+
1950}
+
1951
+
1952static void _do_readdirplus(fuse_req_t req, const fuse_ino_t nodeid,
+
1953 const void *op_in, const void *in_payload)
+
1954{
+
1955 (void)in_payload;
+
1956 struct fuse_read_in *arg = (struct fuse_read_in *)op_in;
+
1957 struct fuse_file_info fi;
+
1958
+
1959 memset(&fi, 0, sizeof(fi));
+
1960 fi.fh = arg->fh;
+
1961
+
1962 if (req->se->op.readdirplus)
+
1963 req->se->op.readdirplus(req, nodeid, arg->size, arg->offset, &fi);
+
1964 else
+
1965 fuse_reply_err(req, ENOSYS);
+
1966}
+
1967
+
1968static void do_readdirplus(fuse_req_t req, const fuse_ino_t nodeid,
+
1969 const void *inarg)
+
1970{
+
1971 _do_readdirplus(req, nodeid, inarg, NULL);
+
1972}
+
1973
+
1974static void _do_releasedir(fuse_req_t req, const fuse_ino_t nodeid,
+
1975 const void *op_in, const void *in_payload)
+
1976{
+
1977 (void)in_payload;
+
1978 struct fuse_release_in *arg = (struct fuse_release_in *)op_in;
+
1979 struct fuse_file_info fi;
+
1980
+
1981 memset(&fi, 0, sizeof(fi));
+
1982 fi.flags = arg->flags;
+
1983 fi.fh = arg->fh;
+
1984
+
1985 if (req->se->op.releasedir)
+
1986 req->se->op.releasedir(req, nodeid, &fi);
+
1987 else
+
1988 fuse_reply_err(req, 0);
+
1989}
+
1990
+
1991static void do_releasedir(fuse_req_t req, const fuse_ino_t nodeid,
+
1992 const void *inarg)
+
1993{
+
1994 _do_releasedir(req, nodeid, inarg, NULL);
+
1995}
+
1996
+
1997static void _do_fsyncdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1998 const void *op_in, const void *in_payload)
+
1999{
+
2000 (void)in_payload;
+
2001 struct fuse_fsync_in *arg = (struct fuse_fsync_in *)op_in;
+
2002 struct fuse_file_info fi;
+
2003 int datasync = arg->fsync_flags & 1;
+
2004
+
2005 memset(&fi, 0, sizeof(fi));
+
2006 fi.fh = arg->fh;
+
2007
+
2008 if (req->se->op.fsyncdir)
+
2009 req->se->op.fsyncdir(req, nodeid, datasync, &fi);
+
2010 else
+
2011 fuse_reply_err(req, ENOSYS);
+
2012}
+
2013static void do_fsyncdir(fuse_req_t req, const fuse_ino_t nodeid,
+
2014 const void *inarg)
+
2015{
+
2016 _do_fsyncdir(req, nodeid, inarg, NULL);
+
2017}
+
2018
+
2019static void _do_statfs(fuse_req_t req, const fuse_ino_t nodeid,
+
2020 const void *op_in, const void *in_payload)
+
2021{
+
2022 (void) nodeid;
+
2023 (void)op_in;
+
2024 (void)in_payload;
+
2025
+
2026 if (req->se->op.statfs)
+
2027 req->se->op.statfs(req, nodeid);
+
2028 else {
+
2029 struct statvfs buf = {
+
2030 .f_namemax = 255,
+
2031 .f_bsize = 512,
+
2032 };
+
2033 fuse_reply_statfs(req, &buf);
+
2034 }
+
2035}
+
2036static void do_statfs(fuse_req_t req, const fuse_ino_t nodeid,
+
2037 const void *inarg)
+
2038{
+
2039 _do_statfs(req, nodeid, inarg, NULL);
+
2040}
+
2041
+
2042static void _do_setxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2043 const void *op_in, const void *in_payload)
+
2044{
+
2045 struct fuse_setxattr_in *arg = (struct fuse_setxattr_in *)op_in;
+
2046 const char *name = in_payload;
+
2047 const char *value = name + strlen(name) + 1;
+
2048
+
2049 /* XXX:The API should be extended to support extra_flags/setxattr_flags */
+
2050
+
2051 if (req->se->op.setxattr)
+
2052 req->se->op.setxattr(req, nodeid, name, value, arg->size,
+
2053 arg->flags);
+
2054 else
+
2055 fuse_reply_err(req, ENOSYS);
+
2056}
+
2057static void do_setxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2058 const void *inarg)
+
2059{
+
2060 struct fuse_session *se = req->se;
+
2061 unsigned int xattr_ext = !!(se->conn.want & FUSE_CAP_SETXATTR_EXT);
+
2062 const struct fuse_setxattr_in *arg = inarg;
+
2063 char *payload = xattr_ext ? PARAM(arg) :
+
2064 (char *)arg + FUSE_COMPAT_SETXATTR_IN_SIZE;
+
2065
+
2066 _do_setxattr(req, nodeid, arg, payload);
+
2067}
+
2068
+
2069static void _do_getxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2070 const void *op_in, const void *in_payload)
+
2071{
+
2072 const struct fuse_getxattr_in *arg = op_in;
+
2073
+
2074 if (req->se->op.getxattr)
+
2075 req->se->op.getxattr(req, nodeid, in_payload, arg->size);
+
2076 else
+
2077 fuse_reply_err(req, ENOSYS);
+
2078}
+
2079
+
2080static void do_getxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2081 const void *inarg)
+
2082{
+
2083 const struct fuse_getxattr_in *arg = inarg;
+
2084 const void *payload = PARAM(arg);
+
2085
+
2086 _do_getxattr(req, nodeid, arg, payload);
+
2087}
+
2088
+
2089static void _do_listxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2090 const void *inarg, const void *in_payload)
+
2091{
+
2092 (void)in_payload;
+
2093 const struct fuse_getxattr_in *arg = inarg;
+
2094
+
2095 if (req->se->op.listxattr)
+
2096 req->se->op.listxattr(req, nodeid, arg->size);
+
2097 else
+
2098 fuse_reply_err(req, ENOSYS);
+
2099}
+
2100static void do_listxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2101 const void *inarg)
+
2102{
+
2103 _do_listxattr(req, nodeid, inarg, NULL);
+
2104}
+
2105
+
2106static void _do_removexattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2107 const void *inarg, const void *in_payload)
+
2108{
+
2109 (void)inarg;
+
2110 const char *name = in_payload;
+
2111
+
2112 if (req->se->op.removexattr)
+
2113 req->se->op.removexattr(req, nodeid, name);
+
2114 else
+
2115 fuse_reply_err(req, ENOSYS);
+
2116}
+
2117static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2118{
+
2119 _do_removexattr(req, nodeid, NULL, inarg);
+
2120}
+
2121
+
2122static void convert_fuse_file_lock(const struct fuse_file_lock *fl,
+
2123 struct flock *flock)
+
2124{
+
2125 memset(flock, 0, sizeof(struct flock));
+
2126 flock->l_type = fl->type;
+
2127 flock->l_whence = SEEK_SET;
+
2128 flock->l_start = fl->start;
+
2129 if (fl->end == OFFSET_MAX)
+
2130 flock->l_len = 0;
+
2131 else
+
2132 flock->l_len = fl->end - fl->start + 1;
+
2133 flock->l_pid = fl->pid;
+
2134}
+
2135
+
2136static void _do_getlk(fuse_req_t req, const fuse_ino_t nodeid,
+
2137 const void *op_in, const void *in_payload)
+
2138{
+
2139 (void)in_payload;
+
2140 const struct fuse_lk_in *arg = op_in;
+
2141 struct fuse_file_info fi;
+
2142 struct flock flock;
+
2143
+
2144 memset(&fi, 0, sizeof(fi));
+
2145 fi.fh = arg->fh;
+
2146 fi.lock_owner = arg->owner;
+
2147
+
2148 convert_fuse_file_lock(&arg->lk, &flock);
+
2149 if (req->se->op.getlk)
+
2150 req->se->op.getlk(req, nodeid, &fi, &flock);
+
2151 else
+
2152 fuse_reply_err(req, ENOSYS);
+
2153}
+
2154static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2155{
+
2156 _do_getlk(req, nodeid, inarg, NULL);
+
2157}
+
2158
+
2159static void do_setlk_common(fuse_req_t req, const fuse_ino_t nodeid,
+
2160 const void *op_in, int sleep)
+
2161{
+
2162 const struct fuse_lk_in *arg = op_in;
+
2163 struct fuse_file_info fi;
+
2164 struct flock flock;
+
2165
+
2166 memset(&fi, 0, sizeof(fi));
+
2167 fi.fh = arg->fh;
+
2168 fi.lock_owner = arg->owner;
+
2169
+
2170 if (arg->lk_flags & FUSE_LK_FLOCK) {
+
2171 int op = 0;
+
2172
+
2173 switch (arg->lk.type) {
+
2174 case F_RDLCK:
+
2175 op = LOCK_SH;
+
2176 break;
+
2177 case F_WRLCK:
+
2178 op = LOCK_EX;
+
2179 break;
+
2180 case F_UNLCK:
+
2181 op = LOCK_UN;
+
2182 break;
+
2183 }
+
2184 if (!sleep)
+
2185 op |= LOCK_NB;
+
2186
+
2187 if (req->se->op.flock)
+
2188 req->se->op.flock(req, nodeid, &fi, op);
+
2189 else
+
2190 fuse_reply_err(req, ENOSYS);
+
2191 } else {
+
2192 convert_fuse_file_lock(&arg->lk, &flock);
+
2193 if (req->se->op.setlk)
+
2194 req->se->op.setlk(req, nodeid, &fi, &flock, sleep);
+
2195 else
+
2196 fuse_reply_err(req, ENOSYS);
+
2197 }
+
2198}
+
2199
+
2200static void _do_setlk(fuse_req_t req, const fuse_ino_t nodeid,
+
2201 const void *op_in, const void *in_payload)
+
2202{
+
2203 (void)in_payload;
+
2204 do_setlk_common(req, nodeid, op_in, 0);
+
2205}
+
2206
+
2207static void do_setlk(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
2208{
+
2209 _do_setlk(req, nodeid, inarg, NULL);
+
2210}
+
2211
+
2212static void _do_setlkw(fuse_req_t req, const fuse_ino_t nodeid,
+
2213 const void *op_in, const void *in_payload)
+
2214{
+
2215 (void)in_payload;
+
2216 do_setlk_common(req, nodeid, op_in, 1);
+
2217}
+
2218static void do_setlkw(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2219{
+
2220 _do_setlkw(req, nodeid, inarg, NULL);
+
2221}
+
2222
+
2223static int find_interrupted(struct fuse_session *se, struct fuse_req *req)
+
2224{
+
2225 struct fuse_req *curr;
+
2226
+
2227 for (curr = se->list.next; curr != &se->list; curr = curr->next) {
+
2228 if (curr->unique == req->u.i.unique) {
+ +
2230 void *data;
+
2231
+
2232 curr->ref_cnt++;
+
2233 pthread_mutex_unlock(&se->lock);
+
2234
+
2235 /* Ugh, ugly locking */
+
2236 pthread_mutex_lock(&curr->lock);
+
2237 pthread_mutex_lock(&se->lock);
+
2238 curr->interrupted = 1;
+
2239 func = curr->u.ni.func;
+
2240 data = curr->u.ni.data;
+
2241 pthread_mutex_unlock(&se->lock);
+
2242 if (func)
+
2243 func(curr, data);
+
2244 pthread_mutex_unlock(&curr->lock);
+
2245
+
2246 pthread_mutex_lock(&se->lock);
+
2247 curr->ref_cnt--;
+
2248 if (!curr->ref_cnt) {
+
2249 destroy_req(curr);
+
2250 }
+
2251
+
2252 return 1;
+
2253 }
+
2254 }
+
2255 for (curr = se->interrupts.next; curr != &se->interrupts;
+
2256 curr = curr->next) {
+
2257 if (curr->u.i.unique == req->u.i.unique)
+
2258 return 1;
+
2259 }
+
2260 return 0;
+
2261}
+
2262
+
2263static void _do_interrupt(fuse_req_t req, const fuse_ino_t nodeid,
+
2264 const void *op_in, const void *in_payload)
+
2265{
+
2266 (void)in_payload;
+
2267 const struct fuse_interrupt_in *arg = op_in;
+
2268 struct fuse_session *se = req->se;
+
2269
+
2270 (void) nodeid;
+
2271 if (se->debug)
+
2272 fuse_log(FUSE_LOG_DEBUG, "INTERRUPT: %llu\n",
+
2273 (unsigned long long) arg->unique);
+
2274
+
2275 req->u.i.unique = arg->unique;
+
2276
+
2277 pthread_mutex_lock(&se->lock);
+
2278 if (find_interrupted(se, req)) {
+
2279 fuse_chan_put(req->ch);
+
2280 req->ch = NULL;
+
2281 destroy_req(req);
+
2282 } else
+
2283 list_add_req(req, &se->interrupts);
+
2284 pthread_mutex_unlock(&se->lock);
+
2285}
+
2286static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2287{
+
2288 _do_interrupt(req, nodeid, inarg, NULL);
+
2289}
+
2290
+
2291static struct fuse_req *check_interrupt(struct fuse_session *se,
+
2292 struct fuse_req *req)
+
2293{
+
2294 struct fuse_req *curr;
+
2295
+
2296 for (curr = se->interrupts.next; curr != &se->interrupts;
+
2297 curr = curr->next) {
+
2298 if (curr->u.i.unique == req->unique) {
+
2299 req->interrupted = 1;
+
2300 list_del_req(curr);
+
2301 fuse_chan_put(curr->ch);
+
2302 curr->ch = NULL;
+
2303 destroy_req(curr);
+
2304 return NULL;
+
2305 }
+
2306 }
+
2307 curr = se->interrupts.next;
+
2308 if (curr != &se->interrupts) {
+
2309 list_del_req(curr);
+
2310 list_init_req(curr);
+
2311 return curr;
+
2312 } else
+
2313 return NULL;
+
2314}
+
2315
+
2316static void _do_bmap(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
2317 const void *in_payload)
+
2318{
+
2319 (void)in_payload;
+
2320 const struct fuse_bmap_in *arg = op_in;
+
2321
+
2322 if (req->se->op.bmap)
+
2323 req->se->op.bmap(req, nodeid, arg->blocksize, arg->block);
+
2324 else
+
2325 fuse_reply_err(req, ENOSYS);
+
2326}
+
2327static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2328{
+
2329 _do_bmap(req, nodeid, inarg, NULL);
+
2330}
+
2331
+
2332static void _do_ioctl(fuse_req_t req, const fuse_ino_t nodeid,
+
2333 const void *op_in, const void *in_payload)
+
2334{
+
2335 struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *)op_in;
+
2336 unsigned int flags = arg->flags;
+
2337 const void *in_buf = in_payload;
+
2338 struct fuse_file_info fi;
+
2339
+
2340 if (flags & FUSE_IOCTL_DIR &&
+
2341 !(req->se->conn.want_ext & FUSE_CAP_IOCTL_DIR)) {
+
2342 fuse_reply_err(req, ENOTTY);
+
2343 return;
+
2344 }
+
2345
+
2346 memset(&fi, 0, sizeof(fi));
+
2347 fi.fh = arg->fh;
+
2348
+
2349 if (sizeof(void *) == 4 && req->se->conn.proto_minor >= 16 &&
+
2350 !(flags & FUSE_IOCTL_32BIT)) {
+
2351 req->flags.ioctl_64bit = 1;
+
2352 }
+
2353
+
2354 if (req->se->op.ioctl)
+
2355 req->se->op.ioctl(req, nodeid, arg->cmd,
+
2356 (void *)(uintptr_t)arg->arg, &fi, flags,
+
2357 in_buf, arg->in_size, arg->out_size);
+
2358 else
+
2359 fuse_reply_err(req, ENOSYS);
+
2360}
+
2361static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2362{
+
2363 const struct fuse_ioctl_in *arg = inarg;
+
2364 void *in_buf = arg->in_size ? PARAM(arg) : NULL;
+
2365
+
2366 _do_ioctl(req, nodeid, arg, in_buf);
+
2367}
+
2368
+
2369void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
2370{
+
2371 free(ph);
+
2372}
+
2373
+
2374static void _do_poll(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
2375 const void *in_payload)
+
2376{
+
2377 (void)in_payload;
+
2378 struct fuse_poll_in *arg = (struct fuse_poll_in *)op_in;
+
2379 struct fuse_file_info fi;
+
2380
+
2381 memset(&fi, 0, sizeof(fi));
+
2382 fi.fh = arg->fh;
+
2383 fi.poll_events = arg->events;
+
2384
+
2385 if (req->se->op.poll) {
+
2386 struct fuse_pollhandle *ph = NULL;
+
2387
+
2388 if (arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) {
+
2389 ph = malloc(sizeof(struct fuse_pollhandle));
+
2390 if (ph == NULL) {
+
2391 fuse_reply_err(req, ENOMEM);
+
2392 return;
+
2393 }
+
2394 ph->kh = arg->kh;
+
2395 ph->se = req->se;
+
2396 }
+
2397
+
2398 req->se->op.poll(req, nodeid, &fi, ph);
+
2399 } else {
+
2400 fuse_reply_err(req, ENOSYS);
+
2401 }
+
2402}
+
2403
+
2404static void do_poll(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
2405{
+
2406 _do_poll(req, nodeid, inarg, NULL);
+
2407}
+
2408
+
2409static void _do_fallocate(fuse_req_t req, const fuse_ino_t nodeid,
+
2410 const void *op_in, const void *in_payload)
+
2411{
+
2412 (void)in_payload;
+
2413 const struct fuse_fallocate_in *arg = op_in;
+
2414 struct fuse_file_info fi;
+
2415
+
2416 memset(&fi, 0, sizeof(fi));
+
2417 fi.fh = arg->fh;
+
2418
+
2419 if (req->se->op.fallocate)
+
2420 req->se->op.fallocate(req, nodeid, arg->mode, arg->offset,
+
2421 arg->length, &fi);
+
2422 else
+
2423 fuse_reply_err(req, ENOSYS);
+
2424}
+
2425
+
2426static void do_fallocate(fuse_req_t req, const fuse_ino_t nodeid,
+
2427 const void *inarg)
+
2428{
+
2429 _do_fallocate(req, nodeid, inarg, NULL);
+
2430}
+
2431
+
2432static void copy_file_range_common(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2433 const struct fuse_copy_file_range_in *arg)
+
2434{
+
2435 struct fuse_file_info fi_in, fi_out;
+
2436
+
2437 memset(&fi_in, 0, sizeof(fi_in));
+
2438 fi_in.fh = arg->fh_in;
+
2439
+
2440 memset(&fi_out, 0, sizeof(fi_out));
+
2441 fi_out.fh = arg->fh_out;
+
2442
+
2443 if (req->se->op.copy_file_range)
+
2444 req->se->op.copy_file_range(req, nodeid_in, arg->off_in, &fi_in,
+
2445 arg->nodeid_out, arg->off_out,
+
2446 &fi_out, arg->len, arg->flags);
+
2447 else
+
2448 fuse_reply_err(req, ENOSYS);
+
2449}
+
2450
+
2451static void _do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2452 const void *op_in, const void *in_payload)
+
2453{
+
2454 const struct fuse_copy_file_range_in *arg = op_in;
+
2455 struct fuse_copy_file_range_in arg_tmp;
+
2456
+
2457 (void) in_payload;
+
2458 /* fuse_write_out can only handle 32bit copy size */
+
2459 if (arg->len > 0xfffff000) {
+
2460 arg_tmp = *arg;
+
2461 arg_tmp.len = 0xfffff000;
+
2462 arg = &arg_tmp;
+
2463 }
+
2464 copy_file_range_common(req, nodeid_in, arg);
+
2465}
+
2466
+
2467static void do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2468 const void *inarg)
+
2469{
+
2470 _do_copy_file_range(req, nodeid_in, inarg, NULL);
+
2471}
+
2472
+
2473static void _do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2474 const void *op_in, const void *in_payload)
+
2475{
+
2476 (void) in_payload;
+
2477 req->flags.is_copy_file_range_64 = 1;
+
2478 /* Limit size on 32bit userspace to avoid conversion overflow */
+
2479 if (sizeof(size_t) == 4)
+
2480 _do_copy_file_range(req, nodeid_in, op_in, NULL);
+
2481 else
+
2482 copy_file_range_common(req, nodeid_in, op_in);
+
2483}
+
2484
+
2485static void do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2486 const void *inarg)
+
2487{
+
2488 _do_copy_file_range_64(req, nodeid_in, inarg, NULL);
+
2489}
+
2490
+
2491/*
+
2492 * Note that the uint64_t offset in struct fuse_lseek_in is derived from
+
2493 * linux kernel loff_t and is therefore signed.
+
2494 */
+
2495static void _do_lseek(fuse_req_t req, const fuse_ino_t nodeid,
+
2496 const void *op_in, const void *in_payload)
+
2497{
+
2498 (void)in_payload;
+
2499 const struct fuse_lseek_in *arg = op_in;
+
2500 struct fuse_file_info fi;
+
2501
+
2502 memset(&fi, 0, sizeof(fi));
+
2503 fi.fh = arg->fh;
+
2504
+
2505 if (req->se->op.lseek)
+
2506 req->se->op.lseek(req, nodeid, arg->offset, arg->whence, &fi);
+
2507 else
+
2508 fuse_reply_err(req, ENOSYS);
+
2509}
+
2510
+
2511static void do_lseek(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
2512{
+
2513 _do_lseek(req, nodeid, inarg, NULL);
+
2514}
+
2515
+
2516#ifdef HAVE_STATX
+
2517static void _do_statx(fuse_req_t req, const fuse_ino_t nodeid,
+
2518 const void *op_in, const void *in_payload)
+
2519{
+
2520 (void)in_payload;
+
2521 const struct fuse_statx_in *arg = op_in;
+
2522 struct fuse_file_info *fip = NULL;
+
2523 struct fuse_file_info fi;
+
2524
+
2525 if (arg->getattr_flags & FUSE_GETATTR_FH) {
+
2526 memset(&fi, 0, sizeof(fi));
+
2527 fi.fh = arg->fh;
+
2528 fip = &fi;
+
2529 }
+
2530
+
2531 if (req->se->op.statx)
+
2532 req->se->op.statx(req, nodeid, arg->sx_flags, arg->sx_mask, fip);
+
2533 else
+
2534 fuse_reply_err(req, ENOSYS);
+
2535}
+
2536#else
+
2537static void _do_statx(fuse_req_t req, const fuse_ino_t nodeid,
+
2538 const void *op_in, const void *in_payload)
+
2539{
+
2540 (void)in_payload;
+
2541 (void)req;
+
2542 (void)nodeid;
+
2543 (void)op_in;
+
2544}
+
2545#endif
+
2546
+
2547static void do_statx(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2548{
+
2549 _do_statx(req, nodeid, inarg, NULL);
+
2550}
+
2551
+
2552static bool want_flags_valid(uint64_t capable, uint64_t want)
+
2553{
+
2554 uint64_t unknown_flags = want & (~capable);
+
2555 if (unknown_flags != 0) {
+
2556 fuse_log(FUSE_LOG_ERR,
+
2557 "fuse: unknown connection 'want' flags: 0x%08llx\n",
+
2558 (unsigned long long)unknown_flags);
+
2559 return false;
+
2560 }
+
2561 return true;
+
2562}
+
2563
+ +
2568{
+
2569 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
2570
+
2571 /*
+
2572 * Convert want to want_ext if necessary.
+
2573 * For the high level interface this function might be called
+
2574 * twice, once from the high level interface and once from the
+
2575 * low level interface. Both, with different want_ext_default and
+
2576 * want_default values. In order to suppress a failure for the
+
2577 * second call, we check if the lower 32 bits of want_ext are
+
2578 * already set to the value of want.
+
2579 */
+
2580 if (conn->want != se->conn_want &&
+
2581 fuse_lower_32_bits(conn->want_ext) != conn->want) {
+
2582 if (conn->want_ext != se->conn_want_ext) {
+
2583 fuse_log(FUSE_LOG_ERR,
+
2584 "%s: Both conn->want_ext and conn->want are set.\n"
+
2585 "want=%x want_ext=%llx, se->want=%x se->want_ext=%llx\n",
+
2586 __func__, conn->want,
+
2587 (unsigned long long)conn->want_ext,
+
2588 se->conn_want,
+
2589 (unsigned long long)se->conn_want_ext);
+
2590 return -EINVAL;
+
2591 }
+
2592
+
2593 /* high bits from want_ext, low bits from want */
+
2594 conn->want_ext = fuse_higher_32_bits(conn->want_ext) |
+
2595 conn->want;
+
2596 }
+
2597
+
2598 /* ensure there won't be a second conversion */
+
2599 conn->want = fuse_lower_32_bits(conn->want_ext);
+
2600
+
2601 return 0;
+
2602}
+
2603
+
2604bool fuse_set_feature_flag(struct fuse_conn_info *conn,
+
2605 uint64_t flag)
+
2606{
+
2607 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
2608
+
2609 if (conn->capable_ext & flag) {
+
2610 conn->want_ext |= flag;
+
2611 se->conn_want_ext |= flag;
+
2612 conn->want |= flag;
+
2613 se->conn_want |= flag;
+
2614 return true;
+
2615 }
+
2616 return false;
+
2617}
+
2618
+
2619void fuse_unset_feature_flag(struct fuse_conn_info *conn,
+
2620 uint64_t flag)
+
2621{
+
2622 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
2623
+
2624 conn->want_ext &= ~flag;
+
2625 se->conn_want_ext &= ~flag;
+
2626 conn->want &= ~flag;
+
2627 se->conn_want &= ~flag;
+
2628}
+
2629
+
2630bool fuse_get_feature_flag(struct fuse_conn_info *conn,
+
2631 uint64_t flag)
+
2632{
+
2633 return conn->capable_ext & flag ? true : false;
+
2634}
+
2635
+
2636/* Prevent bogus data races (bogus since "init" is called before
+
2637 * multi-threading becomes relevant */
+
2638static __attribute__((no_sanitize("thread"))) void
+
2639_do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
2640 const void *in_payload)
+
2641{
+
2642 (void)in_payload;
+
2643 const struct fuse_init_in *arg = op_in;
+
2644 struct fuse_init_out outarg;
+
2645 struct fuse_session *se = req->se;
+
2646 size_t bufsize = se->bufsize;
+
2647 size_t outargsize = sizeof(outarg);
+
2648 uint64_t inargflags = 0;
+
2649 uint64_t outargflags = 0;
+
2650 bool buf_reallocable = se->buf_reallocable;
+
2651 (void) nodeid;
+
2652 bool enable_io_uring = false;
+
2653
+
2654 if (se->debug) {
+
2655 fuse_log(FUSE_LOG_DEBUG, "INIT: %u.%u\n", arg->major, arg->minor);
+
2656 if (arg->major == 7 && arg->minor >= 6) {
+
2657 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
2658 fuse_log(FUSE_LOG_DEBUG, "max_readahead=0x%08x\n",
+
2659 arg->max_readahead);
+
2660 }
+
2661 }
+
2662 se->conn.proto_major = arg->major;
+
2663 se->conn.proto_minor = arg->minor;
+
2664 se->conn.capable_ext = 0;
+
2665 se->conn.want_ext = 0;
+
2666
+
2667 memset(&outarg, 0, sizeof(outarg));
+
2668 outarg.major = FUSE_KERNEL_VERSION;
+
2669 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
2670
+
2671 if (arg->major < 7) {
+
2672 fuse_log(FUSE_LOG_ERR, "fuse: unsupported protocol version: %u.%u\n",
+
2673 arg->major, arg->minor);
+
2674 fuse_reply_err(req, EPROTO);
+
2675 return;
+
2676 }
+
2677
+
2678 if (arg->major > 7) {
+
2679 /* Wait for a second INIT request with a 7.X version */
+
2680 send_reply_ok(req, &outarg, sizeof(outarg));
+
2681 return;
+
2682 }
+
2683
+
2684 if (arg->minor >= 6) {
+
2685 if (arg->max_readahead < se->conn.max_readahead)
+
2686 se->conn.max_readahead = arg->max_readahead;
+
2687 inargflags = arg->flags;
+
2688 if (inargflags & FUSE_INIT_EXT)
+
2689 inargflags = inargflags | (uint64_t) arg->flags2 << 32;
+
2690 if (inargflags & FUSE_ASYNC_READ)
+
2691 se->conn.capable_ext |= FUSE_CAP_ASYNC_READ;
+
2692 if (inargflags & FUSE_POSIX_LOCKS)
+
2693 se->conn.capable_ext |= FUSE_CAP_POSIX_LOCKS;
+
2694 if (inargflags & FUSE_ATOMIC_O_TRUNC)
+
2695 se->conn.capable_ext |= FUSE_CAP_ATOMIC_O_TRUNC;
+
2696 if (inargflags & FUSE_EXPORT_SUPPORT)
+
2697 se->conn.capable_ext |= FUSE_CAP_EXPORT_SUPPORT;
+
2698 if (inargflags & FUSE_DONT_MASK)
+
2699 se->conn.capable_ext |= FUSE_CAP_DONT_MASK;
+
2700 if (inargflags & FUSE_FLOCK_LOCKS)
+
2701 se->conn.capable_ext |= FUSE_CAP_FLOCK_LOCKS;
+
2702 if (inargflags & FUSE_AUTO_INVAL_DATA)
+
2703 se->conn.capable_ext |= FUSE_CAP_AUTO_INVAL_DATA;
+
2704 if (inargflags & FUSE_DO_READDIRPLUS)
+
2705 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS;
+
2706 if (inargflags & FUSE_READDIRPLUS_AUTO)
+
2707 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS_AUTO;
+
2708 if (inargflags & FUSE_ASYNC_DIO)
+
2709 se->conn.capable_ext |= FUSE_CAP_ASYNC_DIO;
+
2710 if (inargflags & FUSE_WRITEBACK_CACHE)
+
2711 se->conn.capable_ext |= FUSE_CAP_WRITEBACK_CACHE;
+
2712 if (inargflags & FUSE_NO_OPEN_SUPPORT)
+
2713 se->conn.capable_ext |= FUSE_CAP_NO_OPEN_SUPPORT;
+
2714 if (inargflags & FUSE_PARALLEL_DIROPS)
+
2715 se->conn.capable_ext |= FUSE_CAP_PARALLEL_DIROPS;
+
2716 if (inargflags & FUSE_POSIX_ACL)
+
2717 se->conn.capable_ext |= FUSE_CAP_POSIX_ACL;
+
2718 if (inargflags & FUSE_HANDLE_KILLPRIV)
+
2719 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV;
+
2720 if (inargflags & FUSE_HANDLE_KILLPRIV_V2)
+
2721 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV_V2;
+
2722 if (inargflags & FUSE_CACHE_SYMLINKS)
+
2723 se->conn.capable_ext |= FUSE_CAP_CACHE_SYMLINKS;
+
2724 if (inargflags & FUSE_NO_OPENDIR_SUPPORT)
+
2725 se->conn.capable_ext |= FUSE_CAP_NO_OPENDIR_SUPPORT;
+
2726 if (inargflags & FUSE_EXPLICIT_INVAL_DATA)
+
2727 se->conn.capable_ext |= FUSE_CAP_EXPLICIT_INVAL_DATA;
+
2728 if (inargflags & FUSE_SETXATTR_EXT)
+
2729 se->conn.capable_ext |= FUSE_CAP_SETXATTR_EXT;
+
2730 if (!(inargflags & FUSE_MAX_PAGES)) {
+
2731 size_t max_bufsize =
+
2732 FUSE_DEFAULT_MAX_PAGES_PER_REQ * getpagesize()
+
2733 + FUSE_BUFFER_HEADER_SIZE;
+
2734 if (bufsize > max_bufsize) {
+
2735 bufsize = max_bufsize;
+
2736 }
+
2737 buf_reallocable = false;
+
2738 }
+
2739 if (inargflags & FUSE_DIRECT_IO_ALLOW_MMAP)
+
2740 se->conn.capable_ext |= FUSE_CAP_DIRECT_IO_ALLOW_MMAP;
+
2741 if (arg->minor >= 38 || (inargflags & FUSE_HAS_EXPIRE_ONLY))
+
2742 se->conn.capable_ext |= FUSE_CAP_EXPIRE_ONLY;
+
2743 if (inargflags & FUSE_PASSTHROUGH)
+
2744 se->conn.capable_ext |= FUSE_CAP_PASSTHROUGH;
+
2745 if (inargflags & FUSE_NO_EXPORT_SUPPORT)
+
2746 se->conn.capable_ext |= FUSE_CAP_NO_EXPORT_SUPPORT;
+
2747 if (inargflags & FUSE_OVER_IO_URING)
+
2748 se->conn.capable_ext |= FUSE_CAP_OVER_IO_URING;
+
2749
+
2750 } else {
+
2751 se->conn.max_readahead = 0;
+
2752 }
+
2753
+
2754 if (se->conn.proto_minor >= 14) {
+
2755#ifdef HAVE_SPLICE
+
2756#ifdef HAVE_VMSPLICE
+
2757 if ((se->io == NULL) || (se->io->splice_send != NULL)) {
+
2758 se->conn.capable_ext |= FUSE_CAP_SPLICE_WRITE |
+ +
2760 }
+
2761#endif
+
2762 if ((se->io == NULL) || (se->io->splice_receive != NULL)) {
+
2763 se->conn.capable_ext |= FUSE_CAP_SPLICE_READ;
+
2764 }
+
2765#endif
+
2766 }
+
2767 if (se->conn.proto_minor >= 18)
+
2768 se->conn.capable_ext |= FUSE_CAP_IOCTL_DIR;
+
2769
+
2770 /* Default settings for modern filesystems.
+
2771 *
+
2772 * Most of these capabilities were disabled by default in
+
2773 * libfuse2 for backwards compatibility reasons. In libfuse3,
+
2774 * we can finally enable them by default (as long as they're
+
2775 * supported by the kernel).
+
2776 */
+
2777#define LL_SET_DEFAULT(cond, cap) \
+
2778 if ((cond)) \
+
2779 fuse_set_feature_flag(&se->conn, cap)
+
2780
+
2781 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_READ);
+
2782 LL_SET_DEFAULT(1, FUSE_CAP_AUTO_INVAL_DATA);
+
2783 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_DIO);
+
2784 LL_SET_DEFAULT(1, FUSE_CAP_IOCTL_DIR);
+
2785 LL_SET_DEFAULT(1, FUSE_CAP_ATOMIC_O_TRUNC);
+
2786 LL_SET_DEFAULT(se->op.write_buf, FUSE_CAP_SPLICE_READ);
+
2787 LL_SET_DEFAULT(se->op.getlk && se->op.setlk,
+ +
2789 LL_SET_DEFAULT(se->op.flock, FUSE_CAP_FLOCK_LOCKS);
+
2790 LL_SET_DEFAULT(se->op.readdirplus, FUSE_CAP_READDIRPLUS);
+
2791 LL_SET_DEFAULT(se->op.readdirplus && se->op.readdir,
+ +
2793 LL_SET_DEFAULT(1, FUSE_CAP_OVER_IO_URING);
+
2794
+
2795 /* This could safely become default, but libfuse needs an API extension
+
2796 * to support it
+
2797 * LL_SET_DEFAULT(1, FUSE_CAP_SETXATTR_EXT);
+
2798 */
+
2799
+
2800 se->conn.time_gran = 1;
+
2801
+
2802 if (se->op.init) {
+
2803 // Apply the first 32 bits of capable_ext to capable
+
2804 se->conn.capable = fuse_lower_32_bits(se->conn.capable_ext);
+
2805
+
2806 se->op.init(se->userdata, &se->conn);
+
2807
+
2808 /*
+
2809 * se->conn.want is 32-bit value and deprecated in favour of
+
2810 * se->conn.want_ext
+
2811 * Userspace might still use conn.want - we need to convert it
+
2812 */
+ +
2814 }
+
2815
+
2816 if (!want_flags_valid(se->conn.capable_ext, se->conn.want_ext)) {
+
2817 fuse_reply_err(req, EPROTO);
+
2818 se->error = -EPROTO;
+ +
2820 return;
+
2821 }
+
2822
+
2823 unsigned max_read_mo = get_max_read(se->mo);
+
2824 if (se->conn.max_read != max_read_mo) {
+
2825 fuse_log(FUSE_LOG_ERR, "fuse: error: init() and fuse_session_new() "
+
2826 "requested different maximum read size (%u vs %u)\n",
+
2827 se->conn.max_read, max_read_mo);
+
2828 fuse_reply_err(req, EPROTO);
+
2829 se->error = -EPROTO;
+ +
2831 return;
+
2832 }
+
2833
+
2834 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
2835 fuse_log(FUSE_LOG_ERR,
+
2836 "fuse: warning: buffer size too small: %zu\n",
+
2837 bufsize);
+
2838 bufsize = FUSE_MIN_READ_BUFFER;
+
2839 }
+
2840
+
2841 if (buf_reallocable)
+
2842 bufsize = UINT_MAX;
+
2843 se->conn.max_write = MIN(se->conn.max_write, bufsize - FUSE_BUFFER_HEADER_SIZE);
+
2844 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
2845
+
2846 if (arg->flags & FUSE_MAX_PAGES) {
+
2847 outarg.flags |= FUSE_MAX_PAGES;
+
2848 outarg.max_pages = (se->conn.max_write - 1) / getpagesize() + 1;
+
2849 }
+
2850 outargflags = outarg.flags;
+
2851 /* Always enable big writes, this is superseded
+
2852 by the max_write option */
+
2853 outargflags |= FUSE_BIG_WRITES;
+
2854
+
2855 if (se->conn.want_ext & FUSE_CAP_ASYNC_READ)
+
2856 outargflags |= FUSE_ASYNC_READ;
+
2857 if (se->conn.want_ext & FUSE_CAP_POSIX_LOCKS)
+
2858 outargflags |= FUSE_POSIX_LOCKS;
+
2859 if (se->conn.want_ext & FUSE_CAP_ATOMIC_O_TRUNC)
+
2860 outargflags |= FUSE_ATOMIC_O_TRUNC;
+
2861 if (se->conn.want_ext & FUSE_CAP_EXPORT_SUPPORT)
+
2862 outargflags |= FUSE_EXPORT_SUPPORT;
+
2863 if (se->conn.want_ext & FUSE_CAP_DONT_MASK)
+
2864 outargflags |= FUSE_DONT_MASK;
+
2865 if (se->conn.want_ext & FUSE_CAP_FLOCK_LOCKS)
+
2866 outargflags |= FUSE_FLOCK_LOCKS;
+
2867 if (se->conn.want_ext & FUSE_CAP_AUTO_INVAL_DATA)
+
2868 outargflags |= FUSE_AUTO_INVAL_DATA;
+
2869 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS)
+
2870 outargflags |= FUSE_DO_READDIRPLUS;
+
2871 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS_AUTO)
+
2872 outargflags |= FUSE_READDIRPLUS_AUTO;
+
2873 if (se->conn.want_ext & FUSE_CAP_ASYNC_DIO)
+
2874 outargflags |= FUSE_ASYNC_DIO;
+
2875 if (se->conn.want_ext & FUSE_CAP_WRITEBACK_CACHE)
+
2876 outargflags |= FUSE_WRITEBACK_CACHE;
+
2877 if (se->conn.want_ext & FUSE_CAP_PARALLEL_DIROPS)
+
2878 outargflags |= FUSE_PARALLEL_DIROPS;
+
2879 if (se->conn.want_ext & FUSE_CAP_POSIX_ACL)
+
2880 outargflags |= FUSE_POSIX_ACL;
+
2881 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV)
+
2882 outargflags |= FUSE_HANDLE_KILLPRIV;
+
2883 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2)
+
2884 outargflags |= FUSE_HANDLE_KILLPRIV_V2;
+
2885 if (se->conn.want_ext & FUSE_CAP_CACHE_SYMLINKS)
+
2886 outargflags |= FUSE_CACHE_SYMLINKS;
+
2887 if (se->conn.want_ext & FUSE_CAP_EXPLICIT_INVAL_DATA)
+
2888 outargflags |= FUSE_EXPLICIT_INVAL_DATA;
+
2889 if (se->conn.want_ext & FUSE_CAP_SETXATTR_EXT)
+
2890 outargflags |= FUSE_SETXATTR_EXT;
+
2891 if (se->conn.want_ext & FUSE_CAP_DIRECT_IO_ALLOW_MMAP)
+
2892 outargflags |= FUSE_DIRECT_IO_ALLOW_MMAP;
+
2893 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH) {
+
2894 outargflags |= FUSE_PASSTHROUGH;
+
2895 /*
+
2896 * outarg.max_stack_depth includes the fuse stack layer,
+
2897 * so it is one more than max_backing_stack_depth.
+
2898 */
+
2899 outarg.max_stack_depth = se->conn.max_backing_stack_depth + 1;
+
2900 }
+
2901 if (se->conn.want_ext & FUSE_CAP_NO_EXPORT_SUPPORT)
+
2902 outargflags |= FUSE_NO_EXPORT_SUPPORT;
+
2903 if (se->uring.enable && se->conn.want_ext & FUSE_CAP_OVER_IO_URING) {
+
2904 outargflags |= FUSE_OVER_IO_URING;
+
2905 enable_io_uring = true;
+
2906 }
+
2907
+
2908 if ((inargflags & FUSE_REQUEST_TIMEOUT) && se->conn.request_timeout) {
+
2909 outargflags |= FUSE_REQUEST_TIMEOUT;
+
2910 outarg.request_timeout = se->conn.request_timeout;
+
2911 }
+
2912
+
2913 outarg.max_readahead = se->conn.max_readahead;
+
2914 outarg.max_write = se->conn.max_write;
+
2915 if (se->conn.proto_minor >= 13) {
+
2916 if (se->conn.max_background >= (1 << 16))
+
2917 se->conn.max_background = (1 << 16) - 1;
+
2918 if (se->conn.congestion_threshold > se->conn.max_background)
+
2919 se->conn.congestion_threshold = se->conn.max_background;
+
2920 if (!se->conn.congestion_threshold) {
+
2921 se->conn.congestion_threshold =
+
2922 se->conn.max_background * 3 / 4;
+
2923 }
+
2924
+
2925 outarg.max_background = se->conn.max_background;
+
2926 outarg.congestion_threshold = se->conn.congestion_threshold;
+
2927 }
+
2928 if (se->conn.proto_minor >= 23)
+
2929 outarg.time_gran = se->conn.time_gran;
+
2930
+
2931 if (se->debug) {
+
2932 fuse_log(FUSE_LOG_DEBUG, " INIT: %u.%u\n", outarg.major, outarg.minor);
+
2933 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
2934 fuse_log(FUSE_LOG_DEBUG, " max_readahead=0x%08x\n",
+
2935 outarg.max_readahead);
+
2936 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
2937 fuse_log(FUSE_LOG_DEBUG, " max_background=%i\n",
+
2938 outarg.max_background);
+
2939 fuse_log(FUSE_LOG_DEBUG, " congestion_threshold=%i\n",
+
2940 outarg.congestion_threshold);
+
2941 fuse_log(FUSE_LOG_DEBUG, " time_gran=%u\n",
+
2942 outarg.time_gran);
+
2943 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH)
+
2944 fuse_log(FUSE_LOG_DEBUG, " max_stack_depth=%u\n",
+
2945 outarg.max_stack_depth);
+
2946 }
+
2947 if (arg->minor < 5)
+
2948 outargsize = FUSE_COMPAT_INIT_OUT_SIZE;
+
2949 else if (arg->minor < 23)
+
2950 outargsize = FUSE_COMPAT_22_INIT_OUT_SIZE;
+
2951
+
2952 /* XXX: Add an option to make non-available io-uring fatal */
+
2953 if (enable_io_uring) {
+
2954 int ring_rc = fuse_uring_start(se);
+
2955
+
2956 if (ring_rc != 0) {
+
2957 fuse_log(FUSE_LOG_INFO,
+
2958 "fuse: failed to start io-uring: %s\n",
+
2959 strerror(ring_rc));
+
2960 outargflags &= ~FUSE_OVER_IO_URING;
+
2961 enable_io_uring = false;
+
2962 }
+
2963 }
+
2964
+
2965 if (inargflags & FUSE_INIT_EXT) {
+
2966 outargflags |= FUSE_INIT_EXT;
+
2967 outarg.flags2 = outargflags >> 32;
+
2968 }
+
2969 outarg.flags = outargflags;
+
2970
+
2971 /*
+
2972 * Has to be set before replying, as new kernel requests might
+
2973 * immediately arrive and got_init is used for op-code sanity.
+
2974 * Especially with external handlers, where we have no control
+
2975 * over the thread scheduling.
+
2976 */
+
2977 se->got_init = 1;
+
2978 send_reply_ok(req, &outarg, outargsize);
+
2979 if (enable_io_uring)
+
2980 fuse_uring_wake_ring_threads(se);
+
2981}
+
2982
+
2983static __attribute__((no_sanitize("thread"))) void
+
2984do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2985{
+
2986 _do_init(req, nodeid, inarg, NULL);
+
2987}
+
2988
+
2989static void _do_destroy(fuse_req_t req, const fuse_ino_t nodeid,
+
2990 const void *op_in, const void *in_payload)
+
2991{
+
2992 struct fuse_session *se = req->se;
+
2993 char *mountpoint;
+
2994
+
2995 (void) nodeid;
+
2996 (void)op_in;
+
2997 (void)in_payload;
+
2998
+
2999 mountpoint = atomic_exchange(&se->mountpoint, NULL);
+
3000 free(mountpoint);
+
3001
+
3002 se->got_destroy = 1;
+
3003 se->got_init = 0;
+
3004 if (se->op.destroy)
+
3005 se->op.destroy(se->userdata);
+
3006
+
3007 send_reply_ok(req, NULL, 0);
+
3008}
+
3009
+
3010static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
3011{
+
3012 _do_destroy(req, nodeid, inarg, NULL);
+
3013}
+
3014
+
3015static void list_del_nreq(struct fuse_notify_req *nreq)
+
3016{
+
3017 struct fuse_notify_req *prev = nreq->prev;
+
3018 struct fuse_notify_req *next = nreq->next;
+
3019 prev->next = next;
+
3020 next->prev = prev;
+
3021}
+
3022
+
3023static void list_add_nreq(struct fuse_notify_req *nreq,
+
3024 struct fuse_notify_req *next)
+
3025{
+
3026 struct fuse_notify_req *prev = next->prev;
+
3027 nreq->next = next;
+
3028 nreq->prev = prev;
+
3029 prev->next = nreq;
+
3030 next->prev = nreq;
+
3031}
+
3032
+
3033static void list_init_nreq(struct fuse_notify_req *nreq)
+
3034{
+
3035 nreq->next = nreq;
+
3036 nreq->prev = nreq;
+
3037}
+
3038
+
3039static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid,
+
3040 const void *inarg, const struct fuse_buf *buf)
+
3041{
+
3042 struct fuse_session *se = req->se;
+
3043 struct fuse_notify_req *nreq;
+
3044 struct fuse_notify_req *head;
+
3045
+
3046 pthread_mutex_lock(&se->lock);
+
3047 head = &se->notify_list;
+
3048 for (nreq = head->next; nreq != head; nreq = nreq->next) {
+
3049 if (nreq->unique == req->unique) {
+
3050 list_del_nreq(nreq);
+
3051 break;
+
3052 }
+
3053 }
+
3054 pthread_mutex_unlock(&se->lock);
+
3055
+
3056 if (nreq != head)
+
3057 nreq->reply(nreq, req, nodeid, inarg, buf);
+
3058}
+
3059
+
3060static int send_notify_iov(struct fuse_session *se, int notify_code,
+
3061 struct iovec *iov, int count)
+
3062{
+
3063 struct fuse_out_header out;
+
3064 struct fuse_req *req = NULL;
+
3065
+
3066 if (!se->got_init)
+
3067 return -ENOTCONN;
+
3068
+
3069 out.unique = 0;
+
3070 out.error = notify_code;
+
3071 iov[0].iov_base = &out;
+
3072 iov[0].iov_len = sizeof(struct fuse_out_header);
+
3073
+
3074 return fuse_send_msg(se, NULL, iov, count, req);
+
3075}
+
3076
+
3077int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
3078{
+
3079 if (ph != NULL) {
+
3080 struct fuse_notify_poll_wakeup_out outarg;
+
3081 struct iovec iov[2];
+
3082
+
3083 outarg.kh = ph->kh;
+
3084
+
3085 iov[1].iov_base = &outarg;
+
3086 iov[1].iov_len = sizeof(outarg);
+
3087
+
3088 return send_notify_iov(ph->se, FUSE_NOTIFY_POLL, iov, 2);
+
3089 } else {
+
3090 return 0;
+
3091 }
+
3092}
+
3093
+
3094int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
3095 off_t off, off_t len)
+
3096{
+
3097 struct fuse_notify_inval_inode_out outarg;
+
3098 struct iovec iov[2];
+
3099
+
3100 if (!se)
+
3101 return -EINVAL;
+
3102
+
3103 if (se->conn.proto_minor < 12)
+
3104 return -ENOSYS;
+
3105
+
3106 outarg.ino = ino;
+
3107 outarg.off = off;
+
3108 outarg.len = len;
+
3109
+
3110 iov[1].iov_base = &outarg;
+
3111 iov[1].iov_len = sizeof(outarg);
+
3112
+
3113 return send_notify_iov(se, FUSE_NOTIFY_INVAL_INODE, iov, 2);
+
3114}
+
3115
+
3116int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
+
3117{
+
3118 struct iovec iov[1];
+
3119
+
3120 if (!se)
+
3121 return -EINVAL;
+
3122
+
3123 if (se->conn.proto_minor < 44)
+
3124 return -ENOSYS;
+
3125
+
3126 return send_notify_iov(se, FUSE_NOTIFY_INC_EPOCH, iov, 1);
+
3127}
+
3128
+
3148static int fuse_lowlevel_notify_entry(struct fuse_session *se, fuse_ino_t parent,
+
3149 const char *name, size_t namelen,
+
3150 enum fuse_notify_entry_flags flags)
+
3151{
+
3152 struct fuse_notify_inval_entry_out outarg;
+
3153 struct iovec iov[3];
+
3154
+
3155 if (!se)
+
3156 return -EINVAL;
+
3157
+
3158 if (se->conn.proto_minor < 12)
+
3159 return -ENOSYS;
+
3160
+
3161 outarg.parent = parent;
+
3162 outarg.namelen = namelen;
+
3163 outarg.flags = 0;
+
3164 if (flags & FUSE_LL_EXPIRE_ONLY)
+
3165 outarg.flags |= FUSE_EXPIRE_ONLY;
+
3166
+
3167 iov[1].iov_base = &outarg;
+
3168 iov[1].iov_len = sizeof(outarg);
+
3169 iov[2].iov_base = (void *)name;
+
3170 iov[2].iov_len = namelen + 1;
+
3171
+
3172 return send_notify_iov(se, FUSE_NOTIFY_INVAL_ENTRY, iov, 3);
+
3173}
+
3174
+
3175int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
3176 const char *name, size_t namelen)
+
3177{
+
3178 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_INVALIDATE);
+
3179}
+
3180
+
3181int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
3182 const char *name, size_t namelen)
+
3183{
+
3184 if (!se)
+
3185 return -EINVAL;
+
3186
+
3187 if (!(se->conn.capable_ext & FUSE_CAP_EXPIRE_ONLY))
+
3188 return -ENOSYS;
+
3189
+
3190 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_EXPIRE_ONLY);
+
3191}
+
3192
+
3193
+
3194int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
3195 fuse_ino_t parent, fuse_ino_t child,
+
3196 const char *name, size_t namelen)
+
3197{
+
3198 struct fuse_notify_delete_out outarg;
+
3199 struct iovec iov[3];
+
3200
+
3201 if (!se)
+
3202 return -EINVAL;
+
3203
+
3204 if (se->conn.proto_minor < 18)
+
3205 return -ENOSYS;
+
3206
+
3207 outarg.parent = parent;
+
3208 outarg.child = child;
+
3209 outarg.namelen = namelen;
+
3210 outarg.padding = 0;
+
3211
+
3212 iov[1].iov_base = &outarg;
+
3213 iov[1].iov_len = sizeof(outarg);
+
3214 iov[2].iov_base = (void *)name;
+
3215 iov[2].iov_len = namelen + 1;
+
3216
+
3217 return send_notify_iov(se, FUSE_NOTIFY_DELETE, iov, 3);
+
3218}
+
3219
+
3220int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
3221 off_t offset, struct fuse_bufvec *bufv,
+
3222 enum fuse_buf_copy_flags flags)
+
3223{
+
3224 struct fuse_out_header out;
+
3225 struct fuse_notify_store_out outarg;
+
3226 struct iovec iov[3];
+
3227 size_t size = fuse_buf_size(bufv);
+
3228 int res;
+
3229 struct fuse_req *req = NULL;
+
3230
+
3231 if (!se)
+
3232 return -EINVAL;
+
3233
+
3234 if (se->conn.proto_minor < 15)
+
3235 return -ENOSYS;
+
3236
+
3237 out.unique = 0;
+
3238 out.error = FUSE_NOTIFY_STORE;
+
3239
+
3240 outarg.nodeid = ino;
+
3241 outarg.offset = offset;
+
3242 outarg.size = size;
+
3243 outarg.padding = 0;
+
3244
+
3245 iov[0].iov_base = &out;
+
3246 iov[0].iov_len = sizeof(out);
+
3247 iov[1].iov_base = &outarg;
+
3248 iov[1].iov_len = sizeof(outarg);
+
3249
+
3250 res = fuse_send_data_iov(se, NULL, iov, 2, bufv, flags, req);
+
3251 if (res > 0)
+
3252 res = -res;
+
3253
+
3254 return res;
+
3255}
+
3256
+
3257struct fuse_retrieve_req {
+
3258 struct fuse_notify_req nreq;
+
3259 void *cookie;
+
3260};
+
3261
+
3262static void fuse_ll_retrieve_reply(struct fuse_notify_req *nreq,
+
3263 fuse_req_t req, fuse_ino_t ino,
+
3264 const void *inarg,
+
3265 const struct fuse_buf *ibuf)
+
3266{
+
3267 struct fuse_session *se = req->se;
+
3268 struct fuse_retrieve_req *rreq =
+
3269 container_of(nreq, struct fuse_retrieve_req, nreq);
+
3270 const struct fuse_notify_retrieve_in *arg = inarg;
+
3271 struct fuse_bufvec bufv = {
+
3272 .buf[0] = *ibuf,
+
3273 .count = 1,
+
3274 };
+
3275
+
3276 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
3277 bufv.buf[0].mem = PARAM(arg);
+
3278
+
3279 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
3280 sizeof(struct fuse_notify_retrieve_in);
+
3281
+
3282 if (bufv.buf[0].size < arg->size) {
+
3283 fuse_log(FUSE_LOG_ERR, "fuse: retrieve reply: buffer size too small\n");
+
3284 fuse_reply_none(req);
+
3285 goto out;
+
3286 }
+
3287 bufv.buf[0].size = arg->size;
+
3288
+
3289 if (se->op.retrieve_reply) {
+
3290 se->op.retrieve_reply(req, rreq->cookie, ino,
+
3291 arg->offset, &bufv);
+
3292 } else {
+
3293 fuse_reply_none(req);
+
3294 }
+
3295out:
+
3296 free(rreq);
+
3297 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
3298 fuse_ll_clear_pipe(se);
+
3299}
+
3300
+
3301int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
3302 size_t size, off_t offset, void *cookie)
+
3303{
+
3304 struct fuse_notify_retrieve_out outarg;
+
3305 struct iovec iov[2];
+
3306 struct fuse_retrieve_req *rreq;
+
3307 int err;
+
3308
+
3309 if (!se)
+
3310 return -EINVAL;
+
3311
+
3312 if (se->conn.proto_minor < 15)
+
3313 return -ENOSYS;
+
3314
+
3315 rreq = malloc(sizeof(*rreq));
+
3316 if (rreq == NULL)
+
3317 return -ENOMEM;
+
3318
+
3319 pthread_mutex_lock(&se->lock);
+
3320 rreq->cookie = cookie;
+
3321 rreq->nreq.unique = se->notify_ctr++;
+
3322 rreq->nreq.reply = fuse_ll_retrieve_reply;
+
3323 list_add_nreq(&rreq->nreq, &se->notify_list);
+
3324 pthread_mutex_unlock(&se->lock);
+
3325
+
3326 outarg.notify_unique = rreq->nreq.unique;
+
3327 outarg.nodeid = ino;
+
3328 outarg.offset = offset;
+
3329 outarg.size = size;
+
3330 outarg.padding = 0;
+
3331
+
3332 iov[1].iov_base = &outarg;
+
3333 iov[1].iov_len = sizeof(outarg);
+
3334
+
3335 err = send_notify_iov(se, FUSE_NOTIFY_RETRIEVE, iov, 2);
+
3336 if (err) {
+
3337 pthread_mutex_lock(&se->lock);
+
3338 list_del_nreq(&rreq->nreq);
+
3339 pthread_mutex_unlock(&se->lock);
+
3340 free(rreq);
+
3341 }
+
3342
+
3343 return err;
+
3344}
+
3345
+ +
3347{
+
3348 return req->se->userdata;
+
3349}
+
3350
+
3351const struct fuse_ctx *fuse_req_ctx(fuse_req_t req)
+
3352{
+
3353 return &req->ctx;
+
3354}
+
3355
+ +
3357 void *data)
+
3358{
+
3359 pthread_mutex_lock(&req->lock);
+
3360 pthread_mutex_lock(&req->se->lock);
+
3361 req->u.ni.func = func;
+
3362 req->u.ni.data = data;
+
3363 pthread_mutex_unlock(&req->se->lock);
+
3364 if (req->interrupted && func)
+
3365 func(req, data);
+
3366 pthread_mutex_unlock(&req->lock);
+
3367}
+
3368
+ +
3370{
+
3371 int interrupted;
+
3372
+
3373 pthread_mutex_lock(&req->se->lock);
+
3374 interrupted = req->interrupted;
+
3375 pthread_mutex_unlock(&req->se->lock);
+
3376
+
3377 return interrupted;
+
3378}
+
3379
+ +
3381{
+
3382 return req->flags.is_uring;
+
3383}
+
3384
+
3385#ifndef HAVE_URING
+
3386int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz,
+
3387 void **mr)
+
3388{
+
3389 (void)req;
+
3390 (void)payload;
+
3391 (void)payload_sz;
+
3392 (void)mr;
+
3393 return -ENOTSUP;
+
3394}
+
3395#endif
+
3396
+
3397static struct {
+
3398 void (*func)(fuse_req_t req, const fuse_ino_t node, const void *arg);
+
3399 const char *name;
+
3400} fuse_ll_ops[] = {
+
3401 [FUSE_LOOKUP] = { do_lookup, "LOOKUP" },
+
3402 [FUSE_FORGET] = { do_forget, "FORGET" },
+
3403 [FUSE_GETATTR] = { do_getattr, "GETATTR" },
+
3404 [FUSE_SETATTR] = { do_setattr, "SETATTR" },
+
3405 [FUSE_READLINK] = { do_readlink, "READLINK" },
+
3406 [FUSE_SYMLINK] = { do_symlink, "SYMLINK" },
+
3407 [FUSE_MKNOD] = { do_mknod, "MKNOD" },
+
3408 [FUSE_MKDIR] = { do_mkdir, "MKDIR" },
+
3409 [FUSE_UNLINK] = { do_unlink, "UNLINK" },
+
3410 [FUSE_RMDIR] = { do_rmdir, "RMDIR" },
+
3411 [FUSE_RENAME] = { do_rename, "RENAME" },
+
3412 [FUSE_LINK] = { do_link, "LINK" },
+
3413 [FUSE_OPEN] = { do_open, "OPEN" },
+
3414 [FUSE_READ] = { do_read, "READ" },
+
3415 [FUSE_WRITE] = { do_write, "WRITE" },
+
3416 [FUSE_STATFS] = { do_statfs, "STATFS" },
+
3417 [FUSE_RELEASE] = { do_release, "RELEASE" },
+
3418 [FUSE_FSYNC] = { do_fsync, "FSYNC" },
+
3419 [FUSE_SETXATTR] = { do_setxattr, "SETXATTR" },
+
3420 [FUSE_GETXATTR] = { do_getxattr, "GETXATTR" },
+
3421 [FUSE_LISTXATTR] = { do_listxattr, "LISTXATTR" },
+
3422 [FUSE_REMOVEXATTR] = { do_removexattr, "REMOVEXATTR" },
+
3423 [FUSE_FLUSH] = { do_flush, "FLUSH" },
+
3424 [FUSE_INIT] = { do_init, "INIT" },
+
3425 [FUSE_OPENDIR] = { do_opendir, "OPENDIR" },
+
3426 [FUSE_READDIR] = { do_readdir, "READDIR" },
+
3427 [FUSE_RELEASEDIR] = { do_releasedir, "RELEASEDIR" },
+
3428 [FUSE_FSYNCDIR] = { do_fsyncdir, "FSYNCDIR" },
+
3429 [FUSE_GETLK] = { do_getlk, "GETLK" },
+
3430 [FUSE_SETLK] = { do_setlk, "SETLK" },
+
3431 [FUSE_SETLKW] = { do_setlkw, "SETLKW" },
+
3432 [FUSE_ACCESS] = { do_access, "ACCESS" },
+
3433 [FUSE_CREATE] = { do_create, "CREATE" },
+
3434 [FUSE_TMPFILE] = { do_tmpfile, "TMPFILE" },
+
3435 [FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" },
+
3436 [FUSE_BMAP] = { do_bmap, "BMAP" },
+
3437 [FUSE_IOCTL] = { do_ioctl, "IOCTL" },
+
3438 [FUSE_POLL] = { do_poll, "POLL" },
+
3439 [FUSE_FALLOCATE] = { do_fallocate, "FALLOCATE" },
+
3440 [FUSE_DESTROY] = { do_destroy, "DESTROY" },
+
3441 [FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" },
+
3442 [FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" },
+
3443 [FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"},
+
3444 [FUSE_RENAME2] = { do_rename2, "RENAME2" },
+
3445 [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" },
+
3446 [FUSE_COPY_FILE_RANGE_64] = { do_copy_file_range_64, "COPY_FILE_RANGE_64" },
+
3447 [FUSE_LSEEK] = { do_lseek, "LSEEK" },
+
3448 [FUSE_STATX] = { do_statx, "STATX" },
+
3449 [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" },
+
3450};
+
3451
+
3452static struct {
+
3453 void (*func)(fuse_req_t req, const fuse_ino_t ino, const void *op_in,
+
3454 const void *op_payload);
+
3455 const char *name;
+
3456} fuse_ll_ops2[] __attribute__((unused)) = {
+
3457 [FUSE_LOOKUP] = { _do_lookup, "LOOKUP" },
+
3458 [FUSE_FORGET] = { _do_forget, "FORGET" },
+
3459 [FUSE_GETATTR] = { _do_getattr, "GETATTR" },
+
3460 [FUSE_SETATTR] = { _do_setattr, "SETATTR" },
+
3461 [FUSE_READLINK] = { _do_readlink, "READLINK" },
+
3462 [FUSE_SYMLINK] = { _do_symlink, "SYMLINK" },
+
3463 [FUSE_MKNOD] = { _do_mknod, "MKNOD" },
+
3464 [FUSE_MKDIR] = { _do_mkdir, "MKDIR" },
+
3465 [FUSE_UNLINK] = { _do_unlink, "UNLINK" },
+
3466 [FUSE_RMDIR] = { _do_rmdir, "RMDIR" },
+
3467 [FUSE_RENAME] = { _do_rename, "RENAME" },
+
3468 [FUSE_LINK] = { _do_link, "LINK" },
+
3469 [FUSE_OPEN] = { _do_open, "OPEN" },
+
3470 [FUSE_READ] = { _do_read, "READ" },
+
3471 [FUSE_WRITE] = { _do_write, "WRITE" },
+
3472 [FUSE_STATFS] = { _do_statfs, "STATFS" },
+
3473 [FUSE_RELEASE] = { _do_release, "RELEASE" },
+
3474 [FUSE_FSYNC] = { _do_fsync, "FSYNC" },
+
3475 [FUSE_SETXATTR] = { _do_setxattr, "SETXATTR" },
+
3476 [FUSE_GETXATTR] = { _do_getxattr, "GETXATTR" },
+
3477 [FUSE_LISTXATTR] = { _do_listxattr, "LISTXATTR" },
+
3478 [FUSE_REMOVEXATTR] = { _do_removexattr, "REMOVEXATTR" },
+
3479 [FUSE_FLUSH] = { _do_flush, "FLUSH" },
+
3480 [FUSE_INIT] = { _do_init, "INIT" },
+
3481 [FUSE_OPENDIR] = { _do_opendir, "OPENDIR" },
+
3482 [FUSE_READDIR] = { _do_readdir, "READDIR" },
+
3483 [FUSE_RELEASEDIR] = { _do_releasedir, "RELEASEDIR" },
+
3484 [FUSE_FSYNCDIR] = { _do_fsyncdir, "FSYNCDIR" },
+
3485 [FUSE_GETLK] = { _do_getlk, "GETLK" },
+
3486 [FUSE_SETLK] = { _do_setlk, "SETLK" },
+
3487 [FUSE_SETLKW] = { _do_setlkw, "SETLKW" },
+
3488 [FUSE_ACCESS] = { _do_access, "ACCESS" },
+
3489 [FUSE_CREATE] = { _do_create, "CREATE" },
+
3490 [FUSE_TMPFILE] = { _do_tmpfile, "TMPFILE" },
+
3491 [FUSE_INTERRUPT] = { _do_interrupt, "INTERRUPT" },
+
3492 [FUSE_BMAP] = { _do_bmap, "BMAP" },
+
3493 [FUSE_IOCTL] = { _do_ioctl, "IOCTL" },
+
3494 [FUSE_POLL] = { _do_poll, "POLL" },
+
3495 [FUSE_FALLOCATE] = { _do_fallocate, "FALLOCATE" },
+
3496 [FUSE_DESTROY] = { _do_destroy, "DESTROY" },
+
3497 [FUSE_NOTIFY_REPLY] = { (void *)1, "NOTIFY_REPLY" },
+
3498 [FUSE_BATCH_FORGET] = { _do_batch_forget, "BATCH_FORGET" },
+
3499 [FUSE_READDIRPLUS] = { _do_readdirplus, "READDIRPLUS" },
+
3500 [FUSE_RENAME2] = { _do_rename2, "RENAME2" },
+
3501 [FUSE_COPY_FILE_RANGE] = { _do_copy_file_range, "COPY_FILE_RANGE" },
+
3502 [FUSE_COPY_FILE_RANGE_64] = { _do_copy_file_range_64, "COPY_FILE_RANGE_64" },
+
3503 [FUSE_LSEEK] = { _do_lseek, "LSEEK" },
+
3504 [FUSE_STATX] = { _do_statx, "STATX" },
+
3505 [CUSE_INIT] = { _cuse_lowlevel_init, "CUSE_INIT" },
+
3506};
+
3507
+
3508/*
+
3509 * For ABI compatibility we cannot allow higher values than CUSE_INIT.
+
3510 * Without ABI compatibility we could use the size of the array.
+
3511 * #define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
+
3512 */
+
3513#define FUSE_MAXOP (CUSE_INIT + 1)
+
3514
+
3515
+
3520static inline int
+
3521fuse_req_opcode_sanity_ok(struct fuse_session *se, enum fuse_opcode in_op)
+
3522{
+
3523 int err = EIO;
+
3524
+
3525 if (!se->got_init) {
+
3526 enum fuse_opcode expected;
+
3527
+
3528 expected = se->cuse_data ? CUSE_INIT : FUSE_INIT;
+
3529 if (in_op != expected)
+
3530 return err;
+
3531 } else if (in_op == FUSE_INIT || in_op == CUSE_INIT)
+
3532 return err;
+
3533
+
3534 return 0;
+
3535}
+
3536
+
3537static inline void
+
3538fuse_session_in2req(struct fuse_req *req, struct fuse_in_header *in)
+
3539{
+
3540 req->unique = in->unique;
+
3541 req->ctx.uid = in->uid;
+
3542 req->ctx.gid = in->gid;
+
3543 req->ctx.pid = in->pid;
+
3544}
+
3545
+
3549static inline int
+
3550fuse_req_check_allow_root(struct fuse_session *se, enum fuse_opcode in_op,
+
3551 uid_t in_uid)
+
3552{
+
3553 int err = EACCES;
+
3554
+
3555 if (se->deny_others && in_uid != se->owner && in_uid != 0 &&
+
3556 in_op != FUSE_INIT && in_op != FUSE_READ &&
+
3557 in_op != FUSE_WRITE && in_op != FUSE_FSYNC &&
+
3558 in_op != FUSE_RELEASE && in_op != FUSE_READDIR &&
+
3559 in_op != FUSE_FSYNCDIR && in_op != FUSE_RELEASEDIR &&
+
3560 in_op != FUSE_NOTIFY_REPLY &&
+
3561 in_op != FUSE_READDIRPLUS)
+
3562 return err;
+
3563
+
3564 return 0;
+
3565}
+
3566
+
3567static const char *opname(enum fuse_opcode opcode)
+
3568{
+
3569 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
+
3570 return "???";
+
3571 else
+
3572 return fuse_ll_ops[opcode].name;
+
3573}
+
3574
+
3575static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst,
+
3576 struct fuse_bufvec *src)
+
3577{
+
3578 ssize_t res = fuse_buf_copy(dst, src, 0);
+
3579 if (res < 0) {
+
3580 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n", strerror(-res));
+
3581 return res;
+
3582 }
+
3583 if ((size_t)res < fuse_buf_size(dst)) {
+
3584 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: short read\n");
+
3585 return -1;
+
3586 }
+
3587 return 0;
+
3588}
+
3589
+
3590void fuse_session_process_buf(struct fuse_session *se,
+
3591 const struct fuse_buf *buf)
+
3592{
+
3593 fuse_session_process_buf_internal(se, buf, NULL);
+
3594}
+
3595
+
3596/* libfuse internal handler */
+
3597void fuse_session_process_buf_internal(struct fuse_session *se,
+
3598 const struct fuse_buf *buf, struct fuse_chan *ch)
+
3599{
+
3600 const size_t write_header_size = sizeof(struct fuse_in_header) +
+
3601 sizeof(struct fuse_write_in);
+
3602 struct fuse_bufvec bufv = { .buf[0] = *buf, .count = 1 };
+
3603 struct fuse_bufvec tmpbuf = FUSE_BUFVEC_INIT(write_header_size);
+
3604 struct fuse_in_header *in;
+
3605 const void *inarg;
+
3606 struct fuse_req *req;
+
3607 void *mbuf = NULL;
+
3608 int err;
+
3609 int res;
+
3610
+
3611 if (buf->flags & FUSE_BUF_IS_FD) {
+
3612 if (buf->size < tmpbuf.buf[0].size)
+
3613 tmpbuf.buf[0].size = buf->size;
+
3614
+
3615 mbuf = malloc(tmpbuf.buf[0].size);
+
3616 if (mbuf == NULL) {
+
3617 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate header\n");
+
3618 goto clear_pipe;
+
3619 }
+
3620 tmpbuf.buf[0].mem = mbuf;
+
3621
+
3622 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
3623 if (res < 0)
+
3624 goto clear_pipe;
+
3625
+
3626 in = mbuf;
+
3627 } else {
+
3628 in = buf->mem;
+
3629 }
+
3630
+
3631 trace_request_process(in->opcode, in->unique);
+
3632
+
3633 if (se->debug) {
+
3634 fuse_log(FUSE_LOG_DEBUG,
+
3635 "dev unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n",
+
3636 (unsigned long long) in->unique,
+
3637 opname((enum fuse_opcode) in->opcode), in->opcode,
+
3638 (unsigned long long) in->nodeid, buf->size, in->pid);
+
3639 }
+
3640
+
3641 req = fuse_ll_alloc_req(se);
+
3642 if (req == NULL) {
+
3643 struct fuse_out_header out = {
+
3644 .unique = in->unique,
+
3645 .error = -ENOMEM,
+
3646 };
+
3647 struct iovec iov = {
+
3648 .iov_base = &out,
+
3649 .iov_len = sizeof(struct fuse_out_header),
+
3650 };
+
3651
+
3652 fuse_send_msg(se, ch, &iov, 1, NULL);
+
3653 goto clear_pipe;
+
3654 }
+
3655
+
3656 fuse_session_in2req(req, in);
+
3657 req->ch = ch ? fuse_chan_get(ch) : NULL;
+
3658
+
3659 err = fuse_req_opcode_sanity_ok(se, in->opcode);
+
3660 if (err)
+
3661 goto reply_err;
+
3662
+
3663 err = fuse_req_check_allow_root(se, in->opcode, in->uid);
+
3664 if (err)
+
3665 goto reply_err;
+
3666
+
3667 err = ENOSYS;
+
3668 if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func)
+
3669 goto reply_err;
+
3670 /* Do not process interrupt request */
+
3671 if (se->conn.no_interrupt && in->opcode == FUSE_INTERRUPT) {
+
3672 if (se->debug)
+
3673 fuse_log(FUSE_LOG_DEBUG, "FUSE_INTERRUPT: reply to kernel to disable interrupt\n");
+
3674 goto reply_err;
+
3675 }
+
3676 if (!se->conn.no_interrupt && in->opcode != FUSE_INTERRUPT) {
+
3677 struct fuse_req *intr;
+
3678 pthread_mutex_lock(&se->lock);
+
3679 intr = check_interrupt(se, req);
+
3680 list_add_req(req, &se->list);
+
3681 pthread_mutex_unlock(&se->lock);
+
3682 if (intr)
+
3683 fuse_reply_err(intr, EAGAIN);
+
3684 }
+
3685
+
3686 if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size &&
+
3687 (in->opcode != FUSE_WRITE || !se->op.write_buf) &&
+
3688 in->opcode != FUSE_NOTIFY_REPLY) {
+
3689 void *newmbuf;
+
3690
+
3691 err = ENOMEM;
+
3692 newmbuf = realloc(mbuf, buf->size);
+
3693 if (newmbuf == NULL)
+
3694 goto reply_err;
+
3695 mbuf = newmbuf;
+
3696
+
3697 tmpbuf = FUSE_BUFVEC_INIT(buf->size - write_header_size);
+
3698 tmpbuf.buf[0].mem = (char *)mbuf + write_header_size;
+
3699
+
3700 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
3701 err = -res;
+
3702 if (res < 0)
+
3703 goto reply_err;
+
3704
+
3705 in = mbuf;
+
3706 }
+
3707
+
3708 inarg = (void *) &in[1];
+
3709 if (in->opcode == FUSE_WRITE && se->op.write_buf)
+
3710 do_write_buf(req, in->nodeid, inarg, buf);
+
3711 else if (in->opcode == FUSE_NOTIFY_REPLY)
+
3712 do_notify_reply(req, in->nodeid, inarg, buf);
+
3713 else
+
3714 fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg);
+
3715
+
3716out_free:
+
3717 free(mbuf);
+
3718 return;
+
3719
+
3720reply_err:
+
3721 fuse_reply_err(req, err);
+
3722clear_pipe:
+
3723 if (buf->flags & FUSE_BUF_IS_FD)
+
3724 fuse_ll_clear_pipe(se);
+
3725 goto out_free;
+
3726}
+
3727
+
3728void fuse_session_process_uring_cqe(struct fuse_session *se,
+
3729 struct fuse_req *req,
+
3730 struct fuse_in_header *in, void *op_in,
+
3731 void *op_payload, size_t payload_len)
+
3732{
+
3733 int err;
+
3734
+
3735 fuse_session_in2req(req, in);
+
3736
+
3737 err = fuse_req_opcode_sanity_ok(se, in->opcode);
+
3738 if (err)
+
3739 goto reply_err;
+
3740
+
3741 err = fuse_req_check_allow_root(se, in->opcode, in->uid);
+
3742 if (err)
+
3743 goto reply_err;
+
3744
+
3745 err = ENOSYS;
+
3746 if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func)
+
3747 goto reply_err;
+
3748
+
3749 if (se->debug) {
+
3750 fuse_log(
+
3751 FUSE_LOG_DEBUG,
+
3752 "cqe unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n",
+
3753 (unsigned long long)in->unique,
+
3754 opname((enum fuse_opcode)in->opcode), in->opcode,
+
3755 (unsigned long long)in->nodeid, payload_len, in->pid);
+
3756 }
+
3757
+
3758 if (in->opcode == FUSE_WRITE && se->op.write_buf) {
+
3759 struct fuse_bufvec bufv = {
+
3760 .buf[0] = { .size = payload_len,
+
3761 .flags = 0,
+
3762 .mem = op_payload },
+
3763 .count = 1,
+
3764 };
+
3765 _do_write_buf(req, in->nodeid, op_in, &bufv);
+
3766 } else if (in->opcode == FUSE_NOTIFY_REPLY) {
+
3767 struct fuse_buf buf = { .size = payload_len,
+
3768 .mem = op_payload };
+
3769 do_notify_reply(req, in->nodeid, op_in, &buf);
+
3770 } else {
+
3771 fuse_ll_ops2[in->opcode].func(req, in->nodeid, op_in,
+
3772 op_payload);
+
3773 }
+
3774
+
3775 return;
+
3776
+
3777reply_err:
+
3778 fuse_reply_err(req, err);
+
3779}
+
3780
+
3781#define LL_OPTION(n,o,v) \
+
3782 { n, offsetof(struct fuse_session, o), v }
+
3783
+
3784static const struct fuse_opt fuse_ll_opts[] = {
+
3785 LL_OPTION("debug", debug, 1),
+
3786 LL_OPTION("-d", debug, 1),
+
3787 LL_OPTION("--debug", debug, 1),
+
3788 LL_OPTION("allow_root", deny_others, 1),
+
3789 LL_OPTION("io_uring", uring.enable, 1),
+
3790 LL_OPTION("io_uring_q_depth=%u", uring.q_depth, -1),
+ +
3792};
+
3793
+
3794void fuse_lowlevel_version(void)
+
3795{
+
3796 printf("using FUSE kernel interface version %i.%i\n",
+
3797 FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION);
+
3798 fuse_mount_version();
+
3799}
+
3800
+
3801void fuse_lowlevel_help(void)
+
3802{
+
3803 /* These are not all options, but the ones that are
+
3804 potentially of interest to an end-user */
+
3805 printf(
+
3806" -o allow_other allow access by all users\n"
+
3807" -o allow_root allow access by root\n"
+
3808" -o auto_unmount auto unmount on process termination\n"
+
3809" -o io_uring enable io-uring\n"
+
3810" -o io_uring_q_depth=<n> io-uring queue depth\n"
+
3811);
+
3812}
+
3813
+
3814void fuse_session_destroy(struct fuse_session *se)
+
3815{
+
3816 struct fuse_ll_pipe *llp;
+
3817
+
3818 if (se->got_init && !se->got_destroy) {
+
3819 if (se->op.destroy)
+
3820 se->op.destroy(se->userdata);
+
3821 }
+
3822 llp = pthread_getspecific(se->pipe_key);
+
3823 if (llp != NULL)
+
3824 fuse_ll_pipe_free(llp);
+
3825 pthread_key_delete(se->pipe_key);
+
3826 sem_destroy(&se->mt_finish);
+
3827 pthread_mutex_destroy(&se->mt_lock);
+
3828 pthread_mutex_destroy(&se->lock);
+
3829 free(se->cuse_data);
+
3830 if (se->fd != -1)
+
3831 close(se->fd);
+
3832 if (se->io != NULL)
+
3833 free(se->io);
+
3834 destroy_mount_opts(se->mo);
+
3835 free(se);
+
3836}
+
3837
+
3838
+
3839static void fuse_ll_pipe_destructor(void *data)
+
3840{
+
3841 struct fuse_ll_pipe *llp = data;
+
3842 fuse_ll_pipe_free(llp);
+
3843}
+
3844
+
3845void fuse_buf_free(struct fuse_buf *buf)
+
3846{
+
3847 if (buf->mem == NULL)
+
3848 return;
+
3849
+
3850 size_t write_header_sz =
+
3851 sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in);
+
3852
+
3853 char *ptr = (char *)buf->mem - pagesize + write_header_sz;
+
3854 free(ptr);
+
3855 buf->mem = NULL;
+
3856}
+
3857
+
3858/*
+
3859 * This is used to allocate buffers that hold fuse requests
+
3860 */
+
3861static void *buf_alloc(size_t size, bool internal)
+
3862{
+
3863 /*
+
3864 * For libfuse internal caller add in alignment. That cannot be done
+
3865 * for an external caller, as it is not guaranteed that the external
+
3866 * caller frees the raw pointer.
+
3867 */
+
3868 if (internal) {
+
3869 size_t write_header_sz = sizeof(struct fuse_in_header) +
+
3870 sizeof(struct fuse_write_in);
+
3871 size_t new_size = ROUND_UP(size + write_header_sz, pagesize);
+
3872
+
3873 char *buf = aligned_alloc(pagesize, new_size);
+
3874 if (buf == NULL)
+
3875 return NULL;
+
3876
+
3877 buf += pagesize - write_header_sz;
+
3878
+
3879 return buf;
+
3880 } else {
+
3881 return malloc(size);
+
3882 }
+
3883}
+
3884
+
3885/*
+
3886 *@param internal true if called from libfuse internal code
+
3887 */
+
3888static int _fuse_session_receive_buf(struct fuse_session *se,
+
3889 struct fuse_buf *buf, struct fuse_chan *ch,
+
3890 bool internal)
+
3891{
+
3892 int err;
+
3893 ssize_t res;
+
3894 size_t bufsize;
+
3895#ifdef HAVE_SPLICE
+
3896 struct fuse_ll_pipe *llp;
+
3897 struct fuse_buf tmpbuf;
+
3898
+
3899pipe_retry:
+
3900 bufsize = se->bufsize;
+
3901
+
3902 if (se->conn.proto_minor < 14 ||
+
3903 !(se->conn.want_ext & FUSE_CAP_SPLICE_READ))
+
3904 goto fallback;
+
3905
+
3906 llp = fuse_ll_get_pipe(se);
+
3907 if (llp == NULL)
+
3908 goto fallback;
+
3909
+
3910 if (llp->size < bufsize) {
+
3911 if (llp->can_grow) {
+
3912 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, bufsize);
+
3913 if (res == -1) {
+
3914 llp->can_grow = 0;
+
3915 res = grow_pipe_to_max(llp->pipe[0]);
+
3916 if (res > 0)
+
3917 llp->size = res;
+
3918 goto fallback;
+
3919 }
+
3920 llp->size = res;
+
3921 }
+
3922 if (llp->size < bufsize)
+
3923 goto fallback;
+
3924 }
+
3925
+
3926 if (se->io != NULL && se->io->splice_receive != NULL) {
+
3927 res = se->io->splice_receive(ch ? ch->fd : se->fd, NULL,
+
3928 llp->pipe[1], NULL, bufsize, 0,
+
3929 se->userdata);
+
3930 } else {
+
3931 res = splice(ch ? ch->fd : se->fd, NULL, llp->pipe[1], NULL,
+
3932 bufsize, 0);
+
3933 }
+
3934 err = errno;
+
3935 trace_request_receive(err);
+
3936
+
3937 if (fuse_session_exited(se))
+
3938 return 0;
+
3939
+
3940 if (res == -1) {
+
3941 if (err == ENODEV) {
+
3942 /* Filesystem was unmounted, or connection was aborted
+
3943 via /sys/fs/fuse/connections */
+ +
3945 return 0;
+
3946 }
+
3947
+
3948 /* FUSE_INIT might have increased the required bufsize */
+
3949 if (err == EINVAL && bufsize < se->bufsize) {
+
3950 fuse_ll_clear_pipe(se);
+
3951 goto pipe_retry;
+
3952 }
+
3953
+
3954 if (err != EINTR && err != EAGAIN)
+
3955 perror("fuse: splice from device");
+
3956 return -err;
+
3957 }
+
3958
+
3959 if (res < sizeof(struct fuse_in_header)) {
+
3960 fuse_log(FUSE_LOG_ERR, "short splice from fuse device\n");
+
3961 return -EIO;
+
3962 }
+
3963
+
3964 tmpbuf = (struct fuse_buf){
+
3965 .size = res,
+
3966 .flags = FUSE_BUF_IS_FD,
+
3967 .fd = llp->pipe[0],
+
3968 };
+
3969
+
3970 /*
+
3971 * Don't bother with zero copy for small requests.
+
3972 * fuse_loop_mt() needs to check for FORGET so this more than
+
3973 * just an optimization.
+
3974 */
+
3975 if (res < sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) +
+
3976 pagesize) {
+
3977 struct fuse_bufvec src = { .buf[0] = tmpbuf, .count = 1 };
+
3978 struct fuse_bufvec dst = { .count = 1 };
+
3979
+
3980 if (!buf->mem) {
+
3981 buf->mem = buf_alloc(bufsize, internal);
+
3982 if (!buf->mem) {
+
3983 fuse_log(
+
3984 FUSE_LOG_ERR,
+
3985 "fuse: failed to allocate read buffer\n");
+
3986 return -ENOMEM;
+
3987 }
+
3988 buf->mem_size = bufsize;
+
3989 }
+
3990 buf->size = bufsize;
+
3991 buf->flags = 0;
+
3992 dst.buf[0] = *buf;
+
3993
+
3994 res = fuse_buf_copy(&dst, &src, 0);
+
3995 if (res < 0) {
+
3996 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n",
+
3997 strerror(-res));
+
3998 fuse_ll_clear_pipe(se);
+
3999 return res;
+
4000 }
+
4001 if (res < tmpbuf.size) {
+
4002 fuse_log(FUSE_LOG_ERR,
+
4003 "fuse: copy from pipe: short read\n");
+
4004 fuse_ll_clear_pipe(se);
+
4005 return -EIO;
+
4006 }
+
4007 assert(res == tmpbuf.size);
+
4008
+
4009 } else {
+
4010 /* Don't overwrite buf->mem, as that would cause a leak */
+
4011 buf->fd = tmpbuf.fd;
+
4012 buf->flags = tmpbuf.flags;
+
4013 }
+
4014 buf->size = tmpbuf.size;
+
4015
+
4016 return res;
+
4017
+
4018fallback:
+
4019#endif
+
4020 bufsize = internal ? buf->mem_size : se->bufsize;
+
4021 if (!buf->mem) {
+
4022 bufsize = se->bufsize; /* might have changed */
+
4023 buf->mem = buf_alloc(bufsize, internal);
+
4024 if (!buf->mem) {
+
4025 fuse_log(FUSE_LOG_ERR,
+
4026 "fuse: failed to allocate read buffer\n");
+
4027 return -ENOMEM;
+
4028 }
+
4029
+
4030 if (internal)
+
4031 buf->mem_size = bufsize;
+
4032 }
+
4033
+
4034restart:
+
4035 if (se->io != NULL) {
+
4036 /* se->io->read is never NULL if se->io is not NULL as
+
4037 specified by fuse_session_custom_io()*/
+
4038 res = se->io->read(ch ? ch->fd : se->fd, buf->mem, bufsize,
+
4039 se->userdata);
+
4040 } else {
+
4041 res = read(ch ? ch->fd : se->fd, buf->mem, bufsize);
+
4042 }
+
4043 err = errno;
+
4044 trace_request_receive(err);
+
4045
+
4046 if (fuse_session_exited(se))
+
4047 return 0;
+
4048 if (res == -1) {
+
4049 if (err == EINVAL && internal && se->bufsize > bufsize) {
+
4050 /* FUSE_INIT might have increased the required bufsize */
+
4051 bufsize = se->bufsize;
+
4052 void *newbuf = buf_alloc(bufsize, internal);
+
4053 if (!newbuf) {
+
4054 fuse_log(
+
4055 FUSE_LOG_ERR,
+
4056 "fuse: failed to (re)allocate read buffer\n");
+
4057 return -ENOMEM;
+
4058 }
+
4059 fuse_buf_free(buf);
+
4060 buf->mem = newbuf;
+
4061 buf->mem_size = bufsize;
+
4062 goto restart;
+
4063 }
+
4064
+
4065 /* ENOENT means the operation was interrupted, it's safe
+
4066 to restart */
+
4067 if (err == ENOENT)
+
4068 goto restart;
+
4069
+
4070 if (err == ENODEV) {
+
4071 /* Filesystem was unmounted, or connection was aborted
+
4072 via /sys/fs/fuse/connections */
+ +
4074 return 0;
+
4075 }
+
4076 /* Errors occurring during normal operation: EINTR (read
+
4077 interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem
+
4078 umounted) */
+
4079 if (err != EINTR && err != EAGAIN)
+
4080 perror("fuse: reading device");
+
4081 return -err;
+
4082 }
+
4083 if ((size_t)res < sizeof(struct fuse_in_header)) {
+
4084 fuse_log(FUSE_LOG_ERR, "short read on fuse device\n");
+
4085 return -EIO;
+
4086 }
+
4087
+
4088 buf->size = res;
+
4089
+
4090 return res;
+
4091}
+
4092
+
4093int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
4094{
+
4095 return _fuse_session_receive_buf(se, buf, NULL, false);
+
4096}
+
4097
+
4098/* libfuse internal handler */
+
4099int fuse_session_receive_buf_internal(struct fuse_session *se,
+
4100 struct fuse_buf *buf,
+
4101 struct fuse_chan *ch)
+
4102{
+
4103 /*
+
4104 * if run internally thread buffers are from libfuse - we can
+
4105 * reallocate them
+
4106 */
+
4107 if (unlikely(!se->got_init) && !se->buf_reallocable)
+
4108 se->buf_reallocable = true;
+
4109
+
4110 return _fuse_session_receive_buf(se, buf, ch, true);
+
4111}
+
4112
+
4113struct fuse_session *
+
4114fuse_session_new_versioned(struct fuse_args *args,
+
4115 const struct fuse_lowlevel_ops *op, size_t op_size,
+
4116 struct libfuse_version *version, void *userdata)
+
4117{
+
4118 int err;
+
4119 struct fuse_session *se;
+
4120 struct mount_opts *mo;
+
4121
+
4122 if (op == NULL || op_size == 0) {
+
4123 fuse_log(FUSE_LOG_ERR,
+
4124 "fuse: warning: empty op list passed to fuse_session_new()\n");
+
4125 return NULL;
+
4126 }
+
4127
+
4128 if (version == NULL) {
+
4129 fuse_log(FUSE_LOG_ERR, "fuse: warning: version not passed to fuse_session_new()\n");
+
4130 return NULL;
+
4131 }
+
4132
+
4133 if (sizeof(struct fuse_lowlevel_ops) < op_size) {
+
4134 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
4135 op_size = sizeof(struct fuse_lowlevel_ops);
+
4136 }
+
4137
+
4138 if (args == NULL || args->argc == 0) {
+
4139 fuse_log(FUSE_LOG_ERR, "fuse: empty argv passed to fuse_session_new().\n");
+
4140 return NULL;
+
4141 }
+
4142
+
4143 se = (struct fuse_session *) calloc(1, sizeof(struct fuse_session));
+
4144 if (se == NULL) {
+
4145 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
4146 goto out1;
+
4147 }
+
4148 se->fd = -1;
+
4149 se->conn.max_write = FUSE_DEFAULT_MAX_PAGES_LIMIT * getpagesize();
+
4150 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
4151 se->conn.max_readahead = UINT_MAX;
+
4152
+
4153 /*
+
4154 * Allow overriding with env, mostly to avoid the need to modify
+
4155 * all tests. I.e. to test with and without io-uring being enabled.
+
4156 */
+
4157 se->uring.enable = getenv("FUSE_URING_ENABLE") ?
+
4158 atoi(getenv("FUSE_URING_ENABLE")) :
+
4159 SESSION_DEF_URING_ENABLE;
+
4160 se->uring.q_depth = getenv("FUSE_URING_QUEUE_DEPTH") ?
+
4161 atoi(getenv("FUSE_URING_QUEUE_DEPTH")) :
+
4162 SESSION_DEF_URING_Q_DEPTH;
+
4163
+
4164 /* Parse options */
+
4165 if(fuse_opt_parse(args, se, fuse_ll_opts, NULL) == -1)
+
4166 goto out2;
+
4167 if(se->deny_others) {
+
4168 /* Allowing access only by root is done by instructing
+
4169 * kernel to allow access by everyone, and then restricting
+
4170 * access to root and mountpoint owner in libfuse.
+
4171 */
+
4172 // We may be adding the option a second time, but
+
4173 // that doesn't hurt.
+
4174 if(fuse_opt_add_arg(args, "-oallow_other") == -1)
+
4175 goto out2;
+
4176 }
+
4177 mo = parse_mount_opts(args);
+
4178 if (mo == NULL)
+
4179 goto out3;
+
4180
+
4181 if(args->argc == 1 &&
+
4182 args->argv[0][0] == '-') {
+
4183 fuse_log(FUSE_LOG_ERR, "fuse: warning: argv[0] looks like an option, but "
+
4184 "will be ignored\n");
+
4185 } else if (args->argc != 1) {
+
4186 int i;
+
4187 fuse_log(FUSE_LOG_ERR, "fuse: unknown option(s): `");
+
4188 for(i = 1; i < args->argc-1; i++)
+
4189 fuse_log(FUSE_LOG_ERR, "%s ", args->argv[i]);
+
4190 fuse_log(FUSE_LOG_ERR, "%s'\n", args->argv[i]);
+
4191 goto out4;
+
4192 }
+
4193
+
4194 if (se->debug)
+
4195 fuse_log(FUSE_LOG_DEBUG, "FUSE library version: %s\n", PACKAGE_VERSION);
+
4196
+
4197 list_init_req(&se->list);
+
4198 list_init_req(&se->interrupts);
+
4199 list_init_nreq(&se->notify_list);
+
4200 se->notify_ctr = 1;
+
4201 pthread_mutex_init(&se->lock, NULL);
+
4202 sem_init(&se->mt_finish, 0, 0);
+
4203 pthread_mutex_init(&se->mt_lock, NULL);
+
4204
+
4205 err = pthread_key_create(&se->pipe_key, fuse_ll_pipe_destructor);
+
4206 if (err) {
+
4207 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
4208 strerror(err));
+
4209 goto out5;
+
4210 }
+
4211
+
4212 memcpy(&se->op, op, op_size);
+
4213 se->owner = getuid();
+
4214 se->userdata = userdata;
+
4215
+
4216 se->mo = mo;
+
4217
+
4218 /* Fuse server application should pass the version it was compiled
+
4219 * against and pass it. If a libfuse version accidentally introduces an
+
4220 * ABI incompatibility, it might be possible to 'fix' that at run time,
+
4221 * by checking the version numbers.
+
4222 */
+
4223 se->version = *version;
+
4224
+
4225 return se;
+
4226
+
4227out5:
+
4228 sem_destroy(&se->mt_finish);
+
4229 pthread_mutex_destroy(&se->mt_lock);
+
4230 pthread_mutex_destroy(&se->lock);
+
4231out4:
+
4232 fuse_opt_free_args(args);
+
4233out3:
+
4234 if (mo != NULL)
+
4235 destroy_mount_opts(mo);
+
4236out2:
+
4237 free(se);
+
4238out1:
+
4239 return NULL;
+
4240}
+
4241
+
4242struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
4243 const struct fuse_lowlevel_ops *op,
+
4244 size_t op_size, void *userdata);
+
4245struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
4246 const struct fuse_lowlevel_ops *op,
+
4247 size_t op_size,
+
4248 void *userdata)
+
4249{
+
4250 struct fuse_lowlevel_ops null_ops = { 0 };
+
4251
+
4252 /* unknown version */
+
4253 struct libfuse_version version = { 0 };
+
4254
+
4255 /*
+
4256 * This function is the ABI interface function from fuse_session_new in
+
4257 * compat.c. External libraries like "fuser" might call fuse_session_new()
+
4258 * with NULL ops and then pass that session to fuse_session_mount().
+
4259 * The actual FUSE operations are handled in their own library.
+
4260 */
+
4261 if (op == NULL) {
+
4262 op = &null_ops;
+
4263 op_size = sizeof(null_ops);
+
4264 }
+
4265
+
4266 return fuse_session_new_versioned(args, op, op_size, &version,
+
4267 userdata);
+
4268}
+
4269
+
4270FUSE_SYMVER("fuse_session_custom_io_317", "fuse_session_custom_io@@FUSE_3.17")
+
4271int fuse_session_custom_io_317(struct fuse_session *se,
+
4272 const struct fuse_custom_io *io, size_t op_size, int fd)
+
4273{
+
4274 if (sizeof(struct fuse_custom_io) < op_size) {
+
4275 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
4276 op_size = sizeof(struct fuse_custom_io);
+
4277 }
+
4278
+
4279 if (fd < 0) {
+
4280 fuse_log(FUSE_LOG_ERR, "Invalid file descriptor value %d passed to "
+
4281 "fuse_session_custom_io()\n", fd);
+
4282 return -EBADF;
+
4283 }
+
4284 if (io == NULL) {
+
4285 fuse_log(FUSE_LOG_ERR, "No custom IO passed to "
+
4286 "fuse_session_custom_io()\n");
+
4287 return -EINVAL;
+
4288 } else if (io->read == NULL || io->writev == NULL) {
+
4289 /* If the user provides their own file descriptor, we can't
+
4290 guarantee that the default behavior of the io operations made
+
4291 in libfuse will function properly. Therefore, we enforce the
+
4292 user to implement these io operations when using custom io. */
+
4293 fuse_log(FUSE_LOG_ERR, "io passed to fuse_session_custom_io() must "
+
4294 "implement both io->read() and io->writev\n");
+
4295 return -EINVAL;
+
4296 }
+
4297
+
4298 se->io = calloc(1, sizeof(struct fuse_custom_io));
+
4299 if (se->io == NULL) {
+
4300 fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for custom io. "
+
4301 "Error: %s\n", strerror(errno));
+
4302 return -errno;
+
4303 }
+
4304
+
4305 se->fd = fd;
+
4306 memcpy(se->io, io, op_size);
+
4307 return 0;
+
4308}
+
4309
+
4310int fuse_session_custom_io_30(struct fuse_session *se,
+
4311 const struct fuse_custom_io *io, int fd);
+
4312FUSE_SYMVER("fuse_session_custom_io_30", "fuse_session_custom_io@FUSE_3.0")
+
4313int fuse_session_custom_io_30(struct fuse_session *se,
+
4314 const struct fuse_custom_io *io, int fd)
+
4315{
+
4316 return fuse_session_custom_io_317(se, io,
+
4317 offsetof(struct fuse_custom_io, clone_fd), fd);
+
4318}
+
4319
+
4320int fuse_session_mount(struct fuse_session *se, const char *_mountpoint)
+
4321{
+
4322 int fd;
+
4323 char *mountpoint;
+
4324
+
4325 if (_mountpoint == NULL) {
+
4326 fuse_log(FUSE_LOG_ERR, "Invalid null-ptr mountpoint!\n");
+
4327 return -1;
+
4328 }
+
4329
+
4330 mountpoint = strdup(_mountpoint);
+
4331 if (mountpoint == NULL) {
+
4332 fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for mountpoint. Error: %s\n",
+
4333 strerror(errno));
+
4334 return -1;
+
4335 }
+
4336
+
4337 /*
+
4338 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
4339 * would ensue.
+
4340 */
+
4341 do {
+
4342 fd = open("/dev/null", O_RDWR);
+
4343 if (fd > 2)
+
4344 close(fd);
+
4345 } while (fd >= 0 && fd <= 2);
+
4346
+
4347 /*
+
4348 * To allow FUSE daemons to run without privileges, the caller may open
+
4349 * /dev/fuse before launching the file system and pass on the file
+
4350 * descriptor by specifying /dev/fd/N as the mount point. Note that the
+
4351 * parent process takes care of performing the mount in this case.
+
4352 */
+
4353 fd = fuse_mnt_parse_fuse_fd(mountpoint);
+
4354 if (fd != -1) {
+
4355 if (fcntl(fd, F_GETFD) == -1) {
+
4356 fuse_log(FUSE_LOG_ERR,
+
4357 "fuse: Invalid file descriptor /dev/fd/%u\n",
+
4358 fd);
+
4359 goto error_out;
+
4360 }
+
4361 se->fd = fd;
+
4362 return 0;
+
4363 }
+
4364
+
4365 /* Open channel */
+
4366 fd = fuse_kern_mount(mountpoint, se->mo);
+
4367 if (fd == -1)
+
4368 goto error_out;
+
4369 se->fd = fd;
+
4370
+
4371 /* Save mountpoint */
+
4372 se->mountpoint = mountpoint;
+
4373
+
4374 return 0;
+
4375
+
4376error_out:
+
4377 free(mountpoint);
+
4378 return -1;
+
4379}
+
4380
+
4381int fuse_session_fd(struct fuse_session *se)
+
4382{
+
4383 return se->fd;
+
4384}
+
4385
+
4386void fuse_session_unmount(struct fuse_session *se)
+
4387{
+
4388 if (se->mountpoint != NULL) {
+
4389 char *mountpoint = atomic_exchange(&se->mountpoint, NULL);
+
4390
+
4391 fuse_kern_unmount(mountpoint, se->fd);
+
4392 se->fd = -1;
+
4393 free(mountpoint);
+
4394 }
+
4395}
+
4396
+
4397#ifdef linux
+
4398int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
4399{
+
4400 char *buf;
+
4401 size_t bufsize = 1024;
+
4402 char path[128];
+
4403 int ret;
+
4404 int fd;
+
4405 unsigned long pid = req->ctx.pid;
+
4406 char *s;
+
4407
+
4408 sprintf(path, "/proc/%lu/task/%lu/status", pid, pid);
+
4409
+
4410retry:
+
4411 buf = malloc(bufsize);
+
4412 if (buf == NULL)
+
4413 return -ENOMEM;
+
4414
+
4415 ret = -EIO;
+
4416 fd = open(path, O_RDONLY);
+
4417 if (fd == -1)
+
4418 goto out_free;
+
4419
+
4420 ret = read(fd, buf, bufsize);
+
4421 close(fd);
+
4422 if (ret < 0) {
+
4423 ret = -EIO;
+
4424 goto out_free;
+
4425 }
+
4426
+
4427 if ((size_t)ret == bufsize) {
+
4428 free(buf);
+
4429 bufsize *= 4;
+
4430 goto retry;
+
4431 }
+
4432
+
4433 buf[ret] = '\0';
+
4434 ret = -EIO;
+
4435 s = strstr(buf, "\nGroups:");
+
4436 if (s == NULL)
+
4437 goto out_free;
+
4438
+
4439 s += 8;
+
4440 ret = 0;
+
4441 while (1) {
+
4442 char *end;
+
4443 unsigned long val = strtoul(s, &end, 0);
+
4444 if (end == s)
+
4445 break;
+
4446
+
4447 s = end;
+
4448 if (ret < size)
+
4449 list[ret] = val;
+
4450 ret++;
+
4451 }
+
4452
+
4453out_free:
+
4454 free(buf);
+
4455 return ret;
+
4456}
+
4457#else /* linux */
+
4458/*
+
4459 * This is currently not implemented on other than Linux...
+
4460 */
+
4461int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
4462{
+
4463 (void) req; (void) size; (void) list;
+
4464 return -ENOSYS;
+
4465}
+
4466#endif
+
4467
+
4468/* Prevent spurious data race warning - we don't care
+
4469 * about races for this flag */
+
4470__attribute__((no_sanitize_thread))
+
4471void fuse_session_exit(struct fuse_session *se)
+
4472{
+
4473 atomic_store_explicit(&se->mt_exited, 1, memory_order_relaxed);
+
4474 sem_post(&se->mt_finish);
+
4475}
+
4476
+
4477__attribute__((no_sanitize_thread))
+
4478void fuse_session_reset(struct fuse_session *se)
+
4479{
+
4480 se->mt_exited = false;
+
4481 se->error = 0;
+
4482}
+
4483
+
4484__attribute__((no_sanitize_thread))
+
4485int fuse_session_exited(struct fuse_session *se)
+
4486{
+
4487 bool exited =
+
4488 atomic_load_explicit(&se->mt_exited, memory_order_relaxed);
+
4489
+
4490 return exited ? 1 : 0;
+
4491}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
@ FUSE_BUF_IS_FD
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_CAP_OVER_IO_URING
+
int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+
int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
+
@ FUSE_BUF_IS_FD
+
bool fuse_req_is_uring(fuse_req_t req)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, void **mr)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
enum fuse_buf_flags flags
+
size_t mem_size
+ +
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + + + +
uint64_t capable_ext
+
uint64_t want_ext
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:60
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t nonseekable
Definition fuse_common.h:78
+
int32_t backing_id
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t noflush
Definition fuse_common.h:93
+
uint32_t flush
Definition fuse_common.h:74
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + + +
+ + + + diff --git a/doc/html/lib_2fuse__misc_8h_source.html b/doc/html/lib_2fuse__misc_8h_source.html new file mode 100644 index 0000000..0480c4f --- /dev/null +++ b/doc/html/lib_2fuse__misc_8h_source.html @@ -0,0 +1,111 @@ + + + + + + + +libfuse: lib/fuse_misc.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_misc.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt
+
7*/
+
8
+
9#include <pthread.h>
+
10
+
11/*
+
12 Versioned symbols cannot be used in some cases because it
+
13 - not supported on MacOSX (in MachO binary format)
+
14
+
15 Note: "@@" denotes the default symbol, "@" is binary a compat version.
+
16
+
17*/
+
18#ifdef LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS
+
19# if HAVE_SYMVER_ATTRIBUTE
+
20# define FUSE_SYMVER(sym1, sym2) __attribute__ ((symver (sym2)))
+
21# else
+
22# define FUSE_SYMVER(sym1, sym2) __asm__("\t.symver " sym1 "," sym2);
+
23# endif
+
24#else
+
25#define FUSE_SYMVER(sym1, sym2)
+
26#endif
+
27
+
28#ifdef HAVE_STRUCT_STAT_ST_ATIM
+
29/* Linux */
+
30#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec)
+
31#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec)
+
32#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec)
+
33#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atim.tv_nsec = (val)
+
34#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctim.tv_nsec = (val)
+
35#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtim.tv_nsec = (val)
+
36#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC)
+
37/* FreeBSD */
+
38#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimespec.tv_nsec)
+
39#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimespec.tv_nsec)
+
40#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimespec.tv_nsec)
+
41#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atimespec.tv_nsec = (val)
+
42#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctimespec.tv_nsec = (val)
+
43#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtimespec.tv_nsec = (val)
+
44#else
+
45#define ST_ATIM_NSEC(stbuf) 0
+
46#define ST_CTIM_NSEC(stbuf) 0
+
47#define ST_MTIM_NSEC(stbuf) 0
+
48#define ST_ATIM_NSEC_SET(stbuf, val) do { } while (0)
+
49#define ST_CTIM_NSEC_SET(stbuf, val) do { } while (0)
+
50#define ST_MTIM_NSEC_SET(stbuf, val) do { } while (0)
+
51#endif
+
+ + + + diff --git a/doc/html/lib_2fuse__opt_8c_source.html b/doc/html/lib_2fuse__opt_8c_source.html new file mode 100644 index 0000000..32666b2 --- /dev/null +++ b/doc/html/lib_2fuse__opt_8c_source.html @@ -0,0 +1,504 @@ + + + + + + + +libfuse: lib/fuse_opt.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_opt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of option parsing routines (dealing with `struct
+
6 fuse_args`).
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file LGPL2.txt
+
10*/
+
11
+
12#include "fuse_config.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15#include "fuse_misc.h"
+
16
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <assert.h>
+
21
+
22struct fuse_opt_context {
+
23 void *data;
+
24 const struct fuse_opt *opt;
+
25 fuse_opt_proc_t proc;
+
26 int argctr;
+
27 int argc;
+
28 char **argv;
+
29 struct fuse_args outargs;
+
30 char *opts;
+
31 int nonopt;
+
32};
+
33
+
34void fuse_opt_free_args(struct fuse_args *args)
+
35{
+
36 if (args) {
+
37 if (args->argv && args->allocated) {
+
38 int i;
+
39 for (i = 0; i < args->argc; i++)
+
40 free(args->argv[i]);
+
41 free(args->argv);
+
42 }
+
43 args->argc = 0;
+
44 args->argv = NULL;
+
45 args->allocated = 0;
+
46 }
+
47}
+
48
+
49static int alloc_failed(void)
+
50{
+
51 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
52 return -1;
+
53}
+
54
+
55int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
+
56{
+
57 char **newargv;
+
58 char *newarg;
+
59
+
60 assert(!args->argv || args->allocated);
+
61
+
62 newarg = strdup(arg);
+
63 if (!newarg)
+
64 return alloc_failed();
+
65
+
66 newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *));
+
67 if (!newargv) {
+
68 free(newarg);
+
69 return alloc_failed();
+
70 }
+
71
+
72 args->argv = newargv;
+
73 args->allocated = 1;
+
74 args->argv[args->argc++] = newarg;
+
75 args->argv[args->argc] = NULL;
+
76 return 0;
+
77}
+
78
+
79static int fuse_opt_insert_arg_common(struct fuse_args *args, int pos,
+
80 const char *arg)
+
81{
+
82 assert(pos <= args->argc);
+
83 if (fuse_opt_add_arg(args, arg) == -1)
+
84 return -1;
+
85
+
86 if (pos != args->argc - 1) {
+
87 char *newarg = args->argv[args->argc - 1];
+
88 memmove(&args->argv[pos + 1], &args->argv[pos],
+
89 sizeof(char *) * (args->argc - pos - 1));
+
90 args->argv[pos] = newarg;
+
91 }
+
92 return 0;
+
93}
+
94
+
95int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
+
96{
+
97 return fuse_opt_insert_arg_common(args, pos, arg);
+
98}
+
99
+
100static int next_arg(struct fuse_opt_context *ctx, const char *opt)
+
101{
+
102 if (ctx->argctr + 1 >= ctx->argc) {
+
103 fuse_log(FUSE_LOG_ERR, "fuse: missing argument after `%s'\n", opt);
+
104 return -1;
+
105 }
+
106 ctx->argctr++;
+
107 return 0;
+
108}
+
109
+
110static int add_arg(struct fuse_opt_context *ctx, const char *arg)
+
111{
+
112 return fuse_opt_add_arg(&ctx->outargs, arg);
+
113}
+
114
+
115static int add_opt_common(char **opts, const char *opt, int esc)
+
116{
+
117 unsigned oldlen = *opts ? strlen(*opts) : 0;
+
118 char *d = realloc(*opts, oldlen + 1 + strlen(opt) * 2 + 1);
+
119
+
120 if (!d)
+
121 return alloc_failed();
+
122
+
123 *opts = d;
+
124 if (oldlen) {
+
125 d += oldlen;
+
126 *d++ = ',';
+
127 }
+
128
+
129 for (; *opt; opt++) {
+
130 if (esc && (*opt == ',' || *opt == '\\'))
+
131 *d++ = '\\';
+
132 *d++ = *opt;
+
133 }
+
134 *d = '\0';
+
135
+
136 return 0;
+
137}
+
138
+
139int fuse_opt_add_opt(char **opts, const char *opt)
+
140{
+
141 return add_opt_common(opts, opt, 0);
+
142}
+
143
+
144int fuse_opt_add_opt_escaped(char **opts, const char *opt)
+
145{
+
146 return add_opt_common(opts, opt, 1);
+
147}
+
148
+
149static int add_opt(struct fuse_opt_context *ctx, const char *opt)
+
150{
+
151 return add_opt_common(&ctx->opts, opt, 1);
+
152}
+
153
+
154static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key,
+
155 int iso)
+
156{
+
157 if (key == FUSE_OPT_KEY_DISCARD)
+
158 return 0;
+
159
+
160 if (key != FUSE_OPT_KEY_KEEP && ctx->proc) {
+
161 int res = ctx->proc(ctx->data, arg, key, &ctx->outargs);
+
162 if (res == -1 || !res)
+
163 return res;
+
164 }
+
165 if (iso)
+
166 return add_opt(ctx, arg);
+
167 else
+
168 return add_arg(ctx, arg);
+
169}
+
170
+
171static int match_template(const char *t, const char *arg, unsigned *sepp)
+
172{
+
173 int arglen = strlen(arg);
+
174 const char *sep = strchr(t, '=');
+
175 sep = sep ? sep : strchr(t, ' ');
+
176 if (sep && (!sep[1] || sep[1] == '%')) {
+
177 int tlen = sep - t;
+
178 if (sep[0] == '=')
+
179 tlen ++;
+
180 if (arglen >= tlen && strncmp(arg, t, tlen) == 0) {
+
181 *sepp = sep - t;
+
182 return 1;
+
183 }
+
184 }
+
185 if (strcmp(t, arg) == 0) {
+
186 *sepp = 0;
+
187 return 1;
+
188 }
+
189 return 0;
+
190}
+
191
+
192static const struct fuse_opt *find_opt(const struct fuse_opt *opt,
+
193 const char *arg, unsigned *sepp)
+
194{
+
195 for (; opt && opt->templ; opt++)
+
196 if (match_template(opt->templ, arg, sepp))
+
197 return opt;
+
198 return NULL;
+
199}
+
200
+
201int fuse_opt_match(const struct fuse_opt *opts, const char *opt)
+
202{
+
203 unsigned dummy;
+
204 return find_opt(opts, opt, &dummy) ? 1 : 0;
+
205}
+
206
+
207static int process_opt_param(void *var, const char *format, const char *param,
+
208 const char *arg)
+
209{
+
210 assert(format[0] == '%');
+
211 if (format[1] == 's') {
+
212 char **s = var;
+
213 char *copy = strdup(param);
+
214 if (!copy)
+
215 return alloc_failed();
+
216
+
217 free(*s);
+
218 *s = copy;
+
219 } else {
+
220 if (sscanf(param, format, var) != 1) {
+
221 fuse_log(FUSE_LOG_ERR, "fuse: invalid parameter in option `%s'\n", arg);
+
222 return -1;
+
223 }
+
224 }
+
225 return 0;
+
226}
+
227
+
228static int process_opt(struct fuse_opt_context *ctx,
+
229 const struct fuse_opt *opt, unsigned sep,
+
230 const char *arg, int iso)
+
231{
+
232 if (opt->offset == -1U) {
+
233 if (call_proc(ctx, arg, opt->value, iso) == -1)
+
234 return -1;
+
235 } else {
+
236 void *var = (char *)ctx->data + opt->offset;
+
237 if (sep && opt->templ[sep + 1]) {
+
238 const char *param = arg + sep;
+
239 if (opt->templ[sep] == '=')
+
240 param ++;
+
241 if (process_opt_param(var, opt->templ + sep + 1,
+
242 param, arg) == -1)
+
243 return -1;
+
244 } else
+
245 *(int *)var = opt->value;
+
246 }
+
247 return 0;
+
248}
+
249
+
250static int process_opt_sep_arg(struct fuse_opt_context *ctx,
+
251 const struct fuse_opt *opt, unsigned sep,
+
252 const char *arg, int iso)
+
253{
+
254 int res;
+
255 char *newarg;
+
256 char *param;
+
257
+
258 if (next_arg(ctx, arg) == -1)
+
259 return -1;
+
260
+
261 param = ctx->argv[ctx->argctr];
+
262 newarg = malloc(sep + strlen(param) + 1);
+
263 if (!newarg)
+
264 return alloc_failed();
+
265
+
266 memcpy(newarg, arg, sep);
+
267 strcpy(newarg + sep, param);
+
268 res = process_opt(ctx, opt, sep, newarg, iso);
+
269 free(newarg);
+
270
+
271 return res;
+
272}
+
273
+
274static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso)
+
275{
+
276 unsigned sep;
+
277 const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep);
+
278 if (opt) {
+
279 for (; opt; opt = find_opt(opt + 1, arg, &sep)) {
+
280 int res;
+
281 if (sep && opt->templ[sep] == ' ' && !arg[sep])
+
282 res = process_opt_sep_arg(ctx, opt, sep, arg,
+
283 iso);
+
284 else
+
285 res = process_opt(ctx, opt, sep, arg, iso);
+
286 if (res == -1)
+
287 return -1;
+
288 }
+
289 return 0;
+
290 } else
+
291 return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso);
+
292}
+
293
+
294static int process_real_option_group(struct fuse_opt_context *ctx, char *opts)
+
295{
+
296 char *s = opts;
+
297 char *d = s;
+
298 int end = 0;
+
299
+
300 while (!end) {
+
301 if (*s == '\0')
+
302 end = 1;
+
303 if (*s == ',' || end) {
+
304 int res;
+
305
+
306 *d = '\0';
+
307 res = process_gopt(ctx, opts, 1);
+
308 if (res == -1)
+
309 return -1;
+
310 d = opts;
+
311 } else {
+
312 if (s[0] == '\\' && s[1] != '\0') {
+
313 s++;
+
314 if (s[0] >= '0' && s[0] <= '3' &&
+
315 s[1] >= '0' && s[1] <= '7' &&
+
316 s[2] >= '0' && s[2] <= '7') {
+
317 *d++ = (s[0] - '0') * 0100 +
+
318 (s[1] - '0') * 0010 +
+
319 (s[2] - '0');
+
320 s += 2;
+
321 } else {
+
322 *d++ = *s;
+
323 }
+
324 } else {
+
325 *d++ = *s;
+
326 }
+
327 }
+
328 s++;
+
329 }
+
330
+
331 return 0;
+
332}
+
333
+
334static int process_option_group(struct fuse_opt_context *ctx, const char *opts)
+
335{
+
336 int res;
+
337 char *copy = strdup(opts);
+
338
+
339 if (!copy) {
+
340 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
341 return -1;
+
342 }
+
343 res = process_real_option_group(ctx, copy);
+
344 free(copy);
+
345 return res;
+
346}
+
347
+
348static int process_one(struct fuse_opt_context *ctx, const char *arg)
+
349{
+
350 if (ctx->nonopt || arg[0] != '-')
+
351 return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0);
+
352 else if (arg[1] == 'o') {
+
353 if (arg[2])
+
354 return process_option_group(ctx, arg + 2);
+
355 else {
+
356 if (next_arg(ctx, arg) == -1)
+
357 return -1;
+
358
+
359 return process_option_group(ctx,
+
360 ctx->argv[ctx->argctr]);
+
361 }
+
362 } else if (arg[1] == '-' && !arg[2]) {
+
363 if (add_arg(ctx, arg) == -1)
+
364 return -1;
+
365 ctx->nonopt = ctx->outargs.argc;
+
366 return 0;
+
367 } else
+
368 return process_gopt(ctx, arg, 0);
+
369}
+
370
+
371static int opt_parse(struct fuse_opt_context *ctx)
+
372{
+
373 if (ctx->argc) {
+
374 if (add_arg(ctx, ctx->argv[0]) == -1)
+
375 return -1;
+
376 }
+
377
+
378 for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++)
+
379 if (process_one(ctx, ctx->argv[ctx->argctr]) == -1)
+
380 return -1;
+
381
+
382 if (ctx->opts) {
+
383 if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 ||
+
384 fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1)
+
385 return -1;
+
386 }
+
387
+
388 /* If option separator ("--") is the last argument, remove it */
+
389 if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc &&
+
390 strcmp(ctx->outargs.argv[ctx->outargs.argc - 1], "--") == 0) {
+
391 free(ctx->outargs.argv[ctx->outargs.argc - 1]);
+
392 ctx->outargs.argv[--ctx->outargs.argc] = NULL;
+
393 }
+
394
+
395 return 0;
+
396}
+
397
+
398int fuse_opt_parse(struct fuse_args *args, void *data,
+
399 const struct fuse_opt opts[], fuse_opt_proc_t proc)
+
400{
+
401 int res;
+
402 struct fuse_opt_context ctx = {
+
403 .data = data,
+
404 .opt = opts,
+
405 .proc = proc,
+
406 };
+
407
+
408 if (!args || !args->argv || !args->argc)
+
409 return 0;
+
410
+
411 ctx.argc = args->argc;
+
412 ctx.argv = args->argv;
+
413
+
414 res = opt_parse(&ctx);
+
415 if (res != -1) {
+
416 struct fuse_args tmp = *args;
+
417 *args = ctx.outargs;
+
418 ctx.outargs = tmp;
+
419 }
+
420 free(ctx.opts);
+
421 fuse_opt_free_args(&ctx.outargs);
+
422 return res;
+
423}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
#define FUSE_OPT_KEY_OPT
Definition fuse_opt.h:129
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
unsigned long offset
Definition fuse_opt.h:85
+
const char * templ
Definition fuse_opt.h:79
+
int value
Definition fuse_opt.h:91
+
+ + + + diff --git a/doc/html/lib_2fuse__signals_8c_source.html b/doc/html/lib_2fuse__signals_8c_source.html new file mode 100644 index 0000000..32f78f3 --- /dev/null +++ b/doc/html/lib_2fuse__signals_8c_source.html @@ -0,0 +1,281 @@ + + + + + + + +libfuse: lib/fuse_signals.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_signals.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Utility functions for setting signal handlers.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14
+
15#include <stdio.h>
+
16#include <string.h>
+
17#include <signal.h>
+
18#include <stdlib.h>
+
19#include <errno.h>
+
20
+
21#ifdef HAVE_BACKTRACE
+
22#include <execinfo.h>
+
23#endif
+
24
+
25/*
+
26 * Must not handle SIGCANCEL, as that is used to wake up threads from
+
27 * syscalls reading requests from /dev/fuse
+
28 */
+
29static int teardown_sigs[] = { SIGHUP, SIGINT, SIGTERM };
+
30
+
31static int ignore_sigs[] = { SIGPIPE};
+
32static int fail_sigs[] = { SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGSEGV };
+
33static struct fuse_session *fuse_instance;
+
34
+
35#ifdef HAVE_BACKTRACE
+
36#define BT_STACK_SZ (1024 * 1024)
+
37static void *backtrace_buffer[BT_STACK_SZ];
+
38#endif
+
39
+
40static void dump_stack(void)
+
41{
+
42#ifdef HAVE_BACKTRACE
+
43 char **strings;
+
44
+
45 int nptrs = backtrace(backtrace_buffer, BT_STACK_SZ);
+
46 strings = backtrace_symbols(backtrace_buffer, nptrs);
+
47
+
48 if (strings == NULL) {
+
49 fuse_log(FUSE_LOG_ERR, "Failed to get backtrace symbols: %s\n",
+
50 strerror(errno));
+
51 return;
+
52 }
+
53
+
54 for (int idx = 0; idx < nptrs; idx++)
+
55 fuse_log(FUSE_LOG_ERR, "%s\n", strings[idx]);
+
56
+
57 free(strings);
+
58#endif
+
59}
+
60
+
61static void exit_handler(int sig)
+
62{
+
63 if (fuse_instance == NULL) {
+
64 fuse_log(FUSE_LOG_ERR, "fuse_instance is NULL\n");
+
65 return;
+
66 }
+
67
+
68 if (fuse_instance->debug)
+
69 fuse_log(FUSE_LOG_ERR, "exit_handler called with sig %d\n",
+
70 sig);
+
71
+
72 fuse_session_exit(fuse_instance);
+
73
+
74 if (sig < 0) {
+
75 fuse_log(FUSE_LOG_ERR,
+
76 "assertion error: signal value <= 0\n");
+
77 dump_stack();
+
78 abort();
+
79 fuse_instance->error = sig;
+
80 }
+
81
+
82 fuse_instance->error = sig;
+
83}
+
84
+
85static void exit_backtrace(int sig)
+
86{
+
87 if (fuse_instance == NULL)
+
88 return;
+
89
+
90 fuse_session_exit(fuse_instance);
+
91
+
92 fuse_remove_signal_handlers(fuse_instance);
+
93 fuse_log(FUSE_LOG_ERR, "Got signal: %d\n", sig);
+
94 dump_stack();
+
95 abort();
+
96}
+
97
+
98
+
99static void do_nothing(int sig)
+
100{
+
101 (void) sig;
+
102}
+
103
+
104static int set_one_signal_handler(int sig, void (*handler)(int), int remove)
+
105{
+
106 struct sigaction sa;
+
107 struct sigaction old_sa;
+
108
+
109 memset(&sa, 0, sizeof(struct sigaction));
+
110 sa.sa_handler = remove ? SIG_DFL : handler;
+
111 sigemptyset(&(sa.sa_mask));
+
112 sa.sa_flags = 0;
+
113
+
114 if (sigaction(sig, NULL, &old_sa) == -1) {
+
115 perror("fuse: cannot get old signal handler");
+
116 return -1;
+
117 }
+
118
+
119 if (old_sa.sa_handler == (remove ? handler : SIG_DFL) &&
+
120 sigaction(sig, &sa, NULL) == -1) {
+
121 perror("fuse: cannot set signal handler");
+
122 return -1;
+
123 }
+
124 return 0;
+
125}
+
126
+
127static int _fuse_set_signal_handlers(int signals[], int nr_signals,
+
128 void (*handler)(int))
+
129{
+
130 for (int idx = 0; idx < nr_signals; idx++) {
+
131 int signal = signals[idx];
+
132
+
133 /*
+
134 * If we used SIG_IGN instead of the do_nothing function,
+
135 * then we would be unable to tell if we set SIG_IGN (and
+
136 * thus should reset to SIG_DFL in fuse_remove_signal_handlers)
+
137 * or if it was already set to SIG_IGN (and should be left
+
138 * untouched.
+
139 */
+
140 if (set_one_signal_handler(signal, handler, 0) == -1) {
+
141 fuse_log(FUSE_LOG_ERR,
+
142 "Failed to install signal handler for sig %d\n",
+
143 signal);
+
144 return -1;
+
145 }
+
146 }
+
147
+
148 return 0;
+
149}
+
150
+
151int fuse_set_signal_handlers(struct fuse_session *se)
+
152{
+
153 size_t nr_signals;
+
154 int rc;
+
155
+
156 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
157 rc = _fuse_set_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
158 if (rc < 0)
+
159 return rc;
+
160
+
161 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
162 rc = _fuse_set_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
163 if (rc < 0)
+
164 return rc;
+
165
+
166 /*
+
167 * needs to be set independently if already set, as some applications
+
168 * may have multiple sessions and might rely on traditional behavior
+
169 * that the last session is used.
+
170 */
+
171 fuse_instance = se;
+
172
+
173 return 0;
+
174}
+
175
+
176int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
177{
+
178 size_t nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
179
+
180 int rc = _fuse_set_signal_handlers(fail_sigs, nr_signals,
+
181 exit_backtrace);
+
182 if (rc < 0)
+
183 return rc;
+
184
+
185 /* See fuse_set_signal_handlers, why set unconditionally */
+
186 fuse_instance = se;
+
187
+
188 return 0;
+
189}
+
190
+
191static void _fuse_remove_signal_handlers(int signals[], int nr_signals,
+
192 void (*handler)(int))
+
193{
+
194 for (int idx = 0; idx < nr_signals; idx++)
+
195 set_one_signal_handler(signals[idx], handler, 1);
+
196}
+
197
+
198void fuse_remove_signal_handlers(struct fuse_session *se)
+
199{
+
200 size_t nr_signals;
+
201
+
202 if (fuse_instance != se)
+
203 fuse_log(FUSE_LOG_ERR,
+
204 "fuse: fuse_remove_signal_handlers: unknown session\n");
+
205 else
+
206 fuse_instance = NULL;
+
207
+
208 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
209 _fuse_remove_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
210
+
211 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
212 _fuse_remove_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
213
+
214 nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
215 _fuse_remove_signal_handlers(fail_sigs, nr_signals, exit_backtrace);
+
216}
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
+ + + + diff --git a/doc/html/lib_2fuse__uring_8c_source.html b/doc/html/lib_2fuse__uring_8c_source.html new file mode 100644 index 0000000..61b3358 --- /dev/null +++ b/doc/html/lib_2fuse__uring_8c_source.html @@ -0,0 +1,1015 @@ + + + + + + + +libfuse: lib/fuse_uring.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_uring.c
+
+
+
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 * Copyright (C) 2025 Bernd Schubert <bschubert@ddn.com>
+
4 *
+
5 * Implementation of (most of) FUSE-over-io-uring.
+
6 *
+
7 * This program can be distributed under the terms of the GNU LGPLv2.
+
8 * See the file LGPL2.txt
+
9 */
+
10
+
11#define _GNU_SOURCE
+
12
+
13#include "fuse_i.h"
+
14#include "fuse_kernel.h"
+
15#include "fuse_uring_i.h"
+
16
+
17#include <stdlib.h>
+
18#include <liburing.h>
+
19#include <sys/sysinfo.h>
+
20#include <stdint.h>
+
21#include <inttypes.h>
+
22#include <stdbool.h>
+
23#include <string.h>
+
24#include <unistd.h>
+
25#include <numa.h>
+
26#include <pthread.h>
+
27#include <stdio.h>
+
28#include <linux/sched.h>
+
29#include <poll.h>
+
30#include <sys/eventfd.h>
+
31
+
32/* Size of command data area in SQE when IORING_SETUP_SQE128 is used */
+
33#define FUSE_URING_MAX_SQE128_CMD_DATA 80
+
34
+
35struct fuse_ring_ent {
+
36 struct fuse_ring_queue *ring_queue; /* back pointer */
+
37 struct fuse_req req;
+
38
+
39 struct fuse_uring_req_header *req_header;
+
40 void *op_payload;
+
41 size_t req_payload_sz;
+
42
+
43 /* commit id of a fuse request */
+
44 uint64_t req_commit_id;
+
45
+
46 enum fuse_uring_cmd last_cmd;
+
47
+
48 /* header and payload */
+
49 struct iovec iov[2];
+
50};
+
51
+
52struct fuse_ring_queue {
+
53 /* back pointer */
+
54 struct fuse_ring_pool *ring_pool;
+
55 int qid;
+
56 int numa_node;
+
57 pthread_t tid;
+
58 int eventfd;
+
59 size_t req_header_sz;
+
60 struct io_uring ring;
+
61
+
62 pthread_mutex_t ring_lock;
+
63 bool cqe_processing;
+
64
+
65 /* size depends on queue depth */
+
66 struct fuse_ring_ent ent[];
+
67};
+
68
+
72struct fuse_ring_pool {
+
73 struct fuse_session *se;
+
74
+
75 /* number of queues */
+
76 size_t nr_queues;
+
77
+
78 /* number of per queue entries */
+
79 size_t queue_depth;
+
80
+
81 /* max payload size for fuse requests*/
+
82 size_t max_req_payload_sz;
+
83
+
84 /* size of a single queue */
+
85 size_t queue_mem_size;
+
86
+
87 unsigned int started_threads;
+
88 unsigned int failed_threads;
+
89
+
90 /* Avoid sending queue entries before FUSE_INIT reply*/
+
91 sem_t init_sem;
+
92
+
93 pthread_cond_t thread_start_cond;
+
94 pthread_mutex_t thread_start_mutex;
+
95
+
96 /* pointer to the first queue */
+
97 struct fuse_ring_queue *queues;
+
98};
+
99
+
100static size_t
+
101fuse_ring_queue_size(const size_t q_depth)
+
102{
+
103 const size_t req_size = sizeof(struct fuse_ring_ent) * q_depth;
+
104
+
105 return sizeof(struct fuse_ring_queue) + req_size;
+
106}
+
107
+
108static struct fuse_ring_queue *
+
109fuse_uring_get_queue(struct fuse_ring_pool *fuse_ring, int qid)
+
110{
+
111 void *ptr =
+
112 ((char *)fuse_ring->queues) + (qid * fuse_ring->queue_mem_size);
+
113
+
114 return ptr;
+
115}
+
116
+
120static void *fuse_uring_get_sqe_cmd(struct io_uring_sqe *sqe)
+
121{
+
122 return (void *)&sqe->cmd[0];
+
123}
+
124
+
125static void fuse_uring_sqe_set_req_data(struct fuse_uring_cmd_req *req,
+
126 const unsigned int qid,
+
127 const uint64_t commit_id)
+
128{
+
129 req->qid = qid;
+
130 req->commit_id = commit_id;
+
131 req->flags = 0;
+
132}
+
133
+
134static void
+
135fuse_uring_sqe_prepare(struct io_uring_sqe *sqe, struct fuse_ring_ent *req,
+
136 __u32 cmd_op)
+
137{
+
138 /* These fields should be written once, never change */
+
139 sqe->opcode = IORING_OP_URING_CMD;
+
140
+
141 /*
+
142 * IOSQE_FIXED_FILE: fd is the index to the fd *array*
+
143 * given to io_uring_register_files()
+
144 */
+
145 sqe->flags = IOSQE_FIXED_FILE;
+
146 sqe->fd = 0;
+
147
+
148 sqe->rw_flags = 0;
+
149 sqe->ioprio = 0;
+
150 sqe->off = 0;
+
151
+
152 io_uring_sqe_set_data(sqe, req);
+
153
+
154 sqe->cmd_op = cmd_op;
+
155 sqe->__pad1 = 0;
+
156}
+
157
+
158static int fuse_uring_commit_sqe(struct fuse_ring_pool *ring_pool,
+
159 struct fuse_ring_queue *queue,
+
160 struct fuse_ring_ent *ring_ent)
+
161{
+
162 bool locked = false;
+
163 struct fuse_session *se = ring_pool->se;
+
164 struct fuse_uring_req_header *rrh = ring_ent->req_header;
+
165 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
+
166 struct fuse_uring_ent_in_out *ent_in_out =
+
167 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
+
168 struct io_uring_sqe *sqe;
+
169
+
170 if (pthread_self() != queue->tid) {
+
171 pthread_mutex_lock(&queue->ring_lock);
+
172 locked = true;
+
173 }
+
174
+
175 sqe = io_uring_get_sqe(&queue->ring);
+
176
+
177 if (sqe == NULL) {
+
178 /* This is an impossible condition, unless there is a bug.
+
179 * The kernel sent back an SQEs, which is assigned to a request.
+
180 * There is no way to get out of SQEs, as the number of
+
181 * SQEs matches the number tof requests.
+
182 */
+
183
+
184 se->error = -EIO;
+
185 fuse_log(FUSE_LOG_ERR, "Failed to get a ring SQEs\n");
+
186
+
187 return -EIO;
+
188 }
+
189
+
190 ring_ent->last_cmd = FUSE_IO_URING_CMD_COMMIT_AND_FETCH;
+
191 fuse_uring_sqe_prepare(sqe, ring_ent, ring_ent->last_cmd);
+
192 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), queue->qid,
+
193 ring_ent->req_commit_id);
+
194
+
195 if (se->debug) {
+
196 fuse_log(FUSE_LOG_DEBUG, " unique: %" PRIu64 ", result=%d\n",
+
197 out->unique, ent_in_out->payload_sz);
+
198 }
+
199
+
200 if (!queue->cqe_processing)
+
201 io_uring_submit(&queue->ring);
+
202
+
203 if (locked)
+
204 pthread_mutex_unlock(&queue->ring_lock);
+
205
+
206 return 0;
+
207}
+
208
+
209int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz,
+
210 void **mr)
+
211{
+
212 struct fuse_ring_ent *ring_ent;
+
213
+
214 /* Not possible without io-uring interface */
+
215 if (!req->flags.is_uring)
+
216 return -EINVAL;
+
217
+
218 ring_ent = container_of(req, struct fuse_ring_ent, req);
+
219
+
220 *payload = ring_ent->op_payload;
+
221 *payload_sz = ring_ent->req_payload_sz;
+
222
+
223 /*
+
224 * For now unused, but will be used later when the application can
+
225 * allocate the buffers itself and register them for rdma.
+
226 */
+
227 if (mr)
+
228 *mr = NULL;
+
229
+
230 return 0;
+
231}
+
232
+
233int send_reply_uring(fuse_req_t req, int error, const void *arg, size_t argsize)
+
234{
+
235 int res;
+
236 struct fuse_ring_ent *ring_ent =
+
237 container_of(req, struct fuse_ring_ent, req);
+
238 struct fuse_uring_req_header *rrh = ring_ent->req_header;
+
239 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
+
240 struct fuse_uring_ent_in_out *ent_in_out =
+
241 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
+
242
+
243 struct fuse_ring_queue *queue = ring_ent->ring_queue;
+
244 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
245 size_t max_payload_sz = ring_pool->max_req_payload_sz;
+
246
+
247 if (argsize > max_payload_sz) {
+
248 fuse_log(FUSE_LOG_ERR, "argsize %zu exceeds buffer size %zu",
+
249 argsize, max_payload_sz);
+
250 error = -EINVAL;
+
251 } else if (argsize) {
+
252 if (arg != ring_ent->op_payload)
+
253 memcpy(ring_ent->op_payload, arg, argsize);
+
254 }
+
255 ent_in_out->payload_sz = argsize;
+
256
+
257 out->error = error;
+
258 out->unique = req->unique;
+
259
+
260 res = fuse_uring_commit_sqe(ring_pool, queue, ring_ent);
+
261
+
262 fuse_free_req(req);
+
263
+
264 return res;
+
265}
+
266
+
267int fuse_reply_data_uring(fuse_req_t req, struct fuse_bufvec *bufv,
+
268 enum fuse_buf_copy_flags flags)
+
269{
+
270 struct fuse_ring_ent *ring_ent =
+
271 container_of(req, struct fuse_ring_ent, req);
+
272
+
273 struct fuse_ring_queue *queue = ring_ent->ring_queue;
+
274 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
275 struct fuse_uring_req_header *rrh = ring_ent->req_header;
+
276 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
+
277 struct fuse_uring_ent_in_out *ent_in_out =
+
278 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
+
279 size_t max_payload_sz = ring_ent->req_payload_sz;
+
280 struct fuse_bufvec dest_vec = FUSE_BUFVEC_INIT(max_payload_sz);
+
281 int res;
+
282
+
283 dest_vec.buf[0].mem = ring_ent->op_payload;
+
284 dest_vec.buf[0].size = max_payload_sz;
+
285
+
286 res = fuse_buf_copy(&dest_vec, bufv, flags);
+
287
+
288 out->error = res < 0 ? res : 0;
+
289 out->unique = req->unique;
+
290
+
291 ent_in_out->payload_sz = res > 0 ? res : 0;
+
292
+
293 res = fuse_uring_commit_sqe(ring_pool, queue, ring_ent);
+
294
+
295 fuse_free_req(req);
+
296
+
297 return res;
+
298}
+
299
+
303int fuse_send_msg_uring(fuse_req_t req, struct iovec *iov, int count)
+
304{
+
305 struct fuse_ring_ent *ring_ent =
+
306 container_of(req, struct fuse_ring_ent, req);
+
307
+
308 struct fuse_ring_queue *queue = ring_ent->ring_queue;
+
309 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
310 struct fuse_uring_req_header *rrh = ring_ent->req_header;
+
311 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
+
312 struct fuse_uring_ent_in_out *ent_in_out =
+
313 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
+
314 size_t max_buf = ring_pool->max_req_payload_sz;
+
315 size_t len = 0;
+
316 int res = 0;
+
317
+
318 /* copy iov into the payload, idx=0 is the header section */
+
319 for (int idx = 1; idx < count; idx++) {
+
320 struct iovec *cur = &iov[idx];
+
321
+
322 if (len + cur->iov_len > max_buf) {
+
323 fuse_log(FUSE_LOG_ERR,
+
324 "iov[%d] exceeds buffer size %zu",
+
325 idx, max_buf);
+
326 res = -EINVAL; /* Gracefully handle this? */
+
327 break;
+
328 }
+
329
+
330 memcpy(ring_ent->op_payload + len, cur->iov_base, cur->iov_len);
+
331 len += cur->iov_len;
+
332 }
+
333
+
334 ent_in_out->payload_sz = len;
+
335
+
336 out->error = res;
+
337 out->unique = req->unique;
+
338 out->len = len;
+
339
+
340 return fuse_uring_commit_sqe(ring_pool, queue, ring_ent);
+
341}
+
342
+
343static int fuse_queue_setup_io_uring(struct io_uring *ring, size_t qid,
+
344 size_t depth, int fd, int evfd)
+
345{
+
346 int rc;
+
347 struct io_uring_params params = {0};
+
348 int files[2] = { fd, evfd };
+
349
+
350 depth += 1; /* for the eventfd poll SQE */
+
351
+
352 params.flags = IORING_SETUP_SQE128;
+
353
+
354 /* Avoid cq overflow */
+
355 params.flags |= IORING_SETUP_CQSIZE;
+
356 params.cq_entries = depth * 2;
+
357
+
358 /* These flags should help to increase performance, but actually
+
359 * make it a bit slower - reason should get investigated.
+
360 */
+
361 if (0) {
+
362 /* Has the main slow down effect */
+
363 params.flags |= IORING_SETUP_SINGLE_ISSUER;
+
364
+
365 // params.flags |= IORING_SETUP_DEFER_TASKRUN;
+
366 params.flags |= IORING_SETUP_TASKRUN_FLAG;
+
367
+
368 /* Second main effect to make it slower */
+
369 params.flags |= IORING_SETUP_COOP_TASKRUN;
+
370 }
+
371
+
372 rc = io_uring_queue_init_params(depth, ring, &params);
+
373 if (rc != 0) {
+
374 fuse_log(FUSE_LOG_ERR, "Failed to setup qid %zu: %d (%s)\n",
+
375 qid, rc, strerror(-rc));
+
376 return rc;
+
377 }
+
378
+
379 rc = io_uring_register_files(ring, files, 1);
+
380 if (rc != 0) {
+
381 rc = -errno;
+
382 fuse_log(FUSE_LOG_ERR,
+
383 "Failed to register files for ring idx %zu: %s",
+
384 qid, strerror(errno));
+
385 return rc;
+
386 }
+
387
+
388 return 0;
+
389}
+
390
+
391static void fuse_session_destruct_uring(struct fuse_ring_pool *fuse_ring)
+
392{
+
393 for (size_t qid = 0; qid < fuse_ring->nr_queues; qid++) {
+
394 struct fuse_ring_queue *queue =
+
395 fuse_uring_get_queue(fuse_ring, qid);
+
396
+
397 if (queue->tid != 0) {
+
398 uint64_t value = 1ULL;
+
399 int rc;
+
400
+
401 rc = write(queue->eventfd, &value, sizeof(value));
+
402 if (rc != sizeof(value))
+
403 fprintf(stderr,
+
404 "Wrote to eventfd=%d err=%s: rc=%d\n",
+
405 queue->eventfd, strerror(errno), rc);
+
406 pthread_cancel(queue->tid);
+
407 pthread_join(queue->tid, NULL);
+
408 queue->tid = 0;
+
409 }
+
410
+
411 if (queue->eventfd >= 0) {
+
412 close(queue->eventfd);
+
413 queue->eventfd = -1;
+
414 }
+
415
+
416 if (queue->ring.ring_fd != -1)
+
417 io_uring_queue_exit(&queue->ring);
+
418
+
419 for (size_t idx = 0; idx < fuse_ring->queue_depth; idx++) {
+
420 struct fuse_ring_ent *ent = &queue->ent[idx];
+
421
+
422 numa_free(ent->op_payload, ent->req_payload_sz);
+
423 numa_free(ent->req_header, queue->req_header_sz);
+
424 }
+
425
+
426 pthread_mutex_destroy(&queue->ring_lock);
+
427 }
+
428
+
429 free(fuse_ring->queues);
+
430 pthread_cond_destroy(&fuse_ring->thread_start_cond);
+
431 pthread_mutex_destroy(&fuse_ring->thread_start_mutex);
+
432 free(fuse_ring);
+
433}
+
434
+
435static int fuse_uring_register_ent(struct fuse_ring_queue *queue,
+
436 struct fuse_ring_ent *ent)
+
437{
+
438 struct io_uring_sqe *sqe;
+
439
+
440 sqe = io_uring_get_sqe(&queue->ring);
+
441 if (sqe == NULL) {
+
442 /*
+
443 * All SQEs are idle here - no good reason this
+
444 * could fail
+
445 */
+
446 fuse_log(FUSE_LOG_ERR, "Failed to get all ring SQEs");
+
447 return -EIO;
+
448 }
+
449
+
450 ent->last_cmd = FUSE_IO_URING_CMD_REGISTER;
+
451 fuse_uring_sqe_prepare(sqe, ent, ent->last_cmd);
+
452
+
453 /* only needed for fetch */
+
454 ent->iov[0].iov_base = ent->req_header;
+
455 ent->iov[0].iov_len = queue->req_header_sz;
+
456
+
457 ent->iov[1].iov_base = ent->op_payload;
+
458 ent->iov[1].iov_len = ent->req_payload_sz;
+
459
+
460 sqe->addr = (uint64_t)(ent->iov);
+
461 sqe->len = 2;
+
462
+
463 /* this is a fetch, kernel does not read commit id */
+
464 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), queue->qid, 0);
+
465
+
466 return 0;
+
467
+
468}
+
469
+
470static int fuse_uring_register_queue(struct fuse_ring_queue *queue)
+
471{
+
472 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
473 unsigned int sq_ready;
+
474 struct io_uring_sqe *sqe;
+
475 int res;
+
476
+
477 for (size_t idx = 0; idx < ring_pool->queue_depth; idx++) {
+
478 struct fuse_ring_ent *ent = &queue->ent[idx];
+
479
+
480 res = fuse_uring_register_ent(queue, ent);
+
481 if (res != 0)
+
482 return res;
+
483 }
+
484
+
485 sq_ready = io_uring_sq_ready(&queue->ring);
+
486 if (sq_ready != ring_pool->queue_depth) {
+
487 fuse_log(FUSE_LOG_ERR,
+
488 "SQE ready mismatch, expected %zu got %u\n",
+
489 ring_pool->queue_depth, sq_ready);
+
490 return -EINVAL;
+
491 }
+
492
+
493 /* Poll SQE for the eventfd to wake up on teardown */
+
494 sqe = io_uring_get_sqe(&queue->ring);
+
495 if (sqe == NULL) {
+
496 fuse_log(FUSE_LOG_ERR, "Failed to get eventfd SQE");
+
497 return -EIO;
+
498 }
+
499
+
500 io_uring_prep_poll_add(sqe, queue->eventfd, POLLIN);
+
501 io_uring_sqe_set_data(sqe, (void *)(uintptr_t)queue->eventfd);
+
502
+
503 /* Only preparation until here, no submission yet */
+
504
+
505 return 0;
+
506}
+
507
+
508static struct fuse_ring_pool *fuse_create_ring(struct fuse_session *se)
+
509{
+
510 struct fuse_ring_pool *fuse_ring = NULL;
+
511 const size_t nr_queues = get_nprocs_conf();
+
512 size_t payload_sz = se->bufsize - FUSE_BUFFER_HEADER_SIZE;
+
513 size_t queue_sz;
+
514
+
515 if (se->debug)
+
516 fuse_log(FUSE_LOG_DEBUG, "starting io-uring q-depth=%d\n",
+
517 se->uring.q_depth);
+
518
+
519 fuse_ring = calloc(1, sizeof(*fuse_ring));
+
520 if (fuse_ring == NULL) {
+
521 fuse_log(FUSE_LOG_ERR, "Allocating the ring failed\n");
+
522 goto err;
+
523 }
+
524
+
525 queue_sz = fuse_ring_queue_size(se->uring.q_depth);
+
526 fuse_ring->queues = calloc(1, queue_sz * nr_queues);
+
527 if (fuse_ring->queues == NULL) {
+
528 fuse_log(FUSE_LOG_ERR, "Allocating the queues failed\n");
+
529 goto err;
+
530 }
+
531
+
532 fuse_ring->se = se;
+
533 fuse_ring->nr_queues = nr_queues;
+
534 fuse_ring->queue_depth = se->uring.q_depth;
+
535 fuse_ring->max_req_payload_sz = payload_sz;
+
536 fuse_ring->queue_mem_size = queue_sz;
+
537
+
538 /*
+
539 * very basic queue initialization, that cannot fail and will
+
540 * allow easy cleanup if something (like mmap) fails in the middle
+
541 * below
+
542 */
+
543 for (size_t qid = 0; qid < nr_queues; qid++) {
+
544 struct fuse_ring_queue *queue =
+
545 fuse_uring_get_queue(fuse_ring, qid);
+
546
+
547 queue->ring.ring_fd = -1;
+
548 queue->numa_node = numa_node_of_cpu(qid);
+
549 queue->qid = qid;
+
550 queue->ring_pool = fuse_ring;
+
551 queue->eventfd = -1;
+
552 pthread_mutex_init(&queue->ring_lock, NULL);
+
553 }
+
554
+
555 pthread_cond_init(&fuse_ring->thread_start_cond, NULL);
+
556 pthread_mutex_init(&fuse_ring->thread_start_mutex, NULL);
+
557 sem_init(&fuse_ring->init_sem, 0, 0);
+
558
+
559 return fuse_ring;
+
560
+
561err:
+
562 if (fuse_ring)
+
563 fuse_session_destruct_uring(fuse_ring);
+
564
+
565 return NULL;
+
566}
+
567
+
568static void fuse_uring_resubmit(struct fuse_ring_queue *queue,
+
569 struct fuse_ring_ent *ent)
+
570{
+
571 struct io_uring_sqe *sqe;
+
572
+
573 sqe = io_uring_get_sqe(&queue->ring);
+
574 if (sqe == NULL) {
+
575 /* This is an impossible condition, unless there is a bug.
+
576 * The kernel sent back an SQEs, which is assigned to a request.
+
577 * There is no way to get out of SQEs, as the number of
+
578 * SQEs matches the number tof requests.
+
579 */
+
580
+
581 queue->ring_pool->se->error = -EIO;
+
582 fuse_log(FUSE_LOG_ERR, "Failed to get a ring SQEs\n");
+
583
+
584 return;
+
585 }
+
586
+
587 fuse_uring_sqe_prepare(sqe, ent, ent->last_cmd);
+
588
+
589 switch (ent->last_cmd) {
+
590 case FUSE_IO_URING_CMD_REGISTER:
+
591 sqe->addr = (uint64_t)(ent->iov);
+
592 sqe->len = 2;
+
593 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe),
+
594 queue->qid, 0);
+
595 break;
+
596 case FUSE_IO_URING_CMD_COMMIT_AND_FETCH:
+
597 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe),
+
598 queue->qid, ent->req_commit_id);
+
599 break;
+
600 default:
+
601 fuse_log(FUSE_LOG_ERR, "Unknown command type: %d\n",
+
602 ent->last_cmd);
+
603 queue->ring_pool->se->error = -EINVAL;
+
604 break;
+
605 }
+
606
+
607 /* caller submits */
+
608}
+
609
+
610static void fuse_uring_handle_cqe(struct fuse_ring_queue *queue,
+
611 struct io_uring_cqe *cqe)
+
612{
+
613 struct fuse_ring_ent *ent = io_uring_cqe_get_data(cqe);
+
614
+
615 if (!ent) {
+
616 fuse_log(FUSE_LOG_ERR,
+
617 "cqe=%p io_uring_cqe_get_data returned NULL\n", cqe);
+
618 return;
+
619 }
+
620
+
621 struct fuse_req *req = &ent->req;
+
622 struct fuse_ring_pool *fuse_ring = queue->ring_pool;
+
623 struct fuse_uring_req_header *rrh = ent->req_header;
+
624
+
625 struct fuse_in_header *in = (struct fuse_in_header *)&rrh->in_out;
+
626 struct fuse_uring_ent_in_out *ent_in_out = &rrh->ring_ent_in_out;
+
627
+
628 ent->req_commit_id = ent_in_out->commit_id;
+
629 if (unlikely(ent->req_commit_id == 0)) {
+
630 /*
+
631 * If this happens kernel will not find the response - it will
+
632 * be stuck forever - better to abort immediately.
+
633 */
+
634 fuse_log(FUSE_LOG_ERR, "Received invalid commit_id=0\n");
+
635 abort();
+
636 }
+
637
+
638 memset(&req->flags, 0, sizeof(req->flags));
+
639 memset(&req->u, 0, sizeof(req->u));
+
640 req->flags.is_uring = 1;
+
641 req->ref_cnt++;
+
642 req->ch = NULL; /* not needed for uring */
+
643 req->interrupted = 0;
+
644 list_init_req(req);
+
645
+
646 fuse_session_process_uring_cqe(fuse_ring->se, req, in, &rrh->op_in,
+
647 ent->op_payload, ent_in_out->payload_sz);
+
648}
+
649
+
650static int fuse_uring_queue_handle_cqes(struct fuse_ring_queue *queue)
+
651{
+
652 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
653 struct fuse_session *se = ring_pool->se;
+
654 size_t num_completed = 0;
+
655 struct io_uring_cqe *cqe;
+
656 unsigned int head;
+
657 struct fuse_ring_ent *ent;
+
658 int ret = 0;
+
659
+
660 io_uring_for_each_cqe(&queue->ring, head, cqe) {
+
661 int err = 0;
+
662
+
663 num_completed++;
+
664
+
665 err = cqe->res;
+
666 if (unlikely(err != 0)) {
+
667 if (err > 0 && ((uintptr_t)io_uring_cqe_get_data(cqe) ==
+
668 (unsigned int)queue->eventfd)) {
+
669 /* teardown from eventfd */
+
670 return -ENOTCONN;
+
671 }
+
672
+
673
+
674 switch (err) {
+
675 case -EAGAIN:
+
676 fallthrough;
+
677 case -EINTR:
+
678 ent = io_uring_cqe_get_data(cqe);
+
679 fuse_uring_resubmit(queue, ent);
+
680 continue;
+
681 default:
+
682 break;
+
683 }
+
684
+
685 /* -ENOTCONN is ok on umount */
+
686 if (err != -ENOTCONN) {
+
687 se->error = cqe->res;
+
688
+
689 /* return first error */
+
690 if (ret == 0)
+
691 ret = err;
+
692 }
+
693
+
694 } else {
+
695 fuse_uring_handle_cqe(queue, cqe);
+
696 }
+
697 }
+
698
+
699 if (num_completed)
+
700 io_uring_cq_advance(&queue->ring, num_completed);
+
701
+
702 return ret == 0 ? 0 : num_completed;
+
703}
+
704
+
709static void fuse_uring_set_thread_core(int qid)
+
710{
+
711 cpu_set_t mask;
+
712 int rc;
+
713
+
714 CPU_ZERO(&mask);
+
715 CPU_SET(qid, &mask);
+
716 rc = sched_setaffinity(0, sizeof(cpu_set_t), &mask);
+
717 if (rc != 0)
+
718 fuse_log(FUSE_LOG_ERR, "Failed to bind qid=%d to its core: %s\n",
+
719 qid, strerror(errno));
+
720
+
721 if (0) {
+
722 const int policy = SCHED_IDLE;
+
723 const struct sched_param param = {
+
724 .sched_priority = sched_get_priority_min(policy),
+
725 };
+
726
+
727 /* Set the lowest possible priority, so that the application
+
728 * submitting requests is not moved away from the current core.
+
729 */
+
730 rc = sched_setscheduler(0, policy, &param);
+
731 if (rc != 0)
+
732 fuse_log(FUSE_LOG_ERR, "Failed to set scheduler: %s\n",
+
733 strerror(errno));
+
734 }
+
735}
+
736
+
737/*
+
738 * @return negative error code or io-uring file descriptor
+
739 */
+
740static int fuse_uring_init_queue(struct fuse_ring_queue *queue)
+
741{
+
742 struct fuse_ring_pool *ring = queue->ring_pool;
+
743 struct fuse_session *se = ring->se;
+
744 int res;
+
745 size_t page_sz = sysconf(_SC_PAGESIZE);
+
746
+
747 queue->eventfd = eventfd(0, EFD_CLOEXEC);
+
748 if (queue->eventfd < 0) {
+
749 res = -errno;
+
750 fuse_log(FUSE_LOG_ERR,
+
751 "Failed to create eventfd for qid %d: %s\n",
+
752 queue->qid, strerror(errno));
+
753 return res;
+
754 }
+
755
+
756 res = fuse_queue_setup_io_uring(&queue->ring, queue->qid,
+
757 ring->queue_depth, se->fd,
+
758 queue->eventfd);
+
759 if (res != 0) {
+
760 fuse_log(FUSE_LOG_ERR, "qid=%d io_uring init failed\n",
+
761 queue->qid);
+
762 return res;
+
763 }
+
764
+
765 queue->req_header_sz = ROUND_UP(sizeof(struct fuse_ring_ent),
+
766 page_sz);
+
767
+
768 for (size_t idx = 0; idx < ring->queue_depth; idx++) {
+
769 struct fuse_ring_ent *ring_ent = &queue->ent[idx];
+
770 struct fuse_req *req = &ring_ent->req;
+
771
+
772 ring_ent->ring_queue = queue;
+
773
+
774 /*
+
775 * Also allocate the header to have it page aligned, which
+
776 * is a requirement for page pinning
+
777 */
+
778 ring_ent->req_header =
+
779 numa_alloc_local(queue->req_header_sz);
+
780 if (!ring_ent->req_header)
+
781 return -ENOMEM;
+
782 ring_ent->req_payload_sz = ring->max_req_payload_sz;
+
783
+
784 ring_ent->op_payload =
+
785 numa_alloc_local(ring_ent->req_payload_sz);
+
786 if (!ring_ent->op_payload)
+
787 return -ENOMEM;
+
788
+
789 req->se = se;
+
790 pthread_mutex_init(&req->lock, NULL);
+
791 req->flags.is_uring = 1;
+
792 req->ref_cnt = 1; /* extra ref to avoid destruction */
+
793 list_init_req(req);
+
794 }
+
795
+
796 res = fuse_uring_register_queue(queue);
+
797 if (res != 0) {
+
798 fuse_log(
+
799 FUSE_LOG_ERR,
+
800 "Grave fuse-uring error on preparing SQEs, aborting\n");
+
801 se->error = -EIO;
+ +
803 return res;
+
804 }
+
805
+
806 return queue->ring.ring_fd;
+
807}
+
808
+
809static void *fuse_uring_thread(void *arg)
+
810{
+
811 struct fuse_ring_queue *queue = arg;
+
812 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
813 struct fuse_session *se = ring_pool->se;
+
814 int err;
+
815 char thread_name[16] = { 0 };
+
816
+
817 snprintf(thread_name, 16, "fuse-ring-%d", queue->qid);
+
818 thread_name[15] = '\0';
+
819 fuse_set_thread_name(thread_name);
+
820
+
821 fuse_uring_set_thread_core(queue->qid);
+
822
+
823 err = fuse_uring_init_queue(queue);
+
824 pthread_mutex_lock(&ring_pool->thread_start_mutex);
+
825 if (err < 0)
+
826 ring_pool->failed_threads++;
+
827 ring_pool->started_threads++;
+
828 pthread_cond_broadcast(&ring_pool->thread_start_cond);
+
829 pthread_mutex_unlock(&ring_pool->thread_start_mutex);
+
830
+
831 if (err < 0) {
+
832 fuse_log(FUSE_LOG_ERR, "qid=%d queue setup failed\n",
+
833 queue->qid);
+
834 goto err_non_fatal;
+
835 }
+
836
+
837 sem_wait(&ring_pool->init_sem);
+
838
+
839 /* Not using fuse_session_exited(se), as that cannot be inlined */
+
840 while (!atomic_load_explicit(&se->mt_exited, memory_order_relaxed)) {
+
841 io_uring_submit_and_wait(&queue->ring, 1);
+
842
+
843 pthread_mutex_lock(&queue->ring_lock);
+
844 queue->cqe_processing = true;
+
845 err = fuse_uring_queue_handle_cqes(queue);
+
846 queue->cqe_processing = false;
+
847 pthread_mutex_unlock(&queue->ring_lock);
+
848 if (err < 0)
+
849 goto err;
+
850 }
+
851
+
852 return NULL;
+
853
+
854err:
+ +
856err_non_fatal:
+
857 return NULL;
+
858}
+
859
+
860static int fuse_uring_start_ring_threads(struct fuse_ring_pool *ring)
+
861{
+
862 int rc = 0;
+
863
+
864 for (size_t qid = 0; qid < ring->nr_queues; qid++) {
+
865 struct fuse_ring_queue *queue = fuse_uring_get_queue(ring, qid);
+
866
+
867 rc = pthread_create(&queue->tid, NULL, fuse_uring_thread, queue);
+
868 if (rc != 0)
+
869 break;
+
870 }
+
871
+
872 return rc;
+
873}
+
874
+
875static int fuse_uring_sanity_check(struct fuse_session *se)
+
876{
+
877 if (se->uring.q_depth == 0) {
+
878 fuse_log(FUSE_LOG_ERR, "io-uring queue depth must be > 0\n");
+
879 return -EINVAL;
+
880 }
+
881
+
882 _Static_assert(sizeof(struct fuse_uring_cmd_req) <=
+
883 FUSE_URING_MAX_SQE128_CMD_DATA,
+
884 "SQE128_CMD_DATA has 80B cmd data");
+
885
+
886 return 0;
+
887}
+
888
+
889int fuse_uring_start(struct fuse_session *se)
+
890{
+
891 int err = 0;
+
892 struct fuse_ring_pool *fuse_ring;
+
893
+
894 fuse_uring_sanity_check(se);
+
895
+
896 fuse_ring = fuse_create_ring(se);
+
897 if (fuse_ring == NULL) {
+
898 err = -EADDRNOTAVAIL;
+
899 goto err;
+
900 }
+
901
+
902 se->uring.pool = fuse_ring;
+
903
+
904 /* Hold off threads from send fuse ring entries (SQEs) */
+
905 sem_init(&fuse_ring->init_sem, 0, 0);
+
906 pthread_cond_init(&fuse_ring->thread_start_cond, NULL);
+
907 pthread_mutex_init(&fuse_ring->thread_start_mutex, NULL);
+
908
+
909 err = fuse_uring_start_ring_threads(fuse_ring);
+
910 if (err)
+
911 goto err;
+
912
+
913 /*
+
914 * Wait for all threads to start or to fail
+
915 */
+
916 pthread_mutex_lock(&fuse_ring->thread_start_mutex);
+
917 while (fuse_ring->started_threads < fuse_ring->nr_queues)
+
918 pthread_cond_wait(&fuse_ring->thread_start_cond,
+
919 &fuse_ring->thread_start_mutex);
+
920
+
921 if (fuse_ring->failed_threads != 0)
+
922 err = -EADDRNOTAVAIL;
+
923 pthread_mutex_unlock(&fuse_ring->thread_start_mutex);
+
924
+
925err:
+
926 if (err) {
+
927 /* Note all threads need to have been started */
+
928 if (fuse_ring)
+
929 fuse_session_destruct_uring(fuse_ring);
+
930 se->uring.pool = NULL;
+
931 }
+
932 return err;
+
933}
+
934
+
935int fuse_uring_stop(struct fuse_session *se)
+
936{
+
937 struct fuse_ring_pool *ring = se->uring.pool;
+
938
+
939 if (ring == NULL)
+
940 return 0;
+
941
+
942 fuse_session_destruct_uring(ring);
+
943
+
944 return 0;
+
945}
+
946
+
947void fuse_uring_wake_ring_threads(struct fuse_session *se)
+
948{
+
949 struct fuse_ring_pool *ring = se->uring.pool;
+
950
+
951 /* Wake up the threads to let them send SQEs */
+
952 for (size_t qid = 0; qid < ring->nr_queues; qid++)
+
953 sem_post(&ring->init_sem);
+
954}
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
fuse_buf_copy_flags
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
struct fuse_req * fuse_req_t
+
int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, void **mr)
+
void * mem
+
size_t size
+ +
struct fuse_buf buf[1]
+ + + + +
+ + + + diff --git a/doc/html/lib_2fuse__uring__i_8h_source.html b/doc/html/lib_2fuse__uring__i_8h_source.html new file mode 100644 index 0000000..5876227 --- /dev/null +++ b/doc/html/lib_2fuse__uring__i_8h_source.html @@ -0,0 +1,149 @@ + + + + + + + +libfuse: lib/fuse_uring_i.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_uring_i.h
+
+
+
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 * Copyright (C) 2025 Bernd Schubert <bschubert@ddn.com>
+
4 * This program can be distributed under the terms of the GNU LGPLv2.
+
5 * See the file LGPL2.txt
+
6 */
+
7
+
8#ifndef FUSE_URING_I_H_
+
9#define FUSE_URING_I_H_
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_kernel.h"
+
14
+
15#ifndef HAVE_URING
+
16#include "util.h"
+
17#endif
+
18
+
19#include <errno.h> // IWYU pragma: keep
+
20
+
21/* io-uring defaults */
+
22#define SESSION_DEF_URING_ENABLE (0)
+
23#define SESSION_DEF_URING_Q_DEPTH (8)
+
24
+
25void fuse_session_process_uring_cqe(struct fuse_session *se,
+
26 struct fuse_req *req,
+
27 struct fuse_in_header *in, void *in_header,
+
28 void *in_payload, size_t payload_len);
+
29
+
30#ifdef HAVE_URING
+
31
+
32struct fuse_in_header;
+
33
+
34int fuse_uring_start(struct fuse_session *se);
+
35void fuse_uring_wake_ring_threads(struct fuse_session *se);
+
36int fuse_uring_stop(struct fuse_session *se);
+
37int send_reply_uring(fuse_req_t req, int error, const void *arg,
+
38 size_t argsize);
+
39
+
40int fuse_reply_data_uring(fuse_req_t req, struct fuse_bufvec *bufv,
+
41 enum fuse_buf_copy_flags flags);
+
42int fuse_send_msg_uring(fuse_req_t req, struct iovec *iov, int count);
+
43
+
44#else // HAVE_URING
+
45
+
46static inline int fuse_uring_start(struct fuse_session *se FUSE_VAR_UNUSED)
+
47{
+
48 return -ENOTSUP;
+
49}
+
50
+
51static inline void
+
52fuse_uring_wake_ring_threads(struct fuse_session *se FUSE_VAR_UNUSED)
+
53{
+
54}
+
55
+
56static inline int fuse_uring_stop(struct fuse_session *se FUSE_VAR_UNUSED)
+
57{
+
58 return -ENOTSUP;
+
59}
+
60
+
61static inline int send_reply_uring(fuse_req_t req FUSE_VAR_UNUSED,
+
62 int error FUSE_VAR_UNUSED,
+
63 const void *arg FUSE_VAR_UNUSED,
+
64 size_t argsize FUSE_VAR_UNUSED)
+
65{
+
66 return -ENOTSUP;
+
67}
+
68
+
69static inline int
+
70fuse_reply_data_uring(fuse_req_t req FUSE_VAR_UNUSED,
+
71 struct fuse_bufvec *bufv FUSE_VAR_UNUSED,
+
72 enum fuse_buf_copy_flags flags FUSE_VAR_UNUSED)
+
73{
+
74 return -ENOTSUP;
+
75}
+
76
+
77static inline int fuse_send_msg_uring(fuse_req_t req FUSE_VAR_UNUSED,
+
78 struct iovec *iov FUSE_VAR_UNUSED,
+
79 int count FUSE_VAR_UNUSED)
+
80{
+
81 return -ENOTSUP;
+
82}
+
83
+
84#endif // HAVE_URING
+
85
+
86#endif // FUSE_URING_I_H_
+
fuse_buf_copy_flags
+
struct fuse_req * fuse_req_t
+ +
+ + + + diff --git a/doc/html/lib_2helper_8c_source.html b/doc/html/lib_2helper_8c_source.html new file mode 100644 index 0000000..7d365c7 --- /dev/null +++ b/doc/html/lib_2helper_8c_source.html @@ -0,0 +1,606 @@ + + + + + + + +libfuse: lib/helper.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
helper.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file LGPL2.txt.
+
11*/
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_i.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_opt.h"
+
17#include "fuse_lowlevel.h"
+
18#include "mount_util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <stddef.h>
+
23#include <unistd.h>
+
24#include <string.h>
+
25#include <limits.h>
+
26#include <errno.h>
+
27#include <sys/param.h>
+
28
+
29#define FUSE_HELPER_OPT(t, p) \
+
30 { t, offsetof(struct fuse_cmdline_opts, p), 1 }
+
31
+
32static const struct fuse_opt fuse_helper_opts[] = {
+
33 FUSE_HELPER_OPT("-h", show_help),
+
34 FUSE_HELPER_OPT("--help", show_help),
+
35 FUSE_HELPER_OPT("-V", show_version),
+
36 FUSE_HELPER_OPT("--version", show_version),
+
37 FUSE_HELPER_OPT("-d", debug),
+
38 FUSE_HELPER_OPT("debug", debug),
+
39 FUSE_HELPER_OPT("-d", foreground),
+
40 FUSE_HELPER_OPT("debug", foreground),
+ + +
43 FUSE_HELPER_OPT("-f", foreground),
+
44 FUSE_HELPER_OPT("-s", singlethread),
+
45 FUSE_HELPER_OPT("fsname=", nodefault_subtype),
+ +
47#ifndef __FreeBSD__
+
48 FUSE_HELPER_OPT("subtype=", nodefault_subtype),
+ +
50#endif
+
51 FUSE_HELPER_OPT("clone_fd", clone_fd),
+
52 FUSE_HELPER_OPT("max_idle_threads=%u", max_idle_threads),
+
53 FUSE_HELPER_OPT("max_threads=%u", max_threads),
+ +
55};
+
56
+
57struct fuse_conn_info_opts {
+
58 int atomic_o_trunc;
+
59 int no_remote_posix_lock;
+
60 int no_remote_flock;
+
61 int splice_write;
+
62 int splice_move;
+
63 int splice_read;
+
64 int no_splice_write;
+
65 int no_splice_move;
+
66 int no_splice_read;
+
67 int auto_inval_data;
+
68 int no_auto_inval_data;
+
69 int no_readdirplus;
+
70 int no_readdirplus_auto;
+
71 int async_dio;
+
72 int no_async_dio;
+
73 int writeback_cache;
+
74 int no_writeback_cache;
+
75 int async_read;
+
76 int sync_read;
+
77 unsigned max_write;
+
78 unsigned max_readahead;
+
79 unsigned max_background;
+
80 unsigned congestion_threshold;
+
81 unsigned time_gran;
+
82 int set_max_write;
+
83 int set_max_readahead;
+
84 int set_max_background;
+
85 int set_congestion_threshold;
+
86 int set_time_gran;
+
87};
+
88
+
89#define CONN_OPTION(t, p, v) \
+
90 { t, offsetof(struct fuse_conn_info_opts, p), v }
+
91static const struct fuse_opt conn_info_opt_spec[] = {
+
92 CONN_OPTION("max_write=%u", max_write, 0),
+
93 CONN_OPTION("max_write=", set_max_write, 1),
+
94 CONN_OPTION("max_readahead=%u", max_readahead, 0),
+
95 CONN_OPTION("max_readahead=", set_max_readahead, 1),
+
96 CONN_OPTION("max_background=%u", max_background, 0),
+
97 CONN_OPTION("max_background=", set_max_background, 1),
+
98 CONN_OPTION("congestion_threshold=%u", congestion_threshold, 0),
+
99 CONN_OPTION("congestion_threshold=", set_congestion_threshold, 1),
+
100 CONN_OPTION("sync_read", sync_read, 1),
+
101 CONN_OPTION("async_read", async_read, 1),
+
102 CONN_OPTION("atomic_o_trunc", atomic_o_trunc, 1),
+
103 CONN_OPTION("no_remote_lock", no_remote_posix_lock, 1),
+
104 CONN_OPTION("no_remote_lock", no_remote_flock, 1),
+
105 CONN_OPTION("no_remote_flock", no_remote_flock, 1),
+
106 CONN_OPTION("no_remote_posix_lock", no_remote_posix_lock, 1),
+
107 CONN_OPTION("splice_write", splice_write, 1),
+
108 CONN_OPTION("no_splice_write", no_splice_write, 1),
+
109 CONN_OPTION("splice_move", splice_move, 1),
+
110 CONN_OPTION("no_splice_move", no_splice_move, 1),
+
111 CONN_OPTION("splice_read", splice_read, 1),
+
112 CONN_OPTION("no_splice_read", no_splice_read, 1),
+
113 CONN_OPTION("auto_inval_data", auto_inval_data, 1),
+
114 CONN_OPTION("no_auto_inval_data", no_auto_inval_data, 1),
+
115 CONN_OPTION("readdirplus=no", no_readdirplus, 1),
+
116 CONN_OPTION("readdirplus=yes", no_readdirplus, 0),
+
117 CONN_OPTION("readdirplus=yes", no_readdirplus_auto, 1),
+
118 CONN_OPTION("readdirplus=auto", no_readdirplus, 0),
+
119 CONN_OPTION("readdirplus=auto", no_readdirplus_auto, 0),
+
120 CONN_OPTION("async_dio", async_dio, 1),
+
121 CONN_OPTION("no_async_dio", no_async_dio, 1),
+
122 CONN_OPTION("writeback_cache", writeback_cache, 1),
+
123 CONN_OPTION("no_writeback_cache", no_writeback_cache, 1),
+
124 CONN_OPTION("time_gran=%u", time_gran, 0),
+
125 CONN_OPTION("time_gran=", set_time_gran, 1),
+ +
127};
+
128
+
129
+
130void fuse_cmdline_help(void)
+
131{
+
132 printf(" -h --help print help\n"
+
133 " -V --version print version\n"
+
134 " -d -o debug enable debug output (implies -f)\n"
+
135 " -f foreground operation\n"
+
136 " -s disable multi-threaded operation\n"
+
137 " -o clone_fd use separate fuse device fd for each thread\n"
+
138 " (may improve performance)\n"
+
139 " -o max_idle_threads the maximum number of idle worker threads\n"
+
140 " allowed (default: -1)\n"
+
141 " -o max_threads the maximum number of worker threads\n"
+
142 " allowed (default: 10)\n");
+
143}
+
144
+
145static int fuse_helper_opt_proc(void *data, const char *arg, int key,
+
146 struct fuse_args *outargs)
+
147{
+
148 (void) outargs;
+
149 struct fuse_cmdline_opts *opts = data;
+
150
+
151 switch (key) {
+ +
153 if (!opts->mountpoint) {
+
154 if (fuse_mnt_parse_fuse_fd(arg) != -1) {
+
155 return fuse_opt_add_opt(&opts->mountpoint, arg);
+
156 }
+
157
+
158 char mountpoint[PATH_MAX] = "";
+
159 if (realpath(arg, mountpoint) == NULL) {
+
160 fuse_log(FUSE_LOG_ERR,
+
161 "fuse: bad mount point `%s': %s\n",
+
162 arg, strerror(errno));
+
163 return -1;
+
164 }
+
165 return fuse_opt_add_opt(&opts->mountpoint, mountpoint);
+
166 } else {
+
167 fuse_log(FUSE_LOG_ERR, "fuse: invalid argument `%s'\n", arg);
+
168 return -1;
+
169 }
+
170
+
171 default:
+
172 /* Pass through unknown options */
+
173 return 1;
+
174 }
+
175}
+
176
+
177/* Under FreeBSD, there is no subtype option so this
+
178 function actually sets the fsname */
+
179static int add_default_subtype(const char *progname, struct fuse_args *args)
+
180{
+
181 int res;
+
182 char *subtype_opt;
+
183
+
184 const char *basename = strrchr(progname, '/');
+
185 if (basename == NULL)
+
186 basename = progname;
+
187 else if (basename[1] != '\0')
+
188 basename++;
+
189
+
190 subtype_opt = (char *) malloc(strlen(basename) + 64);
+
191 if (subtype_opt == NULL) {
+
192 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
193 return -1;
+
194 }
+
195#ifdef __FreeBSD__
+
196 sprintf(subtype_opt, "-ofsname=%s", basename);
+
197#else
+
198 sprintf(subtype_opt, "-osubtype=%s", basename);
+
199#endif
+
200 res = fuse_opt_add_arg(args, subtype_opt);
+
201 free(subtype_opt);
+
202 return res;
+
203}
+
204
+
205int fuse_parse_cmdline_312(struct fuse_args *args,
+
206 struct fuse_cmdline_opts *opts);
+
207FUSE_SYMVER("fuse_parse_cmdline_312", "fuse_parse_cmdline@@FUSE_3.12")
+
208int fuse_parse_cmdline_312(struct fuse_args *args,
+
209 struct fuse_cmdline_opts *opts)
+
210{
+
211 memset(opts, 0, sizeof(struct fuse_cmdline_opts));
+
212
+
213 opts->max_idle_threads = UINT_MAX; /* new default in fuse version 3.12 */
+
214 opts->max_threads = 10;
+
215
+
216 if (fuse_opt_parse(args, opts, fuse_helper_opts,
+
217 fuse_helper_opt_proc) == -1)
+
218 return -1;
+
219
+
220 /* *Linux*: if neither -o subtype nor -o fsname are specified,
+
221 set subtype to program's basename.
+
222 *FreeBSD*: if fsname is not specified, set to program's
+
223 basename. */
+
224 if (!opts->nodefault_subtype)
+
225 if (add_default_subtype(args->argv[0], args) == -1)
+
226 return -1;
+
227
+
228 return 0;
+
229}
+
230
+
234int fuse_parse_cmdline_30(struct fuse_args *args,
+
235 struct fuse_cmdline_opts *opts);
+
236FUSE_SYMVER("fuse_parse_cmdline_30", "fuse_parse_cmdline@FUSE_3.0")
+
237int fuse_parse_cmdline_30(struct fuse_args *args,
+
238 struct fuse_cmdline_opts *out_opts)
+
239{
+
240 struct fuse_cmdline_opts opts;
+
241
+
242 int rc = fuse_parse_cmdline_312(args, &opts);
+
243 if (rc == 0) {
+
244 /* copy up to the size of the old pre 3.12 struct */
+
245 memcpy(out_opts, &opts,
+
246 offsetof(struct fuse_cmdline_opts, max_idle_threads) +
+
247 sizeof(opts.max_idle_threads));
+
248 }
+
249
+
250 return rc;
+
251}
+
252
+
253int fuse_daemonize(int foreground)
+
254{
+
255 if (!foreground) {
+
256 int nullfd;
+
257 int waiter[2];
+
258 char completed;
+
259
+
260 if (pipe(waiter)) {
+
261 perror("fuse_daemonize: pipe");
+
262 return -1;
+
263 }
+
264
+
265 /*
+
266 * demonize current process by forking it and killing the
+
267 * parent. This makes current process as a child of 'init'.
+
268 */
+
269 switch(fork()) {
+
270 case -1:
+
271 perror("fuse_daemonize: fork");
+
272 return -1;
+
273 case 0:
+
274 break;
+
275 default:
+
276 (void) read(waiter[0], &completed, sizeof(completed));
+
277 _exit(0);
+
278 }
+
279
+
280 if (setsid() == -1) {
+
281 perror("fuse_daemonize: setsid");
+
282 return -1;
+
283 }
+
284
+
285 (void) chdir("/");
+
286
+
287 nullfd = open("/dev/null", O_RDWR, 0);
+
288 if (nullfd != -1) {
+
289 (void) dup2(nullfd, 0);
+
290 (void) dup2(nullfd, 1);
+
291 (void) dup2(nullfd, 2);
+
292 if (nullfd > 2)
+
293 close(nullfd);
+
294 }
+
295
+
296 /* Propagate completion of daemon initialization */
+
297 completed = 1;
+
298 (void) write(waiter[1], &completed, sizeof(completed));
+
299 close(waiter[0]);
+
300 close(waiter[1]);
+
301 } else {
+
302 (void) chdir("/");
+
303 }
+
304 return 0;
+
305}
+
306
+
307int fuse_main_real_versioned(int argc, char *argv[],
+
308 const struct fuse_operations *op, size_t op_size,
+
309 struct libfuse_version *version, void *user_data)
+
310{
+
311 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
312 struct fuse *fuse;
+
313 struct fuse_cmdline_opts opts;
+
314 int res;
+
315 struct fuse_loop_config *loop_config = NULL;
+
316
+
317 if (fuse_parse_cmdline(&args, &opts) != 0)
+
318 return 1;
+
319
+
320 if (opts.show_version) {
+
321 printf("FUSE library version %s\n", PACKAGE_VERSION);
+ +
323 res = 0;
+
324 goto out1;
+
325 }
+
326
+
327 if (opts.show_help) {
+
328 if(args.argv[0][0] != '\0')
+
329 printf("usage: %s [options] <mountpoint>\n\n",
+
330 args.argv[0]);
+
331 printf("FUSE options:\n");
+ +
333 fuse_lib_help(&args);
+
334 res = 0;
+
335 goto out1;
+
336 }
+
337
+
338 if (!opts.show_help &&
+
339 !opts.mountpoint) {
+
340 fuse_log(FUSE_LOG_ERR, "error: no mountpoint specified\n");
+
341 res = 2;
+
342 goto out1;
+
343 }
+
344
+
345 struct fuse *_fuse_new_31(struct fuse_args *args,
+
346 const struct fuse_operations *op, size_t op_size,
+
347 struct libfuse_version *version,
+
348 void *user_data);
+
349 fuse = _fuse_new_31(&args, op, op_size, version, user_data);
+
350 if (fuse == NULL) {
+
351 res = 3;
+
352 goto out1;
+
353 }
+
354
+
355 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
356 res = 4;
+
357 goto out2;
+
358 }
+
359
+
360 if (fuse_daemonize(opts.foreground) != 0) {
+
361 res = 5;
+
362 goto out3;
+
363 }
+
364
+
365 struct fuse_session *se = fuse_get_session(fuse);
+
366 if (fuse_set_signal_handlers(se) != 0) {
+
367 res = 6;
+
368 goto out3;
+
369 }
+
370
+
371 if (opts.singlethread)
+
372 res = fuse_loop(fuse);
+
373 else {
+
374 loop_config = fuse_loop_cfg_create();
+
375 if (loop_config == NULL) {
+
376 res = 7;
+
377 goto out3;
+
378 }
+
379
+
380 fuse_loop_cfg_set_clone_fd(loop_config, opts.clone_fd);
+
381
+
382 fuse_loop_cfg_set_idle_threads(loop_config, opts.max_idle_threads);
+
383 fuse_loop_cfg_set_max_threads(loop_config, opts.max_threads);
+
384 res = fuse_loop_mt(fuse, loop_config);
+
385 }
+
386 if (res)
+
387 res = 8;
+
388
+ +
390out3:
+
391 fuse_unmount(fuse);
+
392out2:
+
393 fuse_destroy(fuse);
+
394out1:
+
395 fuse_loop_cfg_destroy(loop_config);
+
396 free(opts.mountpoint);
+
397 fuse_opt_free_args(&args);
+
398 return res;
+
399}
+
400
+
401/* Not symboled, as not part of the official API */
+
402int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
403 size_t op_size, void *user_data);
+
404int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
405 size_t op_size, void *user_data)
+
406{
+
407 struct libfuse_version version = { 0 };
+
408 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
409 user_data);
+
410}
+
411
+
412void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
413 struct fuse_conn_info *conn)
+
414{
+
415 if(opts->set_max_write)
+
416 conn->max_write = opts->max_write;
+
417 if(opts->set_max_background)
+
418 conn->max_background = opts->max_background;
+
419 if(opts->set_congestion_threshold)
+
420 conn->congestion_threshold = opts->congestion_threshold;
+
421 if(opts->set_time_gran)
+
422 conn->time_gran = opts->time_gran;
+
423 if(opts->set_max_readahead)
+
424 conn->max_readahead = opts->max_readahead;
+
425
+
426#define LL_ENABLE(cond, cap) \
+
427 do { \
+
428 if (cond) \
+
429 fuse_set_feature_flag(conn, cap); \
+
430 } while (0)
+
431
+
432#define LL_DISABLE(cond, cap) \
+
433 do { \
+
434 if (cond) \
+
435 fuse_unset_feature_flag(conn, cap); \
+
436 } while (0)
+
437
+
438 LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ);
+
439 LL_DISABLE(opts->no_splice_read, FUSE_CAP_SPLICE_READ);
+
440
+
441 LL_ENABLE(opts->splice_write, FUSE_CAP_SPLICE_WRITE);
+
442 LL_DISABLE(opts->no_splice_write, FUSE_CAP_SPLICE_WRITE);
+
443
+
444 LL_ENABLE(opts->splice_move, FUSE_CAP_SPLICE_MOVE);
+
445 LL_DISABLE(opts->no_splice_move, FUSE_CAP_SPLICE_MOVE);
+
446
+
447 LL_ENABLE(opts->auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
448 LL_DISABLE(opts->no_auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
449
+
450 LL_DISABLE(opts->no_readdirplus, FUSE_CAP_READDIRPLUS);
+
451 LL_DISABLE(opts->no_readdirplus_auto, FUSE_CAP_READDIRPLUS_AUTO);
+
452
+
453 LL_ENABLE(opts->async_dio, FUSE_CAP_ASYNC_DIO);
+
454 LL_DISABLE(opts->no_async_dio, FUSE_CAP_ASYNC_DIO);
+
455
+
456 LL_ENABLE(opts->writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
457 LL_DISABLE(opts->no_writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
458
+
459 LL_ENABLE(opts->async_read, FUSE_CAP_ASYNC_READ);
+
460 LL_DISABLE(opts->sync_read, FUSE_CAP_ASYNC_READ);
+
461
+
462 LL_DISABLE(opts->no_remote_posix_lock, FUSE_CAP_POSIX_LOCKS);
+
463 LL_DISABLE(opts->no_remote_flock, FUSE_CAP_FLOCK_LOCKS);
+
464}
+
465
+
466struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args)
+
467{
+
468 struct fuse_conn_info_opts *opts;
+
469
+
470 opts = calloc(1, sizeof(struct fuse_conn_info_opts));
+
471 if(opts == NULL) {
+
472 fuse_log(FUSE_LOG_ERR, "calloc failed\n");
+
473 return NULL;
+
474 }
+
475 if(fuse_opt_parse(args, opts, conn_info_opt_spec, NULL) == -1) {
+
476 free(opts);
+
477 return NULL;
+
478 }
+
479 return opts;
+
480}
+
481
+
482int fuse_open_channel(const char *mountpoint, const char* options)
+
483{
+
484 struct mount_opts *opts = NULL;
+
485 int fd = -1;
+
486 const char *argv[] = { "", "-o", options };
+
487 int argc = sizeof(argv) / sizeof(argv[0]);
+
488 struct fuse_args args = FUSE_ARGS_INIT(argc, (char**) argv);
+
489
+
490 opts = parse_mount_opts(&args);
+
491 if (opts == NULL)
+
492 return -1;
+
493
+
494 fd = fuse_kern_mount(mountpoint, opts);
+
495 destroy_mount_opts(opts);
+
496
+
497 return fd;
+
498}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5197
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5146
+
int fuse_main_real_versioned(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
Definition helper.c:307
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:482
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5202
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:466
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_READDIRPLUS
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:412
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t time_gran
+
uint32_t congestion_threshold
+
uint32_t max_write
+
uint32_t max_readahead
+
uint32_t max_background
+ + + + +
+ + + + diff --git a/doc/html/lib_2modules_2iconv_8c_source.html b/doc/html/lib_2modules_2iconv_8c_source.html new file mode 100644 index 0000000..0a82c73 --- /dev/null +++ b/doc/html/lib_2modules_2iconv_8c_source.html @@ -0,0 +1,835 @@ + + + + + + + +libfuse: lib/modules/iconv.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
iconv.c
+
+
+
1/*
+
2 fuse iconv module: file name charset conversion
+
3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt
+
7*/
+
8
+
9#include <fuse_config.h>
+
10
+
11#include <fuse.h>
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <stddef.h>
+
15#include <string.h>
+
16#include <errno.h>
+
17#include <iconv.h>
+
18#include <pthread.h>
+
19#include <locale.h>
+
20#include <langinfo.h>
+
21
+
22struct iconv {
+
23 struct fuse_fs *next;
+
24 pthread_mutex_t lock;
+
25 char *from_code;
+
26 char *to_code;
+
27 iconv_t tofs;
+
28 iconv_t fromfs;
+
29};
+
30
+
31struct iconv_dh {
+
32 struct iconv *ic;
+
33 void *prev_buf;
+
34 fuse_fill_dir_t prev_filler;
+
35};
+
36
+
37static struct iconv *iconv_get(void)
+
38{
+ +
40}
+
41
+
42static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp,
+
43 int fromfs)
+
44{
+
45 size_t pathlen;
+
46 size_t newpathlen;
+
47 char *newpath;
+
48 size_t plen;
+
49 char *p;
+
50 size_t res;
+
51 int err;
+
52
+
53 if (path == NULL) {
+
54 *newpathp = NULL;
+
55 return 0;
+
56 }
+
57
+
58 pathlen = strlen(path);
+
59 newpathlen = pathlen * 4;
+
60 newpath = malloc(newpathlen + 1);
+
61 if (!newpath)
+
62 return -ENOMEM;
+
63
+
64 plen = newpathlen;
+
65 p = newpath;
+
66 pthread_mutex_lock(&ic->lock);
+
67 do {
+
68 res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path,
+
69 &pathlen, &p, &plen);
+
70 if (res == (size_t) -1) {
+
71 char *tmp;
+
72 size_t inc;
+
73
+
74 err = -EILSEQ;
+
75 if (errno != E2BIG)
+
76 goto err;
+
77
+
78 inc = (pathlen + 1) * 4;
+
79 newpathlen += inc;
+
80 int dp = p - newpath;
+
81 tmp = realloc(newpath, newpathlen + 1);
+
82 err = -ENOMEM;
+
83 if (!tmp)
+
84 goto err;
+
85
+
86 p = tmp + dp;
+
87 plen += inc;
+
88 newpath = tmp;
+
89 }
+
90 } while (res == (size_t) -1);
+
91 pthread_mutex_unlock(&ic->lock);
+
92 *p = '\0';
+
93 *newpathp = newpath;
+
94 return 0;
+
95
+
96err:
+
97 iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL);
+
98 pthread_mutex_unlock(&ic->lock);
+
99 free(newpath);
+
100 return err;
+
101}
+
102
+
103static int iconv_getattr(const char *path, struct stat *stbuf,
+
104 struct fuse_file_info *fi)
+
105{
+
106 struct iconv *ic = iconv_get();
+
107 char *newpath;
+
108 int err = iconv_convpath(ic, path, &newpath, 0);
+
109 if (!err) {
+
110 err = fuse_fs_getattr(ic->next, newpath, stbuf, fi);
+
111 free(newpath);
+
112 }
+
113 return err;
+
114}
+
115
+
116static int iconv_access(const char *path, int mask)
+
117{
+
118 struct iconv *ic = iconv_get();
+
119 char *newpath;
+
120 int err = iconv_convpath(ic, path, &newpath, 0);
+
121 if (!err) {
+
122 err = fuse_fs_access(ic->next, newpath, mask);
+
123 free(newpath);
+
124 }
+
125 return err;
+
126}
+
127
+
128static int iconv_readlink(const char *path, char *buf, size_t size)
+
129{
+
130 struct iconv *ic = iconv_get();
+
131 char *newpath;
+
132 int err = iconv_convpath(ic, path, &newpath, 0);
+
133 if (!err) {
+
134 err = fuse_fs_readlink(ic->next, newpath, buf, size);
+
135 if (!err) {
+
136 char *newlink;
+
137 err = iconv_convpath(ic, buf, &newlink, 1);
+
138 if (!err) {
+
139 strncpy(buf, newlink, size - 1);
+
140 buf[size - 1] = '\0';
+
141 free(newlink);
+
142 }
+
143 }
+
144 free(newpath);
+
145 }
+
146 return err;
+
147}
+
148
+
149static int iconv_opendir(const char *path, struct fuse_file_info *fi)
+
150{
+
151 struct iconv *ic = iconv_get();
+
152 char *newpath;
+
153 int err = iconv_convpath(ic, path, &newpath, 0);
+
154 if (!err) {
+
155 err = fuse_fs_opendir(ic->next, newpath, fi);
+
156 free(newpath);
+
157 }
+
158 return err;
+
159}
+
160
+
161static int iconv_dir_fill(void *buf, const char *name,
+
162 const struct stat *stbuf, off_t off,
+
163 enum fuse_fill_dir_flags flags)
+
164{
+
165 struct iconv_dh *dh = buf;
+
166 char *newname;
+
167 int res = 0;
+
168 if (iconv_convpath(dh->ic, name, &newname, 1) == 0) {
+
169 res = dh->prev_filler(dh->prev_buf, newname, stbuf, off, flags);
+
170 free(newname);
+
171 }
+
172 return res;
+
173}
+
174
+
175static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
176 off_t offset, struct fuse_file_info *fi,
+
177 enum fuse_readdir_flags flags)
+
178{
+
179 struct iconv *ic = iconv_get();
+
180 char *newpath;
+
181 int err = iconv_convpath(ic, path, &newpath, 0);
+
182 if (!err) {
+
183 struct iconv_dh dh;
+
184 dh.ic = ic;
+
185 dh.prev_buf = buf;
+
186 dh.prev_filler = filler;
+
187 err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill,
+
188 offset, fi, flags);
+
189 free(newpath);
+
190 }
+
191 return err;
+
192}
+
193
+
194static int iconv_releasedir(const char *path, struct fuse_file_info *fi)
+
195{
+
196 struct iconv *ic = iconv_get();
+
197 char *newpath;
+
198 int err = iconv_convpath(ic, path, &newpath, 0);
+
199 if (!err) {
+
200 err = fuse_fs_releasedir(ic->next, newpath, fi);
+
201 free(newpath);
+
202 }
+
203 return err;
+
204}
+
205
+
206static int iconv_mknod(const char *path, mode_t mode, dev_t rdev)
+
207{
+
208 struct iconv *ic = iconv_get();
+
209 char *newpath;
+
210 int err = iconv_convpath(ic, path, &newpath, 0);
+
211 if (!err) {
+
212 err = fuse_fs_mknod(ic->next, newpath, mode, rdev);
+
213 free(newpath);
+
214 }
+
215 return err;
+
216}
+
217
+
218static int iconv_mkdir(const char *path, mode_t mode)
+
219{
+
220 struct iconv *ic = iconv_get();
+
221 char *newpath;
+
222 int err = iconv_convpath(ic, path, &newpath, 0);
+
223 if (!err) {
+
224 err = fuse_fs_mkdir(ic->next, newpath, mode);
+
225 free(newpath);
+
226 }
+
227 return err;
+
228}
+
229
+
230static int iconv_unlink(const char *path)
+
231{
+
232 struct iconv *ic = iconv_get();
+
233 char *newpath;
+
234 int err = iconv_convpath(ic, path, &newpath, 0);
+
235 if (!err) {
+
236 err = fuse_fs_unlink(ic->next, newpath);
+
237 free(newpath);
+
238 }
+
239 return err;
+
240}
+
241
+
242static int iconv_rmdir(const char *path)
+
243{
+
244 struct iconv *ic = iconv_get();
+
245 char *newpath;
+
246 int err = iconv_convpath(ic, path, &newpath, 0);
+
247 if (!err) {
+
248 err = fuse_fs_rmdir(ic->next, newpath);
+
249 free(newpath);
+
250 }
+
251 return err;
+
252}
+
253
+
254static int iconv_symlink(const char *from, const char *to)
+
255{
+
256 struct iconv *ic = iconv_get();
+
257 char *newfrom;
+
258 char *newto;
+
259 int err = iconv_convpath(ic, from, &newfrom, 0);
+
260 if (!err) {
+
261 err = iconv_convpath(ic, to, &newto, 0);
+
262 if (!err) {
+
263 err = fuse_fs_symlink(ic->next, newfrom, newto);
+
264 free(newto);
+
265 }
+
266 free(newfrom);
+
267 }
+
268 return err;
+
269}
+
270
+
271static int iconv_rename(const char *from, const char *to, unsigned int flags)
+
272{
+
273 struct iconv *ic = iconv_get();
+
274 char *newfrom;
+
275 char *newto;
+
276 int err = iconv_convpath(ic, from, &newfrom, 0);
+
277 if (!err) {
+
278 err = iconv_convpath(ic, to, &newto, 0);
+
279 if (!err) {
+
280 err = fuse_fs_rename(ic->next, newfrom, newto, flags);
+
281 free(newto);
+
282 }
+
283 free(newfrom);
+
284 }
+
285 return err;
+
286}
+
287
+
288static int iconv_link(const char *from, const char *to)
+
289{
+
290 struct iconv *ic = iconv_get();
+
291 char *newfrom;
+
292 char *newto;
+
293 int err = iconv_convpath(ic, from, &newfrom, 0);
+
294 if (!err) {
+
295 err = iconv_convpath(ic, to, &newto, 0);
+
296 if (!err) {
+
297 err = fuse_fs_link(ic->next, newfrom, newto);
+
298 free(newto);
+
299 }
+
300 free(newfrom);
+
301 }
+
302 return err;
+
303}
+
304
+
305static int iconv_chmod(const char *path, mode_t mode,
+
306 struct fuse_file_info *fi)
+
307{
+
308 struct iconv *ic = iconv_get();
+
309 char *newpath;
+
310 int err = iconv_convpath(ic, path, &newpath, 0);
+
311 if (!err) {
+
312 err = fuse_fs_chmod(ic->next, newpath, mode, fi);
+
313 free(newpath);
+
314 }
+
315 return err;
+
316}
+
317
+
318static int iconv_chown(const char *path, uid_t uid, gid_t gid,
+
319 struct fuse_file_info *fi)
+
320{
+
321 struct iconv *ic = iconv_get();
+
322 char *newpath;
+
323 int err = iconv_convpath(ic, path, &newpath, 0);
+
324 if (!err) {
+
325 err = fuse_fs_chown(ic->next, newpath, uid, gid, fi);
+
326 free(newpath);
+
327 }
+
328 return err;
+
329}
+
330
+
331static int iconv_truncate(const char *path, off_t size,
+
332 struct fuse_file_info *fi)
+
333{
+
334 struct iconv *ic = iconv_get();
+
335 char *newpath;
+
336 int err = iconv_convpath(ic, path, &newpath, 0);
+
337 if (!err) {
+
338 err = fuse_fs_truncate(ic->next, newpath, size, fi);
+
339 free(newpath);
+
340 }
+
341 return err;
+
342}
+
343
+
344static int iconv_utimens(const char *path, const struct timespec ts[2],
+
345 struct fuse_file_info *fi)
+
346{
+
347 struct iconv *ic = iconv_get();
+
348 char *newpath;
+
349 int err = iconv_convpath(ic, path, &newpath, 0);
+
350 if (!err) {
+
351 err = fuse_fs_utimens(ic->next, newpath, ts, fi);
+
352 free(newpath);
+
353 }
+
354 return err;
+
355}
+
356
+
357static int iconv_create(const char *path, mode_t mode,
+
358 struct fuse_file_info *fi)
+
359{
+
360 struct iconv *ic = iconv_get();
+
361 char *newpath;
+
362 int err = iconv_convpath(ic, path, &newpath, 0);
+
363 if (!err) {
+
364 err = fuse_fs_create(ic->next, newpath, mode, fi);
+
365 free(newpath);
+
366 }
+
367 return err;
+
368}
+
369
+
370static int iconv_open_file(const char *path, struct fuse_file_info *fi)
+
371{
+
372 struct iconv *ic = iconv_get();
+
373 char *newpath;
+
374 int err = iconv_convpath(ic, path, &newpath, 0);
+
375 if (!err) {
+
376 err = fuse_fs_open(ic->next, newpath, fi);
+
377 free(newpath);
+
378 }
+
379 return err;
+
380}
+
381
+
382static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp,
+
383 size_t size, off_t offset, struct fuse_file_info *fi)
+
384{
+
385 struct iconv *ic = iconv_get();
+
386 char *newpath;
+
387 int err = iconv_convpath(ic, path, &newpath, 0);
+
388 if (!err) {
+
389 err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi);
+
390 free(newpath);
+
391 }
+
392 return err;
+
393}
+
394
+
395static int iconv_write_buf(const char *path, struct fuse_bufvec *buf,
+
396 off_t offset, struct fuse_file_info *fi)
+
397{
+
398 struct iconv *ic = iconv_get();
+
399 char *newpath;
+
400 int err = iconv_convpath(ic, path, &newpath, 0);
+
401 if (!err) {
+
402 err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi);
+
403 free(newpath);
+
404 }
+
405 return err;
+
406}
+
407
+
408static int iconv_statfs(const char *path, struct statvfs *stbuf)
+
409{
+
410 struct iconv *ic = iconv_get();
+
411 char *newpath;
+
412 int err = iconv_convpath(ic, path, &newpath, 0);
+
413 if (!err) {
+
414 err = fuse_fs_statfs(ic->next, newpath, stbuf);
+
415 free(newpath);
+
416 }
+
417 return err;
+
418}
+
419
+
420static int iconv_flush(const char *path, struct fuse_file_info *fi)
+
421{
+
422 struct iconv *ic = iconv_get();
+
423 char *newpath;
+
424 int err = iconv_convpath(ic, path, &newpath, 0);
+
425 if (!err) {
+
426 err = fuse_fs_flush(ic->next, newpath, fi);
+
427 free(newpath);
+
428 }
+
429 return err;
+
430}
+
431
+
432static int iconv_release(const char *path, struct fuse_file_info *fi)
+
433{
+
434 struct iconv *ic = iconv_get();
+
435 char *newpath;
+
436 int err = iconv_convpath(ic, path, &newpath, 0);
+
437 if (!err) {
+
438 err = fuse_fs_release(ic->next, newpath, fi);
+
439 free(newpath);
+
440 }
+
441 return err;
+
442}
+
443
+
444static int iconv_fsync(const char *path, int isdatasync,
+
445 struct fuse_file_info *fi)
+
446{
+
447 struct iconv *ic = iconv_get();
+
448 char *newpath;
+
449 int err = iconv_convpath(ic, path, &newpath, 0);
+
450 if (!err) {
+
451 err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi);
+
452 free(newpath);
+
453 }
+
454 return err;
+
455}
+
456
+
457static int iconv_fsyncdir(const char *path, int isdatasync,
+
458 struct fuse_file_info *fi)
+
459{
+
460 struct iconv *ic = iconv_get();
+
461 char *newpath;
+
462 int err = iconv_convpath(ic, path, &newpath, 0);
+
463 if (!err) {
+
464 err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi);
+
465 free(newpath);
+
466 }
+
467 return err;
+
468}
+
469
+
470static int iconv_setxattr(const char *path, const char *name,
+
471 const char *value, size_t size, int flags)
+
472{
+
473 struct iconv *ic = iconv_get();
+
474 char *newpath;
+
475 int err = iconv_convpath(ic, path, &newpath, 0);
+
476 if (!err) {
+
477 err = fuse_fs_setxattr(ic->next, newpath, name, value, size,
+
478 flags);
+
479 free(newpath);
+
480 }
+
481 return err;
+
482}
+
483
+
484static int iconv_getxattr(const char *path, const char *name, char *value,
+
485 size_t size)
+
486{
+
487 struct iconv *ic = iconv_get();
+
488 char *newpath;
+
489 int err = iconv_convpath(ic, path, &newpath, 0);
+
490 if (!err) {
+
491 err = fuse_fs_getxattr(ic->next, newpath, name, value, size);
+
492 free(newpath);
+
493 }
+
494 return err;
+
495}
+
496
+
497static int iconv_listxattr(const char *path, char *list, size_t size)
+
498{
+
499 struct iconv *ic = iconv_get();
+
500 char *newpath;
+
501 int err = iconv_convpath(ic, path, &newpath, 0);
+
502 if (!err) {
+
503 err = fuse_fs_listxattr(ic->next, newpath, list, size);
+
504 free(newpath);
+
505 }
+
506 return err;
+
507}
+
508
+
509static int iconv_removexattr(const char *path, const char *name)
+
510{
+
511 struct iconv *ic = iconv_get();
+
512 char *newpath;
+
513 int err = iconv_convpath(ic, path, &newpath, 0);
+
514 if (!err) {
+
515 err = fuse_fs_removexattr(ic->next, newpath, name);
+
516 free(newpath);
+
517 }
+
518 return err;
+
519}
+
520
+
521static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
522 struct flock *lock)
+
523{
+
524 struct iconv *ic = iconv_get();
+
525 char *newpath;
+
526 int err = iconv_convpath(ic, path, &newpath, 0);
+
527 if (!err) {
+
528 err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock);
+
529 free(newpath);
+
530 }
+
531 return err;
+
532}
+
533
+
534static int iconv_flock(const char *path, struct fuse_file_info *fi, int op)
+
535{
+
536 struct iconv *ic = iconv_get();
+
537 char *newpath;
+
538 int err = iconv_convpath(ic, path, &newpath, 0);
+
539 if (!err) {
+
540 err = fuse_fs_flock(ic->next, newpath, fi, op);
+
541 free(newpath);
+
542 }
+
543 return err;
+
544}
+
545
+
546static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx)
+
547{
+
548 struct iconv *ic = iconv_get();
+
549 char *newpath;
+
550 int err = iconv_convpath(ic, path, &newpath, 0);
+
551 if (!err) {
+
552 err = fuse_fs_bmap(ic->next, newpath, blocksize, idx);
+
553 free(newpath);
+
554 }
+
555 return err;
+
556}
+
557
+
558static off_t iconv_lseek(const char *path, off_t off, int whence,
+
559 struct fuse_file_info *fi)
+
560{
+
561 struct iconv *ic = iconv_get();
+
562 char *newpath;
+
563 int res = iconv_convpath(ic, path, &newpath, 0);
+
564 if (!res) {
+
565 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
+
566 free(newpath);
+
567 }
+
568 return res;
+
569}
+
570
+
571#ifdef HAVE_STATX
+
572static int iconv_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
573 struct fuse_file_info *fi)
+
574{
+
575 struct iconv *ic = iconv_get();
+
576 char *newpath;
+
577 int res = iconv_convpath(ic, path, &newpath, 0);
+
578
+
579 if (!res) {
+
580 res = fuse_fs_statx(ic->next, newpath, flags, mask, stxbuf, fi);
+
581 free(newpath);
+
582 }
+
583 return res;
+
584}
+
585#endif
+
586
+
587static void *iconv_init(struct fuse_conn_info *conn,
+
588 struct fuse_config *cfg)
+
589{
+
590 struct iconv *ic = iconv_get();
+
591 fuse_fs_init(ic->next, conn, cfg);
+
592 /* Don't touch cfg->nullpath_ok, we can work with
+
593 either */
+
594 return ic;
+
595}
+
596
+
597static void iconv_destroy(void *data)
+
598{
+
599 struct iconv *ic = data;
+
600 fuse_fs_destroy(ic->next);
+
601 iconv_close(ic->tofs);
+
602 iconv_close(ic->fromfs);
+
603 pthread_mutex_destroy(&ic->lock);
+
604 free(ic->from_code);
+
605 free(ic->to_code);
+
606 free(ic);
+
607}
+
608
+
609static const struct fuse_operations iconv_oper = {
+
610 .destroy = iconv_destroy,
+
611 .init = iconv_init,
+
612 .getattr = iconv_getattr,
+
613 .access = iconv_access,
+
614 .readlink = iconv_readlink,
+
615 .opendir = iconv_opendir,
+
616 .readdir = iconv_readdir,
+
617 .releasedir = iconv_releasedir,
+
618 .mknod = iconv_mknod,
+
619 .mkdir = iconv_mkdir,
+
620 .symlink = iconv_symlink,
+
621 .unlink = iconv_unlink,
+
622 .rmdir = iconv_rmdir,
+
623 .rename = iconv_rename,
+
624 .link = iconv_link,
+
625 .chmod = iconv_chmod,
+
626 .chown = iconv_chown,
+
627 .truncate = iconv_truncate,
+
628 .utimens = iconv_utimens,
+
629 .create = iconv_create,
+
630 .open = iconv_open_file,
+
631 .read_buf = iconv_read_buf,
+
632 .write_buf = iconv_write_buf,
+
633 .statfs = iconv_statfs,
+
634 .flush = iconv_flush,
+
635 .release = iconv_release,
+
636 .fsync = iconv_fsync,
+
637 .fsyncdir = iconv_fsyncdir,
+
638 .setxattr = iconv_setxattr,
+
639 .getxattr = iconv_getxattr,
+
640 .listxattr = iconv_listxattr,
+
641 .removexattr = iconv_removexattr,
+
642 .lock = iconv_lock,
+
643 .flock = iconv_flock,
+
644 .bmap = iconv_bmap,
+
645 .lseek = iconv_lseek,
+
646#ifdef HAVE_STATX
+
647 .statx = iconv_statx,
+
648#endif
+
649};
+
650
+
651static const struct fuse_opt iconv_opts[] = {
+
652 FUSE_OPT_KEY("-h", 0),
+
653 FUSE_OPT_KEY("--help", 0),
+
654 { "from_code=%s", offsetof(struct iconv, from_code), 0 },
+
655 { "to_code=%s", offsetof(struct iconv, to_code), 1 },
+ +
657};
+
658
+
659static void iconv_help(void)
+
660{
+
661 char *charmap;
+
662 const char *old = setlocale(LC_CTYPE, "");
+
663
+
664 charmap = strdup(nl_langinfo(CODESET));
+
665 if (old)
+
666 setlocale(LC_CTYPE, old);
+
667 else
+
668 perror("setlocale");
+
669
+
670 printf(
+
671" -o from_code=CHARSET original encoding of file names (default: UTF-8)\n"
+
672" -o to_code=CHARSET new encoding of the file names (default: %s)\n",
+
673 charmap);
+
674 free(charmap);
+
675}
+
676
+
677static int iconv_opt_proc(void *data, const char *arg, int key,
+
678 struct fuse_args *outargs)
+
679{
+
680 (void) data; (void) arg; (void) outargs;
+
681
+
682 if (!key) {
+
683 iconv_help();
+
684 return -1;
+
685 }
+
686
+
687 return 1;
+
688}
+
689
+
690static struct fuse_fs *iconv_new(struct fuse_args *args,
+
691 struct fuse_fs *next[])
+
692{
+
693 struct fuse_fs *fs;
+
694 struct iconv *ic;
+
695 const char *old = NULL;
+
696 const char *from;
+
697 const char *to;
+
698
+
699 ic = calloc(1, sizeof(struct iconv));
+
700 if (ic == NULL) {
+
701 fuse_log(FUSE_LOG_ERR, "fuse-iconv: memory allocation failed\n");
+
702 return NULL;
+
703 }
+
704
+
705 if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1)
+
706 goto out_free;
+
707
+
708 if (!next[0] || next[1]) {
+
709 fuse_log(FUSE_LOG_ERR, "fuse-iconv: exactly one next filesystem required\n");
+
710 goto out_free;
+
711 }
+
712
+
713 from = ic->from_code ? ic->from_code : "UTF-8";
+
714 to = ic->to_code ? ic->to_code : "";
+
715 /* FIXME: detect charset equivalence? */
+
716 if (!to[0])
+
717 old = setlocale(LC_CTYPE, "");
+
718 ic->tofs = iconv_open(from, to);
+
719 if (ic->tofs == (iconv_t) -1) {
+
720 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
721 to, from);
+
722 goto out_free;
+
723 }
+
724 ic->fromfs = iconv_open(to, from);
+
725 if (ic->tofs == (iconv_t) -1) {
+
726 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
727 from, to);
+
728 goto out_iconv_close_to;
+
729 }
+
730 if (old) {
+
731 setlocale(LC_CTYPE, old);
+
732 old = NULL;
+
733 }
+
734
+
735 ic->next = next[0];
+
736 fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic);
+
737 if (!fs)
+
738 goto out_iconv_close_from;
+
739
+
740 return fs;
+
741
+
742out_iconv_close_from:
+
743 iconv_close(ic->fromfs);
+
744out_iconv_close_to:
+
745 iconv_close(ic->tofs);
+
746out_free:
+
747 free(ic->from_code);
+
748 free(ic->to_code);
+
749 free(ic);
+
750 if (old) {
+
751 setlocale(LC_CTYPE, old);
+
752 }
+
753 return NULL;
+
754}
+
755
+
756FUSE_REGISTER_MODULE(iconv, iconv_new);
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
fuse_fill_dir_flags
Definition fuse.h:58
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_REGISTER_MODULE(name_, factory_)
Definition fuse.h:1395
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + + +
void * private_data
Definition fuse.h:874
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+ +
+ + + + diff --git a/doc/html/lib_2modules_2subdir_8c_source.html b/doc/html/lib_2modules_2subdir_8c_source.html new file mode 100644 index 0000000..cc24f94 --- /dev/null +++ b/doc/html/lib_2modules_2subdir_8c_source.html @@ -0,0 +1,786 @@ + + + + + + + +libfuse: lib/modules/subdir.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
subdir.c
+
+
+
1/*
+
2 fuse subdir module: offset paths with a base directory
+
3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt
+
7*/
+
8
+
9#include <fuse_config.h>
+
10
+
11#include <fuse.h>
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <stddef.h>
+
15#include <string.h>
+
16#include <errno.h>
+
17
+
18struct subdir {
+
19 char *base;
+
20 size_t baselen;
+
21 int rellinks;
+
22 struct fuse_fs *next;
+
23};
+
24
+
25static struct subdir *subdir_get(void)
+
26{
+ +
28}
+
29
+
30static int subdir_addpath(struct subdir *d, const char *path, char **newpathp)
+
31{
+
32 char *newpath = NULL;
+
33
+
34 if (path != NULL) {
+
35 unsigned newlen = d->baselen + strlen(path);
+
36
+
37 newpath = malloc(newlen + 2);
+
38 if (!newpath)
+
39 return -ENOMEM;
+
40
+
41 if (path[0] == '/')
+
42 path++;
+
43 strcpy(newpath, d->base);
+
44 strcpy(newpath + d->baselen, path);
+
45 if (!newpath[0])
+
46 strcpy(newpath, ".");
+
47 }
+
48 *newpathp = newpath;
+
49
+
50 return 0;
+
51}
+
52
+
53static int subdir_getattr(const char *path, struct stat *stbuf,
+
54 struct fuse_file_info *fi)
+
55{
+
56 struct subdir *d = subdir_get();
+
57 char *newpath;
+
58 int err = subdir_addpath(d, path, &newpath);
+
59 if (!err) {
+
60 err = fuse_fs_getattr(d->next, newpath, stbuf, fi);
+
61 free(newpath);
+
62 }
+
63 return err;
+
64}
+
65
+
66static int subdir_access(const char *path, int mask)
+
67{
+
68 struct subdir *d = subdir_get();
+
69 char *newpath;
+
70 int err = subdir_addpath(d, path, &newpath);
+
71 if (!err) {
+
72 err = fuse_fs_access(d->next, newpath, mask);
+
73 free(newpath);
+
74 }
+
75 return err;
+
76}
+
77
+
78
+
79static int count_components(const char *p)
+
80{
+
81 int ctr;
+
82
+
83 for (; *p == '/'; p++);
+
84 for (ctr = 0; *p; ctr++) {
+
85 for (; *p && *p != '/'; p++);
+
86 for (; *p == '/'; p++);
+
87 }
+
88 return ctr;
+
89}
+
90
+
91static void strip_common(const char **sp, const char **tp)
+
92{
+
93 const char *s = *sp;
+
94 const char *t = *tp;
+
95 do {
+
96 for (; *s == '/'; s++);
+
97 for (; *t == '/'; t++);
+
98 *tp = t;
+
99 *sp = s;
+
100 for (; *s == *t && *s && *s != '/'; s++, t++);
+
101 } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t));
+
102}
+
103
+
104static void transform_symlink(struct subdir *d, const char *path,
+
105 char *buf, size_t size)
+
106{
+
107 const char *l = buf;
+
108 size_t llen;
+
109 char *s;
+
110 int dotdots;
+
111 int i;
+
112
+
113 if (l[0] != '/' || d->base[0] != '/')
+
114 return;
+
115
+
116 strip_common(&l, &path);
+
117 if (l - buf < (long) d->baselen)
+
118 return;
+
119
+
120 dotdots = count_components(path);
+
121 if (!dotdots)
+
122 return;
+
123 dotdots--;
+
124
+
125 llen = strlen(l);
+
126 if (dotdots * 3 + llen + 2 > size)
+
127 return;
+
128
+
129 s = buf + dotdots * 3;
+
130 if (llen)
+
131 memmove(s, l, llen + 1);
+
132 else if (!dotdots)
+
133 strcpy(s, ".");
+
134 else
+
135 *s = '\0';
+
136
+
137 for (s = buf, i = 0; i < dotdots; i++, s += 3)
+
138 memcpy(s, "../", 3);
+
139}
+
140
+
141
+
142static int subdir_readlink(const char *path, char *buf, size_t size)
+
143{
+
144 struct subdir *d = subdir_get();
+
145 char *newpath;
+
146 int err = subdir_addpath(d, path, &newpath);
+
147 if (!err) {
+
148 err = fuse_fs_readlink(d->next, newpath, buf, size);
+
149 if (!err && d->rellinks)
+
150 transform_symlink(d, newpath, buf, size);
+
151 free(newpath);
+
152 }
+
153 return err;
+
154}
+
155
+
156static int subdir_opendir(const char *path, struct fuse_file_info *fi)
+
157{
+
158 struct subdir *d = subdir_get();
+
159 char *newpath;
+
160 int err = subdir_addpath(d, path, &newpath);
+
161 if (!err) {
+
162 err = fuse_fs_opendir(d->next, newpath, fi);
+
163 free(newpath);
+
164 }
+
165 return err;
+
166}
+
167
+
168static int subdir_readdir(const char *path, void *buf,
+
169 fuse_fill_dir_t filler, off_t offset,
+
170 struct fuse_file_info *fi,
+
171 enum fuse_readdir_flags flags)
+
172{
+
173 struct subdir *d = subdir_get();
+
174 char *newpath;
+
175 int err = subdir_addpath(d, path, &newpath);
+
176 if (!err) {
+
177 err = fuse_fs_readdir(d->next, newpath, buf, filler, offset,
+
178 fi, flags);
+
179 free(newpath);
+
180 }
+
181 return err;
+
182}
+
183
+
184static int subdir_releasedir(const char *path, struct fuse_file_info *fi)
+
185{
+
186 struct subdir *d = subdir_get();
+
187 char *newpath;
+
188 int err = subdir_addpath(d, path, &newpath);
+
189 if (!err) {
+
190 err = fuse_fs_releasedir(d->next, newpath, fi);
+
191 free(newpath);
+
192 }
+
193 return err;
+
194}
+
195
+
196static int subdir_mknod(const char *path, mode_t mode, dev_t rdev)
+
197{
+
198 struct subdir *d = subdir_get();
+
199 char *newpath;
+
200 int err = subdir_addpath(d, path, &newpath);
+
201 if (!err) {
+
202 err = fuse_fs_mknod(d->next, newpath, mode, rdev);
+
203 free(newpath);
+
204 }
+
205 return err;
+
206}
+
207
+
208static int subdir_mkdir(const char *path, mode_t mode)
+
209{
+
210 struct subdir *d = subdir_get();
+
211 char *newpath;
+
212 int err = subdir_addpath(d, path, &newpath);
+
213 if (!err) {
+
214 err = fuse_fs_mkdir(d->next, newpath, mode);
+
215 free(newpath);
+
216 }
+
217 return err;
+
218}
+
219
+
220static int subdir_unlink(const char *path)
+
221{
+
222 struct subdir *d = subdir_get();
+
223 char *newpath;
+
224 int err = subdir_addpath(d, path, &newpath);
+
225 if (!err) {
+
226 err = fuse_fs_unlink(d->next, newpath);
+
227 free(newpath);
+
228 }
+
229 return err;
+
230}
+
231
+
232static int subdir_rmdir(const char *path)
+
233{
+
234 struct subdir *d = subdir_get();
+
235 char *newpath;
+
236 int err = subdir_addpath(d, path, &newpath);
+
237 if (!err) {
+
238 err = fuse_fs_rmdir(d->next, newpath);
+
239 free(newpath);
+
240 }
+
241 return err;
+
242}
+
243
+
244static int subdir_symlink(const char *from, const char *path)
+
245{
+
246 struct subdir *d = subdir_get();
+
247 char *newpath;
+
248 int err = subdir_addpath(d, path, &newpath);
+
249 if (!err) {
+
250 err = fuse_fs_symlink(d->next, from, newpath);
+
251 free(newpath);
+
252 }
+
253 return err;
+
254}
+
255
+
256static int subdir_rename(const char *from, const char *to, unsigned int flags)
+
257{
+
258 struct subdir *d = subdir_get();
+
259 char *newfrom;
+
260 char *newto;
+
261 int err = subdir_addpath(d, from, &newfrom);
+
262 if (!err) {
+
263 err = subdir_addpath(d, to, &newto);
+
264 if (!err) {
+
265 err = fuse_fs_rename(d->next, newfrom, newto, flags);
+
266 free(newto);
+
267 }
+
268 free(newfrom);
+
269 }
+
270 return err;
+
271}
+
272
+
273static int subdir_link(const char *from, const char *to)
+
274{
+
275 struct subdir *d = subdir_get();
+
276 char *newfrom;
+
277 char *newto;
+
278 int err = subdir_addpath(d, from, &newfrom);
+
279 if (!err) {
+
280 err = subdir_addpath(d, to, &newto);
+
281 if (!err) {
+
282 err = fuse_fs_link(d->next, newfrom, newto);
+
283 free(newto);
+
284 }
+
285 free(newfrom);
+
286 }
+
287 return err;
+
288}
+
289
+
290static int subdir_chmod(const char *path, mode_t mode,
+
291 struct fuse_file_info *fi)
+
292{
+
293 struct subdir *d = subdir_get();
+
294 char *newpath;
+
295 int err = subdir_addpath(d, path, &newpath);
+
296 if (!err) {
+
297 err = fuse_fs_chmod(d->next, newpath, mode, fi);
+
298 free(newpath);
+
299 }
+
300 return err;
+
301}
+
302
+
303static int subdir_chown(const char *path, uid_t uid, gid_t gid,
+
304 struct fuse_file_info *fi)
+
305{
+
306 struct subdir *d = subdir_get();
+
307 char *newpath;
+
308 int err = subdir_addpath(d, path, &newpath);
+
309 if (!err) {
+
310 err = fuse_fs_chown(d->next, newpath, uid, gid, fi);
+
311 free(newpath);
+
312 }
+
313 return err;
+
314}
+
315
+
316static int subdir_truncate(const char *path, off_t size,
+
317 struct fuse_file_info *fi)
+
318{
+
319 struct subdir *d = subdir_get();
+
320 char *newpath;
+
321 int err = subdir_addpath(d, path, &newpath);
+
322 if (!err) {
+
323 err = fuse_fs_truncate(d->next, newpath, size, fi);
+
324 free(newpath);
+
325 }
+
326 return err;
+
327}
+
328
+
329static int subdir_utimens(const char *path, const struct timespec ts[2],
+
330 struct fuse_file_info *fi)
+
331{
+
332 struct subdir *d = subdir_get();
+
333 char *newpath;
+
334 int err = subdir_addpath(d, path, &newpath);
+
335 if (!err) {
+
336 err = fuse_fs_utimens(d->next, newpath, ts, fi);
+
337 free(newpath);
+
338 }
+
339 return err;
+
340}
+
341
+
342static int subdir_create(const char *path, mode_t mode,
+
343 struct fuse_file_info *fi)
+
344{
+
345 struct subdir *d = subdir_get();
+
346 char *newpath;
+
347 int err = subdir_addpath(d, path, &newpath);
+
348 if (!err) {
+
349 err = fuse_fs_create(d->next, newpath, mode, fi);
+
350 free(newpath);
+
351 }
+
352 return err;
+
353}
+
354
+
355static int subdir_open(const char *path, struct fuse_file_info *fi)
+
356{
+
357 struct subdir *d = subdir_get();
+
358 char *newpath;
+
359 int err = subdir_addpath(d, path, &newpath);
+
360 if (!err) {
+
361 err = fuse_fs_open(d->next, newpath, fi);
+
362 free(newpath);
+
363 }
+
364 return err;
+
365}
+
366
+
367static int subdir_read_buf(const char *path, struct fuse_bufvec **bufp,
+
368 size_t size, off_t offset, struct fuse_file_info *fi)
+
369{
+
370 struct subdir *d = subdir_get();
+
371 char *newpath;
+
372 int err = subdir_addpath(d, path, &newpath);
+
373 if (!err) {
+
374 err = fuse_fs_read_buf(d->next, newpath, bufp, size, offset, fi);
+
375 free(newpath);
+
376 }
+
377 return err;
+
378}
+
379
+
380static int subdir_write_buf(const char *path, struct fuse_bufvec *buf,
+
381 off_t offset, struct fuse_file_info *fi)
+
382{
+
383 struct subdir *d = subdir_get();
+
384 char *newpath;
+
385 int err = subdir_addpath(d, path, &newpath);
+
386 if (!err) {
+
387 err = fuse_fs_write_buf(d->next, newpath, buf, offset, fi);
+
388 free(newpath);
+
389 }
+
390 return err;
+
391}
+
392
+
393static int subdir_statfs(const char *path, struct statvfs *stbuf)
+
394{
+
395 struct subdir *d = subdir_get();
+
396 char *newpath;
+
397 int err = subdir_addpath(d, path, &newpath);
+
398 if (!err) {
+
399 err = fuse_fs_statfs(d->next, newpath, stbuf);
+
400 free(newpath);
+
401 }
+
402 return err;
+
403}
+
404
+
405static int subdir_flush(const char *path, struct fuse_file_info *fi)
+
406{
+
407 struct subdir *d = subdir_get();
+
408 char *newpath;
+
409 int err = subdir_addpath(d, path, &newpath);
+
410 if (!err) {
+
411 err = fuse_fs_flush(d->next, newpath, fi);
+
412 free(newpath);
+
413 }
+
414 return err;
+
415}
+
416
+
417static int subdir_release(const char *path, struct fuse_file_info *fi)
+
418{
+
419 struct subdir *d = subdir_get();
+
420 char *newpath;
+
421 int err = subdir_addpath(d, path, &newpath);
+
422 if (!err) {
+
423 err = fuse_fs_release(d->next, newpath, fi);
+
424 free(newpath);
+
425 }
+
426 return err;
+
427}
+
428
+
429static int subdir_fsync(const char *path, int isdatasync,
+
430 struct fuse_file_info *fi)
+
431{
+
432 struct subdir *d = subdir_get();
+
433 char *newpath;
+
434 int err = subdir_addpath(d, path, &newpath);
+
435 if (!err) {
+
436 err = fuse_fs_fsync(d->next, newpath, isdatasync, fi);
+
437 free(newpath);
+
438 }
+
439 return err;
+
440}
+
441
+
442static int subdir_fsyncdir(const char *path, int isdatasync,
+
443 struct fuse_file_info *fi)
+
444{
+
445 struct subdir *d = subdir_get();
+
446 char *newpath;
+
447 int err = subdir_addpath(d, path, &newpath);
+
448 if (!err) {
+
449 err = fuse_fs_fsyncdir(d->next, newpath, isdatasync, fi);
+
450 free(newpath);
+
451 }
+
452 return err;
+
453}
+
454
+
455static int subdir_setxattr(const char *path, const char *name,
+
456 const char *value, size_t size, int flags)
+
457{
+
458 struct subdir *d = subdir_get();
+
459 char *newpath;
+
460 int err = subdir_addpath(d, path, &newpath);
+
461 if (!err) {
+
462 err = fuse_fs_setxattr(d->next, newpath, name, value, size,
+
463 flags);
+
464 free(newpath);
+
465 }
+
466 return err;
+
467}
+
468
+
469static int subdir_getxattr(const char *path, const char *name, char *value,
+
470 size_t size)
+
471{
+
472 struct subdir *d = subdir_get();
+
473 char *newpath;
+
474 int err = subdir_addpath(d, path, &newpath);
+
475 if (!err) {
+
476 err = fuse_fs_getxattr(d->next, newpath, name, value, size);
+
477 free(newpath);
+
478 }
+
479 return err;
+
480}
+
481
+
482static int subdir_listxattr(const char *path, char *list, size_t size)
+
483{
+
484 struct subdir *d = subdir_get();
+
485 char *newpath;
+
486 int err = subdir_addpath(d, path, &newpath);
+
487 if (!err) {
+
488 err = fuse_fs_listxattr(d->next, newpath, list, size);
+
489 free(newpath);
+
490 }
+
491 return err;
+
492}
+
493
+
494static int subdir_removexattr(const char *path, const char *name)
+
495{
+
496 struct subdir *d = subdir_get();
+
497 char *newpath;
+
498 int err = subdir_addpath(d, path, &newpath);
+
499 if (!err) {
+
500 err = fuse_fs_removexattr(d->next, newpath, name);
+
501 free(newpath);
+
502 }
+
503 return err;
+
504}
+
505
+
506static int subdir_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
507 struct flock *lock)
+
508{
+
509 struct subdir *d = subdir_get();
+
510 char *newpath;
+
511 int err = subdir_addpath(d, path, &newpath);
+
512 if (!err) {
+
513 err = fuse_fs_lock(d->next, newpath, fi, cmd, lock);
+
514 free(newpath);
+
515 }
+
516 return err;
+
517}
+
518
+
519static int subdir_flock(const char *path, struct fuse_file_info *fi, int op)
+
520{
+
521 struct subdir *d = subdir_get();
+
522 char *newpath;
+
523 int err = subdir_addpath(d, path, &newpath);
+
524 if (!err) {
+
525 err = fuse_fs_flock(d->next, newpath, fi, op);
+
526 free(newpath);
+
527 }
+
528 return err;
+
529}
+
530
+
531static int subdir_bmap(const char *path, size_t blocksize, uint64_t *idx)
+
532{
+
533 struct subdir *d = subdir_get();
+
534 char *newpath;
+
535 int err = subdir_addpath(d, path, &newpath);
+
536 if (!err) {
+
537 err = fuse_fs_bmap(d->next, newpath, blocksize, idx);
+
538 free(newpath);
+
539 }
+
540 return err;
+
541}
+
542
+
543static off_t subdir_lseek(const char *path, off_t off, int whence,
+
544 struct fuse_file_info *fi)
+
545{
+
546 struct subdir *ic = subdir_get();
+
547 char *newpath;
+
548 int res = subdir_addpath(ic, path, &newpath);
+
549 if (!res) {
+
550 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
+
551 free(newpath);
+
552 }
+
553 return res;
+
554}
+
555
+
556#ifdef HAVE_STATX
+
557static int subdir_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
558 struct fuse_file_info *fi)
+
559{
+
560 struct subdir *ic = subdir_get();
+
561 char *newpath;
+
562 int res = subdir_addpath(ic, path, &newpath);
+
563
+
564 if (!res) {
+
565 res = fuse_fs_statx(ic->next, newpath, flags, mask, stxbuf, fi);
+
566 free(newpath);
+
567 }
+
568 return res;
+
569}
+
570#endif
+
571
+
572static void *subdir_init(struct fuse_conn_info *conn,
+
573 struct fuse_config *cfg)
+
574{
+
575 struct subdir *d = subdir_get();
+
576 fuse_fs_init(d->next, conn, cfg);
+
577 /* Don't touch cfg->nullpath_ok, we can work with
+
578 either */
+
579 return d;
+
580}
+
581
+
582static void subdir_destroy(void *data)
+
583{
+
584 struct subdir *d = data;
+
585 fuse_fs_destroy(d->next);
+
586 free(d->base);
+
587 free(d);
+
588}
+
589
+
590static const struct fuse_operations subdir_oper = {
+
591 .destroy = subdir_destroy,
+
592 .init = subdir_init,
+
593 .getattr = subdir_getattr,
+
594 .access = subdir_access,
+
595 .readlink = subdir_readlink,
+
596 .opendir = subdir_opendir,
+
597 .readdir = subdir_readdir,
+
598 .releasedir = subdir_releasedir,
+
599 .mknod = subdir_mknod,
+
600 .mkdir = subdir_mkdir,
+
601 .symlink = subdir_symlink,
+
602 .unlink = subdir_unlink,
+
603 .rmdir = subdir_rmdir,
+
604 .rename = subdir_rename,
+
605 .link = subdir_link,
+
606 .chmod = subdir_chmod,
+
607 .chown = subdir_chown,
+
608 .truncate = subdir_truncate,
+
609 .utimens = subdir_utimens,
+
610 .create = subdir_create,
+
611 .open = subdir_open,
+
612 .read_buf = subdir_read_buf,
+
613 .write_buf = subdir_write_buf,
+
614 .statfs = subdir_statfs,
+
615 .flush = subdir_flush,
+
616 .release = subdir_release,
+
617 .fsync = subdir_fsync,
+
618 .fsyncdir = subdir_fsyncdir,
+
619 .setxattr = subdir_setxattr,
+
620 .getxattr = subdir_getxattr,
+
621 .listxattr = subdir_listxattr,
+
622 .removexattr = subdir_removexattr,
+
623 .lock = subdir_lock,
+
624 .flock = subdir_flock,
+
625 .bmap = subdir_bmap,
+
626 .lseek = subdir_lseek,
+
627#ifdef HAVE_STATX
+
628 .statx = subdir_statx,
+
629#endif
+
630};
+
631
+
632static const struct fuse_opt subdir_opts[] = {
+
633 FUSE_OPT_KEY("-h", 0),
+
634 FUSE_OPT_KEY("--help", 0),
+
635 { "subdir=%s", offsetof(struct subdir, base), 0 },
+
636 { "rellinks", offsetof(struct subdir, rellinks), 1 },
+
637 { "norellinks", offsetof(struct subdir, rellinks), 0 },
+ +
639};
+
640
+
641static void subdir_help(void)
+
642{
+
643 printf(
+
644" -o subdir=DIR prepend this directory to all paths (mandatory)\n"
+
645" -o [no]rellinks transform absolute symlinks to relative\n");
+
646}
+
647
+
648static int subdir_opt_proc(void *data, const char *arg, int key,
+
649 struct fuse_args *outargs)
+
650{
+
651 (void) data; (void) arg; (void) outargs;
+
652
+
653 if (!key) {
+
654 subdir_help();
+
655 return -1;
+
656 }
+
657
+
658 return 1;
+
659}
+
660
+
661static struct fuse_fs *subdir_new(struct fuse_args *args,
+
662 struct fuse_fs *next[])
+
663{
+
664 struct fuse_fs *fs;
+
665 struct subdir *d;
+
666
+
667 d = calloc(1, sizeof(struct subdir));
+
668 if (d == NULL) {
+
669 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
+
670 return NULL;
+
671 }
+
672
+
673 if (fuse_opt_parse(args, d, subdir_opts, subdir_opt_proc) == -1)
+
674 goto out_free;
+
675
+
676 if (!next[0] || next[1]) {
+
677 fuse_log(FUSE_LOG_ERR, "fuse-subdir: exactly one next filesystem required\n");
+
678 goto out_free;
+
679 }
+
680
+
681 if (!d->base) {
+
682 fuse_log(FUSE_LOG_ERR, "fuse-subdir: missing 'subdir' option\n");
+
683 goto out_free;
+
684 }
+
685
+
686 if (d->base[0] && d->base[strlen(d->base)-1] != '/') {
+
687 char *tmp = realloc(d->base, strlen(d->base) + 2);
+
688 if (!tmp) {
+
689 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
+
690 goto out_free;
+
691 }
+
692 d->base = tmp;
+
693 strcat(d->base, "/");
+
694 }
+
695 d->baselen = strlen(d->base);
+
696 d->next = next[0];
+
697 fs = fuse_fs_new(&subdir_oper, sizeof(subdir_oper), d);
+
698 if (!fs)
+
699 goto out_free;
+
700 return fs;
+
701
+
702out_free:
+
703 free(d->base);
+
704 free(d);
+
705 return NULL;
+
706}
+
707
+
708FUSE_REGISTER_MODULE(subdir, subdir_new);
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_REGISTER_MODULE(name_, factory_)
Definition fuse.h:1395
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + + +
void * private_data
Definition fuse.h:874
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+ +
+ + + + diff --git a/doc/html/lib_2mount_8c_source.html b/doc/html/lib_2mount_8c_source.html new file mode 100644 index 0000000..afdf875 --- /dev/null +++ b/doc/html/lib_2mount_8c_source.html @@ -0,0 +1,794 @@ + + + + + + + +libfuse: lib/mount.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Architecture specific file system mounting (Linux).
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt.
+
9*/
+
10
+
11/* For environ */
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_misc.h"
+
17#include "fuse_opt.h"
+
18#include "mount_util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <unistd.h>
+
23#include <stddef.h>
+
24#include <string.h>
+
25#include <fcntl.h>
+
26#include <errno.h>
+
27#include <poll.h>
+
28#include <spawn.h>
+
29#include <sys/socket.h>
+
30#include <sys/un.h>
+
31#include <sys/wait.h>
+
32
+
33#include "fuse_mount_compat.h"
+
34
+
35#ifdef __NetBSD__
+
36#include <perfuse.h>
+
37
+
38#define MS_RDONLY MNT_RDONLY
+
39#define MS_NOSUID MNT_NOSUID
+
40#define MS_NODEV MNT_NODEV
+
41#define MS_NOEXEC MNT_NOEXEC
+
42#define MS_SYNCHRONOUS MNT_SYNCHRONOUS
+
43#define MS_NOATIME MNT_NOATIME
+
44#define MS_NOSYMFOLLOW MNT_NOSYMFOLLOW
+
45
+
46#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
+
47#endif
+
48
+
49#define FUSERMOUNT_PROG "fusermount3"
+
50#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
+
51#define FUSE_COMMFD2_ENV "_FUSE_COMMFD2"
+
52#define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE"
+
53
+
54#ifndef MS_DIRSYNC
+
55#define MS_DIRSYNC 128
+
56#endif
+
57
+
58enum {
+
59 KEY_KERN_FLAG,
+
60 KEY_KERN_OPT,
+
61 KEY_FUSERMOUNT_OPT,
+
62 KEY_SUBTYPE_OPT,
+
63 KEY_MTAB_OPT,
+
64 KEY_ALLOW_OTHER,
+
65 KEY_RO,
+
66};
+
67
+
68struct mount_opts {
+
69 int allow_other;
+
70 int flags;
+
71 int auto_unmount;
+
72 int blkdev;
+
73 char *fsname;
+
74 char *subtype;
+
75 char *subtype_opt;
+
76 char *mtab_opts;
+
77 char *fusermount_opts;
+
78 char *kernel_opts;
+
79 unsigned max_read;
+
80};
+
81
+
82#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 }
+
83
+
84static const struct fuse_opt fuse_mount_opts[] = {
+
85 FUSE_MOUNT_OPT("allow_other", allow_other),
+
86 FUSE_MOUNT_OPT("blkdev", blkdev),
+
87 FUSE_MOUNT_OPT("auto_unmount", auto_unmount),
+
88 FUSE_MOUNT_OPT("fsname=%s", fsname),
+
89 FUSE_MOUNT_OPT("max_read=%u", max_read),
+
90 FUSE_MOUNT_OPT("subtype=%s", subtype),
+
91 FUSE_OPT_KEY("allow_other", KEY_KERN_OPT),
+
92 FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT),
+
93 FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT),
+
94 FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT),
+
95 FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT),
+
96 FUSE_OPT_KEY("blksize=", KEY_KERN_OPT),
+
97 FUSE_OPT_KEY("default_permissions", KEY_KERN_OPT),
+
98 FUSE_OPT_KEY("context=", KEY_KERN_OPT),
+
99 FUSE_OPT_KEY("fscontext=", KEY_KERN_OPT),
+
100 FUSE_OPT_KEY("defcontext=", KEY_KERN_OPT),
+
101 FUSE_OPT_KEY("rootcontext=", KEY_KERN_OPT),
+
102 FUSE_OPT_KEY("max_read=", KEY_KERN_OPT),
+
103 FUSE_OPT_KEY("user=", KEY_MTAB_OPT),
+
104 FUSE_OPT_KEY("-n", KEY_MTAB_OPT),
+
105 FUSE_OPT_KEY("-r", KEY_RO),
+
106 FUSE_OPT_KEY("ro", KEY_KERN_FLAG),
+
107 FUSE_OPT_KEY("rw", KEY_KERN_FLAG),
+
108 FUSE_OPT_KEY("suid", KEY_KERN_FLAG),
+
109 FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG),
+
110 FUSE_OPT_KEY("dev", KEY_KERN_FLAG),
+
111 FUSE_OPT_KEY("nodev", KEY_KERN_FLAG),
+
112 FUSE_OPT_KEY("exec", KEY_KERN_FLAG),
+
113 FUSE_OPT_KEY("noexec", KEY_KERN_FLAG),
+
114 FUSE_OPT_KEY("async", KEY_KERN_FLAG),
+
115 FUSE_OPT_KEY("sync", KEY_KERN_FLAG),
+
116 FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG),
+
117 FUSE_OPT_KEY("noatime", KEY_KERN_FLAG),
+
118 FUSE_OPT_KEY("nodiratime", KEY_KERN_FLAG),
+
119 FUSE_OPT_KEY("nostrictatime", KEY_KERN_FLAG),
+
120 FUSE_OPT_KEY("symfollow", KEY_KERN_FLAG),
+
121 FUSE_OPT_KEY("nosymfollow", KEY_KERN_FLAG),
+ +
123};
+
124
+
125/*
+
126 * Running fusermount by calling 'posix_spawn'
+
127 *
+
128 * @param out_pid might be NULL
+
129 */
+
130static int fusermount_posix_spawn(posix_spawn_file_actions_t *action,
+
131 char const * const argv[], pid_t *out_pid)
+
132{
+
133 const char *full_path = FUSERMOUNT_DIR "/" FUSERMOUNT_PROG;
+
134 pid_t pid;
+
135
+
136 /* See man 7 environ for the global environ pointer */
+
137
+
138 /* first try the install path */
+
139 int status = posix_spawn(&pid, full_path, action, NULL,
+
140 (char * const *) argv, environ);
+
141 if (status != 0) {
+
142 /* if that fails, try a system install */
+
143 status = posix_spawnp(&pid, FUSERMOUNT_PROG, action, NULL,
+
144 (char * const *) argv, environ);
+
145 }
+
146
+
147 if (status != 0) {
+
148 fuse_log(FUSE_LOG_ERR, "Failed to call '%s': %s\n",
+
149 FUSERMOUNT_PROG, strerror(status));
+
150 return -status;
+
151 }
+
152
+
153 if (out_pid)
+
154 *out_pid = pid;
+
155 else
+
156 waitpid(pid, NULL, 0); /* FIXME: check exit code and return error if any */
+
157
+
158 return 0;
+
159}
+
160
+
161void fuse_mount_version(void)
+
162{
+
163 char const *const argv[] = {FUSERMOUNT_PROG, "--version", NULL};
+
164 int status = fusermount_posix_spawn(NULL, argv, NULL);
+
165
+
166 if(status != 0)
+
167 fuse_log(FUSE_LOG_ERR, "Running '%s --version' failed",
+
168 FUSERMOUNT_PROG);
+
169}
+
170
+
171struct mount_flags {
+
172 const char *opt;
+
173 unsigned long flag;
+
174 int on;
+
175};
+
176
+
177static const struct mount_flags mount_flags[] = {
+
178 {"rw", MS_RDONLY, 0},
+
179 {"ro", MS_RDONLY, 1},
+
180 {"suid", MS_NOSUID, 0},
+
181 {"nosuid", MS_NOSUID, 1},
+
182 {"dev", MS_NODEV, 0},
+
183 {"nodev", MS_NODEV, 1},
+
184 {"exec", MS_NOEXEC, 0},
+
185 {"noexec", MS_NOEXEC, 1},
+
186 {"async", MS_SYNCHRONOUS, 0},
+
187 {"sync", MS_SYNCHRONOUS, 1},
+
188 {"noatime", MS_NOATIME, 1},
+
189 {"nodiratime", MS_NODIRATIME, 1},
+
190 {"norelatime", MS_RELATIME, 0},
+
191 {"nostrictatime", MS_STRICTATIME, 0},
+
192 {"symfollow", MS_NOSYMFOLLOW, 0},
+
193 {"nosymfollow", MS_NOSYMFOLLOW, 1},
+
194#ifndef __NetBSD__
+
195 {"dirsync", MS_DIRSYNC, 1},
+
196#endif
+
197 {NULL, 0, 0}
+
198};
+
199
+
200unsigned get_max_read(struct mount_opts *o)
+
201{
+
202 return o->max_read;
+
203}
+
204
+
205static void set_mount_flag(const char *s, int *flags)
+
206{
+
207 int i;
+
208
+
209 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
210 const char *opt = mount_flags[i].opt;
+
211 if (strcmp(opt, s) == 0) {
+
212 if (mount_flags[i].on)
+
213 *flags |= mount_flags[i].flag;
+
214 else
+
215 *flags &= ~mount_flags[i].flag;
+
216 return;
+
217 }
+
218 }
+
219 fuse_log(FUSE_LOG_ERR, "fuse: internal error, can't find mount flag\n");
+
220 abort();
+
221}
+
222
+
223static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+
224 struct fuse_args *outargs)
+
225{
+
226 (void) outargs;
+
227 struct mount_opts *mo = data;
+
228
+
229 switch (key) {
+
230 case KEY_RO:
+
231 arg = "ro";
+
232 /* fall through */
+
233 case KEY_KERN_FLAG:
+
234 set_mount_flag(arg, &mo->flags);
+
235 return 0;
+
236
+
237 case KEY_KERN_OPT:
+
238 return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
239
+
240 case KEY_FUSERMOUNT_OPT:
+
241 return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg);
+
242
+
243 case KEY_SUBTYPE_OPT:
+
244 return fuse_opt_add_opt(&mo->subtype_opt, arg);
+
245
+
246 case KEY_MTAB_OPT:
+
247 return fuse_opt_add_opt(&mo->mtab_opts, arg);
+
248
+
249 /* Third party options like 'x-gvfs-notrash' */
+
250 case FUSE_OPT_KEY_OPT:
+
251 return (strncmp("x-", arg, 2) == 0) ?
+
252 fuse_opt_add_opt(&mo->mtab_opts, arg) :
+
253 1;
+
254 }
+
255
+
256 /* Pass through unknown options */
+
257 return 1;
+
258}
+
259
+
260/* return value:
+
261 * >= 0 => fd
+
262 * -1 => error
+
263 */
+
264static int receive_fd(int fd)
+
265{
+
266 struct msghdr msg;
+
267 struct iovec iov;
+
268 char buf[1];
+
269 int rv;
+
270 size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)];
+
271 struct cmsghdr *cmsg;
+
272
+
273 iov.iov_base = buf;
+
274 iov.iov_len = 1;
+
275
+
276 memset(&msg, 0, sizeof(msg));
+
277 msg.msg_name = 0;
+
278 msg.msg_namelen = 0;
+
279 msg.msg_iov = &iov;
+
280 msg.msg_iovlen = 1;
+
281 /* old BSD implementations should use msg_accrights instead of
+
282 * msg_control; the interface is different. */
+
283 msg.msg_control = ccmsg;
+
284 msg.msg_controllen = sizeof(ccmsg);
+
285
+
286 while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR);
+
287 if (rv == -1) {
+
288 fuse_log(FUSE_LOG_ERR, "recvmsg failed: %s", strerror(errno));
+
289 return -1;
+
290 }
+
291 if(!rv) {
+
292 /* EOF */
+
293 return -1;
+
294 }
+
295
+
296 cmsg = CMSG_FIRSTHDR(&msg);
+
297 if (cmsg->cmsg_type != SCM_RIGHTS) {
+
298 fuse_log(FUSE_LOG_ERR, "got control message of unknown type %d\n",
+
299 cmsg->cmsg_type);
+
300 return -1;
+
301 }
+
302 return *(int*)CMSG_DATA(cmsg);
+
303}
+
304
+
305void fuse_kern_unmount(const char *mountpoint, int fd)
+
306{
+
307 int res;
+
308
+
309 if (fd != -1) {
+
310 struct pollfd pfd;
+
311
+
312 pfd.fd = fd;
+
313 pfd.events = 0;
+
314 res = poll(&pfd, 1, 0);
+
315
+
316 /* Need to close file descriptor, otherwise synchronous umount
+
317 would recurse into filesystem, and deadlock.
+
318
+
319 Caller expects fuse_kern_unmount to close the fd, so close it
+
320 anyway. */
+
321 close(fd);
+
322
+
323 /* If file poll returns POLLERR on the device file descriptor,
+
324 then the filesystem is already unmounted or the connection
+
325 was severed via /sys/fs/fuse/connections/NNN/abort */
+
326 if (res == 1 && (pfd.revents & POLLERR))
+
327 return;
+
328 }
+
329
+
330 if (geteuid() == 0) {
+
331 fuse_mnt_umount("fuse", mountpoint, mountpoint, 1);
+
332 return;
+
333 }
+
334
+
335 res = umount2(mountpoint, 2);
+
336 if (res == 0)
+
337 return;
+
338
+
339 char const * const argv[] =
+
340 { FUSERMOUNT_PROG, "--unmount", "--quiet", "--lazy",
+
341 "--", mountpoint, NULL };
+
342 int status = fusermount_posix_spawn(NULL, argv, NULL);
+
343 if(status != 0) {
+
344 fuse_log(FUSE_LOG_ERR, "Spawning %s to unmount failed: %s",
+
345 FUSERMOUNT_PROG, strerror(-status));
+
346 return;
+
347 }
+
348}
+
349
+
350static int setup_auto_unmount(const char *mountpoint, int quiet)
+
351{
+
352 int fds[2];
+
353 pid_t pid;
+
354 int res;
+
355
+
356 if (!mountpoint) {
+
357 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
358 return -1;
+
359 }
+
360
+
361 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+
362 if(res == -1) {
+
363 fuse_log(FUSE_LOG_ERR, "Setting up auto-unmount socketpair() failed: %s\n",
+
364 strerror(errno));
+
365 return -1;
+
366 }
+
367
+
368 char arg_fd_entry[30];
+
369 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
+
370 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
+
371 /*
+
372 * This helps to identify the FD hold by parent process.
+
373 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
+
374 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
+
375 * One potential use case is to satisfy FD-Leak checks.
+
376 */
+
377 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
+
378 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
+
379
+
380 char const *const argv[] = {
+
381 FUSERMOUNT_PROG,
+
382 "--auto-unmount",
+
383 "--",
+
384 mountpoint,
+
385 NULL,
+
386 };
+
387
+
388 // TODO: add error handling for all manipulations of action.
+
389 posix_spawn_file_actions_t action;
+
390 posix_spawn_file_actions_init(&action);
+
391
+
392 if (quiet) {
+
393 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
+
394 posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
+
395 }
+
396 posix_spawn_file_actions_addclose(&action, fds[1]);
+
397
+
398 /*
+
399 * auto-umount runs in the background - it is not waiting for the
+
400 * process
+
401 */
+
402 int status = fusermount_posix_spawn(&action, argv, &pid);
+
403
+
404 posix_spawn_file_actions_destroy(&action);
+
405
+
406 if(status != 0) {
+
407 close(fds[0]);
+
408 close(fds[1]);
+
409 fuse_log(FUSE_LOG_ERR, "fuse: Setting up auto-unmount failed (spawn): %s",
+
410 strerror(-status));
+
411 return -1;
+
412 }
+
413 // passed to child now, so can close here.
+
414 close(fds[0]);
+
415
+
416 // Now fusermount3 will only exit when fds[1] closes automatically when our
+
417 // process exits.
+
418 return 0;
+
419 // Note: fds[1] is leakend and doesn't get FD_CLOEXEC
+
420}
+
421
+
422static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
+
423 const char *opts, int quiet)
+
424{
+
425 int fds[2];
+
426 pid_t pid;
+
427 int res;
+
428
+
429 if (!mountpoint) {
+
430 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
431 return -1;
+
432 }
+
433
+
434 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+
435 if(res == -1) {
+
436 fuse_log(FUSE_LOG_ERR, "Running %s: socketpair() failed: %s\n",
+
437 FUSERMOUNT_PROG, strerror(errno));
+
438 return -1;
+
439 }
+
440
+
441 char arg_fd_entry[30];
+
442 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
+
443 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
+
444 /*
+
445 * This helps to identify the FD hold by parent process.
+
446 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
+
447 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
+
448 * One potential use case is to satisfy FD-Leak checks.
+
449 */
+
450 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
+
451 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
+
452
+
453 char const *const argv[] = {
+
454 FUSERMOUNT_PROG,
+
455 "-o", opts ? opts : "",
+
456 "--",
+
457 mountpoint,
+
458 NULL,
+
459 };
+
460
+
461
+
462 posix_spawn_file_actions_t action;
+
463 posix_spawn_file_actions_init(&action);
+
464
+
465 if (quiet) {
+
466 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
+
467 posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
+
468 }
+
469 posix_spawn_file_actions_addclose(&action, fds[1]);
+
470
+
471 int status = fusermount_posix_spawn(&action, argv, &pid);
+
472
+
473 posix_spawn_file_actions_destroy(&action);
+
474
+
475 if(status != 0) {
+
476 close(fds[0]);
+
477 close(fds[1]);
+
478 fuse_log(FUSE_LOG_ERR, "posix_spawn(p)() for %s failed: %s",
+
479 FUSERMOUNT_PROG, strerror(-status));
+
480 return -1;
+
481 }
+
482
+
483 // passed to child now, so can close here.
+
484 close(fds[0]);
+
485
+
486 int fd = receive_fd(fds[1]);
+
487
+
488 if (!mo->auto_unmount) {
+
489 /* with auto_unmount option fusermount3 will not exit until
+
490 this socket is closed */
+
491 close(fds[1]);
+
492 waitpid(pid, NULL, 0); /* bury zombie */
+
493 }
+
494
+
495 if (fd >= 0)
+
496 fcntl(fd, F_SETFD, FD_CLOEXEC);
+
497
+
498 return fd;
+
499}
+
500
+
501#ifndef O_CLOEXEC
+
502#define O_CLOEXEC 0
+
503#endif
+
504
+
505static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
+
506 const char *mnt_opts)
+
507{
+
508 char tmp[128];
+
509 const char *devname = getenv(FUSE_KERN_DEVICE_ENV) ?: "/dev/fuse";
+
510 char *source = NULL;
+
511 char *type = NULL;
+
512 struct stat stbuf;
+
513 int fd;
+
514 int res;
+
515
+
516 if (!mnt) {
+
517 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
518 return -1;
+
519 }
+
520
+
521 res = stat(mnt, &stbuf);
+
522 if (res == -1) {
+
523 fuse_log(FUSE_LOG_ERR, "fuse: failed to access mountpoint %s: %s\n",
+
524 mnt, strerror(errno));
+
525 return -1;
+
526 }
+
527
+
528 fd = open(devname, O_RDWR | O_CLOEXEC);
+
529 if (fd == -1) {
+
530 if (errno == ENODEV || errno == ENOENT)
+
531 fuse_log(FUSE_LOG_ERR,
+
532 "fuse: device %s not found. Kernel module not loaded?\n",
+
533 devname);
+
534 else
+
535 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n",
+
536 devname, strerror(errno));
+
537 return -1;
+
538 }
+
539 if (!O_CLOEXEC)
+
540 fcntl(fd, F_SETFD, FD_CLOEXEC);
+
541
+
542 snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+
543 fd, stbuf.st_mode & S_IFMT, getuid(), getgid());
+
544
+
545 res = fuse_opt_add_opt(&mo->kernel_opts, tmp);
+
546 if (res == -1)
+
547 goto out_close;
+
548
+
549 source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
+
550 (mo->subtype ? strlen(mo->subtype) : 0) +
+
551 strlen(devname) + 32);
+
552
+
553 type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
+
554 if (!type || !source) {
+
555 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate memory\n");
+
556 goto out_close;
+
557 }
+
558
+
559 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+
560 if (mo->subtype) {
+
561 strcat(type, ".");
+
562 strcat(type, mo->subtype);
+
563 }
+
564 strcpy(source,
+
565 mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
+
566
+
567 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+
568 if (res == -1 && errno == ENODEV && mo->subtype) {
+
569 /* Probably missing subtype support */
+
570 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+
571 if (mo->fsname) {
+
572 if (!mo->blkdev)
+
573 sprintf(source, "%s#%s", mo->subtype,
+
574 mo->fsname);
+
575 } else {
+
576 strcpy(source, type);
+
577 }
+
578 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+
579 }
+
580 if (res == -1) {
+
581 /*
+
582 * Maybe kernel doesn't support unprivileged mounts, in this
+
583 * case try falling back to fusermount3
+
584 */
+
585 if (errno == EPERM) {
+
586 res = -2;
+
587 } else {
+
588 int errno_save = errno;
+
589 if (mo->blkdev && errno == ENODEV &&
+
590 !fuse_mnt_check_fuseblk())
+
591 fuse_log(FUSE_LOG_ERR,
+
592 "fuse: 'fuseblk' support missing\n");
+
593 else
+
594 fuse_log(FUSE_LOG_ERR, "fuse: mount failed: %s\n",
+
595 strerror(errno_save));
+
596 }
+
597
+
598 goto out_close;
+
599 }
+
600
+
601#ifndef IGNORE_MTAB
+
602 if (geteuid() == 0) {
+
603 char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
+
604 res = -1;
+
605 if (!newmnt)
+
606 goto out_umount;
+
607
+
608 res = fuse_mnt_add_mount("fuse", source, newmnt, type,
+
609 mnt_opts);
+
610 free(newmnt);
+
611 if (res == -1)
+
612 goto out_umount;
+
613 }
+
614#endif /* IGNORE_MTAB */
+
615 free(type);
+
616 free(source);
+
617
+
618 return fd;
+
619
+
620out_umount:
+
621 umount2(mnt, 2); /* lazy umount */
+
622out_close:
+
623 free(type);
+
624 free(source);
+
625 close(fd);
+
626 return res;
+
627}
+
628
+
629static int get_mnt_flag_opts(char **mnt_optsp, int flags)
+
630{
+
631 int i;
+
632
+
633 if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1)
+
634 return -1;
+
635
+
636 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
637 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+
638 fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1)
+
639 return -1;
+
640 }
+
641 return 0;
+
642}
+
643
+
644struct mount_opts *parse_mount_opts(struct fuse_args *args)
+
645{
+
646 struct mount_opts *mo;
+
647
+
648 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
+
649 if (mo == NULL)
+
650 return NULL;
+
651
+
652 memset(mo, 0, sizeof(struct mount_opts));
+
653 mo->flags = MS_NOSUID | MS_NODEV;
+
654
+
655 if (args &&
+
656 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+
657 goto err_out;
+
658
+
659 return mo;
+
660
+
661err_out:
+
662 destroy_mount_opts(mo);
+
663 return NULL;
+
664}
+
665
+
666void destroy_mount_opts(struct mount_opts *mo)
+
667{
+
668 free(mo->fsname);
+
669 free(mo->subtype);
+
670 free(mo->fusermount_opts);
+
671 free(mo->subtype_opt);
+
672 free(mo->kernel_opts);
+
673 free(mo->mtab_opts);
+
674 free(mo);
+
675}
+
676
+
677
+
678int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
+
679{
+
680 int res = -1;
+
681 char *mnt_opts = NULL;
+
682
+
683 res = -1;
+
684 if (get_mnt_flag_opts(&mnt_opts, mo->flags) == -1)
+
685 goto out;
+
686 if (mo->kernel_opts && fuse_opt_add_opt(&mnt_opts, mo->kernel_opts) == -1)
+
687 goto out;
+
688 if (mo->mtab_opts && fuse_opt_add_opt(&mnt_opts, mo->mtab_opts) == -1)
+
689 goto out;
+
690
+
691 res = fuse_mount_sys(mountpoint, mo, mnt_opts);
+
692 if (res >= 0 && mo->auto_unmount) {
+
693 if(0 > setup_auto_unmount(mountpoint, 0)) {
+
694 // Something went wrong, let's umount like in fuse_mount_sys.
+
695 umount2(mountpoint, MNT_DETACH); /* lazy umount */
+
696 res = -1;
+
697 }
+
698 } else if (res == -2) {
+
699 if (mo->fusermount_opts &&
+
700 fuse_opt_add_opt(&mnt_opts, mo->fusermount_opts) == -1)
+
701 goto out;
+
702
+
703 if (mo->subtype) {
+
704 char *tmp_opts = NULL;
+
705
+
706 res = -1;
+
707 if (fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1 ||
+
708 fuse_opt_add_opt(&tmp_opts, mo->subtype_opt) == -1) {
+
709 free(tmp_opts);
+
710 goto out;
+
711 }
+
712
+
713 res = fuse_mount_fusermount(mountpoint, mo, tmp_opts, 1);
+
714 free(tmp_opts);
+
715 if (res == -1)
+
716 res = fuse_mount_fusermount(mountpoint, mo,
+
717 mnt_opts, 0);
+
718 } else {
+
719 res = fuse_mount_fusermount(mountpoint, mo, mnt_opts, 0);
+
720 }
+
721 }
+
722out:
+
723 free(mnt_opts);
+
724 return res;
+
725}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
#define FUSE_OPT_KEY_OPT
Definition fuse_opt.h:129
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
+ + + + diff --git a/doc/html/lib_2mount__bsd_8c_source.html b/doc/html/lib_2mount__bsd_8c_source.html new file mode 100644 index 0000000..b7bf5f9 --- /dev/null +++ b/doc/html/lib_2mount__bsd_8c_source.html @@ -0,0 +1,336 @@ + + + + + + + +libfuse: lib/mount_bsd.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_bsd.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2005-2008 Csaba Henk <csaba.henk@creo.hu>
+
4
+
5 Architecture specific file system mounting (FreeBSD).
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt.
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_i.h"
+
13#include "fuse_misc.h"
+
14#include "fuse_opt.h"
+
15#include "util.h"
+
16
+
17#include <sys/param.h>
+
18#include "fuse_mount_compat.h"
+
19
+
20#include <sys/wait.h>
+
21#include <stdio.h>
+
22#include <stdlib.h>
+
23#include <unistd.h>
+
24#include <stddef.h>
+
25#include <fcntl.h>
+
26#include <errno.h>
+
27#include <string.h>
+
28
+
29#define FUSERMOUNT_PROG "mount_fusefs"
+
30#define FUSE_DEV_TRUNK "/dev/fuse"
+
31
+
32enum {
+
33 KEY_RO,
+
34 KEY_KERN
+
35};
+
36
+
37struct mount_opts {
+
38 int allow_other;
+
39 char *kernel_opts;
+
40 unsigned max_read;
+
41};
+
42
+
43#define FUSE_DUAL_OPT_KEY(templ, key) \
+
44 FUSE_OPT_KEY(templ, key), FUSE_OPT_KEY("no" templ, key)
+
45
+
46static const struct fuse_opt fuse_mount_opts[] = {
+
47 { "allow_other", offsetof(struct mount_opts, allow_other), 1 },
+
48 { "max_read=%u", offsetof(struct mount_opts, max_read), 1 },
+
49 FUSE_OPT_KEY("-r", KEY_RO),
+
50 /* standard FreeBSD mount options */
+
51 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
+
52 FUSE_DUAL_OPT_KEY("async", KEY_KERN),
+
53 FUSE_DUAL_OPT_KEY("atime", KEY_KERN),
+
54 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
+
55 FUSE_DUAL_OPT_KEY("exec", KEY_KERN),
+
56 FUSE_DUAL_OPT_KEY("suid", KEY_KERN),
+
57 FUSE_DUAL_OPT_KEY("symfollow", KEY_KERN),
+
58 FUSE_DUAL_OPT_KEY("rdonly", KEY_KERN),
+
59 FUSE_DUAL_OPT_KEY("sync", KEY_KERN),
+
60 FUSE_DUAL_OPT_KEY("union", KEY_KERN),
+
61 FUSE_DUAL_OPT_KEY("userquota", KEY_KERN),
+
62 FUSE_DUAL_OPT_KEY("groupquota", KEY_KERN),
+
63 FUSE_DUAL_OPT_KEY("clusterr", KEY_KERN),
+
64 FUSE_DUAL_OPT_KEY("clusterw", KEY_KERN),
+
65 FUSE_DUAL_OPT_KEY("suiddir", KEY_KERN),
+
66 FUSE_DUAL_OPT_KEY("snapshot", KEY_KERN),
+
67 FUSE_DUAL_OPT_KEY("multilabel", KEY_KERN),
+
68 FUSE_DUAL_OPT_KEY("acls", KEY_KERN),
+
69 FUSE_DUAL_OPT_KEY("force", KEY_KERN),
+
70 FUSE_DUAL_OPT_KEY("update", KEY_KERN),
+
71 FUSE_DUAL_OPT_KEY("ro", KEY_KERN),
+
72 FUSE_DUAL_OPT_KEY("rw", KEY_KERN),
+
73 FUSE_DUAL_OPT_KEY("auto", KEY_KERN),
+
74 FUSE_DUAL_OPT_KEY("automounted", KEY_KERN),
+
75 /* options supported under both Linux and FBSD */
+
76 FUSE_DUAL_OPT_KEY("allow_other", KEY_KERN),
+
77 FUSE_DUAL_OPT_KEY("default_permissions",KEY_KERN),
+
78 FUSE_OPT_KEY("max_read=", KEY_KERN),
+
79 FUSE_OPT_KEY("subtype=", KEY_KERN),
+
80 /* FBSD FUSE specific mount options */
+
81 FUSE_DUAL_OPT_KEY("private", KEY_KERN),
+
82 FUSE_DUAL_OPT_KEY("neglect_shares", KEY_KERN),
+
83 FUSE_DUAL_OPT_KEY("push_symlinks_in", KEY_KERN),
+
84#if __FreeBSD_version >= 1200519
+
85 FUSE_DUAL_OPT_KEY("intr", KEY_KERN),
+
86#endif
+
87 /* stock FBSD mountopt parsing routine lets anything be negated... */
+
88 /*
+
89 * Linux specific mount options, but let just the mount util
+
90 * handle them
+
91 */
+
92 FUSE_OPT_KEY("fsname=", KEY_KERN),
+ +
94};
+
95
+
96void fuse_mount_version(void)
+
97{
+
98 system(FUSERMOUNT_PROG " --version");
+
99}
+
100
+
101unsigned get_max_read(struct mount_opts *o)
+
102{
+
103 return o->max_read;
+
104}
+
105
+
106static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+
107 struct fuse_args *outargs)
+
108{
+
109 (void) outargs;
+
110 struct mount_opts *mo = data;
+
111
+
112 switch (key) {
+
113 case KEY_RO:
+
114 arg = "ro";
+
115 /* fall through */
+
116
+
117 case KEY_KERN:
+
118 return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
119 }
+
120
+
121 /* Pass through unknown options */
+
122 return 1;
+
123}
+
124
+
125void fuse_kern_unmount(const char *mountpoint, int fd)
+
126{
+
127 if (close(fd) < 0)
+
128 fuse_log(FUSE_LOG_ERR, "closing FD %d failed: %s", fd, strerror(errno));
+
129 if (unmount(mountpoint, MNT_FORCE) < 0)
+
130 fuse_log(FUSE_LOG_ERR, "unmounting %s failed: %s",
+
131 mountpoint, strerror(errno));
+
132}
+
133
+
134static int fuse_mount_core(const char *mountpoint, const char *opts)
+
135{
+
136 const char *mountprog = FUSERMOUNT_PROG;
+
137 long fd;
+
138 char *fdnam, *dev;
+
139 pid_t pid, cpid;
+
140 int status;
+
141 int err;
+
142
+
143 fdnam = getenv("FUSE_DEV_FD");
+
144
+
145 if (fdnam) {
+
146 err = libfuse_strtol(fdnam, &fd);
+
147 if (err || fd < 0) {
+
148 fuse_log(FUSE_LOG_ERR, "invalid value given in FUSE_DEV_FD\n");
+
149 return -1;
+
150 }
+
151
+
152 goto mount;
+
153 }
+
154
+
155 dev = getenv("FUSE_DEV_NAME");
+
156
+
157 if (! dev)
+
158 dev = (char *)FUSE_DEV_TRUNK;
+
159
+
160 if ((fd = open(dev, O_RDWR)) < 0) {
+
161 perror("fuse: failed to open fuse device");
+
162 return -1;
+
163 }
+
164
+
165mount:
+
166 if (getenv("FUSE_NO_MOUNT") || ! mountpoint)
+
167 goto out;
+
168
+
169 pid = fork();
+
170 cpid = pid;
+
171
+
172 if (pid == -1) {
+
173 perror("fuse: fork() failed");
+
174 close(fd);
+
175 return -1;
+
176 }
+
177
+
178 if (pid == 0) {
+
179 pid = fork();
+
180
+
181 if (pid == -1) {
+
182 perror("fuse: fork() failed");
+
183 close(fd);
+
184 _exit(EXIT_FAILURE);
+
185 }
+
186
+
187 if (pid == 0) {
+
188 const char *argv[32];
+
189 int a = 0;
+
190 int ret = -1;
+
191
+
192 if (! fdnam)
+
193 {
+
194 ret = asprintf(&fdnam, "%ld", fd);
+
195 if(ret == -1)
+
196 {
+
197 perror("fuse: failed to assemble mount arguments");
+
198 close(fd);
+
199 _exit(EXIT_FAILURE);
+
200 }
+
201 }
+
202
+
203 argv[a++] = mountprog;
+
204 if (opts) {
+
205 argv[a++] = "-o";
+
206 argv[a++] = opts;
+
207 }
+
208 argv[a++] = fdnam;
+
209 argv[a++] = mountpoint;
+
210 argv[a++] = NULL;
+
211 execvp(mountprog, (char **) argv);
+
212 perror("fuse: failed to exec mount program");
+
213 free(fdnam);
+
214 _exit(EXIT_FAILURE);
+
215 }
+
216
+
217 waitpid(pid, &status, 0);
+
218 if (!WIFEXITED(status))
+
219 _exit(EXIT_FAILURE);
+
220 _exit(WEXITSTATUS(status));
+
221 }
+
222
+
223 if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) {
+
224 perror("fuse: failed to mount file system");
+
225 if (close(fd) < 0)
+
226 perror("fuse: closing FD");
+
227 return -1;
+
228 }
+
229
+
230out:
+
231 return fd;
+
232}
+
233
+
234struct mount_opts *parse_mount_opts(struct fuse_args *args)
+
235{
+
236 struct mount_opts *mo;
+
237
+
238 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
+
239 if (mo == NULL)
+
240 return NULL;
+
241
+
242 memset(mo, 0, sizeof(struct mount_opts));
+
243
+
244 if (args &&
+
245 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+
246 goto err_out;
+
247
+
248 return mo;
+
249
+
250err_out:
+
251 destroy_mount_opts(mo);
+
252 return NULL;
+
253}
+
254
+
255void destroy_mount_opts(struct mount_opts *mo)
+
256{
+
257 free(mo->kernel_opts);
+
258 free(mo);
+
259}
+
260
+
261int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
+
262{
+
263 /* mount util should not try to spawn the daemon */
+
264 setenv("MOUNT_FUSEFS_SAFE", "1", 1);
+
265 /* to notify the mount util it's called from lib */
+
266 setenv("MOUNT_FUSEFS_CALL_BY_LIB", "1", 1);
+
267
+
268 return fuse_mount_core(mountpoint, mo->kernel_opts);
+
269}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
+ + + + diff --git a/doc/html/lib_2mount__util_8c_source.html b/doc/html/lib_2mount__util_8c_source.html new file mode 100644 index 0000000..32e1eea --- /dev/null +++ b/doc/html/lib_2mount__util_8c_source.html @@ -0,0 +1,437 @@ + + + + + + + +libfuse: lib/mount_util.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_util.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Architecture-independent mounting code.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt.
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "mount_util.h"
+
13
+
14#include <stdio.h>
+
15#include <unistd.h>
+
16#include <stdlib.h>
+
17#include <string.h>
+
18#include <signal.h>
+
19#include <dirent.h>
+
20#include <errno.h>
+
21#include <fcntl.h>
+
22#include <limits.h>
+
23#include <paths.h>
+
24#if !defined( __NetBSD__) && !defined(__FreeBSD__) && !defined(__DragonFly__) && !defined(__ANDROID__)
+
25#include <mntent.h>
+
26#else
+
27#define IGNORE_MTAB
+
28#endif
+
29#include <sys/stat.h>
+
30#include <sys/wait.h>
+
31
+
32#include "fuse_mount_compat.h"
+
33
+
34#include <sys/param.h>
+
35
+
36#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
+
37#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0)
+
38#endif
+
39
+
40#ifdef IGNORE_MTAB
+
41#define mtab_needs_update(mnt) 0
+
42#else
+
43static int mtab_needs_update(const char *mnt)
+
44{
+
45 int res;
+
46 struct stat stbuf;
+
47
+
48 /* If mtab is within new mount, don't touch it */
+
49 if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 &&
+
50 _PATH_MOUNTED[strlen(mnt)] == '/')
+
51 return 0;
+
52
+
53 /*
+
54 * Skip mtab update if /etc/mtab:
+
55 *
+
56 * - doesn't exist,
+
57 * - is on a read-only filesystem.
+
58 */
+
59 res = lstat(_PATH_MOUNTED, &stbuf);
+
60 if (res == -1) {
+
61 if (errno == ENOENT)
+
62 return 0;
+
63 } else {
+
64 uid_t ruid;
+
65 int err;
+
66
+
67 ruid = getuid();
+
68 if (ruid != 0)
+
69 setreuid(0, -1);
+
70
+
71 res = access(_PATH_MOUNTED, W_OK);
+
72 err = (res == -1) ? errno : 0;
+
73 if (ruid != 0)
+
74 setreuid(ruid, -1);
+
75
+
76 if (err == EROFS)
+
77 return 0;
+
78
+
79 res = access("/run/mount/utab", F_OK);
+
80 if (res == -1)
+
81 return 0;
+
82 }
+
83
+
84 return 1;
+
85}
+
86#endif /* IGNORE_MTAB */
+
87
+
88static int add_mount(const char *progname, const char *fsname,
+
89 const char *mnt, const char *type, const char *opts)
+
90{
+
91 int res;
+
92 int status;
+
93 sigset_t blockmask;
+
94 sigset_t oldmask;
+
95
+
96 sigemptyset(&blockmask);
+
97 sigaddset(&blockmask, SIGCHLD);
+
98 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
99 if (res == -1) {
+
100 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
101 return -1;
+
102 }
+
103
+
104 res = fork();
+
105 if (res == -1) {
+
106 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
107 goto out_restore;
+
108 }
+
109 if (res == 0) {
+
110 char *env = NULL;
+
111
+
112 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
113
+
114 if(setuid(geteuid()) == -1) {
+
115 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
116 res = -1;
+
117 goto out_restore;
+
118 }
+
119
+
120 execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
+
121 "-f", "-t", type, "-o", opts, fsname, mnt, NULL, &env);
+
122 fprintf(stderr, "%s: failed to execute /bin/mount: %s\n",
+
123 progname, strerror(errno));
+
124 exit(1);
+
125 }
+
126 res = waitpid(res, &status, 0);
+
127 if (res == -1)
+
128 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
129
+
130 if (status != 0)
+
131 res = -1;
+
132
+
133 out_restore:
+
134 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
135
+
136 return res;
+
137}
+
138
+
139int fuse_mnt_add_mount(const char *progname, const char *fsname,
+
140 const char *mnt, const char *type, const char *opts)
+
141{
+
142 if (!mtab_needs_update(mnt))
+
143 return 0;
+
144
+
145 return add_mount(progname, fsname, mnt, type, opts);
+
146}
+
147
+
148static int exec_umount(const char *progname, const char *rel_mnt, int lazy)
+
149{
+
150 int res;
+
151 int status;
+
152 sigset_t blockmask;
+
153 sigset_t oldmask;
+
154
+
155 sigemptyset(&blockmask);
+
156 sigaddset(&blockmask, SIGCHLD);
+
157 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
158 if (res == -1) {
+
159 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
160 return -1;
+
161 }
+
162
+
163 res = fork();
+
164 if (res == -1) {
+
165 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
166 goto out_restore;
+
167 }
+
168 if (res == 0) {
+
169 char *env = NULL;
+
170
+
171 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
172
+
173 if(setuid(geteuid()) == -1) {
+
174 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
175 res = -1;
+
176 goto out_restore;
+
177 }
+
178
+
179 if (lazy) {
+
180 execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+
181 "-l", NULL, &env);
+
182 } else {
+
183 execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+
184 NULL, &env);
+
185 }
+
186 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+
187 progname, strerror(errno));
+
188 exit(1);
+
189 }
+
190 res = waitpid(res, &status, 0);
+
191 if (res == -1)
+
192 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
193
+
194 if (status != 0) {
+
195 res = -1;
+
196 }
+
197
+
198 out_restore:
+
199 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
200 return res;
+
201
+
202}
+
203
+
204int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+
205 const char *rel_mnt, int lazy)
+
206{
+
207 int res;
+
208
+
209 if (!mtab_needs_update(abs_mnt)) {
+
210 res = umount2(rel_mnt, lazy ? 2 : 0);
+
211 if (res == -1)
+
212 fprintf(stderr, "%s: failed to unmount %s: %s\n",
+
213 progname, abs_mnt, strerror(errno));
+
214 return res;
+
215 }
+
216
+
217 return exec_umount(progname, rel_mnt, lazy);
+
218}
+
219
+
220static int remove_mount(const char *progname, const char *mnt)
+
221{
+
222 int res;
+
223 int status;
+
224 sigset_t blockmask;
+
225 sigset_t oldmask;
+
226
+
227 sigemptyset(&blockmask);
+
228 sigaddset(&blockmask, SIGCHLD);
+
229 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
230 if (res == -1) {
+
231 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
232 return -1;
+
233 }
+
234
+
235 res = fork();
+
236 if (res == -1) {
+
237 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
238 goto out_restore;
+
239 }
+
240 if (res == 0) {
+
241 char *env = NULL;
+
242
+
243 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
244
+
245 if(setuid(geteuid()) == -1) {
+
246 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
247 res = -1;
+
248 goto out_restore;
+
249 }
+
250
+
251 execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i",
+
252 "--fake", mnt, NULL, &env);
+
253 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+
254 progname, strerror(errno));
+
255 exit(1);
+
256 }
+
257 res = waitpid(res, &status, 0);
+
258 if (res == -1)
+
259 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
260
+
261 if (status != 0)
+
262 res = -1;
+
263
+
264 out_restore:
+
265 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
266 return res;
+
267}
+
268
+
269int fuse_mnt_remove_mount(const char *progname, const char *mnt)
+
270{
+
271 if (!mtab_needs_update(mnt))
+
272 return 0;
+
273
+
274 return remove_mount(progname, mnt);
+
275}
+
276
+
277char *fuse_mnt_resolve_path(const char *progname, const char *orig)
+
278{
+
279 char buf[PATH_MAX];
+
280 char *copy;
+
281 char *dst;
+
282 char *end;
+
283 char *lastcomp;
+
284 const char *toresolv;
+
285
+
286 if (!orig[0]) {
+
287 fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname,
+
288 orig);
+
289 return NULL;
+
290 }
+
291
+
292 copy = strdup(orig);
+
293 if (copy == NULL) {
+
294 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
295 return NULL;
+
296 }
+
297
+
298 toresolv = copy;
+
299 lastcomp = NULL;
+
300 for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --);
+
301 if (end[0] != '/') {
+
302 char *tmp;
+
303 end[1] = '\0';
+
304 tmp = strrchr(copy, '/');
+
305 if (tmp == NULL) {
+
306 lastcomp = copy;
+
307 toresolv = ".";
+
308 } else {
+
309 lastcomp = tmp + 1;
+
310 if (tmp == copy)
+
311 toresolv = "/";
+
312 }
+
313 if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) {
+
314 lastcomp = NULL;
+
315 toresolv = copy;
+
316 }
+
317 else if (tmp)
+
318 tmp[0] = '\0';
+
319 }
+
320 if (realpath(toresolv, buf) == NULL) {
+
321 fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig,
+
322 strerror(errno));
+
323 free(copy);
+
324 return NULL;
+
325 }
+
326 if (lastcomp == NULL)
+
327 dst = strdup(buf);
+
328 else {
+
329 dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1);
+
330 if (dst) {
+
331 unsigned buflen = strlen(buf);
+
332 if (buflen && buf[buflen-1] == '/')
+
333 sprintf(dst, "%s%s", buf, lastcomp);
+
334 else
+
335 sprintf(dst, "%s/%s", buf, lastcomp);
+
336 }
+
337 }
+
338 free(copy);
+
339 if (dst == NULL)
+
340 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
341 return dst;
+
342}
+
343
+
344int fuse_mnt_check_fuseblk(void)
+
345{
+
346 char buf[256];
+
347 FILE *f = fopen("/proc/filesystems", "r");
+
348 if (!f)
+
349 return 1;
+
350
+
351 while (fgets(buf, sizeof(buf), f))
+
352 if (strstr(buf, "fuseblk\n")) {
+
353 fclose(f);
+
354 return 1;
+
355 }
+
356
+
357 fclose(f);
+
358 return 0;
+
359}
+
360
+
361int fuse_mnt_parse_fuse_fd(const char *mountpoint)
+
362{
+
363 int fd = -1;
+
364 int len = 0;
+
365
+
366 if (mountpoint == NULL) {
+
367 fprintf(stderr, "Invalid null-ptr mount-point!\n");
+
368 return -1;
+
369 }
+
370
+
371 if (sscanf(mountpoint, "/dev/fd/%u%n", &fd, &len) == 1 &&
+
372 len == strlen(mountpoint)) {
+
373 return fd;
+
374 }
+
375
+
376 return -1;
+
377}
+
+ + + + diff --git a/doc/html/lib_2mount__util_8h_source.html b/doc/html/lib_2mount__util_8h_source.html new file mode 100644 index 0000000..0a5d87e --- /dev/null +++ b/doc/html/lib_2mount__util_8h_source.html @@ -0,0 +1,78 @@ + + + + + + + +libfuse: lib/mount_util.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_util.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#include <sys/types.h>
+
10
+
11int fuse_mnt_add_mount(const char *progname, const char *fsname,
+
12 const char *mnt, const char *type, const char *opts);
+
13int fuse_mnt_remove_mount(const char *progname, const char *mnt);
+
14int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+
15 const char *rel_mnt, int lazy);
+
16char *fuse_mnt_resolve_path(const char *progname, const char *orig);
+
17int fuse_mnt_check_fuseblk(void);
+
18int fuse_mnt_parse_fuse_fd(const char *mountpoint);
+
+ + + + diff --git a/doc/html/lib_2usdt_8h_source.html b/doc/html/lib_2usdt_8h_source.html new file mode 100644 index 0000000..a10b041 --- /dev/null +++ b/doc/html/lib_2usdt_8h_source.html @@ -0,0 +1,595 @@ + + + + + + + +libfuse: lib/usdt.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
usdt.h
+
+
+
1/*
+
2 * Copied from https://github.com/libbpf/usdt/
+
3 */
+
4
+
5// SPDX-License-Identifier: BSD-2-Clause
+
6/*
+
7 * This single-header library defines a collection of variadic macros for
+
8 * defining and triggering USDTs (User Statically-Defined Tracepoints):
+
9 *
+
10 * - For USDTs without associated semaphore:
+
11 * USDT(group, name, args...)
+
12 *
+
13 * - For USDTs with implicit (transparent to the user) semaphore:
+
14 * USDT_WITH_SEMA(group, name, args...)
+
15 * USDT_IS_ACTIVE(group, name)
+
16 *
+
17 * - For USDTs with explicit (user-defined and provided) semaphore:
+
18 * USDT_WITH_EXPLICIT_SEMA(sema, group, name, args...)
+
19 * USDT_SEMA_IS_ACTIVE(sema)
+
20 *
+
21 * all of which emit a NOP instruction into the instruction stream, and so
+
22 * have *zero* overhead for the surrounding code. USDTs are identified by
+
23 * a combination of `group` and `name` identifiers, which is used by external
+
24 * tracing tooling (tracers) for identifying exact USDTs of interest.
+
25 *
+
26 * USDTs can have an associated (2-byte) activity counter (USDT semaphore),
+
27 * automatically maintained by Linux kernel whenever any correctly written
+
28 * BPF-based tracer is attached to the USDT. This USDT semaphore can be used
+
29 * to check whether there is a need to do any extra data collection and
+
30 * processing for a given USDT (if necessary), and otherwise avoid extra work
+
31 * for a common case of USDT not being traced ("active").
+
32 *
+
33 * See documentation for USDT_WITH_SEMA()/USDT_IS_ACTIVE() or
+
34 * USDT_WITH_EXPLICIT_SEMA()/USDT_SEMA_IS_ACTIVE() APIs below for details on
+
35 * working with USDTs with implicitly or explicitly associated
+
36 * USDT semaphores, respectively.
+
37 *
+
38 * There is also some additional data recorded into an auxiliary note
+
39 * section. The data in the note section describes the operands, in terms of
+
40 * size and location, used by tracing tooling to know where to find USDT
+
41 * arguments. Each location is encoded as an assembler operand string.
+
42 * Tracing tools (bpftrace and BPF-based tracers, systemtap, etc) insert
+
43 * breakpoints on top of the nop, and decode the location operand-strings,
+
44 * like an assembler, to find the values being passed.
+
45 *
+
46 * The operand strings are selected by the compiler for each operand.
+
47 * They are constrained by inline-assembler codes.The default is:
+
48 *
+
49 * #define USDT_ARG_CONSTRAINT nor
+
50 *
+
51 * This is a good default if the operands tend to be integral and
+
52 * moderate in number (smaller than number of registers). In other
+
53 * cases, the compiler may report "'asm' requires impossible reload" or
+
54 * similar. In this case, consider simplifying the macro call (fewer
+
55 * and simpler operands), reduce optimization, or override the default
+
56 * constraints string via:
+
57 *
+
58 * #define USDT_ARG_CONSTRAINT g
+
59 * #include <usdt.h>
+
60 *
+
61 * For some historical description of USDT v3 format (the one used by this
+
62 * library and generally recognized and assumed by BPF-based tracing tools)
+
63 * see [0]. The more formal specification can be found at [1]. Additional
+
64 * argument constraints information can be found at [2].
+
65 *
+
66 * Original SystemTap's sys/sdt.h implementation ([3]) was used as a base for
+
67 * this USDT library implementation. Current implementation differs *a lot* in
+
68 * terms of exposed user API and general usability, which was the main goal
+
69 * and focus of the reimplementation work. Nevertheless, underlying recorded
+
70 * USDT definitions are fully binary compatible and any USDT-based tooling
+
71 * should work equally well with USDTs defined by either SystemTap's or this
+
72 * library's USDT implementation.
+
73 *
+
74 * [0] https://ecos.sourceware.org/ml/systemtap/2010-q3/msg00145.html
+
75 * [1] https://sourceware.org/systemtap/wiki/UserSpaceProbeImplementation
+
76 * [2] https://gcc.gnu.org/onlinedocs/gcc/Constraints.html
+
77 * [3] https://sourceware.org/git/?p=systemtap.git;a=blob;f=includes/sys/sdt.h
+
78 */
+
79#ifndef __USDT_H
+
80#define __USDT_H
+
81
+
82/*
+
83 * Changelog:
+
84 *
+
85 * 0.1.0
+
86 * -----
+
87 * - Initial release
+
88 */
+
89#define USDT_MAJOR_VERSION 0
+
90#define USDT_MINOR_VERSION 1
+
91#define USDT_PATCH_VERSION 0
+
92
+
93/* C++20 and C23 added __VA_OPT__ as a standard replacement for non-standard `##__VA_ARGS__` extension */
+
94#if (defined(__STDC_VERSION__) && __STDC_VERSION__ > 201710L) || (defined(__cplusplus) && __cplusplus > 201703L)
+
95#define __usdt_va_opt 1
+
96#define __usdt_va_args(...) __VA_OPT__(,) __VA_ARGS__
+
97#else
+
98#define __usdt_va_args(...) , ##__VA_ARGS__
+
99#endif
+
100
+
101/*
+
102 * Trigger USDT with `group`:`name` identifier and pass through `args` as its
+
103 * arguments. Zero arguments are acceptable as well. No USDT semaphore is
+
104 * associated with this USDT.
+
105 *
+
106 * Such "semaphoreless" USDTs are commonly used when there is no extra data
+
107 * collection or processing needed to collect and prepare USDT arguments and
+
108 * they are just available in the surrounding code. USDT() macro will just
+
109 * record their locations in CPU registers or in memory for tracing tooling to
+
110 * be able to access them, if necessary.
+
111 */
+
112#ifdef __usdt_va_opt
+
113#define USDT(group, name, ...) \
+
114 __usdt_probe(group, name, __usdt_sema_none, 0 __VA_OPT__(,) __VA_ARGS__)
+
115#else
+
116#define USDT(group, name, ...) \
+
117 __usdt_probe(group, name, __usdt_sema_none, 0, ##__VA_ARGS__)
+
118#endif
+
119
+
120/*
+
121 * Trigger USDT with `group`:`name` identifier and pass through `args` as its
+
122 * arguments. Zero arguments are acceptable as well. USDT also get an
+
123 * implicitly-defined associated USDT semaphore, which will be "activated" by
+
124 * tracing tooling and can be used to check whether USDT is being actively
+
125 * observed.
+
126 *
+
127 * USDTs with semaphore are commonly used when there is a need to perform
+
128 * additional data collection and processing to prepare USDT arguments, which
+
129 * otherwise might not be necessary for the rest of application logic. In such
+
130 * case, USDT semaphore can be used to avoid unnecessary extra work. If USDT
+
131 * is not traced (which is presumed to be a common situation), the associated
+
132 * USDT semaphore is "inactive", and so there is no need to waste resources to
+
133 * prepare USDT arguments. Use USDT_IS_ACTIVE(group, name) to check whether
+
134 * USDT is "active".
+
135 *
+
136 * N.B. There is an inherent (albeit short) gap between checking whether USDT
+
137 * is active and triggering corresponding USDT, in which external tracer can
+
138 * be attached to an USDT and activate USDT semaphore after the activity check.
+
139 * If such a race occurs, tracers might miss one USDT execution. Tracers are
+
140 * expected to accommodate such possibility and this is expected to not be
+
141 * a problem for applications and tracers.
+
142 *
+
143 * N.B. Implicit USDT semaphore defined by USDT_WITH_SEMA() is contained
+
144 * within a single executable or shared library and is not shared outside
+
145 * them. I.e., if you use USDT_WITH_SEMA() with the same USDT group and name
+
146 * identifier across executable and shared library, it will work and won't
+
147 * conflict, per se, but will define independent USDT semaphores, one for each
+
148 * shared library/executable in which USDT_WITH_SEMA(group, name) is used.
+
149 * That is, if you attach to this USDT in one shared library (or executable),
+
150 * then only USDT semaphore within that shared library (or executable) will be
+
151 * updated by the kernel, while other libraries (or executable) will not see
+
152 * activated USDT semaphore. In short, it's best to use unique USDT group:name
+
153 * identifiers across different shared libraries (and, equivalently, between
+
154 * executable and shared library). This is advanced consideration and is
+
155 * rarely (if ever) seen in practice, but just to avoid surprises this is
+
156 * called out here. (Static libraries become a part of final executable, once
+
157 * linked by linker, so the above considerations don't apply to them.)
+
158 */
+
159#ifdef __usdt_va_opt
+
160#define USDT_WITH_SEMA(group, name, ...) \
+
161 __usdt_probe(group, name, \
+
162 __usdt_sema_implicit, __usdt_sema_name(group, name) \
+
163 __VA_OPT__(,) __VA_ARGS__)
+
164#else
+
165#define USDT_WITH_SEMA(group, name, ...) \
+
166 __usdt_probe(group, name, \
+
167 __usdt_sema_implicit, __usdt_sema_name(group, name), \
+
168 ##__VA_ARGS__)
+
169#endif
+
170
+
171struct usdt_sema { volatile unsigned short active; };
+
172
+
173/*
+
174 * Check if USDT with `group`:`name` identifier is "active" (i.e., whether it
+
175 * is attached to by external tracing tooling and is actively observed).
+
176 *
+
177 * This macro can be used to decide whether any additional and potentially
+
178 * expensive data collection or processing should be done to pass extra
+
179 * information into the given USDT. It is assumed that USDT is triggered with
+
180 * USDT_WITH_SEMA() macro which will implicitly define associated USDT
+
181 * semaphore. (If one needs more control over USDT semaphore, see
+
182 * USDT_DEFINE_SEMA() and USDT_WITH_EXPLICIT_SEMA() macros below.)
+
183 *
+
184 * N.B. Such checks are necessarily racy and speculative. Between checking
+
185 * whether USDT is active and triggering the USDT itself, tracer can be
+
186 * detached with no notification. This race should be extremely rare and worst
+
187 * case should result in one-time wasted extra data collection and processing.
+
188 */
+
189#define USDT_IS_ACTIVE(group, name) ({ \
+
190 extern struct usdt_sema __usdt_sema_name(group, name) \
+
191 __usdt_asm_name(__usdt_sema_name(group, name)); \
+
192 __usdt_sema_implicit(__usdt_sema_name(group, name)); \
+
193 __usdt_sema_name(group, name).active > 0; \
+
194})
+
195
+
196/*
+
197 * APIs for working with user-defined explicit USDT semaphores.
+
198 *
+
199 * This is a less commonly used advanced API for use cases in which user needs
+
200 * an explicit control over (potentially shared across multiple USDTs) USDT
+
201 * semaphore instance. This can be used when there is a group of logically
+
202 * related USDTs that all need extra data collection and processing whenever
+
203 * any of a family of related USDTs are "activated" (i.e., traced). In such
+
204 * a case, all such related USDTs will be associated with the same shared USDT
+
205 * semaphore defined with USDT_DEFINE_SEMA() and the USDTs themselves will be
+
206 * triggered with USDT_WITH_EXPLICIT_SEMA() macros, taking an explicit extra
+
207 * USDT semaphore identifier as an extra parameter.
+
208 */
+
209
+
215#define USDT_SEMA(sema) __usdt_sema_##sema
+
216
+
217/*
+
218 * Define storage for user-defined USDT semaphore `sema`.
+
219 *
+
220 * Should be used only once in non-header source file to let compiler allocate
+
221 * space for the semaphore variable. Just like with any other global variable.
+
222 *
+
223 * This macro can be used anywhere where global variable declaration is
+
224 * allowed. Just like with global variable definitions, there should be only
+
225 * one definition of user-defined USDT semaphore with given `sema` identifier,
+
226 * otherwise compiler or linker will complain about duplicate variable
+
227 * definition.
+
228 *
+
229 * For C++, it is allowed to use USDT_DEFINE_SEMA() both in global namespace
+
230 * and inside namespaces (including nested namespaces). Just make sure that
+
231 * USDT_DECLARE_SEMA() is placed within the namespace where this semaphore is
+
232 * referenced, or any of its parent namespaces, so the C++ language-level
+
233 * identifier is visible to the code that needs to reference the semaphore.
+
234 * At the lowest layer, USDT semaphores have global naming and visibility
+
235 * (they have a corresponding `__usdt_sema_<name>` symbol, which can be linked
+
236 * against from C or C++ code, if necessary). To keep it simple, putting
+
237 * USDT_DECLARE_SEMA() declarations into global namespaces is the simplest
+
238 * no-brainer solution. All these aspects are irrelevant for plain C, because
+
239 * C doesn't have namespaces and everything is always in the global namespace.
+
240 *
+
241 * N.B. Due to USDT metadata being recorded in non-allocatable ELF note
+
242 * section, it has limitations when it comes to relocations, which, in
+
243 * practice, means that it's not possible to correctly share USDT semaphores
+
244 * between main executable and shared libraries, or even between multiple
+
245 * shared libraries. USDT semaphore has to be contained to individual shared
+
246 * library or executable to avoid unpleasant surprises with half-working USDT
+
247 * semaphores. We enforce this by marking semaphore ELF symbols as having
+
248 * a hidden visibility. This is quite an advanced use case and consideration
+
249 * and for most users this should have no consequences whatsoever.
+
250 */
+
251#define USDT_DEFINE_SEMA(sema) \
+
252 struct usdt_sema __usdt_sema_sec USDT_SEMA(sema) \
+
253 __usdt_asm_name(USDT_SEMA(sema)) \
+
254 __attribute__((visibility("hidden"))) = { 0 }
+
255
+
256/*
+
257 * Declare extern reference to user-defined USDT semaphore `sema`.
+
258 *
+
259 * Refers to a variable defined in another compilation unit by
+
260 * USDT_DEFINE_SEMA() and allows to use the same USDT semaphore across
+
261 * multiple compilation units (i.e., .c and .cpp files).
+
262 *
+
263 * See USDT_DEFINE_SEMA() notes above for C++ language usage peculiarities.
+
264 */
+
265#define USDT_DECLARE_SEMA(sema) \
+
266 extern struct usdt_sema USDT_SEMA(sema) __usdt_asm_name(USDT_SEMA(sema))
+
267
+
268/*
+
269 * Check if user-defined USDT semaphore `sema` is "active" (i.e., whether it
+
270 * is attached to by external tracing tooling and is actively observed).
+
271 *
+
272 * This macro can be used to decide whether any additional and potentially
+
273 * expensive data collection or processing should be done to pass extra
+
274 * information into USDT(s) associated with USDT semaphore `sema`.
+
275 *
+
276 * N.B. Such checks are necessarily racy. Between checking the state of USDT
+
277 * semaphore and triggering associated USDT(s), the active tracer might attach
+
278 * or detach. This race should be extremely rare and worst case should result
+
279 * in one-time missed USDT event or wasted extra data collection and
+
280 * processing. USDT-using tracers should be written with this in mind and is
+
281 * not a concern of the application defining USDTs with associated semaphore.
+
282 */
+
283#define USDT_SEMA_IS_ACTIVE(sema) (USDT_SEMA(sema).active > 0)
+
284
+
285/*
+
286 * Invoke USDT specified by `group` and `name` identifiers and associate
+
287 * explicitly user-defined semaphore `sema` with it. Pass through `args` as
+
288 * USDT arguments. `args` are optional and zero arguments are acceptable.
+
289 *
+
290 * Semaphore is defined with the help of USDT_DEFINE_SEMA() macro and can be
+
291 * checked whether active with USDT_SEMA_IS_ACTIVE().
+
292 */
+
293#ifdef __usdt_va_opt
+
294#define USDT_WITH_EXPLICIT_SEMA(sema, group, name, ...) \
+
295 __usdt_probe(group, name, __usdt_sema_explicit, USDT_SEMA(sema), ##__VA_ARGS__)
+
296#else
+
297#define USDT_WITH_EXPLICIT_SEMA(sema, group, name, ...) \
+
298 __usdt_probe(group, name, __usdt_sema_explicit, USDT_SEMA(sema) __VA_OPT__(,) __VA_ARGS__)
+
299#endif
+
300
+
301/*
+
302 * Adjustable implementation aspects
+
303 */
+
304#ifndef USDT_ARG_CONSTRAINT
+
305#if defined __powerpc__
+
306#define USDT_ARG_CONSTRAINT nZr
+
307#elif defined __arm__
+
308#define USDT_ARG_CONSTRAINT g
+
309#elif defined __loongarch__
+
310#define USDT_ARG_CONSTRAINT nmr
+
311#else
+
312#define USDT_ARG_CONSTRAINT nor
+
313#endif
+
314#endif /* USDT_ARG_CONSTRAINT */
+
315
+
316#ifndef USDT_NOP
+
317#if defined(__ia64__) || defined(__s390__) || defined(__s390x__)
+
318#define USDT_NOP nop 0
+
319#else
+
320#define USDT_NOP nop
+
321#endif
+
322#endif /* USDT_NOP */
+
323
+
324/*
+
325 * Implementation details
+
326 */
+
327/* USDT name for implicitly-defined USDT semaphore, derived from group:name */
+
328#define __usdt_sema_name(group, name) __usdt_sema_##group##__##name
+
329/* ELF section into which USDT semaphores are put */
+
330#define __usdt_sema_sec __attribute__((section(".probes")))
+
331
+
332#define __usdt_concat(a, b) a ## b
+
333#define __usdt_apply(fn, n) __usdt_concat(fn, n)
+
334
+
335#ifndef __usdt_nth
+
336#define __usdt_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, N, ...) N
+
337#endif
+
338
+
339#ifndef __usdt_narg
+
340#ifdef __usdt_va_opt
+
341#define __usdt_narg(...) __usdt_nth(_ __VA_OPT__(,) __VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
+
342#else
+
343#define __usdt_narg(...) __usdt_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
+
344#endif
+
345#endif /* __usdt_narg */
+
346
+
347#define __usdt_hash #
+
348#define __usdt_str_(x) #x
+
349#define __usdt_str(x) __usdt_str_(x)
+
350
+
351#ifndef __usdt_asm_name
+
352#define __usdt_asm_name(name) __asm__(__usdt_str(name))
+
353#endif
+
354
+
355#define __usdt_asm1(a) __usdt_str(a) "\n"
+
356#define __usdt_asm2(a,b) __usdt_str(a) "," __usdt_str(b) "\n"
+
357#define __usdt_asm3(a,b,c) __usdt_str(a) "," __usdt_str(b) "," __usdt_str(c) "\n"
+
358#define __usdt_asm5(a,b,c,d,e) __usdt_str(a) "," __usdt_str(b) "," __usdt_str(c) "," \
+
359 __usdt_str(d) "," __usdt_str(e) "\n"
+
360
+
361#ifdef __LP64__
+
362#define __usdt_asm_addr .8byte
+
363#else
+
364#define __usdt_asm_addr .4byte
+
365#endif
+
366
+
367#define __usdt_asm_strz_(x) __usdt_asm1(.asciz #x)
+
368#define __usdt_asm_strz(x) __usdt_asm_strz_(x)
+
369#define __usdt_asm_str_(x) __usdt_asm1(.ascii #x)
+
370#define __usdt_asm_str(x) __usdt_asm_str_(x)
+
371
+
372/* "semaphoreless" USDT case */
+
373#ifndef __usdt_sema_none
+
374#define __usdt_sema_none(sema)
+
375#endif
+
376
+
377/* implicitly defined __usdt_sema__group__name semaphore (using weak symbols) */
+
378#ifndef __usdt_sema_implicit
+
379#define __usdt_sema_implicit(sema) \
+
380 __asm__ __volatile__ ( \
+
381 __usdt_asm1(.ifndef sema) \
+
382 __usdt_asm3( .pushsection .probes, "aw", "progbits") \
+
383 __usdt_asm1( .weak sema) \
+
384 __usdt_asm1( .hidden sema) \
+
385 __usdt_asm1( .align 2) \
+
386 __usdt_asm1(sema:) \
+
387 __usdt_asm1( .zero 2) \
+
388 __usdt_asm2( .type sema, @object) \
+
389 __usdt_asm2( .size sema, 2) \
+
390 __usdt_asm1( .popsection) \
+
391 __usdt_asm1(.endif) \
+
392 );
+
393#endif
+
394
+
395/* externally defined semaphore using USDT_DEFINE_SEMA() and passed explicitly by user */
+
396#ifndef __usdt_sema_explicit
+
397#define __usdt_sema_explicit(sema) \
+
398 __asm__ __volatile__ ("" :: "m" (sema));
+
399#endif
+
400
+
401/* main USDT definition (nop and .note.stapsdt metadata) */
+
402#define __usdt_probe(group, name, sema_def, sema, ...) do { \
+
403 sema_def(sema) \
+
404 __asm__ __volatile__ ( \
+
405 __usdt_asm1(990: USDT_NOP) \
+
406 __usdt_asm3( .pushsection .note.stapsdt, "", "note") \
+
407 __usdt_asm1( .balign 4) \
+
408 __usdt_asm3( .4byte 992f-991f,994f-993f,3) \
+
409 __usdt_asm1(991: .asciz "stapsdt") \
+
410 __usdt_asm1(992: .balign 4) \
+
411 __usdt_asm1(993: __usdt_asm_addr 990b) \
+
412 __usdt_asm1( __usdt_asm_addr _.stapsdt.base) \
+
413 __usdt_asm1( __usdt_asm_addr sema) \
+
414 __usdt_asm_strz(group) \
+
415 __usdt_asm_strz(name) \
+
416 __usdt_asm_args(__VA_ARGS__) \
+
417 __usdt_asm1( .ascii "\0") \
+
418 __usdt_asm1(994: .balign 4) \
+
419 __usdt_asm1( .popsection) \
+
420 __usdt_asm1(.ifndef _.stapsdt.base) \
+
421 __usdt_asm5( .pushsection .stapsdt.base,"aG","progbits",.stapsdt.base,comdat)\
+
422 __usdt_asm1( .weak _.stapsdt.base) \
+
423 __usdt_asm1( .hidden _.stapsdt.base) \
+
424 __usdt_asm1(_.stapsdt.base:) \
+
425 __usdt_asm1( .space 1) \
+
426 __usdt_asm2( .size _.stapsdt.base, 1) \
+
427 __usdt_asm1( .popsection) \
+
428 __usdt_asm1(.endif) \
+
429 :: __usdt_asm_ops(__VA_ARGS__) \
+
430 ); \
+
431} while (0)
+
432
+
433/*
+
434 * NB: gdb PR24541 highlighted an unspecified corner of the sdt.h
+
435 * operand note format.
+
436 *
+
437 * The named register may be a longer or shorter (!) alias for the
+
438 * storage where the value in question is found. For example, on
+
439 * i386, 64-bit value may be put in register pairs, and a register
+
440 * name stored would identify just one of them. Previously, gcc was
+
441 * asked to emit the %w[id] (16-bit alias of some registers holding
+
442 * operands), even when a wider 32-bit value was used.
+
443 *
+
444 * Bottom line: the byte-width given before the @ sign governs. If
+
445 * there is a mismatch between that width and that of the named
+
446 * register, then a sys/sdt.h note consumer may need to employ
+
447 * architecture-specific heuristics to figure out where the compiler
+
448 * has actually put the complete value.
+
449 */
+
450#if defined(__powerpc__) || defined(__powerpc64__)
+
451#define __usdt_argref(id) %I[id]%[id]
+
452#elif defined(__i386__)
+
453#define __usdt_argref(id) %k[id] /* gcc.gnu.org/PR80115 sourceware.org/PR24541 */
+
454#else
+
455#define __usdt_argref(id) %[id]
+
456#endif
+
457
+
458#define __usdt_asm_arg(n) __usdt_asm_str(%c[__usdt_asz##n]) \
+
459 __usdt_asm1(.ascii "@") \
+
460 __usdt_asm_str(__usdt_argref(__usdt_aval##n))
+
461
+
462#define __usdt_asm_args0 /* no arguments */
+
463#define __usdt_asm_args1 __usdt_asm_arg(1)
+
464#define __usdt_asm_args2 __usdt_asm_args1 __usdt_asm1(.ascii " ") __usdt_asm_arg(2)
+
465#define __usdt_asm_args3 __usdt_asm_args2 __usdt_asm1(.ascii " ") __usdt_asm_arg(3)
+
466#define __usdt_asm_args4 __usdt_asm_args3 __usdt_asm1(.ascii " ") __usdt_asm_arg(4)
+
467#define __usdt_asm_args5 __usdt_asm_args4 __usdt_asm1(.ascii " ") __usdt_asm_arg(5)
+
468#define __usdt_asm_args6 __usdt_asm_args5 __usdt_asm1(.ascii " ") __usdt_asm_arg(6)
+
469#define __usdt_asm_args7 __usdt_asm_args6 __usdt_asm1(.ascii " ") __usdt_asm_arg(7)
+
470#define __usdt_asm_args8 __usdt_asm_args7 __usdt_asm1(.ascii " ") __usdt_asm_arg(8)
+
471#define __usdt_asm_args9 __usdt_asm_args8 __usdt_asm1(.ascii " ") __usdt_asm_arg(9)
+
472#define __usdt_asm_args10 __usdt_asm_args9 __usdt_asm1(.ascii " ") __usdt_asm_arg(10)
+
473#define __usdt_asm_args11 __usdt_asm_args10 __usdt_asm1(.ascii " ") __usdt_asm_arg(11)
+
474#define __usdt_asm_args12 __usdt_asm_args11 __usdt_asm1(.ascii " ") __usdt_asm_arg(12)
+
475#define __usdt_asm_args(...) __usdt_apply(__usdt_asm_args, __usdt_narg(__VA_ARGS__))
+
476
+
477#define __usdt_is_arr(x) (__builtin_classify_type(x) == 14 || __builtin_classify_type(x) == 5)
+
478#define __usdt_arg_size(x) (__usdt_is_arr(x) ? sizeof(void *) : sizeof(x))
+
479
+
480/*
+
481 * We can't use __builtin_choose_expr() in C++, so fall back to table-based
+
482 * signedness determination for known types, utilizing templates magic.
+
483 */
+
484#ifdef __cplusplus
+
485
+
486#define __usdt_is_signed(x) (!__usdt_is_arr(x) && __usdt_t<__typeof(x)>::is_signed)
+
487
+
488#include <cstddef>
+
489
+
490template<typename T> struct __usdt_t { static const bool is_signed = false; };
+
491template<typename A> struct __usdt_t<A[]> : public __usdt_t<A *> {};
+
492template<typename A, size_t N> struct __usdt_t<A[N]> : public __usdt_t<A *> {};
+
493
+
494#define __usdt_def_signed(T) \
+
495template<> struct __usdt_t<T> { static const bool is_signed = true; }; \
+
496template<> struct __usdt_t<const T> { static const bool is_signed = true; }; \
+
497template<> struct __usdt_t<volatile T> { static const bool is_signed = true; }; \
+
498template<> struct __usdt_t<const volatile T> { static const bool is_signed = true; }
+
499#define __usdt_maybe_signed(T) \
+
500template<> struct __usdt_t<T> { static const bool is_signed = (T)-1 < (T)1; }; \
+
501template<> struct __usdt_t<const T> { static const bool is_signed = (T)-1 < (T)1; }; \
+
502template<> struct __usdt_t<volatile T> { static const bool is_signed = (T)-1 < (T)1; }; \
+
503template<> struct __usdt_t<const volatile T> { static const bool is_signed = (T)-1 < (T)1; }
+
504
+
505__usdt_def_signed(signed char);
+
506__usdt_def_signed(short);
+
507__usdt_def_signed(int);
+
508__usdt_def_signed(long);
+
509__usdt_def_signed(long long);
+
510__usdt_maybe_signed(char);
+
511__usdt_maybe_signed(wchar_t);
+
512
+
513#else /* !__cplusplus */
+
514
+
515#define __usdt_is_inttype(x) (__builtin_classify_type(x) >= 1 && __builtin_classify_type(x) <= 4)
+
516#define __usdt_inttype(x) __typeof(__builtin_choose_expr(__usdt_is_inttype(x), (x), 0U))
+
517#define __usdt_is_signed(x) ((__usdt_inttype(x))-1 < (__usdt_inttype(x))1)
+
518
+
519#endif /* __cplusplus */
+
520
+
521#define __usdt_asm_op(n, x) \
+
522 [__usdt_asz##n] "n" ((__usdt_is_signed(x) ? (int)-1 : 1) * (int)__usdt_arg_size(x)), \
+
523 [__usdt_aval##n] __usdt_str(USDT_ARG_CONSTRAINT)(x)
+
524
+
525#define __usdt_asm_ops0() [__usdt_dummy] "g" (0)
+
526#define __usdt_asm_ops1(x) __usdt_asm_op(1, x)
+
527#define __usdt_asm_ops2(a,x) __usdt_asm_ops1(a), __usdt_asm_op(2, x)
+
528#define __usdt_asm_ops3(a,b,x) __usdt_asm_ops2(a,b), __usdt_asm_op(3, x)
+
529#define __usdt_asm_ops4(a,b,c,x) __usdt_asm_ops3(a,b,c), __usdt_asm_op(4, x)
+
530#define __usdt_asm_ops5(a,b,c,d,x) __usdt_asm_ops4(a,b,c,d), __usdt_asm_op(5, x)
+
531#define __usdt_asm_ops6(a,b,c,d,e,x) __usdt_asm_ops5(a,b,c,d,e), __usdt_asm_op(6, x)
+
532#define __usdt_asm_ops7(a,b,c,d,e,f,x) __usdt_asm_ops6(a,b,c,d,e,f), __usdt_asm_op(7, x)
+
533#define __usdt_asm_ops8(a,b,c,d,e,f,g,x) __usdt_asm_ops7(a,b,c,d,e,f,g), __usdt_asm_op(8, x)
+
534#define __usdt_asm_ops9(a,b,c,d,e,f,g,h,x) __usdt_asm_ops8(a,b,c,d,e,f,g,h), __usdt_asm_op(9, x)
+
535#define __usdt_asm_ops10(a,b,c,d,e,f,g,h,i,x) __usdt_asm_ops9(a,b,c,d,e,f,g,h,i), __usdt_asm_op(10, x)
+
536#define __usdt_asm_ops11(a,b,c,d,e,f,g,h,i,j,x) __usdt_asm_ops10(a,b,c,d,e,f,g,h,i,j), __usdt_asm_op(11, x)
+
537#define __usdt_asm_ops12(a,b,c,d,e,f,g,h,i,j,k,x) __usdt_asm_ops11(a,b,c,d,e,f,g,h,i,j,k), __usdt_asm_op(12, x)
+
538#define __usdt_asm_ops(...) __usdt_apply(__usdt_asm_ops, __usdt_narg(__VA_ARGS__))(__VA_ARGS__)
+
539
+
540#endif /* __USDT_H */
+
+ + + + diff --git a/doc/html/lib_2util_8c_source.html b/doc/html/lib_2util_8c_source.html new file mode 100644 index 0000000..0251f22 --- /dev/null +++ b/doc/html/lib_2util_8c_source.html @@ -0,0 +1,113 @@ + + + + + + + +libfuse: lib/util.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
util.c
+
+
+
1
+
2#include "fuse_config.h"
+
3
+
4#ifdef HAVE_PTHREAD_SETNAME_NP
+
5#define _GNU_SOURCE
+
6#include <pthread.h>
+
7#endif
+
8
+
9#include <errno.h>
+
10#include <stdlib.h>
+
11#include <errno.h>
+
12
+
13#ifndef FUSE_USE_VERSION
+
14#define FUSE_USE_VERSION (FUSE_MAKE_VERSION(3, 18))
+
15#endif
+
16
+
17#include "util.h"
+
18#include "fuse_log.h"
+
19#include "fuse_lowlevel.h"
+
20#include <stdio.h>
+
21
+
22int libfuse_strtol(const char *str, long *res)
+
23{
+
24 char *endptr;
+
25 int base = 10;
+
26 long val;
+
27
+
28 errno = 0;
+
29
+
30 if (!str)
+
31 return -EINVAL;
+
32
+
33 val = strtol(str, &endptr, base);
+
34
+
35 if (errno)
+
36 return -errno;
+
37
+
38 if (endptr == str || *endptr != '\0')
+
39 return -EINVAL;
+
40
+
41 *res = val;
+
42 return 0;
+
43}
+
44
+
45void fuse_set_thread_name(const char *name)
+
46{
+
47#ifdef HAVE_PTHREAD_SETNAME_NP
+
48 pthread_setname_np(pthread_self(), name);
+
49#else
+
50 (void)name;
+
51#endif
+
52}
+
53
+
+ + + + diff --git a/doc/html/lib_2util_8h_source.html b/doc/html/lib_2util_8h_source.html new file mode 100644 index 0000000..94efb0b --- /dev/null +++ b/doc/html/lib_2util_8h_source.html @@ -0,0 +1,104 @@ + + + + + + + +libfuse: lib/util.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
util.h
+
+
+
1#ifndef FUSE_UTIL_H_
+
2#define FUSE_UTIL_H_
+
3
+
4#include <stdint.h>
+
5#include <stdbool.h>
+
6
+
7#define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1))
+
8
+
9#define likely(x) __builtin_expect(!!(x), 1)
+
10#define unlikely(x) __builtin_expect(!!(x), 0)
+
11
+
12struct fuse_conn_info;
+
13
+
14int libfuse_strtol(const char *str, long *res);
+
15void fuse_set_thread_name(const char *name);
+
16
+
20static inline uint32_t fuse_lower_32_bits(uint64_t nr)
+
21{
+
22 return (uint32_t)(nr & 0xffffffff);
+
23}
+
24
+
28static inline uint64_t fuse_higher_32_bits(uint64_t nr)
+
29{
+
30 return nr & ~0xffffffffULL;
+
31}
+
32
+
33#ifndef FUSE_VAR_UNUSED
+
34#define FUSE_VAR_UNUSED __attribute__((__unused__))
+
35#endif
+
36
+
37#define container_of(ptr, type, member) \
+
38 ({ \
+
39 unsigned long __mptr = (unsigned long)(ptr); \
+
40 ((type *)(__mptr - offsetof(type, member))); \
+
41 })
+
42
+
43#if __has_attribute(__fallthrough__)
+
44#define fallthrough __attribute__((__fallthrough__))
+
45#else
+
46#define fallthrough do {} while (0)
+
47#endif
+
48
+
49#endif /* FUSE_UTIL_H_ */
+ +
+ + + + diff --git a/doc/html/menu.js b/doc/html/menu.js new file mode 100644 index 0000000..b0b2693 --- /dev/null +++ b/doc/html/menu.js @@ -0,0 +1,136 @@ +/* + @licstart The following is the entire license notice for the JavaScript code in this file. + + The MIT License (MIT) + + Copyright (C) 1997-2020 by Dimitri van Heesch + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software + and associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, + sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or + substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + @licend The above is the entire license notice for the JavaScript code in this file + */ +function initMenu(relPath,searchEnabled,serverSide,searchPage,search) { + function makeTree(data,relPath) { + var result=''; + if ('children' in data) { + result+='
    '; + for (var i in data.children) { + var url; + var link; + link = data.children[i].url; + if (link.substring(0,1)=='^') { + url = link.substring(1); + } else { + url = relPath+link; + } + result+='
  • '+ + data.children[i].text+''+ + makeTree(data.children[i],relPath)+'
  • '; + } + result+='
'; + } + return result; + } + var searchBoxHtml; + if (searchEnabled) { + if (serverSide) { + searchBoxHtml='
'+ + '
'+ + '
 '+ + ''+ + '
'+ + '
'+ + '
'+ + '
'; + } else { + searchBoxHtml='
'+ + ''+ + ' '+ + ''+ + ''+ + ''+ + ''+ + ''+ + '
'; + } + } + + $('#main-nav').before('
'+ + ''+ + ''+ + '
'); + $('#main-nav').append(makeTree(menudata,relPath)); + $('#main-nav').children(':first').addClass('sm sm-dox').attr('id','main-menu'); + if (searchBoxHtml) { + $('#main-menu').append('
  • '); + } + var $mainMenuState = $('#main-menu-state'); + var prevWidth = 0; + if ($mainMenuState.length) { + function initResizableIfExists() { + if (typeof initResizable==='function') initResizable(); + } + // animate mobile menu + $mainMenuState.change(function(e) { + var $menu = $('#main-menu'); + var options = { duration: 250, step: initResizableIfExists }; + if (this.checked) { + options['complete'] = function() { $menu.css('display', 'block') }; + $menu.hide().slideDown(options); + } else { + options['complete'] = function() { $menu.css('display', 'none') }; + $menu.show().slideUp(options); + } + }); + // set default menu visibility + function resetState() { + var $menu = $('#main-menu'); + var $mainMenuState = $('#main-menu-state'); + var newWidth = $(window).outerWidth(); + if (newWidth!=prevWidth) { + if ($(window).outerWidth()<768) { + $mainMenuState.prop('checked',false); $menu.hide(); + $('#searchBoxPos1').html(searchBoxHtml); + $('#searchBoxPos2').hide(); + } else { + $menu.show(); + $('#searchBoxPos1').empty(); + $('#searchBoxPos2').html(searchBoxHtml); + $('#searchBoxPos2').show(); + } + if (typeof searchBox!=='undefined') { + searchBox.CloseResultsWindow(); + } + prevWidth = newWidth; + } + } + $(window).ready(function() { resetState(); initResizableIfExists(); }); + $(window).resize(resetState); + } + $('#main-menu').smartmenus(); +} +/* @license-end */ diff --git a/doc/html/menudata.js b/doc/html/menudata.js new file mode 100644 index 0000000..2341ca8 --- /dev/null +++ b/doc/html/menudata.js @@ -0,0 +1,85 @@ +/* + @licstart The following is the entire license notice for the JavaScript code in this file. + + The MIT License (MIT) + + Copyright (C) 1997-2020 by Dimitri van Heesch + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software + and associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, + sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or + substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + @licend The above is the entire license notice for the JavaScript code in this file +*/ +var menudata={children:[ +{text:"Main Page",url:"index.html"}, +{text:"Data Structures",url:"annotated.html",children:[ +{text:"Data Structures",url:"annotated.html"}, +{text:"Data Fields",url:"functions.html",children:[ +{text:"All",url:"functions.html",children:[ +{text:"a",url:"functions.html#index_a"}, +{text:"b",url:"functions.html#index_b"}, +{text:"c",url:"functions.html#index_c"}, +{text:"d",url:"functions.html#index_d"}, +{text:"e",url:"functions.html#index_e"}, +{text:"f",url:"functions.html#index_f"}, +{text:"g",url:"functions.html#index_g"}, +{text:"h",url:"functions.html#index_h"}, +{text:"i",url:"functions.html#index_i"}, +{text:"k",url:"functions.html#index_k"}, +{text:"l",url:"functions.html#index_l"}, +{text:"m",url:"functions.html#index_m"}, +{text:"n",url:"functions.html#index_n"}, +{text:"o",url:"functions.html#index_o"}, +{text:"p",url:"functions.html#index_p"}, +{text:"r",url:"functions.html#index_r"}, +{text:"s",url:"functions.html#index_s"}, +{text:"t",url:"functions.html#index_t"}, +{text:"u",url:"functions.html#index_u"}, +{text:"v",url:"functions.html#index_v"}, +{text:"w",url:"functions.html#index_w"}]}, +{text:"Variables",url:"functions_vars.html",children:[ +{text:"a",url:"functions_vars.html#index_a"}, +{text:"b",url:"functions_vars.html#index_b"}, +{text:"c",url:"functions_vars.html#index_c"}, +{text:"d",url:"functions_vars.html#index_d"}, +{text:"e",url:"functions_vars.html#index_e"}, +{text:"f",url:"functions_vars.html#index_f"}, +{text:"g",url:"functions_vars.html#index_g"}, +{text:"h",url:"functions_vars.html#index_h"}, +{text:"i",url:"functions_vars.html#index_i"}, +{text:"k",url:"functions_vars.html#index_k"}, +{text:"l",url:"functions_vars.html#index_l"}, +{text:"m",url:"functions_vars.html#index_m"}, +{text:"n",url:"functions_vars.html#index_n"}, +{text:"o",url:"functions_vars.html#index_o"}, +{text:"p",url:"functions_vars.html#index_p"}, +{text:"r",url:"functions_vars.html#index_r"}, +{text:"s",url:"functions_vars.html#index_s"}, +{text:"t",url:"functions_vars.html#index_t"}, +{text:"u",url:"functions_vars.html#index_u"}, +{text:"v",url:"functions_vars.html#index_v"}, +{text:"w",url:"functions_vars.html#index_w"}]}]}]}, +{text:"Files",url:"files.html",children:[ +{text:"File List",url:"files.html"}, +{text:"Globals",url:"globals.html",children:[ +{text:"All",url:"globals.html",children:[ +{text:"f",url:"globals.html#index_f"}]}, +{text:"Functions",url:"globals_func.html",children:[ +{text:"f",url:"globals_func.html#index_f"}]}, +{text:"Typedefs",url:"globals_type.html"}, +{text:"Enumerations",url:"globals_enum.html"}, +{text:"Enumerator",url:"globals_eval.html"}, +{text:"Macros",url:"globals_defs.html",children:[ +{text:"f",url:"globals_defs.html#index_f"}]}]}]}]} diff --git a/doc/html/minus.svg b/doc/html/minus.svg new file mode 100644 index 0000000..f70d0c1 --- /dev/null +++ b/doc/html/minus.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/doc/html/minusd.svg b/doc/html/minusd.svg new file mode 100644 index 0000000..5f8e879 --- /dev/null +++ b/doc/html/minusd.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/doc/html/nav_f.png b/doc/html/nav_f.png new file mode 100644 index 0000000000000000000000000000000000000000..72a58a529ed3a9ed6aa0c51a79cf207e026deee2 GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^j6iI`!2~2XGqLUlQVE_ejv*C{Z|{2ZH7M}7UYxc) zn!W8uqtnIQ>_z8U literal 0 HcmV?d00001 diff --git a/doc/html/nav_fd.png b/doc/html/nav_fd.png new file mode 100644 index 0000000000000000000000000000000000000000..032fbdd4c54f54fa9a2e6423b94ef4b2ebdfaceb GIT binary patch literal 169 zcmeAS@N?(olHy`uVBq!ia0vp^j6iI`!2~2XGqLUlQU#tajv*C{Z|C~*H7f|XvG1G8 zt7aS*L7xwMeS}!z6R#{C5tIw-s~AJ==F^i}x3XyJseHR@yF& zerFf(Zf;Dd{+(0lDIROL@Sj-Ju2JQ8&-n%4%q?>|^bShc&lR?}7HeMo@BDl5N(aHY Uj$gdr1MOz;boFyt=akR{0D!zeaR2}S literal 0 HcmV?d00001 diff --git a/doc/html/nav_g.png b/doc/html/nav_g.png new file mode 100644 index 0000000000000000000000000000000000000000..2093a237a94f6c83e19ec6e5fd42f7ddabdafa81 GIT binary patch literal 95 zcmeAS@N?(olHy`uVBq!ia0vp^j6lrB!3HFm1ilyoDK$?Q$B+ufw|5PB85lU25BhtE tr?otc=hd~V+ws&_A@j8Fiv!KF$B+ufw|5=67#uj90@pIL wZ=Q8~_Ju`#59=RjDrmm`tMD@M=!-l18IR?&vFVdQ&MBb@0HFXL6W-eg#Jd_@e6*DPn)w;=|1H}Zvm9l6xXXB%>yL=NQU;mg M>FVdQ&MBb@0Bdt1Qvd(} literal 0 HcmV?d00001 diff --git a/doc/html/open.png b/doc/html/open.png new file mode 100644 index 0000000000000000000000000000000000000000..30f75c7efe2dd0c9e956e35b69777a02751f048b GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{VPM$7~Ar*{o?;hlAFyLXmaDC0y znK1_#cQqJWPES%4Uujug^TE?jMft$}Eq^WaR~)%f)vSNs&gek&x%A9X9sM + + + + + + + + diff --git a/doc/html/plusd.svg b/doc/html/plusd.svg new file mode 100644 index 0000000..0c65bfe --- /dev/null +++ b/doc/html/plusd.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/doc/html/splitbar.png b/doc/html/splitbar.png new file mode 100644 index 0000000000000000000000000000000000000000..fe895f2c58179b471a22d8320b39a4bd7312ec8e GIT binary patch literal 314 zcmeAS@N?(olHy`uVBq!ia0vp^Yzz!63>-{AmhX=Jf(#6djGiuzAr*{o?=JLmPLyc> z_*`QK&+BH@jWrYJ7>r6%keRM@)Qyv8R=enp0jiI>aWlGyB58O zFVR20d+y`K7vDw(hJF3;>dD*3-?v=<8M)@x|EEGLnJsniYK!2U1 Y!`|5biEc?d1`HDhPgg&ebxsLQ02F6;9RL6T literal 0 HcmV?d00001 diff --git a/doc/html/splitbard.png b/doc/html/splitbard.png new file mode 100644 index 0000000000000000000000000000000000000000..8367416d757fd7b6dc4272b6432dc75a75abd068 GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^Yzz!63>-{AmhX=Jf@VhhFKy35^fiT zT~&lUj3=cDh^%3HDY9k5CEku}PHXNoNC(_$U3XPb&Q*ME25pT;2(*BOgAf<+R$lzakPG`kF31()Fx{L5Wrac|GQzjeE= zueY1`Ze{#x<8=S|`~MgGetGce)#vN&|J{Cd^tS%;tBYTo?+^d68<#n_Y_xx`J||4O V@QB{^CqU0Kc)I$ztaD0e0svEzbJzd? literal 0 HcmV?d00001 diff --git a/doc/html/structfuse__args.html b/doc/html/structfuse__args.html new file mode 100644 index 0000000..d0c319f --- /dev/null +++ b/doc/html/structfuse__args.html @@ -0,0 +1,126 @@ + + + + + + + +libfuse: fuse_args Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_args Struct Reference
    +
    +
    + +

    #include <fuse_opt.h>

    + + + + + + + + +

    +Data Fields

    int argc
     
    char ** argv
     
    int allocated
     
    +

    Detailed Description

    +

    Argument list

    + +

    Definition at line 109 of file fuse_opt.h.

    +

    Field Documentation

    + +

    ◆ allocated

    + +
    +
    + + + + +
    int fuse_args::allocated
    +
    +

    Is 'argv' allocated?

    + +

    Definition at line 117 of file fuse_opt.h.

    + +
    +
    + +

    ◆ argc

    + +
    +
    + + + + +
    int fuse_args::argc
    +
    +

    Argument count

    + +

    Definition at line 111 of file fuse_opt.h.

    + +
    +
    + +

    ◆ argv

    + +
    +
    + + + + +
    char ** fuse_args::argv
    +
    +

    Argument vector. NULL terminated

    + +

    Definition at line 114 of file fuse_opt.h.

    + +
    +
    +
    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__buf.html b/doc/html/structfuse__buf.html new file mode 100644 index 0000000..6d206f6 --- /dev/null +++ b/doc/html/structfuse__buf.html @@ -0,0 +1,188 @@ + + + + + + + +libfuse: fuse_buf Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_buf Struct Reference
    +
    +
    + +

    #include <fuse_common.h>

    + + + + + + + + + + + + + + +

    +Data Fields

    size_t size
     
    enum fuse_buf_flags flags
     
    void * mem
     
    int fd
     
    off_t pos
     
    size_t mem_size
     
    +

    Detailed Description

    +

    Single data buffer

    +

    Generic data buffer for I/O, extended attributes, etc... Data may be supplied as a memory pointer or as a file descriptor

    + +

    Definition at line 874 of file fuse_common.h.

    +

    Field Documentation

    + +

    ◆ fd

    + +
    +
    + + + + +
    int fuse_buf::fd
    +
    +

    File descriptor

    +

    Used if FUSE_BUF_IS_FD flag is set.

    + +

    Definition at line 897 of file fuse_common.h.

    + +
    +
    + +

    ◆ flags

    + +
    +
    + + + + +
    enum fuse_buf_flags fuse_buf::flags
    +
    +

    Buffer flags

    + +

    Definition at line 883 of file fuse_common.h.

    + +
    +
    + +

    ◆ mem

    + +
    +
    + + + + +
    void * fuse_buf::mem
    +
    +

    Memory pointer

    +

    Used unless FUSE_BUF_IS_FD flag is set.

    + +

    Definition at line 890 of file fuse_common.h.

    + +
    +
    + +

    ◆ mem_size

    + +
    +
    + + + + +
    size_t fuse_buf::mem_size
    +
    +

    Size of memory pointer

    +

    Used only if mem was internally allocated. Not used if mem was user-provided.

    + +

    Definition at line 912 of file fuse_common.h.

    + +
    +
    + +

    ◆ pos

    + +
    +
    + + + + +
    off_t fuse_buf::pos
    +
    +

    File position

    +

    Used if FUSE_BUF_FD_SEEK flag is set.

    + +

    Definition at line 904 of file fuse_common.h.

    + +
    +
    + +

    ◆ size

    + +
    +
    + + + + +
    size_t fuse_buf::size
    +
    +

    Size of data in bytes

    + +

    Definition at line 878 of file fuse_common.h.

    + +
    +
    +
    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__bufvec.html b/doc/html/structfuse__bufvec.html new file mode 100644 index 0000000..c64fddb --- /dev/null +++ b/doc/html/structfuse__bufvec.html @@ -0,0 +1,147 @@ + + + + + + + +libfuse: fuse_bufvec Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_bufvec Struct Reference
    +
    +
    + +

    #include <fuse_common.h>

    + + + + + + + + + + +

    +Data Fields

    size_t count
     
    size_t idx
     
    size_t off
     
    struct fuse_buf buf [1]
     
    +

    Detailed Description

    +

    Data buffer vector

    +

    An array of data buffers, each containing a memory pointer or a file descriptor.

    +

    Allocate dynamically to add more than one buffer.

    + +

    Definition at line 923 of file fuse_common.h.

    +

    Field Documentation

    + +

    ◆ buf

    + +
    +
    + + + + +
    struct fuse_buf fuse_bufvec::buf
    +
    +

    Array of buffers

    + +

    Definition at line 942 of file fuse_common.h.

    + +
    +
    + +

    ◆ count

    + +
    +
    + + + + +
    size_t fuse_bufvec::count
    +
    +

    Number of buffers in the array

    + +

    Definition at line 927 of file fuse_common.h.

    + +
    +
    + +

    ◆ idx

    + +
    +
    + + + + +
    size_t fuse_bufvec::idx
    +
    +

    Index of current buffer within the array

    + +

    Definition at line 932 of file fuse_common.h.

    + +
    +
    + +

    ◆ off

    + +
    +
    + + + + +
    size_t fuse_bufvec::off
    +
    +

    Current offset within the current buffer

    + +

    Definition at line 937 of file fuse_common.h.

    + +
    +
    +
    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__cmdline__opts.html b/doc/html/structfuse__cmdline__opts.html new file mode 100644 index 0000000..7ce57f8 --- /dev/null +++ b/doc/html/structfuse__cmdline__opts.html @@ -0,0 +1,62 @@ + + + + + + + +libfuse: fuse_cmdline_opts Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    +
    fuse_cmdline_opts Struct Reference
    +
    +
    + +

    #include <fuse_lowlevel.h>

    +

    Detailed Description

    +

    Note: Any addition to this struct needs to create a compatibility symbol for fuse_parse_cmdline(). For ABI compatibility reasons it is also not possible to remove struct members.

    + +

    Definition at line 2003 of file fuse_lowlevel.h.

    +

    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__config.html b/doc/html/structfuse__config.html new file mode 100644 index 0000000..090d3b6 --- /dev/null +++ b/doc/html/structfuse__config.html @@ -0,0 +1,502 @@ + + + + + + + +libfuse: fuse_config Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_config Struct Reference
    +
    +
    + +

    #include <fuse.h>

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    +Data Fields

    int32_t set_gid
     
    int32_t set_uid
     
    int32_t set_mode
     
    double entry_timeout
     
    double negative_timeout
     
    double attr_timeout
     
    int32_t intr
     
    int32_t intr_signal
     
    int32_t remember
     
    int32_t hard_remove
     
    int32_t use_ino
     
    int32_t readdir_ino
     
    int32_t direct_io
     
    int32_t kernel_cache
     
    int32_t auto_cache
     
    int32_t nullpath_ok
     
    int32_t show_help
     
    uint32_t fmask
     
    int32_t no_rofd_flush
     
    int32_t parallel_direct_writes
     
    uint32_t flags
     
    uint64_t reserved [48]
     
    +

    Detailed Description

    +

    Configuration of the high-level API

    +

    This structure is initialized from the arguments passed to fuse_new(), and then passed to the file system's init() handler which should ensure that the configuration is compatible with the file system implementation.

    +

    Note: this data structure is ABI sensitive, new options have to be appended at the end of the structure

    + +

    Definition at line 101 of file fuse.h.

    +

    Field Documentation

    + +

    ◆ attr_timeout

    + +
    +
    + + + + +
    double fuse_config::attr_timeout
    +
    +

    The timeout in seconds for which file/directory attributes (as returned by e.g. the getattr handler) are cached.

    + +

    Definition at line 143 of file fuse.h.

    + +
    +
    + +

    ◆ auto_cache

    + +
    +
    + + + + +
    int32_t fuse_config::auto_cache
    +
    +

    This option is an alternative to kernel_cache. Instead of unconditionally keeping cached data, the cached data is invalidated on open(2) if if the modification time or the size of the file has changed since it was last opened.

    + +

    Definition at line 253 of file fuse.h.

    + +
    +
    + +

    ◆ direct_io

    + +
    +
    + + + + +
    int32_t fuse_config::direct_io
    +
    +

    This option disables the use of page cache (file content cache) in the kernel for this filesystem. This has several affects:

    +
      +
    1. Each read(2) or write(2) system call will initiate one or more read or write operations, data will not be cached in the kernel.
    2. +
    3. The return value of the read() and write() system calls will correspond to the return values of the read and write operations. This is useful for example if the file size is not known in advance (before reading it).
    4. +
    +

    Internally, enabling this option causes fuse to set the direct_io field of struct fuse_file_info - overwriting any value that was put there by the file system.

    + +

    Definition at line 226 of file fuse.h.

    + +
    +
    + +

    ◆ entry_timeout

    + +
    +
    + + + + +
    double fuse_config::entry_timeout
    +
    +

    The timeout in seconds for which name lookups will be cached.

    + +

    Definition at line 127 of file fuse.h.

    + +
    +
    + +

    ◆ flags

    + +
    +
    + + + + +
    uint32_t fuse_config::flags
    +
    +

    Reserved for future use.

    + +

    Definition at line 318 of file fuse.h.

    + +
    +
    + +

    ◆ fmask

    + +
    +
    + + + + +
    uint32_t fuse_config::fmask
    +
    +

    fmask and dmask function the same way as umask, but apply to files and directories separately. If non-zero, fmask and dmask take precedence over the umask setting.

    + +

    Definition at line 288 of file fuse.h.

    + +
    +
    + +

    ◆ hard_remove

    + +
    +
    + + + + +
    int32_t fuse_config::hard_remove
    +
    +

    The default behavior is that if an open file is deleted, the file is renamed to a hidden file (.fuse_hiddenXXX), and only removed when the file is finally released. This relieves the filesystem implementation of having to deal with this problem. This option disables the hiding behavior, and files are removed immediately in an unlink operation (or in a rename operation which overwrites an existing file).

    +

    It is recommended that you not use the hard_remove option. When hard_remove is set, the following libc functions fail on unlinked files (returning errno of ENOENT): read(2), write(2), fsync(2), close(2), f*xattr(2), ftruncate(2), fstat(2), fchmod(2), fchown(2)

    + +

    Definition at line 185 of file fuse.h.

    + +
    +
    + +

    ◆ intr

    + +
    +
    + + + + +
    int32_t fuse_config::intr
    +
    +

    Allow requests to be interrupted

    + +

    Definition at line 148 of file fuse.h.

    + +
    +
    + +

    ◆ intr_signal

    + +
    +
    + + + + +
    int32_t fuse_config::intr_signal
    +
    +

    Specify which signal number to send to the filesystem when a request is interrupted. The default is hardcoded to USR1.

    + +

    Definition at line 155 of file fuse.h.

    + +
    +
    + +

    ◆ kernel_cache

    + +
    +
    + + + + +
    int32_t fuse_config::kernel_cache
    +
    +

    This option disables flushing the cache of the file contents on every open(2). This should only be enabled on filesystems where the file data is never changed externally (not through the mounted FUSE filesystem). Thus it is not suitable for network filesystems and other intermediate filesystems.

    +

    NOTE: if this option is not specified (and neither direct_io) data is still cached after the open(2), so a read(2) system call will not always initiate a read operation.

    +

    Internally, enabling this option causes fuse to set the keep_cache field of struct fuse_file_info - overwriting any value that was put there by the file system.

    + +

    Definition at line 245 of file fuse.h.

    + +
    +
    + +

    ◆ negative_timeout

    + +
    +
    + + + + +
    double fuse_config::negative_timeout
    +
    +

    The timeout in seconds for which a negative lookup will be cached. This means, that if file did not exist (lookup returned ENOENT), the lookup will only be redone after the timeout, and the file/directory will be assumed to not exist until then. A value of zero means that negative lookups are not cached.

    + +

    Definition at line 137 of file fuse.h.

    + +
    +
    + +

    ◆ no_rofd_flush

    + +
    +
    + + + + +
    int32_t fuse_config::no_rofd_flush
    +
    +

    By default, fuse waits for all pending writes to complete and calls the FLUSH operation on close(2) of every fuse fd. With this option, wait and FLUSH are not done for read-only fuse fd, similar to the behavior of NFS/SMB clients.

    + +

    Definition at line 297 of file fuse.h.

    + +
    +
    + +

    ◆ nullpath_ok

    + +
    +
    + + + + +
    int32_t fuse_config::nullpath_ok
    +
    +

    If this option is given the file-system handlers for the following operations will not receive path information: read, write, flush, release, fallocate, fsync, readdir, releasedir, fsyncdir, lock, ioctl and poll.

    +

    For the truncate, getattr, chmod, chown and utimens operations the path will be provided only if the struct fuse_file_info argument is NULL.

    + +

    Definition at line 273 of file fuse.h.

    + +
    +
    + +

    ◆ parallel_direct_writes

    + +
    +
    + + + + +
    int32_t fuse_config::parallel_direct_writes
    +
    +

    Allow parallel direct-io writes to operate on the same file.

    +

    FUSE implementations which do not handle parallel writes on same file/region should NOT enable this option at all as it might lead to data inconsistencies.

    +

    For the FUSE implementations which have their own mechanism of cache/data integrity are beneficiaries of this setting as it now open doors to parallel writes on the same file (without enabling this setting, all direct writes on the same file are serialized, resulting in huge data bandwidth loss).

    + +

    Definition at line 312 of file fuse.h.

    + +
    +
    + +

    ◆ readdir_ino

    + +
    +
    + + + + +
    int32_t fuse_config::readdir_ino
    +
    +

    If use_ino option is not given, still try to fill in the d_ino field in readdir(2). If the name was previously looked up, and is still in the cache, the inode number found there will be used. Otherwise it will be set to -1. If use_ino option is given, this option is ignored.

    + +

    Definition at line 207 of file fuse.h.

    + +
    +
    + +

    ◆ remember

    + +
    +
    + + + + +
    int32_t fuse_config::remember
    +
    +

    Normally, FUSE assigns inodes to paths only for as long as the kernel is aware of them. With this option inodes are instead remembered for at least this many seconds. This will require more memory, but may be necessary when using applications that make use of inode numbers.

    +

    A number of -1 means that inodes will be remembered for the entire life-time of the file-system process.

    + +

    Definition at line 167 of file fuse.h.

    + +
    +
    + +

    ◆ reserved

    + +
    +
    + + + + +
    uint64_t fuse_config::reserved
    +
    +

    Reserved for future use.

    + +

    Definition at line 323 of file fuse.h.

    + +
    +
    + +

    ◆ set_gid

    + +
    +
    + + + + +
    int32_t fuse_config::set_gid
    +
    +

    If set_gid is non-zero, the st_gid attribute of each file is overwritten with the value of gid.

    + +

    Definition at line 106 of file fuse.h.

    + +
    +
    + +

    ◆ set_mode

    + +
    +
    + + + + +
    int32_t fuse_config::set_mode
    +
    +

    If set_mode is non-zero, the any permissions bits set in umask are unset in the st_mode attribute of each file.

    + +

    Definition at line 120 of file fuse.h.

    + +
    +
    + +

    ◆ set_uid

    + +
    +
    + + + + +
    int32_t fuse_config::set_uid
    +
    +

    If set_uid is non-zero, the st_uid attribute of each file is overwritten with the value of uid.

    + +

    Definition at line 113 of file fuse.h.

    + +
    +
    + +

    ◆ show_help

    + +
    +
    + + + + +
    int32_t fuse_config::show_help
    +
    +

    These 3 options are used by libfuse internally and should not be touched.

    + +

    Definition at line 279 of file fuse.h.

    + +
    +
    + +

    ◆ use_ino

    + +
    +
    + + + + +
    int32_t fuse_config::use_ino
    +
    +

    Honor the st_ino field in the functions getattr() and fill_dir(). This value is used to fill in the st_ino field in the stat(2), lstat(2), fstat(2) functions and the d_ino field in the readdir(2) function. The filesystem does not have to guarantee uniqueness, however some applications rely on this value being unique for the whole filesystem.

    +

    Note that this does not affect the inode that libfuse and the kernel use internally (also called the "nodeid").

    + +

    Definition at line 198 of file fuse.h.

    + +
    +
    +
    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__conn__info.html b/doc/html/structfuse__conn__info.html new file mode 100644 index 0000000..7a1b524 --- /dev/null +++ b/doc/html/structfuse__conn__info.html @@ -0,0 +1,389 @@ + + + + + + + +libfuse: fuse_conn_info Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_conn_info Struct Reference
    +
    +
    + +

    #include <fuse_common.h>

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    +Data Fields

    uint32_t proto_major
     
    uint32_t proto_minor
     
    uint32_t max_write
     
    uint32_t max_read
     
    uint32_t max_readahead
     
    uint32_t capable
     
    uint32_t want
     
    uint32_t max_background
     
    uint32_t congestion_threshold
     
    uint32_t time_gran
     
    uint32_t no_interrupt: 1
     
    uint64_t capable_ext
     
    uint64_t want_ext
     
    uint32_t reserved [16]
     
    uint16_t request_timeout
     
    uint16_t reserved [31]
     
    +

    Detailed Description

    +

    Connection information, passed to the ->init() method

    +

    Some of the elements are read-write, these can be changed to indicate the value requested by the filesystem. The requested value must usually be smaller than the indicated value.

    +

    Note: The capable and want fields are limited to 32 bits for ABI compatibility. For full 64-bit capability support, use the capable_ext and want_ext fields instead.

    + +

    Definition at line 538 of file fuse_common.h.

    +

    Field Documentation

    + +

    ◆ capable

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::capable
    +
    +

    Capability flags that the kernel supports (read-only)

    +

    Deprecated left over for ABI compatibility, use capable_ext

    + +

    Definition at line 578 of file fuse_common.h.

    + +
    +
    + +

    ◆ capable_ext

    + +
    +
    + + + + +
    uint64_t fuse_conn_info::capable_ext
    +
    +

    Extended capability flags that the kernel supports (read-only) This field provides full 64-bit capability support.

    + +

    Definition at line 686 of file fuse_common.h.

    + +
    +
    + +

    ◆ congestion_threshold

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::congestion_threshold
    +
    +

    Kernel congestion threshold parameter. If the number of pending background requests exceeds this number, the FUSE kernel module will mark the filesystem as "congested". This instructs the kernel to expect that queued requests will take some time to complete, and to adjust its algorithms accordingly (e.g. by putting a waiting thread to sleep instead of using a busy-loop).

    + +

    Definition at line 630 of file fuse_common.h.

    + +
    +
    + +

    ◆ max_background

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::max_background
    +
    +

    Maximum number of pending "background" requests. A background request is any type of request for which the total number is not limited by other means. As of kernel 4.8, only two types of requests fall into this category:

    +
      +
    1. Read-ahead requests
    2. +
    3. Asynchronous direct I/O requests
    4. +
    +

    Read-ahead requests are generated (if max_readahead is non-zero) by the kernel to preemptively fill its caches when it anticipates that userspace will soon read more data.

    +

    Asynchronous direct I/O requests are generated if FUSE_CAP_ASYNC_DIO is enabled and userspace submits a large direct I/O request. In this case the kernel will internally split it up into multiple smaller requests and submit them to the filesystem concurrently.

    +

    Note that the following requests are not background requests: writeback requests (limited by the kernel's flusher algorithm), regular (i.e., synchronous and buffered) userspace read/write requests (limited to one per thread), asynchronous read requests (Linux's io_submit(2) call actually blocks, so these are also limited to one per thread).

    + +

    Definition at line 620 of file fuse_common.h.

    + +
    +
    + +

    ◆ max_read

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::max_read
    +
    +

    Maximum size of read requests. A value of zero indicates no limit. However, even if the filesystem does not specify a limit, the maximum size of read requests will still be limited by the kernel.

    +

    NOTE: For the time being, the maximum size of read requests must be set both here and passed to fuse_session_new() using the -o max_read=<n> mount option. At some point in the future, specifying the mount option will no longer be necessary.

    + +

    Definition at line 566 of file fuse_common.h.

    + +
    +
    + +

    ◆ max_readahead

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::max_readahead
    +
    +

    Maximum readahead

    + +

    Definition at line 571 of file fuse_common.h.

    + +
    +
    + +

    ◆ max_write

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::max_write
    +
    +

    Maximum size of the write buffer

    + +

    Definition at line 552 of file fuse_common.h.

    + +
    +
    + +

    ◆ no_interrupt

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::no_interrupt
    +
    +

    Disable FUSE_INTERRUPT requests.

    +

    Enable no_interrupt option to: 1) Avoid unnecessary locking operations and list operations, 2) Return ENOSYS for the reply of FUSE_INTERRUPT request to inform the kernel not to send the FUSE_INTERRUPT request.

    + +

    Definition at line 677 of file fuse_common.h.

    + +
    +
    + +

    ◆ proto_major

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::proto_major
    +
    +

    Major version of the protocol (read-only)

    + +

    Definition at line 542 of file fuse_common.h.

    + +
    +
    + +

    ◆ proto_minor

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::proto_minor
    +
    +

    Minor version of the protocol (read-only)

    + +

    Definition at line 547 of file fuse_common.h.

    + +
    +
    + +

    ◆ request_timeout

    + +
    +
    + + + + +
    uint16_t fuse_conn_info::request_timeout
    +
    +

    Request timeout (in seconds). If the request is not answered by this timeout, the connection will be aborted by the kernel.

    + +

    Definition at line 707 of file fuse_common.h.

    + +
    +
    + +

    ◆ reserved [1/2]

    + +
    +
    + + + + +
    uint16_t fuse_conn_info::reserved
    +
    +

    For future use.

    + +

    Definition at line 701 of file fuse_common.h.

    + +
    +
    + +

    ◆ reserved [2/2]

    + +
    +
    + + + + +
    uint16_t fuse_conn_info::reserved[31]
    +
    +

    For future use.

    + +

    Definition at line 712 of file fuse_common.h.

    + +
    +
    + +

    ◆ time_gran

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::time_gran
    +
    +

    When FUSE_CAP_WRITEBACK_CACHE is enabled, the kernel is responsible for updating mtime and ctime when write requests are received. The updated values are passed to the filesystem with setattr() requests. However, if the filesystem does not support the full resolution of the kernel timestamps (nanoseconds), the mtime and ctime values used by kernel and filesystem will differ (and result in an apparent change of times after a cache flush).

    +

    To prevent this problem, this variable can be used to inform the kernel about the timestamp granularity supported by the file-system. The value should be power of 10. The default is 1, i.e. full nano-second resolution. Filesystems supporting only second resolution should set this to 1000000000.

    + +

    Definition at line 647 of file fuse_common.h.

    + +
    +
    + +

    ◆ want

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::want
    +
    +

    Capability flags that the filesystem wants to enable.

    +

    libfuse attempts to initialize this field with reasonable default values before calling the init() handler.

    +

    Deprecated left over for ABI compatibility. Use want_ext with the helper functions fuse_set_feature_flag() / fuse_unset_feature_flag()

    + +

    Definition at line 590 of file fuse_common.h.

    + +
    +
    + +

    ◆ want_ext

    + +
    +
    + + + + +
    uint64_t fuse_conn_info::want_ext
    +
    +

    Extended capability flags that the filesystem wants to enable. This field provides full 64-bit capability support.

    +

    Don't set this field directly, but use the helper functions fuse_set_feature_flag() / fuse_unset_feature_flag()

    + +

    Definition at line 696 of file fuse_common.h.

    + +
    +
    +
    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__context.html b/doc/html/structfuse__context.html new file mode 100644 index 0000000..688ba1e --- /dev/null +++ b/doc/html/structfuse__context.html @@ -0,0 +1,184 @@ + + + + + + + +libfuse: fuse_context Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_context Struct Reference
    +
    +
    + +

    #include <fuse.h>

    + + + + + + + + + + + + + + +

    +Data Fields

    struct fuse * fuse
     
    uid_t uid
     
    gid_t gid
     
    pid_t pid
     
    void * private_data
     
    mode_t umask
     
    +

    Detailed Description

    +

    Extra context that may be needed by some filesystems

    +

    The uid, gid and pid fields are not filled in case of a writepage operation.

    + +

    Definition at line 860 of file fuse.h.

    +

    Field Documentation

    + +

    ◆ fuse

    + +
    +
    + + + + +
    struct fuse * fuse_context::fuse
    +
    +

    Pointer to the fuse object

    + +

    Definition at line 862 of file fuse.h.

    + +
    +
    + +

    ◆ gid

    + +
    +
    + + + + +
    gid_t fuse_context::gid
    +
    +

    Group ID of the calling process

    + +

    Definition at line 868 of file fuse.h.

    + +
    +
    + +

    ◆ pid

    + +
    +
    + + + + +
    pid_t fuse_context::pid
    +
    +

    Process ID of the calling thread

    + +

    Definition at line 871 of file fuse.h.

    + +
    +
    + +

    ◆ private_data

    + +
    +
    + + + + +
    void * fuse_context::private_data
    +
    +

    Private filesystem data

    + +

    Definition at line 874 of file fuse.h.

    + +
    +
    + +

    ◆ uid

    + +
    +
    + + + + +
    uid_t fuse_context::uid
    +
    +

    User ID of the calling process

    + +

    Definition at line 865 of file fuse.h.

    + +
    +
    + +

    ◆ umask

    + +
    +
    + + + + +
    mode_t fuse_context::umask
    +
    +

    Umask of the calling process

    + +

    Definition at line 877 of file fuse.h.

    + +
    +
    +
    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__ctx.html b/doc/html/structfuse__ctx.html new file mode 100644 index 0000000..bb5f447 --- /dev/null +++ b/doc/html/structfuse__ctx.html @@ -0,0 +1,146 @@ + + + + + + + +libfuse: fuse_ctx Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_ctx Struct Reference
    +
    +
    + +

    #include <fuse_lowlevel.h>

    + + + + + + + + + + +

    +Data Fields

    uid_t uid
     
    gid_t gid
     
    pid_t pid
     
    mode_t umask
     
    +

    Detailed Description

    +

    Additional context associated with requests.

    +

    Note that the reported client uid, gid and pid may be zero in some situations. For example, if the FUSE file system is running in a PID or user namespace but then accessed from outside the namespace, there is no valid uid/pid/gid that could be reported.

    + +

    Definition at line 112 of file fuse_lowlevel.h.

    +

    Field Documentation

    + +

    ◆ gid

    + +
    +
    + + + + +
    gid_t fuse_ctx::gid
    +
    +

    Group ID of the calling process

    + +

    Definition at line 117 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ pid

    + +
    +
    + + + + +
    pid_t fuse_ctx::pid
    +
    +

    Thread ID of the calling process

    + +

    Definition at line 120 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ uid

    + +
    +
    + + + + +
    uid_t fuse_ctx::uid
    +
    +

    User ID of the calling process

    + +

    Definition at line 114 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ umask

    + +
    +
    + + + + +
    mode_t fuse_ctx::umask
    +
    +

    Umask of the calling process

    + +

    Definition at line 123 of file fuse_lowlevel.h.

    + +
    +
    +
    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__entry__param.html b/doc/html/structfuse__entry__param.html new file mode 100644 index 0000000..1370f19 --- /dev/null +++ b/doc/html/structfuse__entry__param.html @@ -0,0 +1,167 @@ + + + + + + + +libfuse: fuse_entry_param Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_entry_param Struct Reference
    +
    +
    + +

    #include <fuse_lowlevel.h>

    + + + + + + + + + + + + +

    +Data Fields

    fuse_ino_t ino
     
    uint64_t generation
     
    struct stat attr
     
    double attr_timeout
     
    double entry_timeout
     
    +

    Detailed Description

    +

    Directory entry parameters supplied to fuse_reply_entry()

    + +

    Definition at line 60 of file fuse_lowlevel.h.

    +

    Field Documentation

    + +

    ◆ attr

    + +
    +
    + + + + +
    struct stat fuse_entry_param::attr
    +
    +

    Inode attributes.

    +

    Even if attr_timeout == 0, attr must be correct. For example, for open(), FUSE uses attr.st_size from lookup() to determine how many bytes to request. If this value is not correct, incorrect data will be returned.

    + +

    Definition at line 89 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ attr_timeout

    + +
    +
    + + + + +
    double fuse_entry_param::attr_timeout
    +
    +

    Validity timeout (in seconds) for inode attributes. If attributes only change as a result of requests that come through the kernel, this should be set to a very large value.

    + +

    Definition at line 95 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ entry_timeout

    + +
    +
    + + + + +
    double fuse_entry_param::entry_timeout
    +
    +

    Validity timeout (in seconds) for the name. If directory entries are changed/deleted only as a result of requests that come through the kernel, this should be set to a very large value.

    + +

    Definition at line 101 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ generation

    + +
    +
    + + + + +
    uint64_t fuse_entry_param::generation
    +
    +

    Generation number for this entry.

    +

    If the file system will be exported over NFS, the ino/generation pairs need to be unique over the file system's lifetime (rather than just the mount time). So if the file system reuses an inode after it has been deleted, it must assign a new, previously unused generation number to the inode at the same time.

    + +

    Definition at line 80 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ ino

    + +
    +
    + + + + +
    fuse_ino_t fuse_entry_param::ino
    +
    +

    Unique inode number

    +

    In lookup, zero means negative entry (from version 2.5) Returning ENOENT also means negative entry, but by setting zero ino the kernel may cache negative entries for entry_timeout seconds.

    + +

    Definition at line 68 of file fuse_lowlevel.h.

    + +
    +
    +
    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__ext__header.html b/doc/html/structfuse__ext__header.html new file mode 100644 index 0000000..5589f40 --- /dev/null +++ b/doc/html/structfuse__ext__header.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: fuse_ext_header Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    +
    fuse_ext_header Struct Reference
    +
    +
    + +

    #include <fuse_kernel.h>

    +

    Detailed Description

    +

    struct fuse_ext_header - extension header @size: total size of this extension including this header @type: type of extension

    +

    This is made compatible with fuse_secctx_header by using type values > FUSE_MAX_NR_SECCTX

    + +

    Definition at line 1174 of file fuse_kernel.h.

    +

    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__file__info.html b/doc/html/structfuse__file__info.html new file mode 100644 index 0000000..d52f3ab --- /dev/null +++ b/doc/html/structfuse__file__info.html @@ -0,0 +1,357 @@ + + + + + + + +libfuse: fuse_file_info Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_file_info Struct Reference
    +
    +
    + +

    #include <fuse_common.h>

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    +Data Fields

    int32_t flags
     
    uint32_t writepage: 1
     
    uint32_t direct_io: 1
     
    uint32_t keep_cache: 1
     
    uint32_t flush: 1
     
    uint32_t nonseekable: 1
     
    uint32_t cache_readdir: 1
     
    uint32_t noflush: 1
     
    uint32_t parallel_direct_writes: 1
     
    uint32_t padding: 23
     
    uint64_t fh
     
    uint64_t lock_owner
     
    uint32_t poll_events
     
    int32_t backing_id
     
    uint64_t compat_flags
     
    +

    Detailed Description

    +

    Information about an open file.

    +

    File Handles are created by the open, opendir, and create methods and closed by the release and releasedir methods. Multiple file handles may be concurrently open for the same file. Generally, a client will create one file handle per file descriptor, though in some cases multiple file descriptors can share a single file handle.

    +

    Note: This data structure is ABI sensitive, new parameters have to be added within padding/padding2 bits and always below existing parameters.

    + +

    Definition at line 50 of file fuse_common.h.

    +

    Field Documentation

    + +

    ◆ backing_id

    + +
    +
    + + + + +
    int32_t fuse_file_info::backing_id
    +
    +

    Passthrough backing file id. May be filled in by filesystem in create and open. It is used to create a passthrough connection between FUSE file and backing file.

    + +

    Definition at line 119 of file fuse_common.h.

    + +
    +
    + +

    ◆ cache_readdir

    + +
    +
    + + + + +
    uint32_t fuse_file_info::cache_readdir
    +
    +

    Can be filled in by opendir. It signals the kernel to enable caching of entries returned by readdir(). Has no effect when set in other contexts (in particular it does nothing when set by open()).

    + +

    Definition at line 89 of file fuse_common.h.

    + +
    +
    + +

    ◆ compat_flags

    + +
    +
    + + + + +
    uint64_t fuse_file_info::compat_flags
    +
    +

    struct fuse_file_info api and abi flags
    +

    + +

    Definition at line 122 of file fuse_common.h.

    + +
    +
    + +

    ◆ direct_io

    + +
    +
    + + + + +
    uint32_t fuse_file_info::direct_io
    +
    +

    Can be filled in by open/create, to use direct I/O on this file.

    + +

    Definition at line 63 of file fuse_common.h.

    + +
    +
    + +

    ◆ fh

    + +
    +
    + + + + +
    uint64_t fuse_file_info::fh
    +
    +

    File handle id. May be filled in by filesystem in create, open, and opendir(). Available in most other file operations on the same file handle.

    + +

    Definition at line 107 of file fuse_common.h.

    + +
    +
    + +

    ◆ flags

    + +
    +
    + + + + +
    int32_t fuse_file_info::flags
    +
    +

    Open flags. Available in open(), release() and create()

    + +

    Definition at line 52 of file fuse_common.h.

    + +
    +
    + +

    ◆ flush

    + +
    +
    + + + + +
    uint32_t fuse_file_info::flush
    +
    +

    Indicates a flush operation. Set in flush operation, also maybe set in highlevel lock operation and lowlevel release operation.

    + +

    Definition at line 74 of file fuse_common.h.

    + +
    +
    + +

    ◆ keep_cache

    + +
    +
    + + + + +
    uint32_t fuse_file_info::keep_cache
    +
    +

    Can be filled in by open and opendir. It signals the kernel that any currently cached data (ie., data that the filesystem provided the last time the file/directory was open) need not be invalidated when the file/directory is closed.

    + +

    Definition at line 69 of file fuse_common.h.

    + +
    +
    + +

    ◆ lock_owner

    + +
    +
    + + + + +
    uint64_t fuse_file_info::lock_owner
    +
    +

    Lock owner id. Available in locking operations and flush

    + +

    Definition at line 110 of file fuse_common.h.

    + +
    +
    + +

    ◆ noflush

    + +
    +
    + + + + +
    uint32_t fuse_file_info::noflush
    +
    +

    Can be filled in by open, to indicate that flush is not needed on close.

    + +

    Definition at line 93 of file fuse_common.h.

    + +
    +
    + +

    ◆ nonseekable

    + +
    +
    + + + + +
    uint32_t fuse_file_info::nonseekable
    +
    +

    Can be filled in by open, to indicate that the file is not seekable.

    + +

    Definition at line 78 of file fuse_common.h.

    + +
    +
    + +

    ◆ padding

    + +
    +
    + + + + +
    uint32_t fuse_file_info::padding
    +
    +

    Padding. Reserved for future use

    + +

    Definition at line 100 of file fuse_common.h.

    + +
    +
    + +

    ◆ parallel_direct_writes

    + +
    +
    + + + + +
    uint32_t fuse_file_info::parallel_direct_writes
    +
    +

    Can be filled by open/create, to allow parallel direct writes on this file

    + +

    Definition at line 97 of file fuse_common.h.

    + +
    +
    + +

    ◆ poll_events

    + +
    +
    + + + + +
    uint32_t fuse_file_info::poll_events
    +
    +

    Requested poll events. Available in ->poll. Only set on kernels which support it. If unsupported, this field is set to zero.

    + +

    Definition at line 114 of file fuse_common.h.

    + +
    +
    + +

    ◆ writepage

    + +
    +
    + + + + +
    uint32_t fuse_file_info::writepage
    +
    +

    In case of a write operation indicates if this was caused by a delayed write from the page cache. If so, then the context's pid, uid, and gid fields will not be valid, and the fh value may not match the fh value that would have been sent with the corresponding individual write requests if write caching had been disabled.

    + +

    Definition at line 60 of file fuse_common.h.

    + +
    +
    +
    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__loop__config.html b/doc/html/structfuse__loop__config.html new file mode 100644 index 0000000..768c579 --- /dev/null +++ b/doc/html/structfuse__loop__config.html @@ -0,0 +1,154 @@ + + + + + + + +libfuse: fuse_loop_config Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_loop_config Struct Reference
    +
    +
    + +

    #include <fuse_i.h>

    + + + + + + + + + + +

    +Data Fields

    int clone_fd
     
    unsigned int max_idle_threads
     
    int max_idle_threads
     
    unsigned int max_threads
     
    +

    Detailed Description

    +

    Configuration parameters passed to fuse_session_loop_mt() and fuse_loop_mt().

    +

    Internal API to avoid exposing the plain data structure and causing compat issues after adding or removing struct members.

    + +

    Definition at line 139 of file fuse_common.h.

    +

    Field Documentation

    + +

    ◆ clone_fd

    + +
    +
    + + + + +
    int fuse_loop_config::clone_fd
    +
    +

    whether to use separate device fds for each thread (may increase performance)

    + +

    Definition at line 147 of file fuse_common.h.

    + +
    +
    + +

    ◆ max_idle_threads [1/2]

    + +
    +
    + + + + +
    int fuse_loop_config::max_idle_threads
    +
    +

    The maximum number of available worker threads before they start to get deleted when they become idle. If not specified, the default is 10.

    +

    Adjusting this has performance implications; a very small number of threads in the pool will cause a lot of thread creation and deletion overhead and performance may suffer. When set to 0, a new thread will be created to service every operation.

    +

    The maximum number of available worker threads before they start to get deleted when they become idle. If not specified, the default is 10.

    +

    Adjusting this has performance implications; a very small number of threads in the pool will cause a lot of thread creation and deletion overhead and performance may suffer. When set to 0, a new thread will be created to service every operation. The special value of -1 means that this parameter is disabled.

    + +

    Definition at line 159 of file fuse_common.h.

    + +
    +
    + +

    ◆ max_idle_threads [2/2]

    + +
    +
    + + + + +
    int fuse_loop_config::max_idle_threads
    +
    +

    The maximum number of available worker threads before they start to get deleted when they become idle. If not specified, the default is 10.

    +

    Adjusting this has performance implications; a very small number of threads in the pool will cause a lot of thread creation and deletion overhead and performance may suffer. When set to 0, a new thread will be created to service every operation. The special value of -1 means that this parameter is disabled.

    + +

    Definition at line 159 of file fuse_i.h.

    + +
    +
    + +

    ◆ max_threads

    + +
    +
    + + + + +
    unsigned int fuse_loop_config::max_threads
    +
    +

    max number of threads taking and processing kernel requests

    +

    As of now threads are created dynamically

    + +

    Definition at line 166 of file fuse_i.h.

    + +
    +
    +
    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__lowlevel__ops.html b/doc/html/structfuse__lowlevel__ops.html new file mode 100644 index 0000000..f97f3c8 --- /dev/null +++ b/doc/html/structfuse__lowlevel__ops.html @@ -0,0 +1,1530 @@ + + + + + + + +libfuse: fuse_lowlevel_ops Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_lowlevel_ops Struct Reference
    +
    +
    + +

    #include <fuse_lowlevel.h>

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    +Data Fields

    void(* init )(void *userdata, struct fuse_conn_info *conn)
     
    void(* destroy )(void *userdata)
     
    void(* lookup )(fuse_req_t req, fuse_ino_t parent, const char *name)
     
    void(* forget )(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
     
    void(* getattr )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
     
    void(* setattr )(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
     
    void(* readlink )(fuse_req_t req, fuse_ino_t ino)
     
    void(* mknod )(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
     
    void(* mkdir )(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
     
    void(* unlink )(fuse_req_t req, fuse_ino_t parent, const char *name)
     
    void(* rmdir )(fuse_req_t req, fuse_ino_t parent, const char *name)
     
    void(* symlink )(fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
     
    void(* rename )(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
     
    void(* link )(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
     
    void(* open )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
     
    void(* read )(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
     
    void(* write )(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi)
     
    void(* flush )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
     
    void(* release )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
     
    void(* fsync )(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
     
    void(* opendir )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
     
    void(* readdir )(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
     
    void(* releasedir )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
     
    void(* fsyncdir )(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
     
    void(* statfs )(fuse_req_t req, fuse_ino_t ino)
     
    void(* setxattr )(fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags)
     
    void(* getxattr )(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
     
    void(* listxattr )(fuse_req_t req, fuse_ino_t ino, size_t size)
     
    void(* removexattr )(fuse_req_t req, fuse_ino_t ino, const char *name)
     
    void(* access )(fuse_req_t req, fuse_ino_t ino, int mask)
     
    void(* create )(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi)
     
    void(* getlk )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
     
    void(* setlk )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
     
    void(* bmap )(fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx)
     
    void(* ioctl )(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz)
     
    void(* poll )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph)
     
    void(* write_buf )(fuse_req_t req, fuse_ino_t ino, struct fuse_bufvec *bufv, off_t off, struct fuse_file_info *fi)
     
    void(* retrieve_reply )(fuse_req_t req, void *cookie, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv)
     
    void(* forget_multi )(fuse_req_t req, size_t count, struct fuse_forget_data *forgets)
     
    void(* flock )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op)
     
    void(* fallocate )(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi)
     
    void(* readdirplus )(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
     
    void(* copy_file_range )(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags)
     
    void(* lseek )(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi)
     
    void(* tmpfile )(fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi)
     
    void(* statx )(fuse_req_t req, fuse_ino_t ino, int flags, int mask, struct fuse_file_info *fi)
     
    +

    Detailed Description

    +

    Low level filesystem operations

    +

    Most of the methods (with the exception of init and destroy) receive a request handle (fuse_req_t) as their first argument. This handle must be passed to one of the specified reply functions.

    +

    This may be done inside the method invocation, or after the call has returned. The request handle is valid until one of the reply functions is called.

    +

    Other pointer arguments (name, fuse_file_info, etc) are not valid after the call has returned, so if they are needed later, their contents have to be copied.

    +

    In general, all methods are expected to perform any necessary permission checking. However, a filesystem may delegate this task to the kernel by passing the default_permissions mount option to fuse_session_new(). In this case, methods will only be called if the kernel's permission check has succeeded.

    +

    The filesystem sometimes needs to handle a return value of -ENOENT from the reply function, which means, that the request was interrupted, and the reply discarded. For example if fuse_reply_open() return -ENOENT means, that the release method for this file will not be called.

    +

    This data structure is ABI sensitive, on adding new functions these need to be appended at the end of the struct

    +

    Low level filesystem operations

    +

    Most of the methods (with the exception of init and destroy) receive a request handle (fuse_req_t) as their first argument. This handle must be passed to one of the specified reply functions.

    +

    This may be done inside the method invocation, or after the call has returned. The request handle is valid until one of the reply functions is called.

    +

    Other pointer arguments (name, fuse_file_info, etc) are not valid after the call has returned, so if they are needed later, their contents have to be copied.

    +

    In general, all methods are expected to perform any necessary permission checking. However, a filesystem may delegate this task to the kernel by passing the default_permissions mount option to fuse_session_new(). In this case, methods will only be called if the kernel's permission check has succeeded.

    +

    It is generally not really necessary to check the fuse_reply_* return values for errors, as any error in sending a reply indicates an unrecoverable problem with the kernel fuse connection, which will also terminate the session loop anyway.

    +

    This data structure is ABI sensitive, on adding new functions these need to be appended at the end of the struct

    + +

    Definition at line 206 of file fuse_lowlevel.h.

    +

    Field Documentation

    + +

    ◆ access

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::access)(fuse_req_t req, fuse_ino_t ino, int mask)
    +
    +

    Check file access permissions

    +

    This will be called for the access() and chdir() system calls. If the 'default_permissions' mount option is given, this method is not called.

    +

    This method is not called under Linux kernel versions 2.4.x

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent success, i.e. this and all future access() requests will succeed without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    maskrequested access mode
    +
    +
    + +

    Definition at line 951 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ bmap

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::bmap)(fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx)
    +
    +

    Map block index within file to block index within device

    +

    Note: This makes sense only for block device backed filesystems mounted with the 'blkdev' option

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure, i.e. all future bmap() requests will fail with the same error code without being send to the filesystem process.

    +

    Valid replies: fuse_reply_bmap fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    blocksizeunit of block index
    idxblock index within file
    +
    +
    + +

    Definition at line 1044 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ copy_file_range

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::copy_file_range)(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags)
    +
    +

    Copy a range of data from one file to another

    +

    Performs an optimized copy between two file descriptors without the additional cost of transferring data through the FUSE kernel module to user space (glibc) and then back into the FUSE filesystem again.

    +

    In case this method is not implemented, glibc falls back to reading data from the source and writing to the destination. Effectively doing an inefficient copy of the data.

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EOPNOTSUPP, i.e. all future copy_file_range() requests will fail with EOPNOTSUPP without being send to the filesystem process.

    +

    Valid replies: fuse_reply_write fuse_reply_err

    +
    Parameters
    + + + + + + + + + + +
    reqrequest handle
    ino_inthe inode number or the source file
    off_instarting point from were the data should be read
    fi_infile information of the source file
    ino_outthe inode number or the destination file
    off_outstarting point where the data should be written
    fi_outfile information of the destination file
    lenmaximum size of the data to copy
    flagspassed along with the copy_file_range() syscall
    +
    +
    + +

    Definition at line 1279 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ create

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::create)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi)
    +
    +

    Create and open a file

    +

    If the file does not exist, first create it with the specified mode, and then open it.

    +

    See the description of the open handler for more information.

    +

    If this method is not implemented or under Linux kernel versions earlier than 2.6.15, the mknod() and open() methods will be called instead.

    +

    If this request is answered with an error code of ENOSYS, the handler is treated as not implemented (i.e., for this and future requests the mknod() and open() handlers will be called instead).

    +

    Valid replies: fuse_reply_create fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    parentinode number of the parent directory
    nameto create
    modefile type and mode with which to create the new file
    fifile information
    +
    +
    + +

    Definition at line 980 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ destroy

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::destroy)(void *userdata)
    +
    +

    Clean up filesystem.

    +

    Called on filesystem exit. When this method is called, the connection to the kernel may be gone already, so that eg. calls to fuse_lowlevel_notify_* will fail.

    +

    There's no reply to this function

    +
    Parameters
    + + +
    userdatathe user data passed to fuse_session_new()
    +
    +
    + +

    Definition at line 236 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ fallocate

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::fallocate)(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi)
    +
    +

    Allocate requested space. If this function returns success then subsequent writes to the specified range shall not fail due to the lack of free space on the file system storage media.

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EOPNOTSUPP, i.e. all future fallocate() requests will fail with EOPNOTSUPP without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    offsetstarting point for allocated region
    lengthsize of allocated region
    modedetermines the operation to be performed on the given range, see fallocate(2)
    +
    +
    + +

    Definition at line 1218 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ flock

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::flock)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op)
    +
    +

    Acquire, modify or release a BSD file lock

    +

    Note: if the locking methods are not implemented, the kernel will still allow file locking to work locally. Hence these are only interesting for network filesystems and similar.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    opthe locking operation, see flock(2)
    +
    +
    + +

    Definition at line 1195 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ flush

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::flush)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    +

    Flush method

    +

    This is called on each close() of the opened file.

    +

    Since file descriptors can be duplicated (dup, dup2, fork), for one open call there may be many flush calls.

    +

    Filesystems shouldn't assume that flush will always be called after some writes, or that if will be called at all.

    +

    fi->fh will contain the value set by the open method, or will be undefined if the open method didn't set any value.

    +

    NOTE: the name of the method is misleading, since (unlike fsync) the filesystem is not forced to flush pending writes. One reason to flush data is if the filesystem wants to return write errors during close. However, such use is non-portable because POSIX does not require close to wait for delayed I/O to complete.

    +

    If the filesystem supports file locking operations (setlk, getlk) it should remove all locks belonging to 'fi->owner'.

    +

    If this request is answered with an error code of ENOSYS, this is treated as success and future calls to flush() will succeed automatically without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    +
    +
    + +

    Definition at line 652 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ forget

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::forget)(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
    +
    +

    Forget about an inode

    +

    This function is called when the kernel removes an inode from its internal caches.

    +

    The inode's lookup count increases by one for every call to fuse_reply_entry and fuse_reply_create. The nlookup parameter indicates by how much the lookup count should be decreased.

    +

    Inodes with a non-zero lookup count may receive request from the kernel even after calls to unlink, rmdir or (when overwriting an existing file) rename. Filesystems must handle such requests properly and it is recommended to defer removal of the inode until the lookup count reaches zero. Calls to unlink, rmdir or rename will be followed closely by forget unless the file or directory is open, in which case the kernel issues forget only after the release or releasedir calls.

    +

    Note that if a file system will be exported over NFS the inodes lifetime must extend even beyond forget. See the generation field in struct fuse_entry_param above.

    +

    On unmount the lookup count for all inodes implicitly drops to zero. It is not guaranteed that the file system will receive corresponding forget messages for the affected inodes.

    +

    Valid replies: fuse_reply_none

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    nlookupthe number of lookups to forget
    +
    +
    + +

    Definition at line 287 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ forget_multi

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::forget_multi)(fuse_req_t req, size_t count, struct fuse_forget_data *forgets)
    +
    +

    Forget about multiple inodes

    +

    See description of the forget function for more information.

    +

    Valid replies: fuse_reply_none

    +
    Parameters
    + + +
    reqrequest handle
    +
    +
    + +

    Definition at line 1177 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ fsync

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::fsync)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
    +
    +

    Synchronize file contents

    +

    If the datasync parameter is non-zero, then only the user data should be flushed, not the meta data.

    +

    If this request is answered with an error code of ENOSYS, this is treated as success and future calls to fsync() will succeed automatically without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    datasyncflag indicating if only data should be flushed
    fifile information
    +
    +
    + +

    Definition at line 702 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ fsyncdir

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::fsyncdir)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
    +
    +

    Synchronize directory contents

    +

    If the datasync parameter is non-zero, then only the directory contents should be flushed, not the meta data.

    +

    fi->fh will contain the value set by the opendir method, or will be undefined if the opendir method didn't set any value.

    +

    If this request is answered with an error code of ENOSYS, this is treated as success and future calls to fsyncdir() will succeed automatically without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    datasyncflag indicating if only data should be flushed
    fifile information
    +
    +
    + +

    Definition at line 824 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ getattr

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::getattr)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    +

    Get file attributes.

    +

    If writeback caching is enabled, the kernel may have a better idea of a file's length than the FUSE file system (eg if there has been a write that extended the file size, but that has not yet been passed to the filesystem.

    +

    In this case, the st_size value provided by the file system will be ignored.

    +

    Valid replies: fuse_reply_attr fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    fifile information, or NULL
    +
    +
    + +

    Definition at line 308 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ getlk

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
    +
    +

    Test for a POSIX file lock

    +

    Valid replies: fuse_reply_lock fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    lockthe region/type to test
    +
    +
    + +

    Definition at line 995 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ getxattr

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::getxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
    +
    +

    Get an extended attribute

    +

    If size is zero, the size of the value should be sent with fuse_reply_xattr.

    +

    If the size is non-zero, and the value fits in the buffer, the value should be sent with fuse_reply_buf.

    +

    If the size is too small for the value, the ERANGE error should be sent.

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EOPNOTSUPP, i.e. all future getxattr() requests will fail with EOPNOTSUPP without being send to the filesystem process.

    +

    Valid replies: fuse_reply_buf fuse_reply_data fuse_reply_xattr fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    nameof the extended attribute
    sizemaximum size of the value to send
    +
    +
    + +

    Definition at line 881 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ init

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::init)(void *userdata, struct fuse_conn_info *conn)
    +
    +

    Initialize filesystem

    +

    This function is called when libfuse establishes communication with the FUSE kernel module. The file system should use this module to inspect and/or modify the connection parameters provided in the conn structure.

    +

    Note that some parameters may be overwritten by options passed to fuse_session_new() which take precedence over the values set in this handler.

    +

    There's no reply to this function

    +
    Parameters
    + + +
    userdatathe user data passed to fuse_session_new()
    +
    +
    + +

    Definition at line 223 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ ioctl

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::ioctl)(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz)
    +
    +

    Ioctl

    +

    Note: For unrestricted ioctls (not allowed for FUSE servers), data in and out areas can be discovered by giving iovs and setting FUSE_IOCTL_RETRY in flags. For restricted ioctls, kernel prepares in/out data area according to the information encoded in cmd.

    +

    Valid replies: fuse_reply_ioctl_retry fuse_reply_ioctl fuse_reply_ioctl_iov fuse_reply_err

    +
    Parameters
    + + + + + + + + + + +
    reqrequest handle
    inothe inode number
    cmdioctl command
    argioctl argument
    fifile information
    flagsfor FUSE_IOCTL_* flags
    in_bufdata fetched from the caller
    in_bufsznumber of fetched bytes
    out_bufszmaximum size of output data
    +
    +
    +

    Note : the unsigned long request submitted by the application is truncated to 32 bits.

    + +

    Definition at line 1080 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ link

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::link)(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
    +
    +

    Create a hard link

    +

    Valid replies: fuse_reply_entry fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe old inode number
    newparentinode number of the new parent directory
    newnamenew name to create
    +
    +
    + +

    Definition at line 488 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ listxattr

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::listxattr)(fuse_req_t req, fuse_ino_t ino, size_t size)
    +
    +

    List extended attribute names

    +

    If size is zero, the total size of the attribute list should be sent with fuse_reply_xattr.

    +

    If the size is non-zero, and the null character separated attribute list fits in the buffer, the list should be sent with fuse_reply_buf.

    +

    If the size is too small for the list, the ERANGE error should be sent.

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EOPNOTSUPP, i.e. all future listxattr() requests will fail with EOPNOTSUPP without being send to the filesystem process.

    +

    Valid replies: fuse_reply_buf fuse_reply_data fuse_reply_xattr fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    sizemaximum size of the list to send
    +
    +
    + +

    Definition at line 912 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ lookup

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    +

    Look up a directory entry by name and get its attributes.

    +

    Valid replies: fuse_reply_entry fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    parentinode number of the parent directory
    namethe name to look up
    +
    +
    + +

    Definition at line 249 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ lseek

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::lseek)(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi)
    +
    +

    Find next data or hole after the specified offset

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure, i.e. all future lseek() requests will fail with the same error code without being send to the filesystem process.

    +

    Valid replies: fuse_reply_lseek fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    offoffset to start search from
    whenceeither SEEK_DATA or SEEK_HOLE
    fifile information
    +
    +
    + +

    Definition at line 1303 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ mkdir

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::mkdir)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
    +
    +

    Create a directory

    +

    Valid replies: fuse_reply_entry fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    parentinode number of the parent directory
    nameto create
    modewith which to create the new file
    +
    +
    + +

    Definition at line 391 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ mknod

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::mknod)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
    +
    +

    Create file node

    +

    Create a regular file, character device, block device, fifo or socket node.

    +

    Valid replies: fuse_reply_entry fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    parentinode number of the parent directory
    nameto create
    modefile type and mode with which to create the new file
    rdevthe device number (only valid if created file is a device)
    +
    +
    + +

    Definition at line 376 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ open

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::open)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    +

    Open a file

    +

    Open flags are available in fi->flags. The following rules apply.

    +
      +
    • Creation (O_CREAT, O_EXCL, O_NOCTTY) flags will be filtered out / handled by the kernel.
    • +
    • Access modes (O_RDONLY, O_WRONLY, O_RDWR) should be used by the filesystem to check if the operation is permitted. If the -o default_permissions mount option is given, this check is already done by the kernel before calling open() and may thus be omitted by the filesystem.
    • +
    • When writeback caching is enabled, the kernel may send read requests even for files opened with O_WRONLY. The filesystem should be prepared to handle this.
    • +
    • When writeback caching is disabled, the filesystem is expected to properly handle the O_APPEND flag and ensure that each write is appending to the end of the file.
    • +
    • When writeback caching is enabled, the kernel will handle O_APPEND. However, unless all changes to the file come through the kernel this will not work reliably. The filesystem should thus either ignore the O_APPEND flag (and let the kernel handle it), or return an error (indicating that reliably O_APPEND is not available).
    • +
    +

    Filesystem may store an arbitrary file handle (pointer, index, etc) in fi->fh, and use this in other all other file operations (read, write, flush, release, fsync).

    +

    Filesystem may also implement stateless file I/O and not store anything in fi->fh.

    +

    There are also some flags (direct_io, keep_cache) which the filesystem may set in fi, to change the way the file is opened. See fuse_file_info structure in <fuse_common.h> for more details.

    +

    If this request is answered with an error code of ENOSYS and FUSE_CAP_NO_OPEN_SUPPORT is set in fuse_conn_info.capable, this is treated as success and future calls to open and release will also succeed without being sent to the filesystem process.

    +

    To get this behavior without providing an opendir handler, you may set FUSE_CAP_NO_OPEN_SUPPORT in fuse_conn_info.want on supported kernels to automatically get the zero message open().

    +

    If this callback is not provided and FUSE_CAP_NO_OPEN_SUPPORT is not set in fuse_conn_info.want then an empty reply will be sent.

    +

    Valid replies: fuse_reply_open fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    +
    +
    + +

    Definition at line 554 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ opendir

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::opendir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    +

    Open a directory

    +

    Filesystem may store an arbitrary file handle (pointer, index, etc) in fi->fh, and use this in other all other directory stream operations (readdir, releasedir, fsyncdir).

    +

    If this request is answered with an error code of ENOSYS and FUSE_CAP_NO_OPENDIR_SUPPORT is set in fuse_conn_info.capable, this is treated as success and future calls to opendir and releasedir will also succeed without being sent to the filesystem process. In addition, the kernel will cache readdir results as if opendir returned FOPEN_KEEP_CACHE | FOPEN_CACHE_DIR.

    +

    To get this behavior without providing an opendir handler, you may set FUSE_CAP_NO_OPENDIR_SUPPORT in fuse_conn_info.want on supported kernels to automatically get the zero message opendir().

    +

    If this callback is not provided and FUSE_CAP_NO_OPENDIR_SUPPORT is not set in fuse_conn_info.want then an empty reply will be sent.

    +

    Valid replies: fuse_reply_open fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    +
    +
    + +

    Definition at line 734 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ poll

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::poll)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph)
    +
    +

    Poll for IO readiness

    +

    The client should immediately respond with fuse_reply_poll(), setting revents appropriately according to which events are ready.

    +

    Additionally, if ph is non-NULL, the client must retain it and notify when all future IO readiness events occur by calling fuse_lowlevel_notify_poll() with the specified ph.

    +

    Regardless of the number of times poll with a non-NULL ph is received, a single notify_poll is enough to service all. (Notifying more times incurs overhead but doesn't harm correctness.) Any additional received handles can be immediately destroyed.

    +

    The callee is responsible for destroying ph with fuse_pollhandle_destroy() when no longer in use.

    +

    If this request is answered with an error code of ENOSYS, this is treated as success (with a kernel-defined default poll-mask) and future calls to poll() will succeed the same way without being send to the filesystem process.

    +

    Valid replies: fuse_reply_poll fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    phpoll handle to be used for notification
    +
    +
    + +

    Definition at line 1117 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ read

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::read)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
    +
    +

    Read data

    +

    Read should send exactly the number of bytes requested except on EOF or error, otherwise the rest of the data will be substituted with zeroes. An exception to this is when the file has been opened in 'direct_io' mode, in which case the return value of the read system call will reflect the return value of this operation.

    +

    fi->fh will contain the value set by the open method, or will be undefined if the open method didn't set any value.

    +

    Valid replies: fuse_reply_buf fuse_reply_iov fuse_reply_data fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    sizenumber of bytes to read
    offoffset to read from
    fifile information
    +
    +
    + +

    Definition at line 582 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ readdir

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::readdir)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
    +
    +

    Read directory

    +

    Send a buffer filled using fuse_add_direntry(), with size not exceeding the requested size. Send an empty buffer on end of stream.

    +

    fi->fh will contain the value set by the opendir method, or will be undefined if the opendir method didn't set any value.

    +

    Returning a directory entry from readdir() does not affect its lookup count.

    +

    If off_t is non-zero, then it will correspond to one of the off_t values that was previously returned by readdir() for the same directory handle. In this case, readdir() should skip over entries coming before the position defined by the off_t value. If entries are added or removed while the directory handle is open, the filesystem may still include the entries that have been removed, and may not report the entries that have been created. However, addition or removal of entries must never cause readdir() to skip over unrelated entries or to report them more than once. This means that off_t can not be a simple index that enumerates the entries that have been returned but must contain sufficient information to uniquely determine the next directory entry to return even when the set of entries is changing.

    +

    The function does not have to report the '.' and '..' entries, but is allowed to do so. Note that, if readdir does not return '.' or '..', they will not be implicitly returned, and this behavior is observable by the caller.

    +

    Valid replies: fuse_reply_buf fuse_reply_data fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    sizemaximum number of bytes to send
    offoffset to continue reading the directory stream
    fifile information
    +
    +
    + +

    Definition at line 780 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ readdirplus

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::readdirplus)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
    +
    +

    Read directory with attributes

    +

    Send a buffer filled using fuse_add_direntry_plus(), with size not exceeding the requested size. Send an empty buffer on end of stream.

    +

    fi->fh will contain the value set by the opendir method, or will be undefined if the opendir method didn't set any value.

    +

    In contrast to readdir() (which does not affect the lookup counts), the lookup count of every entry returned by readdirplus(), except "." and "..", is incremented by one.

    +

    Valid replies: fuse_reply_buf fuse_reply_data fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    sizemaximum number of bytes to send
    offoffset to continue reading the directory stream
    fifile information
    +
    +
    + +

    Definition at line 1246 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ readlink

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::readlink)(fuse_req_t req, fuse_ino_t ino)
    +
    +

    Read symbolic link

    +

    Valid replies: fuse_reply_readlink fuse_reply_err

    +
    Parameters
    + + + +
    reqrequest handle
    inothe inode number
    +
    +
    + +

    Definition at line 358 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ release

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::release)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    +

    Release an open file

    +

    Release is called when there are no more references to an open file: all file descriptors are closed and all memory mappings are unmapped.

    +

    For every open call there will be exactly one release call (unless the filesystem is force-unmounted).

    +

    The filesystem may reply with an error, but error values are not returned to close() or munmap() which triggered the release.

    +

    fi->fh will contain the value set by the open method, or will be undefined if the open method didn't set any value. fi->flags will contain the same flags as for open.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    +
    +
    + +

    Definition at line 680 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ releasedir

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::releasedir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    +

    Release an open directory

    +

    For every opendir call there will be exactly one releasedir call (unless the filesystem is force-unmounted).

    +

    fi->fh will contain the value set by the opendir method, or will be undefined if the opendir method didn't set any value.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    +
    +
    + +

    Definition at line 799 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ removexattr

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::removexattr)(fuse_req_t req, fuse_ino_t ino, const char *name)
    +
    +

    Remove an extended attribute

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EOPNOTSUPP, i.e. all future removexattr() requests will fail with EOPNOTSUPP without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    nameof the extended attribute
    +
    +
    + +

    Definition at line 929 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ rename

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::rename)(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
    +
    +

    Rename a file

    +

    If the target exists it should be atomically replaced. If the target's inode's lookup count is non-zero, the file system is expected to postpone any removal of the inode until the lookup count reaches zero (see description of the forget function).

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EINVAL, i.e. all future bmap requests will fail with EINVAL without being send to the filesystem process.

    +

    flags may be RENAME_EXCHANGE or RENAME_NOREPLACE. If RENAME_NOREPLACE is specified, the filesystem must not overwrite newname if it exists and return an error instead. If RENAME_EXCHANGE is specified, the filesystem must atomically exchange the two files, i.e. both must exist and neither may be deleted.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    parentinode number of the old parent directory
    nameold name
    newparentinode number of the new parent directory
    newnamenew name
    +
    +
    +

    Rename a file

    +

    If the target exists it should be atomically replaced. If the target's inode's lookup count is non-zero, the file system is expected to postpone any removal of the inode until the lookup count reaches zero (see description of the forget function).

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EINVAL, i.e. all future rename requests will fail with EINVAL without being send to the filesystem process.

    +

    flags may be RENAME_EXCHANGE or RENAME_NOREPLACE. If RENAME_NOREPLACE is specified, the filesystem must not overwrite newname if it exists and return an error instead. If RENAME_EXCHANGE is specified, the filesystem must atomically exchange the two files, i.e. both must exist and neither may be deleted.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    parentinode number of the old parent directory
    nameold name
    newparentinode number of the new parent directory
    newnamenew name
    +
    +
    + +

    Definition at line 472 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ retrieve_reply

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::retrieve_reply)(fuse_req_t req, void *cookie, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv)
    +
    +

    Callback function for the retrieve request

    +

    Valid replies: fuse_reply_none

    +
    Parameters
    + + + + + + +
    reqrequest handle
    cookieuser data supplied to fuse_lowlevel_notify_retrieve()
    inothe inode number supplied to fuse_lowlevel_notify_retrieve()
    offsetthe offset supplied to fuse_lowlevel_notify_retrieve()
    bufvthe buffer containing the returned data
    +
    +
    + +

    Definition at line 1163 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ rmdir

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::rmdir)(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    +

    Remove a directory

    +

    If the directory's inode's lookup count is non-zero, the file system is expected to postpone any removal of the inode until the lookup count reaches zero (see description of the forget function).

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    parentinode number of the parent directory
    nameto remove
    +
    +
    + +

    Definition at line 426 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ setattr

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::setattr)(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
    +
    +

    Set file attributes

    +

    In the 'attr' argument only members indicated by the 'to_set' bitmask contain valid values. Other members contain undefined values.

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits if the file size or owner is being changed.

    +

    This method will not be called to update st_atime or st_ctime implicitly (eg. after a read() request), and only be called to implicitly update st_mtime if writeback caching is active. It is the filesystem's responsibility to update these timestamps when needed, and (if desired) to implement mount options like noatime or relatime.

    +

    If the setattr was invoked from the ftruncate() system call under Linux kernel versions 2.6.15 or later, the fi->fh will contain the value set by the open method or will be undefined if the open method didn't set any value. Otherwise (not ftruncate call, or kernel version earlier than 2.6.15) the fi parameter will be NULL.

    +

    Valid replies: fuse_reply_attr fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    attrthe attributes
    to_setbit mask of attributes which should be set
    fifile information, or NULL
    +
    +
    + +

    Definition at line 345 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ setlk

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
    +
    +

    Acquire, modify or release a POSIX file lock

    +

    For POSIX threads (NPTL) there's a 1-1 relation between pid and owner, but otherwise this is not always the case. For checking lock ownership, 'fi->owner' must be used. The l_pid field in 'struct flock' should only be used to fill in this field in getlk().

    +

    Note: if the locking methods are not implemented, the kernel will still allow file locking to work locally. Hence these are only interesting for network filesystems and similar.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    lockthe region/type to set
    sleeplocking operation may sleep
    +
    +
    + +

    Definition at line 1020 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ setxattr

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::setxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags)
    +
    +

    Set an extended attribute

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EOPNOTSUPP, i.e. all future setxattr() requests will fail with EOPNOTSUPP without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    + +

    Definition at line 850 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ statfs

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::statfs)(fuse_req_t req, fuse_ino_t ino)
    +
    +

    Get file system statistics

    +

    Valid replies: fuse_reply_statfs fuse_reply_err

    +
    Parameters
    + + + +
    reqrequest handle
    inothe inode number, zero means "undefined"
    +
    +
    + +

    Definition at line 837 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ statx

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::statx)(fuse_req_t req, fuse_ino_t ino, int flags, int mask, struct fuse_file_info *fi)
    +
    +

    Get extended file attributes.

    +

    Valid replies: fuse_reply_statx fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    flagsbitmask of requested flags
    maskbitmask of requested fields
    fifile information (may be NULL)
    +
    +
    + +

    Definition at line 1342 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ symlink

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::symlink)(fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
    +
    +

    Create a symbolic link

    +

    Valid replies: fuse_reply_entry fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    linkthe contents of the symbolic link
    parentinode number of the parent directory
    nameto create
    +
    +
    + +

    Definition at line 440 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ tmpfile

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::tmpfile)(fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi)
    +
    +

    Create a tempfile

    +

    Tempfile means an anonymous file. It can be made into a normal file later by using linkat or such.

    +

    If this is answered with an error ENOSYS this is treated by the kernel as a permanent failure and it will disable the feature and not ask again.

    +

    Valid replies: fuse_reply_create fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    parentinode number of the parent directory
    modefile type and mode with which to create the new file
    fifile information
    +
    +
    + +

    Definition at line 1325 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ unlink

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::unlink)(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    +

    Remove a file

    +

    If the file's inode's lookup count is non-zero, the file system is expected to postpone any removal of the inode until the lookup count reaches zero (see description of the forget function).

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    parentinode number of the parent directory
    nameto remove
    +
    +
    + +

    Definition at line 409 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ write

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::write)(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi)
    +
    +

    Write data

    +

    Write should return exactly the number of bytes requested except on error. An exception to this is when the file has been opened in 'direct_io' mode, in which case the return value of the write system call will reflect the return value of this operation.

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits.

    +

    fi->fh will contain the value set by the open method, or will be undefined if the open method didn't set any value.

    +

    Valid replies: fuse_reply_write fuse_reply_err

    +
    Parameters
    + + + + + + + +
    reqrequest handle
    inothe inode number
    bufdata to write
    sizenumber of bytes to write
    offoffset to write to
    fifile information
    +
    +
    + +

    Definition at line 611 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ write_buf

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::write_buf)(fuse_req_t req, fuse_ino_t ino, struct fuse_bufvec *bufv, off_t off, struct fuse_file_info *fi)
    +
    +

    Write data made available in a buffer

    +

    This is a more generic version of the ->write() method. If FUSE_CAP_SPLICE_READ is set in fuse_conn_info.want and the kernel supports splicing from the fuse device, then the data will be made available in pipe for supporting zero copy data transfer.

    +

    buf->count is guaranteed to be one (and thus buf->idx is always zero). The write_buf handler must ensure that bufv->off is correctly updated (reflecting the number of bytes read from bufv->buf[0]).

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits.

    +

    Valid replies: fuse_reply_write fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    bufvbuffer containing the data
    offoffset to write to
    fifile information
    +
    +
    + +

    Definition at line 1147 of file fuse_lowlevel.h.

    + +
    +
    +
    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__module.html b/doc/html/structfuse__module.html new file mode 100644 index 0000000..7142c27 --- /dev/null +++ b/doc/html/structfuse__module.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: fuse_module Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    +
    fuse_module Struct Reference
    +
    +
    + +

    #include <fuse_i.h>

    +

    Detailed Description

    +

    Filesystem module

    +

    Filesystem modules are registered with the FUSE_REGISTER_MODULE() macro.

    + +

    Definition at line 111 of file fuse_i.h.

    +

    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__operations.html b/doc/html/structfuse__operations.html new file mode 100644 index 0000000..fcad8e8 --- /dev/null +++ b/doc/html/structfuse__operations.html @@ -0,0 +1,969 @@ + + + + + + + +libfuse: fuse_operations Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_operations Struct Reference
    +
    +
    + +

    #include <fuse.h>

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    +Data Fields

    int(* getattr )(const char *, struct stat *, struct fuse_file_info *fi)
     
    int(* readlink )(const char *, char *, size_t)
     
    int(* mknod )(const char *, mode_t, dev_t)
     
    int(* mkdir )(const char *, mode_t)
     
    int(* unlink )(const char *)
     
    int(* rmdir )(const char *)
     
    int(* symlink )(const char *, const char *)
     
    int(* rename )(const char *, const char *, unsigned int flags)
     
    int(* link )(const char *, const char *)
     
    int(* chmod )(const char *, mode_t, struct fuse_file_info *fi)
     
    int(* chown )(const char *, uid_t, gid_t, struct fuse_file_info *fi)
     
    int(* truncate )(const char *, off_t, struct fuse_file_info *fi)
     
    int(* open )(const char *, struct fuse_file_info *)
     
    int(* read )(const char *, char *, size_t, off_t, struct fuse_file_info *)
     
    int(* write )(const char *, const char *, size_t, off_t, struct fuse_file_info *)
     
    int(* statfs )(const char *, struct statvfs *)
     
    int(* flush )(const char *, struct fuse_file_info *)
     
    int(* release )(const char *, struct fuse_file_info *)
     
    int(* fsync )(const char *, int, struct fuse_file_info *)
     
    int(* setxattr )(const char *, const char *, const char *, size_t, int)
     
    int(* getxattr )(const char *, const char *, char *, size_t)
     
    int(* listxattr )(const char *, char *, size_t)
     
    int(* removexattr )(const char *, const char *)
     
    int(* opendir )(const char *, struct fuse_file_info *)
     
    int(* readdir )(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags)
     
    int(* releasedir )(const char *, struct fuse_file_info *)
     
    int(* fsyncdir )(const char *, int, struct fuse_file_info *)
     
    void *(* init )(struct fuse_conn_info *conn, struct fuse_config *cfg)
     
    void(* destroy )(void *private_data)
     
    int(* access )(const char *, int)
     
    int(* create )(const char *, mode_t, struct fuse_file_info *)
     
    int(* lock )(const char *, struct fuse_file_info *, int cmd, struct flock *)
     
    int(* utimens )(const char *, const struct timespec tv[2], struct fuse_file_info *fi)
     
    int(* bmap )(const char *, size_t blocksize, uint64_t *idx)
     
    int(* ioctl )(const char *, unsigned int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data)
     
    int(* poll )(const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp)
     
    int(* write_buf )(const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *)
     
    int(* read_buf )(const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *)
     
    int(* flock )(const char *, struct fuse_file_info *, int op)
     
    int(* fallocate )(const char *, int, off_t, off_t, struct fuse_file_info *)
     
    ssize_t(* copy_file_range )(const char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags)
     
    off_t(* lseek )(const char *, off_t off, int whence, struct fuse_file_info *)
     
    int(* statx )(const char *path, int flags, int mask, struct statx *stxbuf, struct fuse_file_info *fi)
     
    +

    Detailed Description

    +

    The file system operations:

    +

    Most of these should work very similarly to the well known UNIX file system operations. A major exception is that instead of returning an error in 'errno', the operation should return the negated error value (-errno) directly.

    +

    All methods are optional, but some are essential for a useful filesystem (e.g. getattr). Open, flush, release, fsync, opendir, releasedir, fsyncdir, access, create, truncate, lock, init and destroy are special purpose methods, without which a full featured filesystem can still be implemented.

    +

    In general, all methods are expected to perform any necessary permission checking. However, a filesystem may delegate this task to the kernel by passing the default_permissions mount option to fuse_new(). In this case, methods will only be called if the kernel's permission check has succeeded.

    +

    Almost all operations take a path which can be of any length.

    + +

    Definition at line 349 of file fuse.h.

    +

    Field Documentation

    + +

    ◆ access

    + +
    +
    + + + + +
    int(* fuse_operations::access)(const char *, int)
    +
    +

    Check file access permissions

    +

    This will be called for the access() system call. If the 'default_permissions' mount option is given, this method is not called.

    +

    This method is not called under Linux kernel versions 2.4.x

    + +

    Definition at line 660 of file fuse.h.

    + +
    +
    + +

    ◆ bmap

    + +
    +
    + + + + +
    int(* fuse_operations::bmap)(const char *, size_t blocksize, uint64_t *idx)
    +
    +

    Map block index within file to block index within device

    +

    Note: This makes sense only for block device backed filesystems mounted with the 'blkdev' option

    + +

    Definition at line 728 of file fuse.h.

    + +
    +
    + +

    ◆ chmod

    + +
    +
    + + + + +
    int(* fuse_operations::chmod)(const char *, mode_t, struct fuse_file_info *fi)
    +
    +

    Change the permission bits of a file

    +

    fi will always be NULL if the file is not currently open, but may also be NULL if the file is open.

    + +

    Definition at line 417 of file fuse.h.

    + +
    +
    + +

    ◆ chown

    + +
    +
    + + + + +
    int(* fuse_operations::chown)(const char *, uid_t, gid_t, struct fuse_file_info *fi)
    +
    +

    Change the owner and group of a file

    +

    fi will always be NULL if the file is not currently open, but may also be NULL if the file is open.

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits.

    + +

    Definition at line 427 of file fuse.h.

    + +
    +
    + +

    ◆ copy_file_range

    + +
    +
    + + + + +
    ssize_t(* fuse_operations::copy_file_range)(const char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags)
    +
    +

    Copy a range of data from one file to another

    +

    Performs an optimized copy between two file descriptors without the additional cost of transferring data through the FUSE kernel module to user space (glibc) and then back into the FUSE filesystem again.

    +

    In case this method is not implemented, applications are expected to fall back to a regular file copy. (Some glibc versions did this emulation automatically, but the emulation has been removed from all glibc release branches.)

    + +

    Definition at line 843 of file fuse.h.

    + +
    +
    + +

    ◆ create

    + +
    +
    + + + + +
    int(* fuse_operations::create)(const char *, mode_t, struct fuse_file_info *)
    +
    +

    Create and open a file

    +

    If the file does not exist, first create it with the specified mode, and then open it.

    +

    If this method is not implemented or under Linux kernel versions earlier than 2.6.15, the mknod() and open() methods will be called instead.

    + +

    Definition at line 672 of file fuse.h.

    + +
    +
    + +

    ◆ destroy

    + +
    +
    + + + + +
    void(* fuse_operations::destroy)(void *private_data)
    +
    +

    Clean up filesystem

    +

    Called on filesystem exit.

    + +

    Definition at line 649 of file fuse.h.

    + +
    +
    + +

    ◆ fallocate

    + +
    +
    + + + + +
    int(* fuse_operations::fallocate)(const char *, int, off_t, off_t, struct fuse_file_info *)
    +
    +

    Allocates space for an open file

    +

    This function ensures that required space is allocated for specified file. If this function returns success then any subsequent write request to specified range is guaranteed not to fail because of lack of space on the file system media.

    + +

    Definition at line 828 of file fuse.h.

    + +
    +
    + +

    ◆ flock

    + +
    +
    + + + + +
    int(* fuse_operations::flock)(const char *, struct fuse_file_info *, int op)
    +
    +

    Perform BSD file locking operation

    +

    The op argument will be either LOCK_SH, LOCK_EX or LOCK_UN

    +

    Nonblocking requests will be indicated by ORing LOCK_NB to the above operations

    +

    For more information see the flock(2) manual page.

    +

    Additionally fi->owner will be set to a value unique to this open file. This same value will be supplied to ->release() when the file is released.

    +

    Note: if this method is not implemented, the kernel will still allow file locking to work locally. Hence it is only interesting for network filesystems and similar.

    + +

    Definition at line 818 of file fuse.h.

    + +
    +
    + +

    ◆ flush

    + +
    +
    + + + + +
    int(* fuse_operations::flush)(const char *, struct fuse_file_info *)
    +
    +

    Possibly flush cached data

    +

    BIG NOTE: This is not equivalent to fsync(). It's not a request to sync dirty data.

    +

    Flush is called on each close() of a file descriptor, as opposed to release which is called on the close of the last file descriptor for a file. Under Linux, errors returned by flush() will be passed to userspace as errors from close(), so flush() is a good place to write back any cached dirty data. However, many applications ignore errors on close(), and on non-Linux systems, close() may succeed even if flush() returns an error. For these reasons, filesystems should not assume that errors returned by flush will ever be noticed or even delivered.

    +

    NOTE: The flush() method may be called more than once for each open(). This happens if more than one file descriptor refers to an open file handle, e.g. due to dup(), dup2() or fork() calls. It is not possible to determine if a flush is final, so each flush should be treated equally. Multiple write-flush sequences are relatively rare, so this shouldn't be a problem.

    +

    Filesystems shouldn't assume that flush will be called at any particular point. It may be called more times than expected, or not at all.

    + +

    Definition at line 546 of file fuse.h.

    + +
    +
    + +

    ◆ fsync

    + +
    +
    + + + + +
    int(* fuse_operations::fsync)(const char *, int, struct fuse_file_info *)
    +
    +

    Synchronize file contents

    +

    If the datasync parameter is non-zero, then only the user data should be flushed, not the meta data.

    + +

    Definition at line 567 of file fuse.h.

    + +
    +
    + +

    ◆ fsyncdir

    + +
    +
    + + + + +
    int(* fuse_operations::fsyncdir)(const char *, int, struct fuse_file_info *)
    +
    +

    Synchronize directory contents

    +

    If the directory has been removed after the call to opendir, the path parameter will be NULL.

    +

    If the datasync parameter is non-zero, then only the user data should be flushed, not the meta data

    + +

    Definition at line 631 of file fuse.h.

    + +
    +
    + +

    ◆ getattr

    + +
    +
    + + + + +
    int(* fuse_operations::getattr)(const char *, struct stat *, struct fuse_file_info *fi)
    +
    +

    Get file attributes.

    +

    Similar to stat(). The 'st_dev' and 'st_blksize' fields are ignored. The 'st_ino' field is ignored except if the 'use_ino' mount option is given. In that case it is passed to userspace, but libfuse and the kernel will still assign a different inode for internal use (called the "nodeid").

    +

    fi will always be NULL if the file is not currently open, but may also be NULL if the file is open.

    + +

    Definition at line 361 of file fuse.h.

    + +
    +
    + +

    ◆ getxattr

    + +
    +
    + + + + +
    int(* fuse_operations::getxattr)(const char *, const char *, char *, size_t)
    +
    +

    Get extended attributes

    + +

    Definition at line 573 of file fuse.h.

    + +
    +
    + +

    ◆ init

    + +
    +
    + + + + +
    void *(* fuse_operations::init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
    +
    +

    Initialize filesystem

    +

    The return value will passed in the private_data field of struct fuse_context to all file operations, and as a parameter to the destroy() method. It overrides the initial value provided to fuse_main() / fuse_new().

    + +

    Definition at line 641 of file fuse.h.

    + +
    +
    + +

    ◆ ioctl

    + +
    +
    + + + + +
    int(* fuse_operations::ioctl)(const char *, unsigned int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data)
    +
    +

    Ioctl

    +

    flags will have FUSE_IOCTL_COMPAT set for 32bit ioctls in 64bit environment. The size and direction of data is determined by IOC*() decoding of cmd. For _IOC_NONE, data will be NULL, for _IOC_WRITE data is out area, for _IOC_READ in area and if both are set in/out area. In all non-NULL cases, the area is of _IOC_SIZE(cmd) bytes.

    +

    If flags has FUSE_IOCTL_DIR then the fuse_file_info refers to a directory file handle.

    +

    Note : the unsigned long request submitted by the application is truncated to 32 bits.

    + +

    Definition at line 750 of file fuse.h.

    + +
    +
    + +

    ◆ link

    + +
    +
    + + + + +
    int(* fuse_operations::link)(const char *, const char *)
    +
    +

    Create a hard link to a file

    + +

    Definition at line 410 of file fuse.h.

    + +
    +
    + +

    ◆ listxattr

    + +
    +
    + + + + +
    int(* fuse_operations::listxattr)(const char *, char *, size_t)
    +
    +

    List extended attributes

    + +

    Definition at line 576 of file fuse.h.

    + +
    +
    + +

    ◆ lock

    + +
    +
    + + + + +
    int(* fuse_operations::lock)(const char *, struct fuse_file_info *, int cmd, struct flock *)
    +
    +

    Perform POSIX file locking operation

    +

    The cmd argument will be either F_GETLK, F_SETLK or F_SETLKW.

    +

    For the meaning of fields in 'struct flock' see the man page for fcntl(2). The l_whence field will always be set to SEEK_SET.

    +

    For checking lock ownership, the 'fuse_file_info->owner' argument must be used.

    +

    For F_GETLK operation, the library will first check currently held locks, and if a conflicting lock is found it will return information without calling this method. This ensures, that for local locks the l_pid field is correctly filled in. The results may not be accurate in case of race conditions and in the presence of hard links, but it's unlikely that an application would rely on accurate GETLK results in these cases. If a conflicting lock is not found, this method will be called, and the filesystem may fill out l_pid by a meaningful value, or it may leave this field zero.

    +

    For F_SETLK and F_SETLKW the l_pid field will be set to the pid of the process performing the locking operation.

    +

    Note: if this method is not implemented, the kernel will still allow file locking to work locally. Hence it is only interesting for network filesystems and similar.

    + +

    Definition at line 704 of file fuse.h.

    + +
    +
    + +

    ◆ lseek

    + +
    +
    + + + + +
    off_t(* fuse_operations::lseek)(const char *, off_t off, int whence, struct fuse_file_info *)
    +
    +

    Find next data or hole after the specified offset

    + +

    Definition at line 852 of file fuse.h.

    + +
    +
    + +

    ◆ mkdir

    + +
    +
    + + + + +
    int(* fuse_operations::mkdir)(const char *, mode_t)
    +
    +

    Create a directory

    +

    Note that the mode argument may not have the type specification bits set, i.e. S_ISDIR(mode) can be false. To obtain the correct directory type bits use mode|S_IFDIR

    + +

    Definition at line 387 of file fuse.h.

    + +
    +
    + +

    ◆ mknod

    + +
    +
    + + + + +
    int(* fuse_operations::mknod)(const char *, mode_t, dev_t)
    +
    +

    Create a file node

    +

    This is called for creation of all non-directory, non-symlink nodes. If the filesystem defines a create() method, then for regular files that will be called instead.

    + +

    Definition at line 379 of file fuse.h.

    + +
    +
    + +

    ◆ open

    + +
    +
    + + + + +
    int(* fuse_operations::open)(const char *, struct fuse_file_info *)
    +
    +

    Open a file

    +

    Open flags are available in fi->flags. The following rules apply.

    +
      +
    • Creation (O_CREAT, O_EXCL, O_NOCTTY) flags will be filtered out / handled by the kernel.
    • +
    • Access modes (O_RDONLY, O_WRONLY, O_RDWR, O_EXEC, O_SEARCH) should be used by the filesystem to check if the operation is permitted. If the -o default_permissions mount option is given, this check is already done by the kernel before calling open() and may thus be omitted by the filesystem.
    • +
    • When writeback caching is enabled, the kernel may send read requests even for files opened with O_WRONLY. The filesystem should be prepared to handle this.
    • +
    • When writeback caching is disabled, the filesystem is expected to properly handle the O_APPEND flag and ensure that each write is appending to the end of the file.
    • +
    • When writeback caching is enabled, the kernel will handle O_APPEND. However, unless all changes to the file come through the kernel this will not work reliably. The filesystem should thus either ignore the O_APPEND flag (and let the kernel handle it), or return an error (indicating that reliably O_APPEND is not available).
    • +
    +

    Filesystem may store an arbitrary file handle (pointer, index, etc) in fi->fh, and use this in other all other file operations (read, write, flush, release, fsync).

    +

    Filesystem may also implement stateless file I/O and not store anything in fi->fh.

    +

    There are also some flags (direct_io, keep_cache) which the filesystem may set in fi, to change the way the file is opened. See fuse_file_info structure in <fuse_common.h> for more details.

    +

    If this request is answered with an error code of ENOSYS and FUSE_CAP_NO_OPEN_SUPPORT is set in fuse_conn_info.capable, this is treated as success and future calls to open will also succeed without being sent to the filesystem process.

    + +

    Definition at line 486 of file fuse.h.

    + +
    +
    + +

    ◆ opendir

    + +
    +
    + + + + +
    int(* fuse_operations::opendir)(const char *, struct fuse_file_info *)
    +
    +

    Open directory

    +

    Unless the 'default_permissions' mount option is given, this method should check if opendir is permitted for this directory. Optionally opendir may also return an arbitrary filehandle in the fuse_file_info structure, which will be passed to readdir, releasedir and fsyncdir.

    + +

    Definition at line 589 of file fuse.h.

    + +
    +
    + +

    ◆ poll

    + +
    +
    + + + + +
    int(* fuse_operations::poll)(const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp)
    +
    +

    Poll for IO readiness events

    +

    Note: If ph is non-NULL, the client should notify when IO readiness events occur by calling fuse_notify_poll() with the specified ph.

    +

    Regardless of the number of times poll with a non-NULL ph is received, single notification is enough to clear all. Notifying more times incurs overhead but doesn't harm correctness.

    +

    The callee is responsible for destroying ph with fuse_pollhandle_destroy() when no longer in use.

    + +

    Definition at line 769 of file fuse.h.

    + +
    +
    + +

    ◆ read

    + +
    +
    + + + + +
    int(* fuse_operations::read)(const char *, char *, size_t, off_t, struct fuse_file_info *)
    +
    +

    Read data from an open file

    +

    Read should return exactly the number of bytes requested except on EOF or error, otherwise the rest of the data will be substituted with zeroes. An exception to this is when the 'direct_io' mount option is specified, in which case the return value of the read system call will reflect the return value of this operation.

    + +

    Definition at line 497 of file fuse.h.

    + +
    +
    + +

    ◆ read_buf

    + +
    +
    + + + + +
    int(* fuse_operations::read_buf)(const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *)
    +
    +

    Store data from an open file in a buffer

    +

    Similar to the read() method, but data is stored and returned in a generic buffer.

    +

    No actual copying of data has to take place, the source file descriptor may simply be stored in the buffer for later data transfer.

    +

    The buffer must be allocated dynamically and stored at the location pointed to by bufp. If the buffer contains memory regions, they too must be allocated using malloc(). The allocated memory will be freed by the caller.

    + +

    Definition at line 798 of file fuse.h.

    + +
    +
    + +

    ◆ readdir

    + +
    +
    + + + + +
    int(* fuse_operations::readdir)(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags)
    +
    +

    Read directory

    +

    The filesystem may choose between two modes of operation:

    +

    1) The readdir implementation ignores the offset parameter, and passes zero to the filler function's offset. The filler function will not return '1' (unless an error happens), so the whole directory is read in a single readdir operation.

    +

    2) The readdir implementation keeps track of the offsets of the directory entries. It uses the offset parameter and always passes non-zero offset to the filler function. When the buffer is full (or an error happens) the filler function will return '1'.

    +

    When FUSE_READDIR_PLUS is not set, only some parameters of the fill function (the fuse_fill_dir_t parameter) are actually used: The file type (which is part of stat::st_mode) is used. And if fuse_config::use_ino is set, the inode (stat::st_ino) is also used. The other fields are ignored when FUSE_READDIR_PLUS is not set.

    + +

    Definition at line 613 of file fuse.h.

    + +
    +
    + +

    ◆ readlink

    + +
    +
    + + + + +
    int(* fuse_operations::readlink)(const char *, char *, size_t)
    +
    +

    Read the target of a symbolic link

    +

    The buffer should be filled with a null terminated string. The buffer size argument includes the space for the terminating null character. If the linkname is too long to fit in the buffer, it should be truncated. The return value should be 0 for success.

    + +

    Definition at line 371 of file fuse.h.

    + +
    +
    + +

    ◆ release

    + +
    +
    + + + + +
    int(* fuse_operations::release)(const char *, struct fuse_file_info *)
    +
    +

    Release an open file

    +

    Release is called when there are no more references to an open file: all file descriptors are closed and all memory mappings are unmapped.

    +

    For every open() call there will be exactly one release() call with the same flags and file handle. It is possible to have a file opened more than once, in which case only the last release will mean, that no more reads/writes will happen on the file. The return value of release is ignored.

    + +

    Definition at line 560 of file fuse.h.

    + +
    +
    + +

    ◆ releasedir

    + +
    +
    + + + + +
    int(* fuse_operations::releasedir)(const char *, struct fuse_file_info *)
    +
    +

    Release directory

    +

    If the directory has been removed after the call to opendir, the path parameter will be NULL.

    + +

    Definition at line 621 of file fuse.h.

    + +
    +
    + +

    ◆ removexattr

    + +
    +
    + + + + +
    int(* fuse_operations::removexattr)(const char *, const char *)
    +
    +

    Remove extended attributes

    + +

    Definition at line 579 of file fuse.h.

    + +
    +
    + +

    ◆ rename

    + +
    +
    + + + + +
    int(* fuse_operations::rename)(const char *, const char *, unsigned int flags)
    +
    +

    Rename a file

    +

    flags may be RENAME_EXCHANGE or RENAME_NOREPLACE. If RENAME_NOREPLACE is specified, the filesystem must not overwrite newname if it exists and return an error instead. If RENAME_EXCHANGE is specified, the filesystem must atomically exchange the two files, i.e. both must exist and neither may be deleted.

    + +

    Definition at line 407 of file fuse.h.

    + +
    +
    + +

    ◆ rmdir

    + +
    +
    + + + + +
    int(* fuse_operations::rmdir)(const char *)
    +
    +

    Remove a directory

    + +

    Definition at line 393 of file fuse.h.

    + +
    +
    + +

    ◆ setxattr

    + +
    +
    + + + + +
    int(* fuse_operations::setxattr)(const char *, const char *, const char *, size_t, int)
    +
    +

    Set extended attributes

    + +

    Definition at line 570 of file fuse.h.

    + +
    +
    + +

    ◆ statfs

    + +
    +
    + + + + +
    int(* fuse_operations::statfs)(const char *, struct statvfs *)
    +
    +

    Get file system statistics

    +

    The 'f_favail', 'f_fsid' and 'f_flag' fields are ignored

    + +

    Definition at line 516 of file fuse.h.

    + +
    +
    + +

    ◆ statx

    + +
    +
    + + + + +
    int(* fuse_operations::statx)(const char *path, int flags, int mask, struct statx *stxbuf, struct fuse_file_info *fi)
    +
    +

    Get extended file attributes.

    +

    fi may be NULL.

    +

    If path is NULL, then the AT_EMPTY_PATH bit in flags will be already set.

    + +

    Definition at line 868 of file fuse.h.

    + +
    +
    + +

    ◆ symlink

    + +
    +
    + + + + +
    int(* fuse_operations::symlink)(const char *, const char *)
    +
    +

    Create a symbolic link

    + +

    Definition at line 396 of file fuse.h.

    + +
    +
    + +

    ◆ truncate

    + +
    +
    + + + + +
    int(* fuse_operations::truncate)(const char *, off_t, struct fuse_file_info *fi)
    +
    +

    Change the size of a file

    +

    fi will always be NULL if the file is not currently open, but may also be NULL if the file is open.

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits.

    + +

    Definition at line 437 of file fuse.h.

    + +
    +
    + +

    ◆ unlink

    + +
    +
    + + + + +
    int(* fuse_operations::unlink)(const char *)
    +
    +

    Remove a file

    + +

    Definition at line 390 of file fuse.h.

    + +
    +
    + +

    ◆ utimens

    + +
    +
    + + + + +
    int(* fuse_operations::utimens)(const char *, const struct timespec tv[2], struct fuse_file_info *fi)
    +
    +

    Change the access and modification times of a file with nanosecond resolution

    +

    This supersedes the old utime() interface. New applications should use this.

    +

    fi will always be NULL if the file is not currently open, but may also be NULL if the file is open.

    +

    See the utimensat(2) man page for details.

    + +

    Definition at line 719 of file fuse.h.

    + +
    +
    + +

    ◆ write

    + +
    +
    + + + + +
    int(* fuse_operations::write)(const char *, const char *, size_t, off_t, struct fuse_file_info *)
    +
    +

    Write data to an open file

    +

    Write should return exactly the number of bytes requested except on error. An exception to this is when the 'direct_io' mount option is specified (see read operation).

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits.

    + +

    Definition at line 509 of file fuse.h.

    + +
    +
    + +

    ◆ write_buf

    + +
    +
    + + + + +
    int(* fuse_operations::write_buf)(const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *)
    +
    +

    Write contents of buffer to an open file

    +

    Similar to the write() method, but data is supplied in a generic buffer. Use fuse_buf_copy() to transfer data to the destination.

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits.

    + +

    Definition at line 781 of file fuse.h.

    + +
    +
    +
    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__opt.html b/doc/html/structfuse__opt.html new file mode 100644 index 0000000..60fc00b --- /dev/null +++ b/doc/html/structfuse__opt.html @@ -0,0 +1,147 @@ + + + + + + + +libfuse: fuse_opt Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_opt Struct Reference
    +
    +
    + +

    #include <fuse_opt.h>

    + + + + + + + + +

    +Data Fields

    const char * templ
     
    unsigned long offset
     
    int value
     
    +

    Detailed Description

    +

    Option description

    +

    This structure describes a single option, and action associated with it, in case it matches.

    +

    More than one such match may occur, in which case the action for each match is executed.

    +

    There are three possible actions in case of a match:

    +

    i) An integer (int or unsigned) variable determined by 'offset' is set to 'value'

    +

    ii) The processing function is called, with 'value' as the key

    +

    iii) An integer (any) or string (char *) variable determined by 'offset' is set to the value of an option parameter

    +

    'offset' should normally be either set to

    +
      +
    • 'offsetof(struct foo, member)' actions i) and iii)
    • +
    • -1 action ii)
    • +
    +

    The 'offsetof()' macro is defined in the <stddef.h> header.

    +

    The template determines which options match, and also have an effect on the action. Normally the action is either i) or ii), but if a format is present in the template, then action iii) is performed.

    +

    The types of templates are:

    +

    1) "-x", "-foo", "--foo", "--foo-bar", etc. These match only themselves. Invalid values are "--" and anything beginning with "-o"

    +

    2) "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or the relevant option in a comma separated option list

    +

    3) "bar=", "--foo=", etc. These are variations of 1) and 2) which have a parameter

    +

    4) "bar=%s", "--foo=%lu", etc. Same matching as above but perform action iii).

    +

    5) "-x ", etc. Matches either "-xparam" or "-x param" as two separate arguments

    +

    6) "-x %s", etc. Combination of 4) and 5)

    +

    If the format is "%s", memory is allocated for the string unlike with scanf(). The previous value (if non-NULL) stored at the this location is freed.

    + +

    Definition at line 77 of file fuse_opt.h.

    +

    Field Documentation

    + +

    ◆ offset

    + +
    +
    + + + + +
    unsigned long fuse_opt::offset
    +
    +

    Offset of variable within 'data' parameter of fuse_opt_parse() or -1

    + +

    Definition at line 85 of file fuse_opt.h.

    + +
    +
    + +

    ◆ templ

    + +
    +
    + + + + +
    const char * fuse_opt::templ
    +
    +

    Matching template and optional parameter formatting

    + +

    Definition at line 79 of file fuse_opt.h.

    + +
    +
    + +

    ◆ value

    + +
    +
    + + + + +
    int fuse_opt::value
    +
    +

    Value to set the variable to, or to be passed as 'key' to the processing function. Ignored if template has a format

    + +

    Definition at line 91 of file fuse_opt.h.

    + +
    +
    +
    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__ring__pool.html b/doc/html/structfuse__ring__pool.html new file mode 100644 index 0000000..2bfa846 --- /dev/null +++ b/doc/html/structfuse__ring__pool.html @@ -0,0 +1,59 @@ + + + + + + + +libfuse: fuse_ring_pool Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    +
    fuse_ring_pool Struct Reference
    +
    +
    +

    Detailed Description

    +

    Main fuse_ring structure, holds all fuse-ring data

    + +

    Definition at line 72 of file fuse_uring.c.

    +

    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__supp__groups.html b/doc/html/structfuse__supp__groups.html new file mode 100644 index 0000000..9d67ecb --- /dev/null +++ b/doc/html/structfuse__supp__groups.html @@ -0,0 +1,62 @@ + + + + + + + +libfuse: fuse_supp_groups Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    +
    fuse_supp_groups Struct Reference
    +
    +
    + +

    #include <fuse_kernel.h>

    +

    Detailed Description

    +

    struct fuse_supp_groups - Supplementary group extension @nr_groups: number of supplementary groups @groups: flexible array of group IDs

    + +

    Definition at line 1184 of file fuse_kernel.h.

    +

    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__uring__cmd__req.html b/doc/html/structfuse__uring__cmd__req.html new file mode 100644 index 0000000..176d711 --- /dev/null +++ b/doc/html/structfuse__uring__cmd__req.html @@ -0,0 +1,61 @@ + + + + + + + +libfuse: fuse_uring_cmd_req Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    +
    fuse_uring_cmd_req Struct Reference
    +
    +
    + +

    #include <fuse_kernel.h>

    +

    Detailed Description

    +

    In the 80B command area of the SQE.

    + +

    Definition at line 1292 of file fuse_kernel.h.

    +

    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__uring__req__header.html b/doc/html/structfuse__uring__req__header.html new file mode 100644 index 0000000..061a43b --- /dev/null +++ b/doc/html/structfuse__uring__req__header.html @@ -0,0 +1,61 @@ + + + + + + + +libfuse: fuse_uring_req_header Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    +
    fuse_uring_req_header Struct Reference
    +
    +
    + +

    #include <fuse_kernel.h>

    +

    Detailed Description

    +

    Header for all fuse-io-uring requests

    + +

    Definition at line 1266 of file fuse_kernel.h.

    +

    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structlibfuse__version.html b/doc/html/structlibfuse__version.html new file mode 100644 index 0000000..bc5d67f --- /dev/null +++ b/doc/html/structlibfuse__version.html @@ -0,0 +1,62 @@ + + + + + + + +libfuse: libfuse_version Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    +
    libfuse_version Struct Reference
    +
    +
    + +

    #include <fuse_common.h>

    +

    Detailed Description

    +

    libfuse version a file system was compiled with. Should be filled in from defines in 'libfuse_config.h'

    + +

    Definition at line 949 of file fuse_common.h.

    +

    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/sync_off.png b/doc/html/sync_off.png new file mode 100644 index 0000000000000000000000000000000000000000..3b443fc62892114406e3d399421b2a881b897acc GIT binary patch literal 853 zcmV-b1FHOqP)oT|#XixUYy%lpuf3i8{fX!o zUyDD0jOrAiT^tq>fLSOOABs-#u{dV^F$b{L9&!2=9&RmV;;8s^x&UqB$PCj4FdKbh zoB1WTskPUPu05XzFbA}=KZ-GP1fPpAfSs>6AHb12UlR%-i&uOlTpFNS7{jm@mkU1V zh`nrXr~+^lsV-s1dkZOaI|kYyVj3WBpPCY{n~yd%u%e+d=f%`N0FItMPtdgBb@py; zq@v6NVArhyTC7)ULw-Jy8y42S1~4n(3LkrW8mW(F-4oXUP3E`e#g**YyqI7h-J2zK zK{m9##m4ri!7N>CqQqCcnI3hqo1I;Yh&QLNY4T`*ptiQGozK>FF$!$+84Z`xwmeMh zJ0WT+OH$WYFALEaGj2_l+#DC3t7_S`vHpSivNeFbP6+r50cO8iu)`7i%Z4BTPh@_m3Tk!nAm^)5Bqnr%Ov|Baunj#&RPtRuK& z4RGz|D5HNrW83-#ydk}tVKJrNmyYt-sTxLGlJY5nc&Re zU4SgHNPx8~Yxwr$bsju?4q&%T1874xxzq+_%?h8_ofw~(bld=o3iC)LUNR*BY%c0y zWd_jX{Y8`l%z+ol1$@Qa?Cy!(0CVIEeYpKZ`(9{z>3$CIe;pJDQk$m3p}$>xBm4lb zKo{4S)`wdU9Ba9jJbVJ0C=SOefZe%d$8=2r={nu<_^a3~>c#t_U6dye5)JrR(_a^E f@}b6j1K9lwFJq@>o)+Ry00000NkvXXu0mjfWa5j* literal 0 HcmV?d00001 diff --git a/doc/html/sync_on.png b/doc/html/sync_on.png new file mode 100644 index 0000000000000000000000000000000000000000..e08320fb64e6fa33b573005ed6d8fe294e19db76 GIT binary patch literal 845 zcmV-T1G4;yP)Y;xxyHF2B5Wzm| zOOGupOTn@c(JmBOl)e;XMNnZuiTJP>rM8<|Q`7I_))aP?*T)ow&n59{}X4$3Goat zgjs?*aasfbrokzG5cT4K=uG`E14xZl@z)F={P0Y^?$4t z>v!teRnNZym<6h{7sLyF1V0HsfEl+l6TrZpsfr1}luH~F7L}ktXu|*uVX^RG$L0`K zWs3j|0tIvVe(N%_?2{(iCPFGf#B6Hjy6o&}D$A%W%jfO8_W%ZO#-mh}EM$LMn7joJ z05dHr!5Y92g+31l<%i1(=L1a1pXX+OYnalY>31V4K}BjyRe3)9n#;-cCVRD_IG1fT zOKGeNY8q;TL@K{dj@D^scf&VCs*-Jb>8b>|`b*osv52-!A?BpbYtTQBns5EAU**$m zSnVSm(teh>tQi*S*A>#ySc=n;`BHz`DuG4&g4Kf8lLhca+zvZ7t7RflD6-i-mcK=M z!=^P$*u2)bkY5asG4gsss!Hn%u~>}kIW`vMs%lJLH+u*9<4PaV_c6U`KqWXQH%+Nu zTv41O(^ZVi@qhjQdG!fbZw&y+2o!iYymO^?ud3{P*HdoX83YV*Uu_HB=?U&W9%AU# z80}k1SS-CXTU7dcQlsm<^oYLxVSseqY6NO}dc`Nj?8vrhNuCdm@^{a3AQ_>6myOj+ z`1RsLUXF|dm|3k7s2jD(B{rzE>WI2scH8i1;=O5Cc9xB3^aJk%fQjqsu+kH#0=_5a z0nCE8@dbQa-|YIuUVvG0L_IwHMEhOj$Mj4Uq05 X8=0q~qBNan00000NkvXXu0mjfptF>5 literal 0 HcmV?d00001 diff --git a/doc/html/tab_a.png b/doc/html/tab_a.png new file mode 100644 index 0000000000000000000000000000000000000000..3b725c41c5a527a3a3e40097077d0e206a681247 GIT binary patch literal 142 zcmeAS@N?(olHy`uVBq!ia0vp^j6kfy!2~3aiye;!QlXwMjv*C{Z|8b*H5dputLHD# z=<0|*y7z(Vor?d;H&?EG&cXR}?!j-Lm&u1OOI7AIF5&c)RFE;&p0MYK>*Kl@eiymD r@|NpwKX@^z+;{u_Z~trSBfrMKa%3`zocFjEXaR$#tDnm{r-UW|TZ1%4 literal 0 HcmV?d00001 diff --git a/doc/html/tab_ad.png b/doc/html/tab_ad.png new file mode 100644 index 0000000000000000000000000000000000000000..e34850acfc24be58da6d2fd1ccc6b29cc84fe34d GIT binary patch literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^j6kfy!2~3aiye;!QhuH;jv*C{Z|5d*H3V=pKi{In zd2jxLclDRPylmD}^l7{QOtL{vUjO{-WqItb5sQp2h-99b8^^Scr-=2mblCdZuUm?4 jzOJvgvt3{(cjKLW5(A@0qPS@<&}0TrS3j3^P6y&q2{!U5bk+Tso_B!YCpDh>v z{CM*1U8YvQRyBUHt^Ju0W_sq-?;9@_4equ-bavTs=gk796zopr0EBT&m;e9( literal 0 HcmV?d00001 diff --git a/doc/html/tab_s.png b/doc/html/tab_s.png new file mode 100644 index 0000000000000000000000000000000000000000..ab478c95b67371d700a20869f7de1ddd73522d50 GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^j6kfy!2~3aiye;!QuUrLjv*C{Z|^p8HaRdjTwH7) zC?wLlL}}I{)n%R&r+1}IGmDnq;&J#%V6)9VsYhS`O^BVBQlxOUep0c$RENLq#g8A$ z)z7%K_bI&n@J+X_=x}fJoEKed-$<>=ZI-;YrdjIl`U`uzuDWSP?o#Dmo{%SgM#oan kX~E1%D-|#H#QbHoIja2U-MgvsK&LQxy85}Sb4q9e0Efg%P5=M^ literal 0 HcmV?d00001 diff --git a/doc/html/tab_sd.png b/doc/html/tab_sd.png new file mode 100644 index 0000000000000000000000000000000000000000..757a565ced4730f85c833fb2547d8e199ae68f19 GIT binary patch literal 188 zcmeAS@N?(olHy`uVBq!ia0vp^j6kfy!2~3aiye;!Qq7(&jv*C{Z|_!fH5o7*c=%9% zcILh!EA=pAQKdx-Cdiev=v{eg{8Ht<{e8_NAN~b=)%W>-WDCE0PyDHGemi$BoXwcK z{>e9^za6*c1ilttWw&V+U;WCPlV9{LdC~Ey%_H(qj`xgfES(4Yz5jSTZfCt`4E$0YRsR*S^mTCR^;V&sxC8{l_Cp7w8-YPgg&ebxsLQ00$vXK>z>% literal 0 HcmV?d00001 diff --git a/doc/html/tabs.css b/doc/html/tabs.css new file mode 100644 index 0000000..df7944b --- /dev/null +++ b/doc/html/tabs.css @@ -0,0 +1 @@ +.sm{position:relative;z-index:9999}.sm,.sm ul,.sm li{display:block;list-style:none;margin:0;padding:0;line-height:normal;direction:ltr;text-align:left;-webkit-tap-highlight-color:rgba(0,0,0,0)}.sm-rtl,.sm-rtl ul,.sm-rtl li{direction:rtl;text-align:right}.sm>li>h1,.sm>li>h2,.sm>li>h3,.sm>li>h4,.sm>li>h5,.sm>li>h6{margin:0;padding:0}.sm ul{display:none}.sm li,.sm a{position:relative}.sm a{display:block}.sm a.disabled{cursor:not-allowed}.sm:after{content:"\00a0";display:block;height:0;font:0px/0 serif;clear:both;visibility:hidden;overflow:hidden}.sm,.sm *,.sm *:before,.sm *:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.main-menu-btn{position:relative;display:inline-block;width:36px;height:36px;text-indent:36px;margin-left:8px;white-space:nowrap;overflow:hidden;cursor:pointer;-webkit-tap-highlight-color:rgba(0,0,0,0)}.main-menu-btn-icon,.main-menu-btn-icon:before,.main-menu-btn-icon:after{position:absolute;top:50%;left:2px;height:2px;width:24px;background:var(--nav-menu-button-color);-webkit-transition:all 0.25s;transition:all 0.25s}.main-menu-btn-icon:before{content:'';top:-7px;left:0}.main-menu-btn-icon:after{content:'';top:7px;left:0}#main-menu-state:checked~.main-menu-btn .main-menu-btn-icon{height:0}#main-menu-state:checked~.main-menu-btn .main-menu-btn-icon:before{top:0;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}#main-menu-state:checked~.main-menu-btn .main-menu-btn-icon:after{top:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}#main-menu-state{position:absolute;width:1px;height:1px;margin:-1px;border:0;padding:0;overflow:hidden;clip:rect(1px, 1px, 1px, 1px)}#main-menu-state:not(:checked)~#main-menu{display:none}#main-menu-state:checked~#main-menu{display:block}@media (min-width: 768px){.main-menu-btn{position:absolute;top:-99999px}#main-menu-state:not(:checked)~#main-menu{display:block}}.sm-dox{background-image:var(--nav-gradient-image)}.sm-dox a,.sm-dox a:focus,.sm-dox a:hover,.sm-dox a:active{padding:0px 12px;padding-right:43px;font-family:var(--font-family-nav);font-size:13px;font-weight:bold;line-height:36px;text-decoration:none;text-shadow:var(--nav-text-normal-shadow);color:var(--nav-text-normal-color);outline:none}.sm-dox a:hover{background-image:var(--nav-gradient-active-image);background-repeat:repeat-x;color:var(--nav-text-hover-color);text-shadow:var(--nav-text-hover-shadow)}.sm-dox a.current{color:#D23600}.sm-dox a.disabled{color:#bbb}.sm-dox a span.sub-arrow{position:absolute;top:50%;margin-top:-14px;left:auto;right:3px;width:28px;height:28px;overflow:hidden;font:bold 12px/28px monospace !important;text-align:center;text-shadow:none;background:var(--nav-menu-toggle-color);border-radius:5px}.sm-dox a span.sub-arrow:before{display:block;content:'+'}.sm-dox a.highlighted span.sub-arrow:before{display:block;content:'-'}.sm-dox>li:first-child>a,.sm-dox>li:first-child>:not(ul) a{border-radius:5px 5px 0 0}.sm-dox>li:last-child>a,.sm-dox>li:last-child>*:not(ul) a,.sm-dox>li:last-child>ul,.sm-dox>li:last-child>ul>li:last-child>a,.sm-dox>li:last-child>ul>li:last-child>*:not(ul) a,.sm-dox>li:last-child>ul>li:last-child>ul,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>*:not(ul) a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>*:not(ul) a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>*:not(ul) a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul{border-radius:0 0 5px 5px}.sm-dox>li:last-child>a.highlighted,.sm-dox>li:last-child>*:not(ul) a.highlighted,.sm-dox>li:last-child>ul>li:last-child>a.highlighted,.sm-dox>li:last-child>ul>li:last-child>*:not(ul) a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>*:not(ul) a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>*:not(ul) a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>*:not(ul) a.highlighted{border-radius:0}.sm-dox ul{background:var(--nav-menu-background-color)}.sm-dox ul a,.sm-dox ul a:focus,.sm-dox ul a:hover,.sm-dox ul a:active{font-size:12px;border-left:8px solid transparent;line-height:36px;text-shadow:none;background-color:var(--nav-menu-background-color);background-image:none}.sm-dox ul a:hover{background-image:var(--nav-gradient-active-image);background-repeat:repeat-x;color:var(--nav-text-hover-color);text-shadow:0px 1px 1px #000}.sm-dox ul ul a,.sm-dox ul ul a:hover,.sm-dox ul ul a:focus,.sm-dox ul ul a:active{border-left:16px solid transparent}.sm-dox ul ul ul a,.sm-dox ul ul ul a:hover,.sm-dox ul ul ul a:focus,.sm-dox ul ul ul a:active{border-left:24px solid transparent}.sm-dox ul ul ul ul a,.sm-dox ul ul ul ul a:hover,.sm-dox ul ul ul ul a:focus,.sm-dox ul ul ul ul a:active{border-left:32px solid transparent}.sm-dox ul ul ul ul ul a,.sm-dox ul ul ul ul ul a:hover,.sm-dox ul ul ul ul ul a:focus,.sm-dox ul ul ul ul ul a:active{border-left:40px solid transparent}@media (min-width: 768px){.sm-dox ul{position:absolute;width:12em}.sm-dox li{float:left}.sm-dox.sm-rtl li{float:right}.sm-dox ul li,.sm-dox.sm-rtl ul li,.sm-dox.sm-vertical li{float:none}.sm-dox a{white-space:nowrap}.sm-dox ul a,.sm-dox.sm-vertical a{white-space:normal}.sm-dox .sm-nowrap>li>a,.sm-dox .sm-nowrap>li>:not(ul) a{white-space:nowrap}.sm-dox{padding:0 10px;background-image:var(--nav-gradient-image);line-height:36px}.sm-dox a span.sub-arrow{top:50%;margin-top:-2px;right:12px;width:0;height:0;border-width:4px;border-style:solid dashed dashed dashed;border-color:var(--nav-text-normal-color) transparent transparent transparent;background:transparent;border-radius:0}.sm-dox a,.sm-dox a:focus,.sm-dox a:active,.sm-dox a:hover,.sm-dox a.highlighted{padding:0px 12px;background-image:var(--nav-separator-image);background-repeat:no-repeat;background-position:right;border-radius:0 !important}.sm-dox a:hover{background-image:var(--nav-gradient-active-image);background-repeat:repeat-x;color:var(--nav-text-hover-color);text-shadow:var(--nav-text-hover-shadow)}.sm-dox a:hover span.sub-arrow{border-color:var(--nav-text-hover-color) transparent transparent transparent}.sm-dox a.has-submenu{padding-right:24px}.sm-dox li{border-top:0}.sm-dox>li>ul:before,.sm-dox>li>ul:after{content:'';position:absolute;top:-18px;left:30px;width:0;height:0;overflow:hidden;border-width:9px;border-style:dashed dashed solid dashed;border-color:transparent transparent #bbb transparent}.sm-dox>li>ul:after{top:-16px;left:31px;border-width:8px;border-color:transparent transparent var(--nav-menu-background-color) transparent}.sm-dox ul{border:1px solid #bbb;padding:5px 0;background:var(--nav-menu-background-color);border-radius:5px !important;box-shadow:0 5px 9px rgba(0,0,0,0.2)}.sm-dox ul a span.sub-arrow{right:8px;top:50%;margin-top:-5px;border-width:5px;border-color:transparent transparent transparent var(--nav-menu-foreground-color);border-style:dashed dashed dashed solid}.sm-dox ul a,.sm-dox ul a:hover,.sm-dox ul a:focus,.sm-dox ul a:active,.sm-dox ul a.highlighted{color:var(--nav-menu-foreground-color);background-image:none;border:0 !important;color:var(--nav-menu-foreground-color);background-image:none}.sm-dox ul a:hover{background-image:var(--nav-gradient-active-image);background-repeat:repeat-x;color:var(--nav-text-hover-color);text-shadow:var(--nav-text-hover-shadow)}.sm-dox ul a:hover span.sub-arrow{border-color:transparent transparent transparent var(--nav-text-hover-color)}.sm-dox span.scroll-up,.sm-dox span.scroll-down{position:absolute;display:none;visibility:hidden;overflow:hidden;background:var(--nav-menu-background-color);height:36px}.sm-dox span.scroll-up:hover,.sm-dox span.scroll-down:hover{background:#eee}.sm-dox span.scroll-up:hover span.scroll-up-arrow,.sm-dox span.scroll-up:hover span.scroll-down-arrow{border-color:transparent transparent #D23600 transparent}.sm-dox span.scroll-down:hover span.scroll-down-arrow{border-color:#D23600 transparent transparent transparent}.sm-dox span.scroll-up-arrow,.sm-dox span.scroll-down-arrow{position:absolute;top:0;left:50%;margin-left:-6px;width:0;height:0;overflow:hidden;border-width:6px;border-style:dashed dashed solid dashed;border-color:transparent transparent var(--nav-menu-foreground-color) transparent}.sm-dox span.scroll-down-arrow{top:8px;border-style:solid dashed dashed dashed;border-color:var(--nav-menu-foreground-color) transparent transparent transparent}.sm-dox.sm-rtl a.has-submenu{padding-right:12px;padding-left:24px}.sm-dox.sm-rtl a span.sub-arrow{right:auto;left:12px}.sm-dox.sm-rtl.sm-vertical a.has-submenu{padding:10px 20px}.sm-dox.sm-rtl.sm-vertical a span.sub-arrow{right:auto;left:8px;border-style:dashed solid dashed dashed;border-color:transparent #555 transparent transparent}.sm-dox.sm-rtl>li>ul:before{left:auto;right:30px}.sm-dox.sm-rtl>li>ul:after{left:auto;right:31px}.sm-dox.sm-rtl ul a.has-submenu{padding:10px 20px !important}.sm-dox.sm-rtl ul a span.sub-arrow{right:auto;left:8px;border-style:dashed solid dashed dashed;border-color:transparent #555 transparent transparent}.sm-dox.sm-vertical{padding:10px 0;border-radius:5px}.sm-dox.sm-vertical a{padding:10px 20px}.sm-dox.sm-vertical a:hover,.sm-dox.sm-vertical a:focus,.sm-dox.sm-vertical a:active,.sm-dox.sm-vertical a.highlighted{background:#fff}.sm-dox.sm-vertical a.disabled{background-image:var(--nav-gradient-image)}.sm-dox.sm-vertical a span.sub-arrow{right:8px;top:50%;margin-top:-5px;border-width:5px;border-style:dashed dashed dashed solid;border-color:transparent transparent transparent #555}.sm-dox.sm-vertical>li>ul:before,.sm-dox.sm-vertical>li>ul:after{display:none}.sm-dox.sm-vertical ul a{padding:10px 20px}.sm-dox.sm-vertical ul a:hover,.sm-dox.sm-vertical ul a:focus,.sm-dox.sm-vertical ul a:active,.sm-dox.sm-vertical ul a.highlighted{background:#eee}.sm-dox.sm-vertical ul a.disabled{background:var(--nav-menu-background-color)}} diff --git a/doc/html/test_2hello_8c.html b/doc/html/test_2hello_8c.html new file mode 100644 index 0000000..bf7f518 --- /dev/null +++ b/doc/html/test_2hello_8c.html @@ -0,0 +1,265 @@ + + + + + + + +libfuse: test/hello.c File Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    hello.c File Reference
    +
    +
    +
    #include <fuse.h>
    +#include <stdio.h>
    +#include <string.h>
    +#include <errno.h>
    +#include <fcntl.h>
    +#include <stddef.h>
    +#include <assert.h>
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    minimal example filesystem using high-level API

    +

    Compile with:

    gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file GPL2.txt.
    +
    */
    +
    +
    #define FUSE_USE_VERSION 31
    +
    +
    #include <fuse.h>
    +
    #include <stdio.h>
    +
    #include <string.h>
    +
    #include <errno.h>
    +
    #include <fcntl.h>
    +
    #include <stddef.h>
    +
    #include <assert.h>
    +
    +
    /*
    +
    * Command line options
    +
    *
    +
    * We can't set default values for the char* fields here because
    +
    * fuse_opt_parse would attempt to free() them when the user specifies
    +
    * different values on the command line.
    +
    */
    +
    static struct options {
    +
    const char *filename;
    +
    const char *contents;
    +
    int show_help;
    +
    } options;
    +
    +
    #define OPTION(t, p) \
    +
    { t, offsetof(struct options, p), 1 }
    +
    static const struct fuse_opt option_spec[] = {
    +
    OPTION("--name=%s", filename),
    +
    OPTION("--contents=%s", contents),
    +
    OPTION("-h", show_help),
    +
    OPTION("--help", show_help),
    + +
    };
    +
    +
    static void *hello_init(struct fuse_conn_info *conn,
    +
    struct fuse_config *cfg)
    +
    {
    +
    (void) conn;
    +
    cfg->kernel_cache = 1;
    +
    +
    /* Test setting flags the old way */
    + + +
    +
    return NULL;
    +
    }
    +
    +
    static int hello_getattr(const char *path, struct stat *stbuf,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void) fi;
    +
    int res = 0;
    +
    +
    memset(stbuf, 0, sizeof(struct stat));
    +
    if (strcmp(path, "/") == 0) {
    +
    stbuf->st_mode = S_IFDIR | 0755;
    +
    stbuf->st_nlink = 2;
    +
    } else if (strcmp(path+1, options.filename) == 0) {
    +
    stbuf->st_mode = S_IFREG | 0444;
    +
    stbuf->st_nlink = 1;
    +
    stbuf->st_size = strlen(options.contents);
    +
    } else
    +
    res = -ENOENT;
    +
    +
    return res;
    +
    }
    +
    +
    static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    off_t offset, struct fuse_file_info *fi,
    +
    enum fuse_readdir_flags flags)
    +
    {
    +
    (void) offset;
    +
    (void) fi;
    +
    (void) flags;
    +
    +
    if (strcmp(path, "/") != 0)
    +
    return -ENOENT;
    +
    +
    filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    +
    return 0;
    +
    }
    +
    +
    static int hello_open(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    if (strcmp(path+1, options.filename) != 0)
    +
    return -ENOENT;
    +
    +
    if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    return -EACCES;
    +
    +
    return 0;
    +
    }
    +
    +
    static int hello_read(const char *path, char *buf, size_t size, off_t offset,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    size_t len;
    +
    (void) fi;
    +
    if(strcmp(path+1, options.filename) != 0)
    +
    return -ENOENT;
    +
    +
    len = strlen(options.contents);
    +
    if (offset < len) {
    +
    if (offset + size > len)
    +
    size = len - offset;
    +
    memcpy(buf, options.contents + offset, size);
    +
    } else
    +
    size = 0;
    +
    +
    return size;
    +
    }
    +
    +
    static const struct fuse_operations hello_oper = {
    +
    .init = hello_init,
    +
    .getattr = hello_getattr,
    +
    .readdir = hello_readdir,
    +
    .open = hello_open,
    +
    .read = hello_read,
    +
    };
    +
    +
    static void show_help(const char *progname)
    +
    {
    +
    printf("usage: %s [options] <mountpoint>\n\n", progname);
    +
    printf("File-system specific options:\n"
    +
    " --name=<s> Name of the \"hello\" file\n"
    +
    " (default: \"hello\")\n"
    +
    " --contents=<s> Contents \"hello\" file\n"
    +
    " (default \"Hello, World!\\n\")\n"
    +
    "\n");
    +
    }
    +
    +
    int main(int argc, char *argv[])
    +
    {
    +
    int ret;
    +
    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    +
    /* Set defaults -- we have to use strdup so that
    +
    fuse_opt_parse can free the defaults if other
    +
    values are specified */
    +
    options.filename = strdup("hello");
    +
    options.contents = strdup("Hello World!\n");
    +
    +
    /* Parse options */
    +
    if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    +
    return 1;
    +
    +
    /* When --help is specified, first print our own file-system
    +
    specific help text, then signal fuse_main to show
    +
    additional help (by adding `--help` to the options again)
    +
    without usage: line (by setting argv[0] to the empty
    +
    string) */
    +
    if (options.show_help) {
    +
    show_help(argv[0]);
    +
    assert(fuse_opt_add_arg(&args, "--help") == 0);
    +
    args.argv[0][0] = '\0';
    +
    }
    +
    +
    ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
    + +
    return ret;
    +
    }
    +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:87
    +
    @ FUSE_FILL_DIR_DEFAULTS
    Definition fuse.h:68
    +
    fuse_readdir_flags
    Definition fuse.h:42
    +
    void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
    +
    bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
    +
    #define FUSE_CAP_ASYNC_READ
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + +
    int32_t kernel_cache
    Definition fuse.h:245
    + + + + +
    void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
    Definition fuse.h:641
    + +
    unsigned long offset
    Definition fuse_opt.h:85
    +
    +

    Definition in file hello.c.

    +
    + + + + diff --git a/doc/html/test_2hello_8c_source.html b/doc/html/test_2hello_8c_source.html new file mode 100644 index 0000000..cf8fa8c --- /dev/null +++ b/doc/html/test_2hello_8c_source.html @@ -0,0 +1,250 @@ + + + + + + + +libfuse: test/hello.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    hello.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 * FUSE: Filesystem in Userspace
    +
    3 * Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4 *
    +
    5 * This program can be distributed under the terms of the GNU GPLv2.
    +
    6 * See the file GPL2.txt.
    +
    7 */
    +
    8
    +
    21#define FUSE_USE_VERSION 31
    +
    22
    +
    23#include <fuse.h>
    +
    24#include <stdio.h>
    +
    25#include <string.h>
    +
    26#include <errno.h>
    +
    27#include <fcntl.h>
    +
    28#include <stddef.h>
    +
    29#include <assert.h>
    +
    30
    +
    31/*
    +
    32 * Command line options
    +
    33 *
    +
    34 * We can't set default values for the char* fields here because
    +
    35 * fuse_opt_parse would attempt to free() them when the user specifies
    +
    36 * different values on the command line.
    +
    37 */
    +
    38static struct options {
    +
    39 const char *filename;
    +
    40 const char *contents;
    +
    41 int show_help;
    +
    42} options;
    +
    43
    +
    44#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
    +
    45static const struct fuse_opt option_spec[] = {
    +
    46 OPTION("--name=%s", filename), OPTION("--contents=%s", contents),
    +
    47 OPTION("-h", show_help), OPTION("--help", show_help), FUSE_OPT_END
    +
    48};
    +
    49
    +
    50static void *hello_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
    +
    51{
    +
    52 (void)conn;
    +
    53 cfg->kernel_cache = 1;
    +
    54
    +
    55 /* Test setting flags the old way */
    + +
    57 conn->want &= ~FUSE_CAP_ASYNC_READ;
    +
    58
    +
    59 return NULL;
    +
    60}
    +
    61
    +
    62static int hello_getattr(const char *path, struct stat *stbuf,
    +
    63 struct fuse_file_info *fi)
    +
    64{
    +
    65 (void)fi;
    +
    66 int res = 0;
    +
    67
    +
    68 memset(stbuf, 0, sizeof(struct stat));
    +
    69 if (strcmp(path, "/") == 0) {
    +
    70 stbuf->st_mode = S_IFDIR | 0755;
    +
    71 stbuf->st_nlink = 2;
    +
    72 } else if (strcmp(path + 1, options.filename) == 0) {
    +
    73 stbuf->st_mode = S_IFREG | 0444;
    +
    74 stbuf->st_nlink = 1;
    +
    75 stbuf->st_size = strlen(options.contents);
    +
    76 } else
    +
    77 res = -ENOENT;
    +
    78
    +
    79 return res;
    +
    80}
    +
    81
    +
    82static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    83 off_t offset, struct fuse_file_info *fi,
    +
    84 enum fuse_readdir_flags flags)
    +
    85{
    +
    86 (void)offset;
    +
    87 (void)fi;
    +
    88 (void)flags;
    +
    89
    +
    90 if (strcmp(path, "/") != 0)
    +
    91 return -ENOENT;
    +
    92
    +
    93 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    94 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    95 filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    96
    +
    97 return 0;
    +
    98}
    +
    99
    +
    100static int hello_open(const char *path, struct fuse_file_info *fi)
    +
    101{
    +
    102 if (strcmp(path + 1, options.filename) != 0)
    +
    103 return -ENOENT;
    +
    104
    +
    105 if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    106 return -EACCES;
    +
    107
    +
    108 return 0;
    +
    109}
    +
    110
    +
    111static int hello_read(const char *path, char *buf, size_t size, off_t offset,
    +
    112 struct fuse_file_info *fi)
    +
    113{
    +
    114 size_t len;
    +
    115 (void)fi;
    +
    116 if (strcmp(path + 1, options.filename) != 0)
    +
    117 return -ENOENT;
    +
    118
    +
    119 len = strlen(options.contents);
    +
    120 if (offset < len) {
    +
    121 if (offset + size > len)
    +
    122 size = len - offset;
    +
    123 memcpy(buf, options.contents + offset, size);
    +
    124 } else
    +
    125 size = 0;
    +
    126
    +
    127 return size;
    +
    128}
    +
    129
    +
    130static const struct fuse_operations hello_oper = {
    +
    131 .init = hello_init,
    +
    132 .getattr = hello_getattr,
    +
    133 .readdir = hello_readdir,
    +
    134 .open = hello_open,
    +
    135 .read = hello_read,
    +
    136};
    +
    137
    +
    138static void show_help(const char *progname)
    +
    139{
    +
    140 printf("usage: %s [options] <mountpoint>\n\n", progname);
    +
    141 printf("File-system specific options:\n"
    +
    142 " --name=<s> Name of the \"hello\" file\n"
    +
    143 " (default: \"hello\")\n"
    +
    144 " --contents=<s> Contents \"hello\" file\n"
    +
    145 " (default \"Hello, World!\\n\")\n"
    +
    146 "\n");
    +
    147}
    +
    148
    +
    149int main(int argc, char *argv[])
    +
    150{
    +
    151 int ret;
    +
    152 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    153
    +
    154 /* Set defaults -- we have to use strdup so that
    +
    155 * fuse_opt_parse can free the defaults if other
    +
    156 * values are specified
    +
    157 */
    +
    158 options.filename = strdup("hello");
    +
    159 options.contents = strdup("Hello World!\n");
    +
    160
    +
    161 /* Parse options */
    +
    162 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    +
    163 return 1;
    +
    164
    +
    165 /* When --help is specified, first print our own file-system
    +
    166 * specific help text, then signal fuse_main to show
    +
    167 * additional help (by adding `--help` to the options again)
    +
    168 * without usage: line (by setting argv[0] to the empty
    +
    169 * string)
    +
    170 */
    +
    171 if (options.show_help) {
    +
    172 show_help(argv[0]);
    +
    173 assert(fuse_opt_add_arg(&args, "--help") == 0);
    +
    174 args.argv[0][0] = '\0';
    +
    175 }
    +
    176
    +
    177 ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
    +
    178 fuse_opt_free_args(&args);
    +
    179 return ret;
    +
    180}
    +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:87
    +
    @ FUSE_FILL_DIR_DEFAULTS
    Definition fuse.h:68
    +
    fuse_readdir_flags
    Definition fuse.h:42
    +
    #define FUSE_CAP_ASYNC_READ
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + +
    int32_t kernel_cache
    Definition fuse.h:245
    + + + + + +
    void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
    Definition fuse.h:641
    + +
    unsigned long offset
    Definition fuse_opt.h:85
    +
    + + + + diff --git a/doc/html/test_2readdir__inode_8c_source.html b/doc/html/test_2readdir__inode_8c_source.html new file mode 100644 index 0000000..bf625c6 --- /dev/null +++ b/doc/html/test_2readdir__inode_8c_source.html @@ -0,0 +1,117 @@ + + + + + + + +libfuse: test/readdir_inode.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    readdir_inode.c
    +
    +
    +
    1/*
    +
    2 * Prints each directory entry, its inode and d_type as returned by 'readdir'.
    +
    3 * Skips '.' and '..' because readdir is not required to return them and
    +
    4 * some of our examples don't. However if they are returned, their d_type
    +
    5 * should be valid.
    +
    6 */
    +
    7
    +
    8#include <stdio.h>
    +
    9#include <string.h>
    +
    10#include <sys/types.h>
    +
    11#include <dirent.h>
    +
    12#include <errno.h>
    +
    13
    +
    14int main(int argc, char* argv[])
    +
    15{
    +
    16 DIR* dirp;
    +
    17 struct dirent* dent;
    +
    18
    +
    19 if (argc != 2) {
    +
    20 fprintf(stderr, "Usage: readdir_inode dir\n");
    +
    21 return 1;
    +
    22 }
    +
    23
    +
    24 dirp = opendir(argv[1]);
    +
    25 if (dirp == NULL) {
    +
    26 perror("failed to open directory");
    +
    27 return 2;
    +
    28 }
    +
    29
    +
    30 errno = 0;
    +
    31 dent = readdir(dirp);
    +
    32 while (dent != NULL) {
    +
    33 if (strcmp(dent->d_name, ".") != 0 && strcmp(dent->d_name, "..") != 0) {
    +
    34 printf("%llu %d %s\n", (unsigned long long)dent->d_ino,
    +
    35 (int)dent->d_type, dent->d_name);
    +
    36 if ((long long)dent->d_ino < 0)
    +
    37 fprintf(stderr,"%s : bad d_ino %llu\n",
    +
    38 dent->d_name, (unsigned long long)dent->d_ino);
    +
    39 if ((dent->d_type < 1) || (dent->d_type > 15))
    +
    40 fprintf(stderr,"%s : bad d_type %d\n",
    +
    41 dent->d_name, (int)dent->d_type);
    +
    42 } else {
    +
    43 if (dent->d_type != DT_DIR)
    +
    44 fprintf(stderr,"%s : bad d_type %d\n",
    +
    45 dent->d_name, (int)dent->d_type);
    +
    46 }
    +
    47 dent = readdir(dirp);
    +
    48 }
    +
    49 if (errno != 0) {
    +
    50 perror("failed to read directory entry");
    +
    51 return 3;
    +
    52 }
    +
    53
    +
    54 closedir(dirp);
    +
    55
    +
    56 return 0;
    +
    57}
    +
    + + + + diff --git a/doc/html/test_2release__unlink__race_8c_source.html b/doc/html/test_2release__unlink__race_8c_source.html new file mode 100644 index 0000000..7d31c25 --- /dev/null +++ b/doc/html/test_2release__unlink__race_8c_source.html @@ -0,0 +1,183 @@ + + + + + + + +libfuse: test/release_unlink_race.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    release_unlink_race.c
    +
    +
    +
    1/*
    +
    2 This program can be distributed under the terms of the GNU GPLv2.
    +
    3 See the file GPL2.txt.
    +
    4*/
    +
    5
    +
    6#define FUSE_USE_VERSION 31
    +
    7
    +
    8#define _GNU_SOURCE
    +
    9
    +
    10#include <fuse.h>
    +
    11
    +
    12#include <stdio.h>
    +
    13#include <stdlib.h>
    +
    14#include <unistd.h>
    +
    15#include <errno.h>
    +
    16
    +
    17static void *xmp_init(struct fuse_conn_info *conn,
    +
    18 struct fuse_config *cfg)
    +
    19{
    +
    20 (void) conn;
    +
    21
    +
    22 cfg->use_ino = 1;
    +
    23 cfg->nullpath_ok = 1;
    +
    24 cfg->entry_timeout = 0;
    +
    25 cfg->attr_timeout = 0;
    +
    26 cfg->negative_timeout = 0;
    +
    27
    +
    28 return NULL;
    +
    29}
    +
    30
    +
    31static int xmp_getattr(const char *path, struct stat *stbuf,
    +
    32 struct fuse_file_info *fi)
    +
    33{
    +
    34 int res;
    +
    35
    +
    36 (void) path;
    +
    37
    +
    38 if(fi)
    +
    39 res = fstat(fi->fh, stbuf);
    +
    40 else
    +
    41 res = lstat(path, stbuf);
    +
    42 if (res == -1)
    +
    43 return -errno;
    +
    44
    +
    45 return 0;
    +
    46}
    +
    47
    +
    48static int xmp_unlink(const char *path)
    +
    49{
    +
    50 int res;
    +
    51
    +
    52 res = unlink(path);
    +
    53 if (res == -1)
    +
    54 return -errno;
    +
    55
    +
    56 return 0;
    +
    57}
    +
    58
    +
    59static int xmp_rename(const char *from, const char *to, unsigned int flags)
    +
    60{
    +
    61 int res;
    +
    62
    +
    63 if (flags)
    +
    64 return -EINVAL;
    +
    65
    +
    66 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
    +
    67
    +
    68 res = rename(from, to);
    +
    69 if (res == -1)
    +
    70 return -errno;
    +
    71
    +
    72 return 0;
    +
    73}
    +
    74
    +
    75static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
    +
    76{
    +
    77 int fd;
    +
    78
    +
    79 fd = open(path, fi->flags, mode);
    +
    80 if (fd == -1)
    +
    81 return -errno;
    +
    82
    +
    83 fi->fh = fd;
    +
    84 return 0;
    +
    85}
    +
    86
    +
    87static int xmp_release(const char *path, struct fuse_file_info *fi)
    +
    88{
    +
    89 (void) path;
    +
    90
    +
    91 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
    +
    92
    +
    93 close(fi->fh);
    +
    94
    +
    95 return 0;
    +
    96}
    +
    97
    +
    98static const struct fuse_operations xmp_oper = {
    +
    99 .init = xmp_init,
    +
    100 .getattr = xmp_getattr,
    +
    101 .unlink = xmp_unlink,
    +
    102 .rename = xmp_rename,
    +
    103 .create = xmp_create,
    +
    104 .release = xmp_release,
    +
    105};
    +
    106
    +
    107int main(int argc, char *argv[])
    +
    108{
    +
    109 umask(0);
    +
    110 return fuse_main(argc, argv, &xmp_oper, NULL);
    +
    111}
    + +
    int32_t nullpath_ok
    Definition fuse.h:273
    +
    int32_t use_ino
    Definition fuse.h:198
    +
    double entry_timeout
    Definition fuse.h:127
    +
    double negative_timeout
    Definition fuse.h:137
    +
    double attr_timeout
    Definition fuse.h:143
    + + + + + +
    void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
    Definition fuse.h:641
    +
    + + + + diff --git a/doc/html/test_2stracedecode_8c_source.html b/doc/html/test_2stracedecode_8c_source.html new file mode 100644 index 0000000..8f898ca --- /dev/null +++ b/doc/html/test_2stracedecode_8c_source.html @@ -0,0 +1,259 @@ + + + + + + + +libfuse: test/stracedecode.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    stracedecode.c
    +
    +
    +
    1#include <stdio.h>
    +
    2#include <string.h>
    +
    3#include "fuse_kernel.h"
    +
    4
    +
    5static struct {
    +
    6 const char *name;
    +
    7} fuse_ll_ops[] = {
    +
    8 [FUSE_LOOKUP] = { "LOOKUP" },
    +
    9 [FUSE_FORGET] = { "FORGET" },
    +
    10 [FUSE_GETATTR] = { "GETATTR" },
    +
    11 [FUSE_SETATTR] = { "SETATTR" },
    +
    12 [FUSE_READLINK] = { "READLINK" },
    +
    13 [FUSE_SYMLINK] = { "SYMLINK" },
    +
    14 [FUSE_MKNOD] = { "MKNOD" },
    +
    15 [FUSE_MKDIR] = { "MKDIR" },
    +
    16 [FUSE_UNLINK] = { "UNLINK" },
    +
    17 [FUSE_RMDIR] = { "RMDIR" },
    +
    18 [FUSE_RENAME] = { "RENAME" },
    +
    19 [FUSE_LINK] = { "LINK" },
    +
    20 [FUSE_OPEN] = { "OPEN" },
    +
    21 [FUSE_READ] = { "READ" },
    +
    22 [FUSE_WRITE] = { "WRITE" },
    +
    23 [FUSE_STATFS] = { "STATFS" },
    +
    24 [FUSE_RELEASE] = { "RELEASE" },
    +
    25 [FUSE_FSYNC] = { "FSYNC" },
    +
    26 [FUSE_SETXATTR] = { "SETXATTR" },
    +
    27 [FUSE_GETXATTR] = { "GETXATTR" },
    +
    28 [FUSE_LISTXATTR] = { "LISTXATTR" },
    +
    29 [FUSE_REMOVEXATTR] = { "REMOVEXATTR" },
    +
    30 [FUSE_FLUSH] = { "FLUSH" },
    +
    31 [FUSE_INIT] = { "INIT" },
    +
    32 [FUSE_OPENDIR] = { "OPENDIR" },
    +
    33 [FUSE_READDIR] = { "READDIR" },
    +
    34 [FUSE_RELEASEDIR] = { "RELEASEDIR" },
    +
    35 [FUSE_FSYNCDIR] = { "FSYNCDIR" },
    +
    36 [FUSE_GETLK] = { "GETLK" },
    +
    37 [FUSE_SETLK] = { "SETLK" },
    +
    38 [FUSE_SETLKW] = { "SETLKW" },
    +
    39 [FUSE_ACCESS] = { "ACCESS" },
    +
    40 [FUSE_CREATE] = { "CREATE" },
    +
    41 [FUSE_TMPFILE] = { "TMPFILE" },
    +
    42 [FUSE_INTERRUPT] = { "INTERRUPT" },
    +
    43 [FUSE_BMAP] = { "BMAP" },
    +
    44 [FUSE_DESTROY] = { "DESTROY" },
    +
    45 [FUSE_READDIRPLUS] = { "READDIRPLUS" },
    +
    46};
    +
    47
    +
    48#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
    +
    49
    +
    50static const char *opname(enum fuse_opcode opcode)
    +
    51{
    +
    52 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
    +
    53 return "???";
    +
    54 else
    +
    55 return fuse_ll_ops[opcode].name;
    +
    56}
    +
    57
    +
    58
    +
    59static void process_buf(int dir, char *buf, int len)
    +
    60{
    +
    61 static unsigned long long prevuniq = -1;
    +
    62 static int prevopcode;
    +
    63
    +
    64 if (!dir) {
    +
    65 struct fuse_in_header *in = (struct fuse_in_header *) buf;
    +
    66 buf += sizeof(struct fuse_in_header);
    +
    67
    +
    68 printf("unique: %llu, opcode: %s (%i), nodeid: %lu, len: %i, insize: %i\n",
    +
    69 (unsigned long long) in->unique,
    +
    70 opname((enum fuse_opcode) in->opcode), in->opcode,
    +
    71 (unsigned long) in->nodeid, in->len, len);
    +
    72
    +
    73 switch (in->opcode) {
    +
    74 case FUSE_READ: {
    +
    75 struct fuse_read_in *arg = (struct fuse_read_in *) buf;
    +
    76 printf("-READ fh:%llu off:%llu siz:%u rfl:%u own:%llu fl:%u\n",
    +
    77 arg->fh, arg->offset, arg->size, arg->read_flags,
    +
    78 arg->lock_owner, arg->flags);
    +
    79 break;
    +
    80 }
    +
    81 case FUSE_WRITE: {
    +
    82 struct fuse_write_in *arg = (struct fuse_write_in *) buf;
    +
    83 printf("-WRITE fh:%llu off:%llu siz:%u wfl:%u own:%llu fl:%u\n",
    +
    84 arg->fh, arg->offset, arg->size, arg->write_flags,
    +
    85 arg->lock_owner, arg->flags);
    +
    86 break;
    +
    87 }
    +
    88 }
    +
    89 prevuniq = in->unique;
    +
    90 prevopcode = in->opcode;
    +
    91 } else {
    +
    92 struct fuse_out_header *out = (struct fuse_out_header *) buf;
    +
    93 buf += sizeof(struct fuse_out_header);
    +
    94
    +
    95 printf(" unique: %llu, error: %i (%s), len: %i, outsize: %i\n",
    +
    96 (unsigned long long) out->unique, out->error,
    +
    97 strerror(-out->error), out->len, len);
    +
    98
    +
    99 if (out->unique == prevuniq) {
    +
    100 switch (prevopcode) {
    +
    101 case FUSE_GETATTR: {
    +
    102 struct fuse_attr_out *arg = (struct fuse_attr_out *) buf;
    +
    103 printf("+ATTR v:%llu.%09u i:%llu s:%llu b:%llu\n",
    +
    104 arg->attr_valid, arg->attr_valid_nsec,
    +
    105 arg->attr.ino, arg->attr.size, arg->attr.blocks);
    +
    106 break;
    +
    107 }
    +
    108 case FUSE_LOOKUP: {
    +
    109 struct fuse_entry_out *arg = (struct fuse_entry_out *) buf;
    +
    110 printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n",
    +
    111 arg->nodeid, arg->attr_valid, arg->attr_valid_nsec,
    +
    112 arg->attr.ino, arg->attr.size, arg->attr.blocks);
    +
    113 break;
    +
    114 }
    +
    115 }
    +
    116 }
    +
    117 }
    +
    118
    +
    119}
    +
    120
    +
    121int main(void)
    +
    122{
    +
    123 FILE *in = stdin;
    +
    124 while (1) {
    +
    125 int dir;
    +
    126 int res;
    +
    127 char buf[1048576];
    +
    128 unsigned len = 0;
    +
    129
    +
    130 memset(buf, 0, sizeof(buf));
    +
    131 while (1) {
    +
    132 char str[32];
    +
    133
    +
    134 res = fscanf(in, "%30s", str);
    +
    135 if (res != 1 && feof(in))
    +
    136 return 0;
    +
    137
    +
    138 if (res == 0)
    +
    139 continue;
    +
    140
    +
    141 if (strncmp(str, "read(", 5) == 0) {
    +
    142 dir = 0;
    +
    143 break;
    +
    144 } else if (strncmp(str, "writev(", 7) == 0) {
    +
    145 dir = 1;
    +
    146 break;
    +
    147 }
    +
    148 }
    +
    149
    +
    150 while (1) {
    +
    151 int c = getc(in);
    +
    152 if (c == '"') {
    +
    153 while (1) {
    +
    154 int val;
    +
    155
    +
    156 c = getc(in);
    +
    157 if (c == EOF) {
    +
    158 fprintf(stderr, "eof in string\n");
    +
    159 break;
    +
    160 }
    +
    161 if (c == '\n') {
    +
    162 fprintf(stderr, "eol in string\n");
    +
    163 break;
    +
    164 }
    +
    165 if (c == '"')
    +
    166 break;
    +
    167 if (c != '\\') {
    +
    168 val = c;
    +
    169 } else {
    +
    170 c = getc(in);
    +
    171 switch (c) {
    +
    172 case 'n': val = '\n'; break;
    +
    173 case 'r': val = '\r'; break;
    +
    174 case 't': val = '\t'; break;
    +
    175 case '"': val = '"'; break;
    +
    176 case '\\': val = '\\'; break;
    +
    177 case 'x':
    +
    178 res = scanf("%x", &val);
    +
    179 if (res != 1) {
    +
    180 fprintf(stderr, "parse error\n");
    +
    181 continue;
    +
    182 }
    +
    183 break;
    +
    184 default:
    +
    185 fprintf(stderr, "unknown sequence: '\\%c'\n", c);
    +
    186 continue;
    +
    187 }
    +
    188 }
    +
    189 buf[len++] = val;
    +
    190 }
    +
    191 }
    +
    192 if (c == '\n')
    +
    193 break;
    +
    194 }
    +
    195 process_buf(dir, buf, len);
    +
    196 memset(buf, 0, len);
    +
    197 len = 0;
    +
    198 }
    +
    199}
    +
    + + + + diff --git a/doc/html/test_2test__abi_8c_source.html b/doc/html/test_2test__abi_8c_source.html new file mode 100644 index 0000000..4cd4cee --- /dev/null +++ b/doc/html/test_2test__abi_8c_source.html @@ -0,0 +1,80 @@ + + + + + + + +libfuse: test/test_abi.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    test_abi.c
    +
    +
    +
    1#define FUSE_USE_VERSION 30
    +
    2
    +
    3#include "fuse.h"
    +
    4
    +
    5#include <stdio.h>
    +
    6#include <stdlib.h>
    +
    7
    +
    8int main(void)
    +
    9{
    +
    10 if (sizeof(struct fuse_file_info) != 64) {
    +
    11 fprintf(stderr, "struct fuse_file_info size mismatch\n");
    +
    12 exit(1);
    +
    13 }
    +
    14 if (sizeof(struct fuse_conn_info) != 128) {
    +
    15 fprintf(stderr, "struct fuse_conn_info size mismatch\n");
    +
    16 exit(1);
    +
    17 }
    +
    18}
    + + +
    + + + + diff --git a/doc/html/test_2test__setattr_8c_source.html b/doc/html/test_2test__setattr_8c_source.html new file mode 100644 index 0000000..4eb8f11 --- /dev/null +++ b/doc/html/test_2test__setattr_8c_source.html @@ -0,0 +1,272 @@ + + + + + + + +libfuse: test/test_setattr.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    test_setattr.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file GPL2.txt.
    +
    7*/
    +
    8
    +
    9
    +
    10#define FUSE_USE_VERSION 30
    +
    11
    +
    12/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
    +
    13#include <fuse.h>
    +
    14
    +
    15#include <fuse_config.h>
    +
    16#include <fuse_lowlevel.h>
    +
    17#include <stdio.h>
    +
    18#include <stdlib.h>
    +
    19#include <string.h>
    +
    20#include <errno.h>
    +
    21#include <fcntl.h>
    +
    22#include <assert.h>
    +
    23#include <stddef.h>
    +
    24#include <unistd.h>
    +
    25#include <pthread.h>
    +
    26
    +
    27#ifndef __linux__
    +
    28#include <limits.h>
    +
    29#else
    +
    30#include <linux/limits.h>
    +
    31#endif
    +
    32
    +
    33#define FILE_INO 2
    +
    34#define FILE_NAME "truncate_me"
    +
    35
    +
    36static int got_fh;
    +
    37static mode_t file_mode = S_IFREG | 0644;
    +
    38
    +
    39static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
    +
    40 stbuf->st_ino = ino;
    +
    41 if (ino == FUSE_ROOT_ID) {
    +
    42 stbuf->st_mode = S_IFDIR | 0755;
    +
    43 stbuf->st_nlink = 1;
    +
    44 }
    +
    45
    +
    46 else if (ino == FILE_INO) {
    +
    47 stbuf->st_mode = file_mode;
    +
    48 stbuf->st_nlink = 1;
    +
    49 stbuf->st_size = 0;
    +
    50 }
    +
    51
    +
    52 else
    +
    53 return -1;
    +
    54
    +
    55 return 0;
    +
    56}
    +
    57
    +
    58static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
    +
    59 const char *name) {
    +
    60 struct fuse_entry_param e;
    +
    61 memset(&e, 0, sizeof(e));
    +
    62
    +
    63 if (parent != FUSE_ROOT_ID)
    +
    64 goto err_out;
    +
    65 else if (strcmp(name, FILE_NAME) == 0)
    +
    66 e.ino = FILE_INO;
    +
    67 else
    +
    68 goto err_out;
    +
    69
    +
    70 if (tfs_stat(e.ino, &e.attr) != 0)
    +
    71 goto err_out;
    +
    72 fuse_reply_entry(req, &e);
    +
    73 return;
    +
    74
    +
    75err_out:
    +
    76 fuse_reply_err(req, ENOENT);
    +
    77}
    +
    78
    +
    79static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    80 struct fuse_file_info *fi) {
    +
    81 struct stat stbuf;
    +
    82
    +
    83 (void) fi;
    +
    84
    +
    85 memset(&stbuf, 0, sizeof(stbuf));
    +
    86 if (tfs_stat(ino, &stbuf) != 0)
    +
    87 fuse_reply_err(req, ENOENT);
    +
    88 else
    +
    89 fuse_reply_attr(req, &stbuf, 5);
    +
    90}
    +
    91
    +
    92static void tfs_open(fuse_req_t req, fuse_ino_t ino,
    +
    93 struct fuse_file_info *fi) {
    +
    94 if (ino == FUSE_ROOT_ID)
    +
    95 fuse_reply_err(req, EISDIR);
    +
    96 else {
    +
    97 assert(ino == FILE_INO);
    +
    98 fi->fh = FILE_INO;
    +
    99 fuse_reply_open(req, fi);
    +
    100 }
    +
    101}
    +
    102
    +
    103static void tfs_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
    +
    104 int to_set, struct fuse_file_info *fi) {
    +
    105 if(ino != FILE_INO ||
    +
    106 !(to_set & FUSE_SET_ATTR_MODE)) {
    +
    107 fuse_reply_err(req, EINVAL);
    +
    108 return;
    +
    109 }
    +
    110
    +
    111 if(fi == NULL)
    +
    112 fprintf(stderr, "setattr with fi == NULL\n");
    +
    113 else if (fi->fh != FILE_INO)
    +
    114 fprintf(stderr, "setattr with wrong fi->fh\n");
    +
    115 else {
    +
    116 fprintf(stderr, "setattr ok\n");
    +
    117 got_fh = 1;
    +
    118 file_mode = attr->st_mode;
    +
    119 }
    +
    120
    +
    121 tfs_getattr(req, ino, fi);
    +
    122}
    +
    123
    +
    124static struct fuse_lowlevel_ops tfs_oper = {
    +
    125 .lookup = tfs_lookup,
    +
    126 .getattr = tfs_getattr,
    +
    127 .open = tfs_open,
    +
    128 .setattr = tfs_setattr,
    +
    129};
    +
    130
    +
    131static void* run_fs(void *data) {
    +
    132 struct fuse_session *se = (struct fuse_session*) data;
    +
    133 assert(fuse_session_loop(se) == 0);
    +
    134 return NULL;
    +
    135}
    +
    136
    +
    137static void test_fs(char *mountpoint) {
    +
    138 char fname[PATH_MAX];
    +
    139 int fd;
    +
    140
    +
    141 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
    +
    142 mountpoint) > 0);
    +
    143 fd = open(fname, O_WRONLY);
    +
    144 if (fd == -1) {
    +
    145 perror(fname);
    +
    146 assert(0);
    +
    147 }
    +
    148
    +
    149 assert(fchmod(fd, 0600) == 0);
    +
    150 close(fd);
    +
    151}
    +
    152
    +
    153int main(int argc, char *argv[]) {
    +
    154 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    155 struct fuse_session *se;
    +
    156 struct fuse_cmdline_opts fuse_opts;
    +
    157 pthread_t fs_thread;
    +
    158
    +
    159 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
    +
    160#ifndef __FreeBSD__
    +
    161 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
    +
    162#endif
    +
    163 se = fuse_session_new(&args, &tfs_oper,
    +
    164 sizeof(tfs_oper), NULL);
    +
    165 assert (se != NULL);
    +
    166 assert(fuse_set_signal_handlers(se) == 0);
    +
    167 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
    +
    168
    +
    169 /* Start file-system thread */
    +
    170 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
    +
    171
    +
    172 /* Do test */
    +
    173 test_fs(fuse_opts.mountpoint);
    +
    174
    +
    175 /* Stop file system */
    +
    176 assert(pthread_cancel(fs_thread) == 0);
    +
    177
    + +
    179 assert(got_fh == 1);
    + + +
    182
    +
    183 printf("Test completed successfully.\n");
    +
    184 return 0;
    +
    185}
    +
    186
    +
    187
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    uint64_t fuse_ino_t
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_ROOT_ID
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + +
    +
    fuse_ino_t ino
    + + + +
    void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    + + + + diff --git a/doc/html/test_2test__signals_8c_source.html b/doc/html/test_2test__signals_8c_source.html new file mode 100644 index 0000000..9e4482a --- /dev/null +++ b/doc/html/test_2test__signals_8c_source.html @@ -0,0 +1,279 @@ + + + + + + + +libfuse: test/test_signals.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    test_signals.c
    +
    +
    +
    1/*
    +
    2 * FUSE: Filesystem in Userspace
    +
    3 * Copyright (C) 2025 Bernd Schubert <bernd@bsbernd.com>
    +
    4 *
    +
    5 * Test for signal handling in libfuse.
    +
    6 *
    +
    7 * This program can be distributed under the terms of the GNU LGPLv2.
    +
    8 * See the file GPL2.txt
    +
    9 */
    +
    10
    +
    11#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17)
    +
    12
    +
    13#include "fuse_config.h"
    +
    14#include "fuse_lowlevel.h"
    +
    15#include "fuse_i.h"
    +
    16
    +
    17#include <pthread.h>
    +
    18#include <stdatomic.h>
    +
    19#include <stdio.h>
    +
    20#include <stdlib.h>
    +
    21#include <string.h>
    +
    22#include <unistd.h>
    +
    23#include <signal.h>
    +
    24#include <errno.h>
    +
    25#include <sys/types.h>
    +
    26#include <sys/wait.h>
    +
    27
    +
    28static void test_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    29{
    +
    30 (void)parent;
    +
    31 (void)name;
    +
    32 /* Simulate slow lookup to test signal interruption */
    +
    33 sleep(2);
    +
    34 fuse_reply_err(req, ENOENT);
    +
    35}
    +
    36
    +
    37static void test_ll_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    38 struct fuse_file_info *fi)
    +
    39{
    +
    40 (void)ino;
    +
    41 (void)fi;
    +
    42 /* Simulate slow getattr to test signal interruption */
    +
    43 sleep(2);
    +
    44 fuse_reply_err(req, ENOENT);
    +
    45}
    +
    46
    +
    47static const struct fuse_lowlevel_ops test_ll_ops = {
    +
    48 .lookup = test_ll_lookup,
    +
    49 .getattr = test_ll_getattr,
    +
    50};
    +
    51
    +
    52static void *signal_sender_thread(void *arg)
    +
    53{
    +
    54 (void)arg;
    +
    55
    +
    56 usleep(2 * 1000 * 1000);
    +
    57
    +
    58 /* Send SIGTERM to the process */
    +
    59 kill(getpid(), SIGTERM);
    +
    60 return NULL;
    +
    61}
    +
    62
    +
    63static void fork_child(void)
    +
    64{
    +
    65 struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
    +
    66 struct fuse_session *se;
    +
    67 struct fuse_loop_config *loop_config;
    +
    68 pthread_t sig_thread;
    +
    69 char *mountpoint = NULL;
    +
    70 int ret = -1;
    +
    71
    +
    72 /* Add the program name to arg[0] */
    +
    73 if (fuse_opt_add_arg(&args, "test_signals")) {
    +
    74 fprintf(stderr, "Failed to add argument\n");
    +
    75 goto out_free_mountpoint;
    +
    76 }
    +
    77
    +
    78 /* Add debug flag to see more output */
    +
    79 fuse_opt_add_arg(&args, "-d");
    +
    80
    +
    81 /* Create temporary mount point */
    +
    82 mountpoint = strdup("/tmp/fuse_test_XXXXXX");
    +
    83 if (!mountpoint || !mkdtemp(mountpoint)) {
    +
    84 fprintf(stderr, "Failed to create temp dir\n");
    +
    85 goto out_free_args;
    +
    86 }
    +
    87
    +
    88 /* Create session */
    +
    89 se = fuse_session_new(&args, &test_ll_ops, sizeof(test_ll_ops), NULL);
    +
    90 if (!se) {
    +
    91 fprintf(stderr, "Failed to create FUSE session\n");
    +
    92 goto out_free_mountpoint;
    +
    93 }
    +
    94
    +
    95 /* Mount filesystem */
    +
    96 if (fuse_session_mount(se, mountpoint)) {
    +
    97 fprintf(stderr, "Failed to mount FUSE filesystem\n");
    +
    98 goto out_destroy_session;
    +
    99 }
    +
    100
    +
    101 /* Create loop config */
    +
    102 loop_config = fuse_loop_cfg_create();
    +
    103 if (!loop_config) {
    +
    104 fprintf(stderr, "Failed to create loop config\n");
    +
    105 goto out_unmount;
    +
    106 }
    +
    107 fuse_loop_cfg_set_clone_fd(loop_config, 0);
    +
    108 fuse_loop_cfg_set_max_threads(loop_config, 2);
    +
    109
    +
    110 /* Set up signal handlers */
    +
    111 if (fuse_set_signal_handlers(se)) {
    +
    112 fprintf(stderr, "Failed to set up signal handlers\n");
    +
    113 goto out_destroy_config;
    +
    114 }
    +
    115
    +
    116 /* Create thread that will send signals */
    +
    117 if (pthread_create(&sig_thread, NULL, signal_sender_thread, NULL)) {
    +
    118 fprintf(stderr, "Failed to create signal sender thread\n");
    +
    119 goto out_remove_handlers;
    +
    120 }
    +
    121
    +
    122 /* Enter FUSE loop */
    +
    123 ret = fuse_session_loop_mt_312(se, loop_config);
    +
    124
    +
    125 printf("Debug: fuse_session_loop_mt_312 returned %d\n", ret);
    +
    126 printf("Debug: session exited state: %d\n", fuse_session_exited(se));
    +
    127 printf("Debug: session status: %d\n", se->error);
    +
    128
    +
    129 /* Check exit status before cleanup */
    +
    130 int clean_exit = (fuse_session_exited(se) && se->error == SIGTERM);
    +
    131
    +
    132 /* Clean up */
    +
    133 pthread_join(sig_thread, NULL);
    + + + +
    137 fuse_loop_cfg_destroy(loop_config);
    +
    138 rmdir(mountpoint);
    +
    139 free(mountpoint);
    +
    140 fuse_opt_free_args(&args);
    +
    141
    +
    142 /* Use saved exit status */
    +
    143 if (clean_exit) {
    +
    144 printf("Debug: Clean shutdown via SIGTERM\n");
    +
    145 exit(0);
    +
    146 }
    +
    147 printf("Debug: Exiting with status %d\n", ret != 0);
    +
    148 exit(ret != 0);
    +
    149
    +
    150out_remove_handlers:
    + +
    152out_destroy_config:
    +
    153 fuse_loop_cfg_destroy(loop_config);
    +
    154out_unmount:
    + +
    156out_destroy_session:
    + +
    158out_free_mountpoint:
    +
    159 rmdir(mountpoint);
    +
    160 free(mountpoint);
    +
    161out_free_args:
    +
    162 fuse_opt_free_args(&args);
    +
    163 exit(1);
    +
    164}
    +
    165
    +
    166static void run_test_in_child(void)
    +
    167{
    +
    168 pid_t child;
    +
    169 int status;
    +
    170
    +
    171 child = fork();
    +
    172 if (child == -1) {
    +
    173 perror("fork");
    +
    174 exit(1);
    +
    175 }
    +
    176
    +
    177 if (child == 0)
    +
    178 fork_child();
    +
    179
    +
    180 /* In parent process */
    +
    181 if (waitpid(child, &status, 0) == -1) {
    +
    182 perror("waitpid");
    +
    183 exit(1);
    +
    184 }
    +
    185
    +
    186 /* Check if child exited due to SIGTERM - this is expected */
    +
    187 if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) {
    +
    188 printf("Child process terminated by SIGTERM as expected\n");
    +
    189 exit(0);
    +
    190 }
    +
    191
    +
    192 /* For any other type of exit, maintain existing behavior */
    +
    193 exit(WIFEXITED(status) ? WEXITSTATUS(status) : 1);
    +
    194}
    +
    195
    +
    196int main(void)
    +
    197{
    +
    198 printf("Testing SIGTERM handling in libfuse\n");
    +
    199 run_test_in_child();
    +
    200 printf("SIGTERM handling test passed\n");
    +
    201 return 0;
    +
    202}
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_exited(struct fuse_session *se)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    uint64_t fuse_ino_t
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    + + + + +
    void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    + + + + diff --git a/doc/html/test_2test__syscalls_8c_source.html b/doc/html/test_2test__syscalls_8c_source.html new file mode 100644 index 0000000..0f22e44 --- /dev/null +++ b/doc/html/test_2test__syscalls_8c_source.html @@ -0,0 +1,2307 @@ + + + + + + + +libfuse: test/test_syscalls.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    test_syscalls.c
    +
    +
    +
    1#define _GNU_SOURCE
    +
    2#include "fuse_config.h"
    +
    3
    +
    4#include <stdio.h>
    +
    5#include <stdlib.h>
    +
    6#include <stdarg.h>
    +
    7#include <string.h>
    +
    8#include <unistd.h>
    +
    9#include <fcntl.h>
    +
    10#include <dirent.h>
    +
    11#include <utime.h>
    +
    12#include <errno.h>
    +
    13#include <assert.h>
    +
    14#include <time.h>
    +
    15#include <sys/socket.h>
    +
    16#include <sys/types.h>
    +
    17#include <sys/stat.h>
    +
    18#include <sys/un.h>
    +
    19
    +
    20#ifndef ALLPERMS
    +
    21# define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)/* 07777 */
    +
    22#endif
    +
    23
    +
    24
    +
    25static const char *basepath;
    +
    26static const char *basepath_r;
    +
    27static char testfile[1024];
    +
    28static char testfile2[1024];
    +
    29static char testdir[1024];
    +
    30static char testdir2[1024];
    +
    31static char testsock[1024];
    +
    32static char subfile[1280];
    +
    33
    +
    34static char testfile_r[1024];
    +
    35static char testfile2_r[1024];
    +
    36static char testdir_r[1024];
    +
    37static char testdir2_r[1024];
    +
    38static char subfile_r[1280];
    +
    39
    +
    40static char testname[256];
    +
    41static char testdata[] = "abcdefghijklmnopqrstuvwxyz";
    +
    42static char testdata2[] = "1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./";
    +
    43static const char *testdir_files[] = { "f1", "f2", NULL};
    +
    44static long seekdir_offsets[4];
    +
    45static char zerodata[4096];
    +
    46static int testdatalen = sizeof(testdata) - 1;
    +
    47static int testdata2len = sizeof(testdata2) - 1;
    +
    48static unsigned int testnum = 0;
    +
    49static unsigned int select_test = 0;
    +
    50static unsigned int skip_test = 0;
    +
    51static unsigned int unlinked_test = 0;
    +
    52
    +
    53#define MAX_ENTRIES 1024
    +
    54#define MAX_TESTS 100
    +
    55
    +
    56static struct test {
    +
    57 int fd;
    +
    58 struct stat stat;
    +
    59} tests[MAX_TESTS];
    +
    60
    +
    61static void test_perror(const char *func, const char *msg)
    +
    62{
    +
    63 fprintf(stderr, "%s %s() - %s: %s\n", testname, func, msg,
    +
    64 strerror(errno));
    +
    65}
    +
    66
    +
    67static void test_error(const char *func, const char *msg, ...)
    +
    68 __attribute__ ((format (printf, 2, 3)));
    +
    69
    +
    70static void __start_test(const char *fmt, ...)
    +
    71 __attribute__ ((format (printf, 1, 2)));
    +
    72
    +
    73static void test_error(const char *func, const char *msg, ...)
    +
    74{
    +
    75 va_list ap;
    +
    76 fprintf(stderr, "%s %s() - ", testname, func);
    +
    77 va_start(ap, msg);
    +
    78 vfprintf(stderr, msg, ap);
    +
    79 va_end(ap);
    +
    80 fprintf(stderr, "\n");
    +
    81}
    +
    82
    +
    83static int is_dot_or_dotdot(const char *name) {
    +
    84 return name[0] == '.' &&
    +
    85 (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
    +
    86}
    +
    87
    +
    88static void success(void)
    +
    89{
    +
    90 fprintf(stderr, "%s OK\n", testname);
    +
    91}
    +
    92
    +
    93#define this_test (&tests[testnum-1])
    +
    94#define next_test (&tests[testnum])
    +
    95
    +
    96static void __start_test(const char *fmt, ...)
    +
    97{
    +
    98 unsigned int n;
    +
    99 va_list ap;
    +
    100 n = sprintf(testname, "%3i [", testnum);
    +
    101 va_start(ap, fmt);
    +
    102 n += vsprintf(testname + n, fmt, ap);
    +
    103 va_end(ap);
    +
    104 sprintf(testname + n, "]");
    +
    105 // Use dedicated testfile per test
    +
    106 sprintf(testfile, "%s/testfile.%d", basepath, testnum);
    +
    107 sprintf(testfile_r, "%s/testfile.%d", basepath_r, testnum);
    +
    108 if (testnum > MAX_TESTS) {
    +
    109 fprintf(stderr, "%s - too many tests\n", testname);
    +
    110 exit(1);
    +
    111 }
    +
    112 this_test->fd = -1;
    +
    113}
    +
    114
    +
    115#define start_test(msg, args...) { \
    +
    116 testnum++; \
    +
    117 if ((select_test && testnum != select_test) || \
    +
    118 (testnum == skip_test)) { \
    +
    119 return 0; \
    +
    120 } \
    +
    121 __start_test(msg, ##args); \
    +
    122}
    +
    123
    +
    124#define PERROR(msg) test_perror(__FUNCTION__, msg)
    +
    125#define ERROR(msg, args...) test_error(__FUNCTION__, msg, ##args)
    +
    126
    +
    127#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
    +
    128
    +
    129static int st_check_size(struct stat *st, int len)
    +
    130{
    +
    131 if (st->st_size != len) {
    +
    132 ERROR("length %u instead of %u", (int) st->st_size,
    +
    133 (int) len);
    +
    134 return -1;
    +
    135 }
    +
    136 return 0;
    +
    137}
    +
    138
    +
    139static int check_size(const char *path, int len)
    +
    140{
    +
    141 struct stat stbuf;
    +
    142 int res = stat(path, &stbuf);
    +
    143 if (res == -1) {
    +
    144 PERROR("stat");
    +
    145 return -1;
    +
    146 }
    +
    147 return st_check_size(&stbuf, len);
    +
    148}
    +
    149
    +
    150static int check_testfile_size(const char *path, int len)
    +
    151{
    +
    152 this_test->stat.st_size = len;
    +
    153 return check_size(path, len);
    +
    154}
    +
    155
    +
    156static int st_check_type(struct stat *st, mode_t type)
    +
    157{
    +
    158 if ((st->st_mode & S_IFMT) != type) {
    +
    159 ERROR("type 0%o instead of 0%o", st->st_mode & S_IFMT, type);
    +
    160 return -1;
    +
    161 }
    +
    162 return 0;
    +
    163}
    +
    164
    +
    165static int check_type(const char *path, mode_t type)
    +
    166{
    +
    167 struct stat stbuf;
    +
    168 int res = lstat(path, &stbuf);
    +
    169 if (res == -1) {
    +
    170 PERROR("lstat");
    +
    171 return -1;
    +
    172 }
    +
    173 return st_check_type(&stbuf, type);
    +
    174}
    +
    175
    +
    176static int st_check_mode(struct stat *st, mode_t mode)
    +
    177{
    +
    178 if ((st->st_mode & ALLPERMS) != mode) {
    +
    179 ERROR("mode 0%o instead of 0%o", st->st_mode & ALLPERMS,
    +
    180 mode);
    +
    181 return -1;
    +
    182 }
    +
    183 return 0;
    +
    184}
    +
    185
    +
    186static int check_mode(const char *path, mode_t mode)
    +
    187{
    +
    188 struct stat stbuf;
    +
    189 int res = lstat(path, &stbuf);
    +
    190 if (res == -1) {
    +
    191 PERROR("lstat");
    +
    192 return -1;
    +
    193 }
    +
    194 return st_check_mode(&stbuf, mode);
    +
    195}
    +
    196
    +
    197static int check_testfile_mode(const char *path, mode_t mode)
    +
    198{
    +
    199 this_test->stat.st_mode &= ~ALLPERMS;
    +
    200 this_test->stat.st_mode |= mode;
    +
    201 return check_mode(path, mode);
    +
    202}
    +
    203
    +
    204static int check_times(const char *path, time_t atime, time_t mtime)
    +
    205{
    +
    206 int err = 0;
    +
    207 struct stat stbuf;
    +
    208 int res = lstat(path, &stbuf);
    +
    209 if (res == -1) {
    +
    210 PERROR("lstat");
    +
    211 return -1;
    +
    212 }
    +
    213 if (stbuf.st_atime != atime) {
    +
    214 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
    +
    215 err--;
    +
    216 }
    +
    217 if (stbuf.st_mtime != mtime) {
    +
    218 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
    +
    219 err--;
    +
    220 }
    +
    221 if (err)
    +
    222 return -1;
    +
    223
    +
    224 return 0;
    +
    225}
    +
    226
    +
    227#if 0
    +
    228static int fcheck_times(int fd, time_t atime, time_t mtime)
    +
    229{
    +
    230 int err = 0;
    +
    231 struct stat stbuf;
    +
    232 int res = fstat(fd, &stbuf);
    +
    233 if (res == -1) {
    +
    234 PERROR("fstat");
    +
    235 return -1;
    +
    236 }
    +
    237 if (stbuf.st_atime != atime) {
    +
    238 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
    +
    239 err--;
    +
    240 }
    +
    241 if (stbuf.st_mtime != mtime) {
    +
    242 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
    +
    243 err--;
    +
    244 }
    +
    245 if (err)
    +
    246 return -1;
    +
    247
    +
    248 return 0;
    +
    249}
    +
    250#endif
    +
    251
    +
    252static int st_check_nlink(struct stat *st, nlink_t nlink)
    +
    253{
    +
    254 if (st->st_nlink != nlink) {
    +
    255 ERROR("nlink %li instead of %li", (long) st->st_nlink,
    +
    256 (long) nlink);
    +
    257 return -1;
    +
    258 }
    +
    259 return 0;
    +
    260}
    +
    261
    +
    262static int check_nlink(const char *path, nlink_t nlink)
    +
    263{
    +
    264 struct stat stbuf;
    +
    265 int res = lstat(path, &stbuf);
    +
    266 if (res == -1) {
    +
    267 PERROR("lstat");
    +
    268 return -1;
    +
    269 }
    +
    270 return st_check_nlink(&stbuf, nlink);
    +
    271}
    +
    272
    +
    273static int fcheck_stat(int fd, int flags, struct stat *st)
    +
    274{
    +
    275 struct stat stbuf;
    +
    276 int res = fstat(fd, &stbuf);
    +
    277 if (res == -1) {
    +
    278 if (flags & O_PATH) {
    +
    279 // With O_PATH fd, the server does not have to keep
    +
    280 // the inode alive so FUSE inode may be stale or bad
    +
    281 if (errno == ESTALE || errno == EIO ||
    +
    282 errno == ENOENT || errno == EBADF)
    +
    283 return 0;
    +
    284 }
    +
    285 PERROR("fstat");
    +
    286 return -1;
    +
    287 }
    +
    288
    +
    289 int err = 0;
    +
    290 err += st_check_type(&stbuf, st->st_mode & S_IFMT);
    +
    291 err += st_check_mode(&stbuf, st->st_mode & ALLPERMS);
    +
    292 err += st_check_size(&stbuf, st->st_size);
    +
    293 err += st_check_nlink(&stbuf, st->st_nlink);
    +
    294
    +
    295 return err;
    +
    296}
    +
    297
    +
    298static int check_nonexist(const char *path)
    +
    299{
    +
    300 struct stat stbuf;
    +
    301 int res = lstat(path, &stbuf);
    +
    302 if (res == 0) {
    +
    303 ERROR("file should not exist");
    +
    304 return -1;
    +
    305 }
    +
    306 if (errno != ENOENT) {
    +
    307 ERROR("file should not exist: %s", strerror(errno));
    +
    308 return -1;
    +
    309 }
    +
    310 return 0;
    +
    311}
    +
    312
    +
    313static int check_buffer(const char *buf, const char *data, unsigned len)
    +
    314{
    +
    315 if (memcmp(buf, data, len) != 0) {
    +
    316 ERROR("data mismatch");
    +
    317 return -1;
    +
    318 }
    +
    319 return 0;
    +
    320}
    +
    321
    +
    322static int check_data(const char *path, const char *data, int offset,
    +
    323 unsigned len)
    +
    324{
    +
    325 char buf[4096];
    +
    326 int res;
    +
    327 int fd = open(path, O_RDONLY);
    +
    328 if (fd == -1) {
    +
    329 PERROR("open");
    +
    330 return -1;
    +
    331 }
    +
    332 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
    +
    333 PERROR("lseek");
    +
    334 close(fd);
    +
    335 return -1;
    +
    336 }
    +
    337 while (len) {
    +
    338 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
    +
    339 res = read(fd, buf, rdlen);
    +
    340 if (res == -1) {
    +
    341 PERROR("read");
    +
    342 close(fd);
    +
    343 return -1;
    +
    344 }
    +
    345 if (res != rdlen) {
    +
    346 ERROR("short read: %u instead of %u", res, rdlen);
    +
    347 close(fd);
    +
    348 return -1;
    +
    349 }
    +
    350 if (check_buffer(buf, data, rdlen) != 0) {
    +
    351 close(fd);
    +
    352 return -1;
    +
    353 }
    +
    354 data += rdlen;
    +
    355 len -= rdlen;
    +
    356 }
    +
    357 res = close(fd);
    +
    358 if (res == -1) {
    +
    359 PERROR("close");
    +
    360 return -1;
    +
    361 }
    +
    362 return 0;
    +
    363}
    +
    364
    +
    365static int fcheck_data(int fd, const char *data, int offset,
    +
    366 unsigned len)
    +
    367{
    +
    368 char buf[4096];
    +
    369 int res;
    +
    370 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
    +
    371 PERROR("lseek");
    +
    372 return -1;
    +
    373 }
    +
    374 while (len) {
    +
    375 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
    +
    376 res = read(fd, buf, rdlen);
    +
    377 if (res == -1) {
    +
    378 PERROR("read");
    +
    379 return -1;
    +
    380 }
    +
    381 if (res != rdlen) {
    +
    382 ERROR("short read: %u instead of %u", res, rdlen);
    +
    383 return -1;
    +
    384 }
    +
    385 if (check_buffer(buf, data, rdlen) != 0) {
    +
    386 return -1;
    +
    387 }
    +
    388 data += rdlen;
    +
    389 len -= rdlen;
    +
    390 }
    +
    391 return 0;
    +
    392}
    +
    393
    +
    394static int check_dir_contents(const char *path, const char **contents)
    +
    395{
    +
    396 int i;
    +
    397 int res;
    +
    398 int err = 0;
    +
    399 int found[MAX_ENTRIES];
    +
    400 const char *cont[MAX_ENTRIES];
    +
    401 DIR *dp;
    +
    402
    +
    403 for (i = 0; contents[i]; i++) {
    +
    404 assert(i < MAX_ENTRIES - 3);
    +
    405 found[i] = 0;
    +
    406 cont[i] = contents[i];
    +
    407 }
    +
    408 cont[i] = NULL;
    +
    409
    +
    410 dp = opendir(path);
    +
    411 if (dp == NULL) {
    +
    412 PERROR("opendir");
    +
    413 return -1;
    +
    414 }
    +
    415 memset(found, 0, sizeof(found));
    +
    416 while(1) {
    +
    417 struct dirent *de;
    +
    418 errno = 0;
    +
    419 de = readdir(dp);
    +
    420 if (de == NULL) {
    +
    421 if (errno) {
    +
    422 PERROR("readdir");
    +
    423 closedir(dp);
    +
    424 return -1;
    +
    425 }
    +
    426 break;
    +
    427 }
    +
    428 if (is_dot_or_dotdot(de->d_name))
    +
    429 continue;
    +
    430 for (i = 0; cont[i] != NULL; i++) {
    +
    431 assert(i < MAX_ENTRIES);
    +
    432 if (strcmp(cont[i], de->d_name) == 0) {
    +
    433 if (found[i]) {
    +
    434 ERROR("duplicate entry <%s>",
    +
    435 de->d_name);
    +
    436 err--;
    +
    437 } else
    +
    438 found[i] = 1;
    +
    439 break;
    +
    440 }
    +
    441 }
    +
    442 if (!cont[i]) {
    +
    443 ERROR("unexpected entry <%s>", de->d_name);
    +
    444 err --;
    +
    445 }
    +
    446 }
    +
    447 for (i = 0; cont[i] != NULL; i++) {
    +
    448 if (!found[i]) {
    +
    449 ERROR("missing entry <%s>", cont[i]);
    +
    450 err--;
    +
    451 }
    +
    452 }
    +
    453 res = closedir(dp);
    +
    454 if (res == -1) {
    +
    455 PERROR("closedir");
    +
    456 return -1;
    +
    457 }
    +
    458 if (err)
    +
    459 return -1;
    +
    460
    +
    461 return 0;
    +
    462}
    +
    463
    +
    464static int create_file(const char *path, const char *data, int len)
    +
    465{
    +
    466 int res;
    +
    467 int fd;
    +
    468
    +
    469 unlink(path);
    +
    470 fd = creat(path, 0644);
    +
    471 if (fd == -1) {
    +
    472 PERROR("creat");
    +
    473 return -1;
    +
    474 }
    +
    475 if (len) {
    +
    476 res = write(fd, data, len);
    +
    477 if (res == -1) {
    +
    478 PERROR("write");
    +
    479 close(fd);
    +
    480 return -1;
    +
    481 }
    +
    482 if (res != len) {
    +
    483 ERROR("write is short: %u instead of %u", res, len);
    +
    484 close(fd);
    +
    485 return -1;
    +
    486 }
    +
    487 }
    +
    488 res = close(fd);
    +
    489 if (res == -1) {
    +
    490 PERROR("close");
    +
    491 return -1;
    +
    492 }
    +
    493 res = check_type(path, S_IFREG);
    +
    494 if (res == -1)
    +
    495 return -1;
    +
    496 res = check_mode(path, 0644);
    +
    497 if (res == -1)
    +
    498 return -1;
    +
    499 res = check_nlink(path, 1);
    +
    500 if (res == -1)
    +
    501 return -1;
    +
    502 res = check_size(path, len);
    +
    503 if (res == -1)
    +
    504 return -1;
    +
    505
    +
    506 if (len) {
    +
    507 res = check_data(path, data, 0, len);
    +
    508 if (res == -1)
    +
    509 return -1;
    +
    510 }
    +
    511
    +
    512 return 0;
    +
    513}
    +
    514
    +
    515static int create_path_fd(const char *path, const char *data, int len)
    +
    516{
    +
    517 int path_fd;
    +
    518 int res;
    +
    519
    +
    520 res = create_file(path, data, len);
    +
    521 if (res == -1)
    +
    522 return -1;
    +
    523
    +
    524 path_fd = open(path, O_PATH);
    +
    525 if (path_fd == -1)
    +
    526 PERROR("open(O_PATH)");
    +
    527
    +
    528 return path_fd;
    +
    529}
    +
    530
    +
    531// Can be called once per test
    +
    532static int create_testfile(const char *path, const char *data, int len)
    +
    533{
    +
    534 struct test *t = this_test;
    +
    535 struct stat *st = &t->stat;
    +
    536 int res, fd;
    +
    537
    +
    538 if (t->fd > 0) {
    +
    539 ERROR("testfile already created");
    +
    540 return -1;
    +
    541 }
    +
    542
    +
    543 fd = create_path_fd(path, data, len);
    +
    544 if (fd == -1)
    +
    545 return -1;
    +
    546
    +
    547 t->fd = fd;
    +
    548
    +
    549 res = fstat(fd, st);
    +
    550 if (res == -1) {
    +
    551 PERROR("fstat");
    +
    552 return -1;
    +
    553 }
    +
    554
    +
    555 return 0;
    +
    556}
    +
    557
    +
    558static int check_unlinked_testfile(int fd)
    +
    559{
    +
    560 struct stat *st = &this_test->stat;
    +
    561
    +
    562 st->st_nlink = 0;
    +
    563 return fcheck_stat(fd, O_PATH, st);
    +
    564}
    +
    565
    +
    566// Check recorded testfiles after all tests completed
    +
    567static int check_unlinked_testfiles(void)
    +
    568{
    +
    569 int fd;
    +
    570 int res, err = 0;
    +
    571 int num = testnum;
    +
    572
    +
    573 if (!unlinked_test)
    +
    574 return 0;
    +
    575
    +
    576 testnum = 0;
    +
    577 while (testnum < num) {
    +
    578 fd = next_test->fd;
    +
    579 start_test("check_unlinked_testfile");
    +
    580 if (fd == -1)
    +
    581 continue;
    +
    582
    +
    583 err += check_unlinked_testfile(fd);
    +
    584 res = close(fd);
    +
    585 if (res == -1) {
    +
    586 PERROR("close(test_fd)");
    +
    587 err--;
    +
    588 }
    +
    589 }
    +
    590
    +
    591 if (err) {
    +
    592 fprintf(stderr, "%i unlinked testfile checks failed\n", -err);
    +
    593 return 1;
    +
    594 }
    +
    595
    +
    596 return err;
    +
    597}
    +
    598
    +
    599static int cleanup_dir(const char *path, const char **dir_files, int quiet)
    +
    600{
    +
    601 int i;
    +
    602 int err = 0;
    +
    603
    +
    604 for (i = 0; dir_files[i]; i++) {
    +
    605 int res;
    +
    606 char fpath[1280];
    +
    607 sprintf(fpath, "%s/%s", path, dir_files[i]);
    +
    608 res = unlink(fpath);
    +
    609 if (res == -1 && !quiet) {
    +
    610 PERROR("unlink");
    +
    611 err --;
    +
    612 }
    +
    613 }
    +
    614 if (err)
    +
    615 return -1;
    +
    616
    +
    617 return 0;
    +
    618}
    +
    619
    +
    620static int create_dir(const char *path, const char **dir_files)
    +
    621{
    +
    622 int res;
    +
    623 int i;
    +
    624
    +
    625 rmdir(path);
    +
    626 res = mkdir(path, 0755);
    +
    627 if (res == -1) {
    +
    628 PERROR("mkdir");
    +
    629 return -1;
    +
    630 }
    +
    631 res = check_type(path, S_IFDIR);
    +
    632 if (res == -1)
    +
    633 return -1;
    +
    634 res = check_mode(path, 0755);
    +
    635 if (res == -1)
    +
    636 return -1;
    +
    637
    +
    638 for (i = 0; dir_files[i]; i++) {
    +
    639 char fpath[1280];
    +
    640 sprintf(fpath, "%s/%s", path, dir_files[i]);
    +
    641 res = create_file(fpath, "", 0);
    +
    642 if (res == -1) {
    +
    643 cleanup_dir(path, dir_files, 1);
    +
    644 return -1;
    +
    645 }
    +
    646 }
    +
    647 res = check_dir_contents(path, dir_files);
    +
    648 if (res == -1) {
    +
    649 cleanup_dir(path, dir_files, 1);
    +
    650 return -1;
    +
    651 }
    +
    652
    +
    653 return 0;
    +
    654}
    +
    655
    +
    656static int test_truncate(int len)
    +
    657{
    +
    658 const char *data = testdata;
    +
    659 int datalen = testdatalen;
    +
    660 int res;
    +
    661
    +
    662 start_test("truncate(%u)", (int) len);
    +
    663 res = create_testfile(testfile, data, datalen);
    +
    664 if (res == -1)
    +
    665 return -1;
    +
    666
    +
    667 res = truncate(testfile, len);
    +
    668 if (res == -1) {
    +
    669 PERROR("truncate");
    +
    670 return -1;
    +
    671 }
    +
    672 res = check_testfile_size(testfile, len);
    +
    673 if (res == -1)
    +
    674 return -1;
    +
    675
    +
    676 if (len > 0) {
    +
    677 if (len <= datalen) {
    +
    678 res = check_data(testfile, data, 0, len);
    +
    679 if (res == -1)
    +
    680 return -1;
    +
    681 } else {
    +
    682 res = check_data(testfile, data, 0, datalen);
    +
    683 if (res == -1)
    +
    684 return -1;
    +
    685 res = check_data(testfile, zerodata, datalen,
    +
    686 len - datalen);
    +
    687 if (res == -1)
    +
    688 return -1;
    +
    689 }
    +
    690 }
    +
    691 res = unlink(testfile);
    +
    692 if (res == -1) {
    +
    693 PERROR("unlink");
    +
    694 return -1;
    +
    695 }
    +
    696 res = check_nonexist(testfile);
    +
    697 if (res == -1)
    +
    698 return -1;
    +
    699
    +
    700 success();
    +
    701 return 0;
    +
    702}
    +
    703
    +
    704static int test_ftruncate(int len, int mode)
    +
    705{
    +
    706 const char *data = testdata;
    +
    707 int datalen = testdatalen;
    +
    708 int res;
    +
    709 int fd;
    +
    710
    +
    711 start_test("ftruncate(%u) mode: 0%03o", len, mode);
    +
    712 res = create_testfile(testfile, data, datalen);
    +
    713 if (res == -1)
    +
    714 return -1;
    +
    715
    +
    716 fd = open(testfile, O_WRONLY);
    +
    717 if (fd == -1) {
    +
    718 PERROR("open");
    +
    719 return -1;
    +
    720 }
    +
    721
    +
    722 res = fchmod(fd, mode);
    +
    723 if (res == -1) {
    +
    724 PERROR("fchmod");
    +
    725 close(fd);
    +
    726 return -1;
    +
    727 }
    +
    728 res = check_testfile_mode(testfile, mode);
    +
    729 if (res == -1) {
    +
    730 close(fd);
    +
    731 return -1;
    +
    732 }
    +
    733 res = ftruncate(fd, len);
    +
    734 if (res == -1) {
    +
    735 PERROR("ftruncate");
    +
    736 close(fd);
    +
    737 return -1;
    +
    738 }
    +
    739 close(fd);
    +
    740 res = check_testfile_size(testfile, len);
    +
    741 if (res == -1)
    +
    742 return -1;
    +
    743
    +
    744 if (len > 0) {
    +
    745 if (len <= datalen) {
    +
    746 res = check_data(testfile, data, 0, len);
    +
    747 if (res == -1)
    +
    748 return -1;
    +
    749 } else {
    +
    750 res = check_data(testfile, data, 0, datalen);
    +
    751 if (res == -1)
    +
    752 return -1;
    +
    753 res = check_data(testfile, zerodata, datalen,
    +
    754 len - datalen);
    +
    755 if (res == -1)
    +
    756 return -1;
    +
    757 }
    +
    758 }
    +
    759 res = unlink(testfile);
    +
    760 if (res == -1) {
    +
    761 PERROR("unlink");
    +
    762 return -1;
    +
    763 }
    +
    764 res = check_nonexist(testfile);
    +
    765 if (res == -1)
    +
    766 return -1;
    +
    767
    +
    768 success();
    +
    769 return 0;
    +
    770}
    +
    771
    +
    772static int test_seekdir(void)
    +
    773{
    +
    774 int i;
    +
    775 int res;
    +
    776 DIR *dp;
    +
    777 struct dirent *de = NULL;
    +
    778
    +
    779 start_test("seekdir");
    +
    780 res = create_dir(testdir, testdir_files);
    +
    781 if (res == -1)
    +
    782 return res;
    +
    783
    +
    784 dp = opendir(testdir);
    +
    785 if (dp == NULL) {
    +
    786 PERROR("opendir");
    +
    787 return -1;
    +
    788 }
    +
    789
    +
    790 /* Remember dir offsets */
    +
    791 for (i = 0; i < ARRAY_SIZE(seekdir_offsets); i++) {
    +
    792 seekdir_offsets[i] = telldir(dp);
    +
    793 errno = 0;
    +
    794 de = readdir(dp);
    +
    795 if (de == NULL) {
    +
    796 if (errno) {
    +
    797 PERROR("readdir");
    +
    798 goto fail;
    +
    799 }
    +
    800 break;
    +
    801 }
    +
    802 }
    +
    803
    +
    804 /* Walk until the end of directory */
    +
    805 while (de)
    +
    806 de = readdir(dp);
    +
    807
    +
    808 /* Start from the last valid dir offset and seek backwards */
    +
    809 for (i--; i >= 0; i--) {
    +
    810 seekdir(dp, seekdir_offsets[i]);
    +
    811 de = readdir(dp);
    +
    812 if (de == NULL) {
    +
    813 ERROR("Unexpected end of directory after seekdir()");
    +
    814 goto fail;
    +
    815 }
    +
    816 }
    +
    817
    +
    818 closedir(dp);
    +
    819 res = cleanup_dir(testdir, testdir_files, 0);
    +
    820 if (!res)
    +
    821 success();
    +
    822 return res;
    +
    823fail:
    +
    824 closedir(dp);
    +
    825 cleanup_dir(testdir, testdir_files, 1);
    +
    826 return -1;
    +
    827}
    +
    828
    +
    829#ifdef HAVE_COPY_FILE_RANGE
    +
    830static int test_copy_file_range(void)
    +
    831{
    +
    832 const char *data = testdata;
    +
    833 int datalen = testdatalen;
    +
    834 int err = 0;
    +
    835 int res;
    +
    836 int fd_in, fd_out;
    +
    837 off_t pos_in = 0, pos_out = 0;
    +
    838
    +
    839 start_test("copy_file_range");
    +
    840 unlink(testfile);
    +
    841 fd_in = open(testfile, O_CREAT | O_RDWR, 0644);
    +
    842 if (fd_in == -1) {
    +
    843 PERROR("creat");
    +
    844 return -1;
    +
    845 }
    +
    846 res = write(fd_in, data, datalen);
    +
    847 if (res == -1) {
    +
    848 PERROR("write");
    +
    849 close(fd_in);
    +
    850 return -1;
    +
    851 }
    +
    852 if (res != datalen) {
    +
    853 ERROR("write is short: %u instead of %u", res, datalen);
    +
    854 close(fd_in);
    +
    855 return -1;
    +
    856 }
    +
    857
    +
    858 unlink(testfile2);
    +
    859 fd_out = creat(testfile2, 0644);
    +
    860 if (fd_out == -1) {
    +
    861 PERROR("creat");
    +
    862 close(fd_in);
    +
    863 return -1;
    +
    864 }
    +
    865 res = copy_file_range(fd_in, &pos_in, fd_out, &pos_out, datalen, 0);
    +
    866 if (res == -1) {
    +
    867 PERROR("copy_file_range");
    +
    868 close(fd_in);
    +
    869 close(fd_out);
    +
    870 return -1;
    +
    871 }
    +
    872 if (res != datalen) {
    +
    873 ERROR("copy is short: %u instead of %u", res, datalen);
    +
    874 close(fd_in);
    +
    875 close(fd_out);
    +
    876 return -1;
    +
    877 }
    +
    878
    +
    879 res = close(fd_in);
    +
    880 if (res == -1) {
    +
    881 PERROR("close");
    +
    882 close(fd_out);
    +
    883 return -1;
    +
    884 }
    +
    885 res = close(fd_out);
    +
    886 if (res == -1) {
    +
    887 PERROR("close");
    +
    888 return -1;
    +
    889 }
    +
    890
    +
    891 err = check_data(testfile2, data, 0, datalen);
    +
    892
    +
    893 res = unlink(testfile);
    +
    894 if (res == -1) {
    +
    895 PERROR("unlink");
    +
    896 return -1;
    +
    897 }
    +
    898 res = check_nonexist(testfile);
    +
    899 if (res == -1)
    +
    900 return -1;
    +
    901 if (err)
    +
    902 return -1;
    +
    903
    +
    904 res = unlink(testfile2);
    +
    905 if (res == -1) {
    +
    906 PERROR("unlink");
    +
    907 return -1;
    +
    908 }
    +
    909 res = check_nonexist(testfile2);
    +
    910 if (res == -1)
    +
    911 return -1;
    +
    912 if (err)
    +
    913 return -1;
    +
    914
    +
    915 success();
    +
    916 return 0;
    +
    917}
    +
    918#else
    +
    919static int test_copy_file_range(void)
    +
    920{
    +
    921 return 0;
    +
    922}
    +
    923#endif
    +
    924
    +
    925#ifdef HAVE_STATX
    +
    926static int test_statx(void)
    +
    927{
    +
    928 struct statx sb;
    +
    929 char msg[] = "hi";
    +
    930 size_t msg_size = sizeof(msg);
    +
    931 struct timespec tp;
    +
    932 int res;
    +
    933
    +
    934 memset(&sb, 0, sizeof(sb));
    +
    935 unlink(testfile);
    +
    936
    +
    937 start_test("statx");
    +
    938
    +
    939 res = create_testfile(testfile, msg, msg_size);
    +
    940 if (res == -1)
    +
    941 return -1;
    +
    942
    +
    943 res = statx(-1, testfile, AT_EMPTY_PATH,
    +
    944 STATX_BASIC_STATS | STATX_BTIME, &sb);
    +
    945 if (res == -1)
    +
    946 return -1;
    +
    947
    +
    948 if (sb.stx_size != msg_size)
    +
    949 return -1;
    +
    950
    +
    951 clock_gettime(CLOCK_REALTIME, &tp);
    +
    952
    +
    953 if (sb.stx_btime.tv_sec > tp.tv_sec)
    +
    954 return -1;
    +
    955
    +
    956 if (sb.stx_btime.tv_sec == tp.tv_sec &&
    +
    957 sb.stx_btime.tv_nsec >= tp.tv_nsec)
    +
    958 return -1;
    +
    959
    +
    960 unlink(testfile);
    +
    961
    +
    962 success();
    +
    963 return 0;
    +
    964}
    +
    965#else
    +
    966static int test_statx(void)
    +
    967{
    +
    968 return 0;
    +
    969}
    +
    970#endif
    +
    971
    +
    972static int test_utime(void)
    +
    973{
    +
    974 struct utimbuf utm;
    +
    975 time_t atime = 987631200;
    +
    976 time_t mtime = 123116400;
    +
    977 int res;
    +
    978
    +
    979 start_test("utime");
    +
    980 res = create_testfile(testfile, NULL, 0);
    +
    981 if (res == -1)
    +
    982 return -1;
    +
    983
    +
    984 utm.actime = atime;
    +
    985 utm.modtime = mtime;
    +
    986 res = utime(testfile, &utm);
    +
    987 if (res == -1) {
    +
    988 PERROR("utime");
    +
    989 return -1;
    +
    990 }
    +
    991 res = check_times(testfile, atime, mtime);
    +
    992 if (res == -1) {
    +
    993 return -1;
    +
    994 }
    +
    995 res = unlink(testfile);
    +
    996 if (res == -1) {
    +
    997 PERROR("unlink");
    +
    998 return -1;
    +
    999 }
    +
    1000 res = check_nonexist(testfile);
    +
    1001 if (res == -1)
    +
    1002 return -1;
    +
    1003
    +
    1004 success();
    +
    1005 return 0;
    +
    1006}
    +
    1007
    +
    1008static int test_create(void)
    +
    1009{
    +
    1010 const char *data = testdata;
    +
    1011 int datalen = testdatalen;
    +
    1012 int err = 0;
    +
    1013 int res;
    +
    1014 int fd;
    +
    1015
    +
    1016 start_test("create");
    +
    1017 unlink(testfile);
    +
    1018 fd = creat(testfile, 0644);
    +
    1019 if (fd == -1) {
    +
    1020 PERROR("creat");
    +
    1021 return -1;
    +
    1022 }
    +
    1023 res = write(fd, data, datalen);
    +
    1024 if (res == -1) {
    +
    1025 PERROR("write");
    +
    1026 close(fd);
    +
    1027 return -1;
    +
    1028 }
    +
    1029 if (res != datalen) {
    +
    1030 ERROR("write is short: %u instead of %u", res, datalen);
    +
    1031 close(fd);
    +
    1032 return -1;
    +
    1033 }
    +
    1034 res = close(fd);
    +
    1035 if (res == -1) {
    +
    1036 PERROR("close");
    +
    1037 return -1;
    +
    1038 }
    +
    1039 res = check_type(testfile, S_IFREG);
    +
    1040 if (res == -1)
    +
    1041 return -1;
    +
    1042 err += check_mode(testfile, 0644);
    +
    1043 err += check_nlink(testfile, 1);
    +
    1044 err += check_size(testfile, datalen);
    +
    1045 err += check_data(testfile, data, 0, datalen);
    +
    1046 res = unlink(testfile);
    +
    1047 if (res == -1) {
    +
    1048 PERROR("unlink");
    +
    1049 return -1;
    +
    1050 }
    +
    1051 res = check_nonexist(testfile);
    +
    1052 if (res == -1)
    +
    1053 return -1;
    +
    1054 if (err)
    +
    1055 return -1;
    +
    1056
    +
    1057 success();
    +
    1058 return 0;
    +
    1059}
    +
    1060
    +
    1061static int test_create_unlink(void)
    +
    1062{
    +
    1063 const char *data = testdata;
    +
    1064 int datalen = testdatalen;
    +
    1065 int err = 0;
    +
    1066 int res;
    +
    1067 int fd;
    +
    1068
    +
    1069 start_test("create+unlink");
    +
    1070 unlink(testfile);
    +
    1071 fd = open(testfile, O_CREAT | O_RDWR | O_TRUNC, 0644);
    +
    1072 if (fd == -1) {
    +
    1073 PERROR("creat");
    +
    1074 return -1;
    +
    1075 }
    +
    1076 res = unlink(testfile);
    +
    1077 if (res == -1) {
    +
    1078 PERROR("unlink");
    +
    1079 close(fd);
    +
    1080 return -1;
    +
    1081 }
    +
    1082 res = check_nonexist(testfile);
    +
    1083 if (res == -1) {
    +
    1084 close(fd);
    +
    1085 return -1;
    +
    1086 }
    +
    1087 res = write(fd, data, datalen);
    +
    1088 if (res == -1) {
    +
    1089 PERROR("write");
    +
    1090 close(fd);
    +
    1091 return -1;
    +
    1092 }
    +
    1093 if (res != datalen) {
    +
    1094 ERROR("write is short: %u instead of %u", res, datalen);
    +
    1095 close(fd);
    +
    1096 return -1;
    +
    1097 }
    +
    1098 struct stat st = {
    +
    1099 .st_mode = S_IFREG | 0644,
    +
    1100 .st_size = datalen,
    +
    1101 };
    +
    1102 err = fcheck_stat(fd, O_RDWR, &st);
    +
    1103 err += fcheck_data(fd, data, 0, datalen);
    +
    1104 res = close(fd);
    +
    1105 if (res == -1) {
    +
    1106 PERROR("close");
    +
    1107 err--;
    +
    1108 }
    +
    1109 if (err)
    +
    1110 return -1;
    +
    1111
    +
    1112 success();
    +
    1113 return 0;
    +
    1114}
    +
    1115
    +
    1116static int test_mknod(void)
    +
    1117{
    +
    1118 int err = 0;
    +
    1119 int res;
    +
    1120
    +
    1121 start_test("mknod");
    +
    1122 unlink(testfile);
    +
    1123 res = mknod(testfile, 0644, 0);
    +
    1124 if (res == -1) {
    +
    1125 PERROR("mknod");
    +
    1126 return -1;
    +
    1127 }
    +
    1128 res = check_type(testfile, S_IFREG);
    +
    1129 if (res == -1)
    +
    1130 return -1;
    +
    1131 err += check_mode(testfile, 0644);
    +
    1132 err += check_nlink(testfile, 1);
    +
    1133 err += check_size(testfile, 0);
    +
    1134 res = unlink(testfile);
    +
    1135 if (res == -1) {
    +
    1136 PERROR("unlink");
    +
    1137 return -1;
    +
    1138 }
    +
    1139 res = check_nonexist(testfile);
    +
    1140 if (res == -1)
    +
    1141 return -1;
    +
    1142 if (err)
    +
    1143 return -1;
    +
    1144
    +
    1145 success();
    +
    1146 return 0;
    +
    1147}
    +
    1148
    +
    1149#define test_open(exist, flags, mode) do_test_open(exist, flags, #flags, mode)
    +
    1150
    +
    1151static int do_test_open(int exist, int flags, const char *flags_str, int mode)
    +
    1152{
    +
    1153 char buf[4096];
    +
    1154 const char *data = testdata;
    +
    1155 int datalen = testdatalen;
    +
    1156 unsigned currlen = 0;
    +
    1157 int err = 0;
    +
    1158 int res;
    +
    1159 int fd;
    +
    1160 off_t off;
    +
    1161
    +
    1162 start_test("open(%s, %s, 0%03o)", exist ? "+" : "-", flags_str, mode);
    +
    1163 unlink(testfile);
    +
    1164 if (exist) {
    +
    1165 res = create_file(testfile_r, testdata2, testdata2len);
    +
    1166 if (res == -1)
    +
    1167 return -1;
    +
    1168
    +
    1169 currlen = testdata2len;
    +
    1170 }
    +
    1171
    +
    1172 fd = open(testfile, flags, mode);
    +
    1173 if ((flags & O_CREAT) && (flags & O_EXCL) && exist) {
    +
    1174 if (fd != -1) {
    +
    1175 ERROR("open should have failed");
    +
    1176 close(fd);
    +
    1177 return -1;
    +
    1178 } else if (errno == EEXIST)
    +
    1179 goto succ;
    +
    1180 }
    +
    1181 if (!(flags & O_CREAT) && !exist) {
    +
    1182 if (fd != -1) {
    +
    1183 ERROR("open should have failed");
    +
    1184 close(fd);
    +
    1185 return -1;
    +
    1186 } else if (errno == ENOENT)
    +
    1187 goto succ;
    +
    1188 }
    +
    1189 if (fd == -1) {
    +
    1190 PERROR("open");
    +
    1191 return -1;
    +
    1192 }
    +
    1193
    +
    1194 if (flags & O_TRUNC)
    +
    1195 currlen = 0;
    +
    1196
    +
    1197 err += check_type(testfile, S_IFREG);
    +
    1198 if (exist)
    +
    1199 err += check_mode(testfile, 0644);
    +
    1200 else
    +
    1201 err += check_mode(testfile, mode);
    +
    1202 err += check_nlink(testfile, 1);
    +
    1203 err += check_size(testfile, currlen);
    +
    1204 if (exist && !(flags & O_TRUNC) && (mode & S_IRUSR))
    +
    1205 err += check_data(testfile, testdata2, 0, testdata2len);
    +
    1206
    +
    1207 res = write(fd, data, datalen);
    +
    1208 if ((flags & O_ACCMODE) != O_RDONLY) {
    +
    1209 if (res == -1) {
    +
    1210 PERROR("write");
    +
    1211 err --;
    +
    1212 } else if (res != datalen) {
    +
    1213 ERROR("write is short: %u instead of %u", res, datalen);
    +
    1214 err --;
    +
    1215 } else {
    +
    1216 if (datalen > (int) currlen)
    +
    1217 currlen = datalen;
    +
    1218
    +
    1219 err += check_size(testfile, currlen);
    +
    1220
    +
    1221 if (mode & S_IRUSR) {
    +
    1222 err += check_data(testfile, data, 0, datalen);
    +
    1223 if (exist && !(flags & O_TRUNC) &&
    +
    1224 testdata2len > datalen)
    +
    1225 err += check_data(testfile,
    +
    1226 testdata2 + datalen,
    +
    1227 datalen,
    +
    1228 testdata2len - datalen);
    +
    1229 }
    +
    1230 }
    +
    1231 } else {
    +
    1232 if (res != -1) {
    +
    1233 ERROR("write should have failed");
    +
    1234 err --;
    +
    1235 } else if (errno != EBADF) {
    +
    1236 PERROR("write");
    +
    1237 err --;
    +
    1238 }
    +
    1239 }
    +
    1240 off = lseek(fd, SEEK_SET, 0);
    +
    1241 if (off == (off_t) -1) {
    +
    1242 PERROR("lseek");
    +
    1243 err--;
    +
    1244 } else if (off != 0) {
    +
    1245 ERROR("offset should have returned 0");
    +
    1246 err --;
    +
    1247 }
    +
    1248 res = read(fd, buf, sizeof(buf));
    +
    1249 if ((flags & O_ACCMODE) != O_WRONLY) {
    +
    1250 if (res == -1) {
    +
    1251 PERROR("read");
    +
    1252 err--;
    +
    1253 } else {
    +
    1254 int readsize =
    +
    1255 currlen < sizeof(buf) ? currlen : sizeof(buf);
    +
    1256 if (res != readsize) {
    +
    1257 ERROR("read is short: %i instead of %u",
    +
    1258 res, readsize);
    +
    1259 err--;
    +
    1260 } else {
    +
    1261 if ((flags & O_ACCMODE) != O_RDONLY) {
    +
    1262 err += check_buffer(buf, data, datalen);
    +
    1263 if (exist && !(flags & O_TRUNC) &&
    +
    1264 testdata2len > datalen)
    +
    1265 err += check_buffer(buf + datalen,
    +
    1266 testdata2 + datalen,
    +
    1267 testdata2len - datalen);
    +
    1268 } else if (exist)
    +
    1269 err += check_buffer(buf, testdata2,
    +
    1270 testdata2len);
    +
    1271 }
    +
    1272 }
    +
    1273 } else {
    +
    1274 if (res != -1) {
    +
    1275 ERROR("read should have failed");
    +
    1276 err --;
    +
    1277 } else if (errno != EBADF) {
    +
    1278 PERROR("read");
    +
    1279 err --;
    +
    1280 }
    +
    1281 }
    +
    1282
    +
    1283 res = close(fd);
    +
    1284 if (res == -1) {
    +
    1285 PERROR("close");
    +
    1286 return -1;
    +
    1287 }
    +
    1288 res = unlink(testfile);
    +
    1289 if (res == -1) {
    +
    1290 PERROR("unlink");
    +
    1291 return -1;
    +
    1292 }
    +
    1293 res = check_nonexist(testfile);
    +
    1294 if (res == -1)
    +
    1295 return -1;
    +
    1296 res = check_nonexist(testfile_r);
    +
    1297 if (res == -1)
    +
    1298 return -1;
    +
    1299 if (err)
    +
    1300 return -1;
    +
    1301
    +
    1302succ:
    +
    1303 success();
    +
    1304 return 0;
    +
    1305}
    +
    1306
    +
    1307#define test_open_acc(flags, mode, err) \
    +
    1308 do_test_open_acc(flags, #flags, mode, err)
    +
    1309
    +
    1310static int do_test_open_acc(int flags, const char *flags_str, int mode, int err)
    +
    1311{
    +
    1312 const char *data = testdata;
    +
    1313 int datalen = testdatalen;
    +
    1314 int res;
    +
    1315 int fd;
    +
    1316
    +
    1317 start_test("open_acc(%s) mode: 0%03o message: '%s'", flags_str, mode,
    +
    1318 strerror(err));
    +
    1319 unlink(testfile);
    +
    1320 res = create_testfile(testfile, data, datalen);
    +
    1321 if (res == -1)
    +
    1322 return -1;
    +
    1323
    +
    1324 res = chmod(testfile, mode);
    +
    1325 if (res == -1) {
    +
    1326 PERROR("chmod");
    +
    1327 return -1;
    +
    1328 }
    +
    1329
    +
    1330 res = check_testfile_mode(testfile, mode);
    +
    1331 if (res == -1)
    +
    1332 return -1;
    +
    1333
    +
    1334 fd = open(testfile, flags);
    +
    1335 if (fd == -1) {
    +
    1336 if (err != errno) {
    +
    1337 PERROR("open");
    +
    1338 return -1;
    +
    1339 }
    +
    1340 } else {
    +
    1341 if (err) {
    +
    1342 ERROR("open should have failed");
    +
    1343 close(fd);
    +
    1344 return -1;
    +
    1345 }
    +
    1346 close(fd);
    +
    1347 }
    +
    1348
    +
    1349 res = unlink(testfile);
    +
    1350 if (res == -1) {
    +
    1351 PERROR("unlink");
    +
    1352 return -1;
    +
    1353 }
    +
    1354 res = check_nonexist(testfile);
    +
    1355 if (res == -1)
    +
    1356 return -1;
    +
    1357 res = check_nonexist(testfile_r);
    +
    1358 if (res == -1)
    +
    1359 return -1;
    +
    1360
    +
    1361 success();
    +
    1362 return 0;
    +
    1363}
    +
    1364
    +
    1365static int test_symlink(void)
    +
    1366{
    +
    1367 char buf[1024];
    +
    1368 const char *data = testdata;
    +
    1369 int datalen = testdatalen;
    +
    1370 int linklen = strlen(testfile);
    +
    1371 int err = 0;
    +
    1372 int res;
    +
    1373
    +
    1374 start_test("symlink");
    +
    1375 res = create_testfile(testfile, data, datalen);
    +
    1376 if (res == -1)
    +
    1377 return -1;
    +
    1378
    +
    1379 unlink(testfile2);
    +
    1380 res = symlink(testfile, testfile2);
    +
    1381 if (res == -1) {
    +
    1382 PERROR("symlink");
    +
    1383 return -1;
    +
    1384 }
    +
    1385 res = check_type(testfile2, S_IFLNK);
    +
    1386 if (res == -1)
    +
    1387 return -1;
    +
    1388 err += check_mode(testfile2, 0777);
    +
    1389 err += check_nlink(testfile2, 1);
    +
    1390 res = readlink(testfile2, buf, sizeof(buf));
    +
    1391 if (res == -1) {
    +
    1392 PERROR("readlink");
    +
    1393 err--;
    +
    1394 }
    +
    1395 if (res != linklen) {
    +
    1396 ERROR("short readlink: %u instead of %u", res, linklen);
    +
    1397 err--;
    +
    1398 }
    +
    1399 if (memcmp(buf, testfile, linklen) != 0) {
    +
    1400 ERROR("link mismatch");
    +
    1401 err--;
    +
    1402 }
    +
    1403 err += check_size(testfile2, datalen);
    +
    1404 err += check_data(testfile2, data, 0, datalen);
    +
    1405 res = unlink(testfile2);
    +
    1406 if (res == -1) {
    +
    1407 PERROR("unlink");
    +
    1408 return -1;
    +
    1409 }
    +
    1410 res = check_nonexist(testfile2);
    +
    1411 if (res == -1)
    +
    1412 return -1;
    +
    1413 if (err)
    +
    1414 return -1;
    +
    1415
    +
    1416 res = unlink(testfile);
    +
    1417 if (res == -1) {
    +
    1418 PERROR("unlink");
    +
    1419 return -1;
    +
    1420 }
    +
    1421 res = check_nonexist(testfile);
    +
    1422 if (res == -1)
    +
    1423 return -1;
    +
    1424
    +
    1425 success();
    +
    1426 return 0;
    +
    1427}
    +
    1428
    +
    1429static int test_link(void)
    +
    1430{
    +
    1431 const char *data = testdata;
    +
    1432 int datalen = testdatalen;
    +
    1433 int err = 0;
    +
    1434 int res;
    +
    1435
    +
    1436 start_test("link");
    +
    1437 res = create_testfile(testfile, data, datalen);
    +
    1438 if (res == -1)
    +
    1439 return -1;
    +
    1440
    +
    1441 unlink(testfile2);
    +
    1442 res = link(testfile, testfile2);
    +
    1443 if (res == -1) {
    +
    1444 PERROR("link");
    +
    1445 return -1;
    +
    1446 }
    +
    1447 res = check_type(testfile2, S_IFREG);
    +
    1448 if (res == -1)
    +
    1449 return -1;
    +
    1450 err += check_mode(testfile2, 0644);
    +
    1451 err += check_nlink(testfile2, 2);
    +
    1452 err += check_size(testfile2, datalen);
    +
    1453 err += check_data(testfile2, data, 0, datalen);
    +
    1454 res = unlink(testfile);
    +
    1455 if (res == -1) {
    +
    1456 PERROR("unlink");
    +
    1457 return -1;
    +
    1458 }
    +
    1459 res = check_nonexist(testfile);
    +
    1460 if (res == -1)
    +
    1461 return -1;
    +
    1462
    +
    1463 err += check_nlink(testfile2, 1);
    +
    1464 res = unlink(testfile2);
    +
    1465 if (res == -1) {
    +
    1466 PERROR("unlink");
    +
    1467 return -1;
    +
    1468 }
    +
    1469 res = check_nonexist(testfile2);
    +
    1470 if (res == -1)
    +
    1471 return -1;
    +
    1472 if (err)
    +
    1473 return -1;
    +
    1474
    +
    1475 success();
    +
    1476 return 0;
    +
    1477}
    +
    1478
    +
    1479static int test_link2(void)
    +
    1480{
    +
    1481 const char *data = testdata;
    +
    1482 int datalen = testdatalen;
    +
    1483 int err = 0;
    +
    1484 int res;
    +
    1485
    +
    1486 start_test("link-unlink-link");
    +
    1487 res = create_testfile(testfile, data, datalen);
    +
    1488 if (res == -1)
    +
    1489 return -1;
    +
    1490
    +
    1491 unlink(testfile2);
    +
    1492 res = link(testfile, testfile2);
    +
    1493 if (res == -1) {
    +
    1494 PERROR("link");
    +
    1495 return -1;
    +
    1496 }
    +
    1497 res = unlink(testfile);
    +
    1498 if (res == -1) {
    +
    1499 PERROR("unlink");
    +
    1500 return -1;
    +
    1501 }
    +
    1502 res = check_nonexist(testfile);
    +
    1503 if (res == -1)
    +
    1504 return -1;
    +
    1505 res = link(testfile2, testfile);
    +
    1506 if (res == -1) {
    +
    1507 PERROR("link");
    +
    1508 }
    +
    1509 res = check_type(testfile, S_IFREG);
    +
    1510 if (res == -1)
    +
    1511 return -1;
    +
    1512 err += check_mode(testfile, 0644);
    +
    1513 err += check_nlink(testfile, 2);
    +
    1514 err += check_size(testfile, datalen);
    +
    1515 err += check_data(testfile, data, 0, datalen);
    +
    1516
    +
    1517 res = unlink(testfile2);
    +
    1518 if (res == -1) {
    +
    1519 PERROR("unlink");
    +
    1520 return -1;
    +
    1521 }
    +
    1522 err += check_nlink(testfile, 1);
    +
    1523 res = unlink(testfile);
    +
    1524 if (res == -1) {
    +
    1525 PERROR("unlink");
    +
    1526 return -1;
    +
    1527 }
    +
    1528 res = check_nonexist(testfile);
    +
    1529 if (res == -1)
    +
    1530 return -1;
    +
    1531 if (err)
    +
    1532 return -1;
    +
    1533
    +
    1534 success();
    +
    1535 return 0;
    +
    1536}
    +
    1537
    +
    1538static int test_rename_file(void)
    +
    1539{
    +
    1540 const char *data = testdata;
    +
    1541 int datalen = testdatalen;
    +
    1542 int err = 0;
    +
    1543 int res;
    +
    1544
    +
    1545 start_test("rename file");
    +
    1546 res = create_testfile(testfile, data, datalen);
    +
    1547 if (res == -1)
    +
    1548 return -1;
    +
    1549
    +
    1550 unlink(testfile2);
    +
    1551 res = rename(testfile, testfile2);
    +
    1552 if (res == -1) {
    +
    1553 PERROR("rename");
    +
    1554 return -1;
    +
    1555 }
    +
    1556 res = check_nonexist(testfile);
    +
    1557 if (res == -1)
    +
    1558 return -1;
    +
    1559 res = check_type(testfile2, S_IFREG);
    +
    1560 if (res == -1)
    +
    1561 return -1;
    +
    1562 err += check_mode(testfile2, 0644);
    +
    1563 err += check_nlink(testfile2, 1);
    +
    1564 err += check_size(testfile2, datalen);
    +
    1565 err += check_data(testfile2, data, 0, datalen);
    +
    1566 res = unlink(testfile2);
    +
    1567 if (res == -1) {
    +
    1568 PERROR("unlink");
    +
    1569 return -1;
    +
    1570 }
    +
    1571 res = check_nonexist(testfile2);
    +
    1572 if (res == -1)
    +
    1573 return -1;
    +
    1574 if (err)
    +
    1575 return -1;
    +
    1576
    +
    1577 success();
    +
    1578 return 0;
    +
    1579}
    +
    1580
    +
    1581static int test_rename_dir(void)
    +
    1582{
    +
    1583 int err = 0;
    +
    1584 int res;
    +
    1585
    +
    1586 start_test("rename dir");
    +
    1587 res = create_dir(testdir, testdir_files);
    +
    1588 if (res == -1)
    +
    1589 return -1;
    +
    1590
    +
    1591 rmdir(testdir2);
    +
    1592 res = rename(testdir, testdir2);
    +
    1593 if (res == -1) {
    +
    1594 PERROR("rename");
    +
    1595 cleanup_dir(testdir, testdir_files, 1);
    +
    1596 return -1;
    +
    1597 }
    +
    1598 res = check_nonexist(testdir);
    +
    1599 if (res == -1) {
    +
    1600 cleanup_dir(testdir, testdir_files, 1);
    +
    1601 return -1;
    +
    1602 }
    +
    1603 res = check_type(testdir2, S_IFDIR);
    +
    1604 if (res == -1) {
    +
    1605 cleanup_dir(testdir2, testdir_files, 1);
    +
    1606 return -1;
    +
    1607 }
    +
    1608 err += check_mode(testdir2, 0755);
    +
    1609 err += check_dir_contents(testdir2, testdir_files);
    +
    1610 err += cleanup_dir(testdir2, testdir_files, 0);
    +
    1611 res = rmdir(testdir2);
    +
    1612 if (res == -1) {
    +
    1613 PERROR("rmdir");
    +
    1614 return -1;
    +
    1615 }
    +
    1616 res = check_nonexist(testdir2);
    +
    1617 if (res == -1)
    +
    1618 return -1;
    +
    1619 if (err)
    +
    1620 return -1;
    +
    1621
    +
    1622 success();
    +
    1623 return 0;
    +
    1624}
    +
    1625
    +
    1626static int test_rename_dir_loop(void)
    +
    1627{
    +
    1628#define PATH(p) (snprintf(path, sizeof path, "%s/%s", testdir, p), path)
    +
    1629#define PATH2(p) (snprintf(path2, sizeof path2, "%s/%s", testdir, p), path2)
    +
    1630
    +
    1631 char path[1280], path2[1280];
    +
    1632 int err = 0;
    +
    1633 int res;
    +
    1634
    +
    1635 start_test("rename dir loop");
    +
    1636
    +
    1637 res = create_dir(testdir, testdir_files);
    +
    1638 if (res == -1)
    +
    1639 return -1;
    +
    1640
    +
    1641 res = mkdir(PATH("a"), 0755);
    +
    1642 if (res == -1) {
    +
    1643 PERROR("mkdir");
    +
    1644 goto fail;
    +
    1645 }
    +
    1646
    +
    1647 res = rename(PATH("a"), PATH2("a"));
    +
    1648 if (res == -1) {
    +
    1649 PERROR("rename");
    +
    1650 goto fail;
    +
    1651 }
    +
    1652
    +
    1653 errno = 0;
    +
    1654 res = rename(PATH("a"), PATH2("a/b"));
    +
    1655 if (res == 0 || errno != EINVAL) {
    +
    1656 PERROR("rename");
    +
    1657 goto fail;
    +
    1658 }
    +
    1659
    +
    1660 res = mkdir(PATH("a/b"), 0755);
    +
    1661 if (res == -1) {
    +
    1662 PERROR("mkdir");
    +
    1663 goto fail;
    +
    1664 }
    +
    1665
    +
    1666 res = mkdir(PATH("a/b/c"), 0755);
    +
    1667 if (res == -1) {
    +
    1668 PERROR("mkdir");
    +
    1669 goto fail;
    +
    1670 }
    +
    1671
    +
    1672 errno = 0;
    +
    1673 res = rename(PATH("a"), PATH2("a/b/c"));
    +
    1674 if (res == 0 || errno != EINVAL) {
    +
    1675 PERROR("rename");
    +
    1676 goto fail;
    +
    1677 }
    +
    1678
    +
    1679 errno = 0;
    +
    1680 res = rename(PATH("a"), PATH2("a/b/c/a"));
    +
    1681 if (res == 0 || errno != EINVAL) {
    +
    1682 PERROR("rename");
    +
    1683 goto fail;
    +
    1684 }
    +
    1685
    +
    1686 errno = 0;
    +
    1687 res = rename(PATH("a/b/c"), PATH2("a"));
    +
    1688 if (res == 0 || errno != ENOTEMPTY) {
    +
    1689 PERROR("rename");
    +
    1690 goto fail;
    +
    1691 }
    +
    1692
    +
    1693 res = open(PATH("a/foo"), O_CREAT, 0644);
    +
    1694 if (res == -1) {
    +
    1695 PERROR("open");
    +
    1696 goto fail;
    +
    1697 }
    +
    1698 close(res);
    +
    1699
    +
    1700 res = rename(PATH("a/foo"), PATH2("a/bar"));
    +
    1701 if (res == -1) {
    +
    1702 PERROR("rename");
    +
    1703 goto fail;
    +
    1704 }
    +
    1705
    +
    1706 res = rename(PATH("a/bar"), PATH2("a/foo"));
    +
    1707 if (res == -1) {
    +
    1708 PERROR("rename");
    +
    1709 goto fail;
    +
    1710 }
    +
    1711
    +
    1712 res = rename(PATH("a/foo"), PATH2("a/b/bar"));
    +
    1713 if (res == -1) {
    +
    1714 PERROR("rename");
    +
    1715 goto fail;
    +
    1716 }
    +
    1717
    +
    1718 res = rename(PATH("a/b/bar"), PATH2("a/foo"));
    +
    1719 if (res == -1) {
    +
    1720 PERROR("rename");
    +
    1721 goto fail;
    +
    1722 }
    +
    1723
    +
    1724 res = rename(PATH("a/foo"), PATH2("a/b/c/bar"));
    +
    1725 if (res == -1) {
    +
    1726 PERROR("rename");
    +
    1727 goto fail;
    +
    1728 }
    +
    1729
    +
    1730 res = rename(PATH("a/b/c/bar"), PATH2("a/foo"));
    +
    1731 if (res == -1) {
    +
    1732 PERROR("rename");
    +
    1733 goto fail;
    +
    1734 }
    +
    1735
    +
    1736 res = open(PATH("a/bar"), O_CREAT, 0644);
    +
    1737 if (res == -1) {
    +
    1738 PERROR("open");
    +
    1739 goto fail;
    +
    1740 }
    +
    1741 close(res);
    +
    1742
    +
    1743 res = rename(PATH("a/foo"), PATH2("a/bar"));
    +
    1744 if (res == -1) {
    +
    1745 PERROR("rename");
    +
    1746 goto fail;
    +
    1747 }
    +
    1748
    +
    1749 unlink(PATH("a/bar"));
    +
    1750
    +
    1751 res = rename(PATH("a/b"), PATH2("a/d"));
    +
    1752 if (res == -1) {
    +
    1753 PERROR("rename");
    +
    1754 goto fail;
    +
    1755 }
    +
    1756
    +
    1757 res = rename(PATH("a/d"), PATH2("a/b"));
    +
    1758 if (res == -1) {
    +
    1759 PERROR("rename");
    +
    1760 goto fail;
    +
    1761 }
    +
    1762
    +
    1763 res = mkdir(PATH("a/d"), 0755);
    +
    1764 if (res == -1) {
    +
    1765 PERROR("mkdir");
    +
    1766 goto fail;
    +
    1767 }
    +
    1768
    +
    1769 res = rename(PATH("a/b"), PATH2("a/d"));
    +
    1770 if (res == -1) {
    +
    1771 PERROR("rename");
    +
    1772 goto fail;
    +
    1773 }
    +
    1774
    +
    1775 res = rename(PATH("a/d"), PATH2("a/b"));
    +
    1776 if (res == -1) {
    +
    1777 PERROR("rename");
    +
    1778 goto fail;
    +
    1779 }
    +
    1780
    +
    1781 res = mkdir(PATH("a/d"), 0755);
    +
    1782 if (res == -1) {
    +
    1783 PERROR("mkdir");
    +
    1784 goto fail;
    +
    1785 }
    +
    1786
    +
    1787 res = mkdir(PATH("a/d/e"), 0755);
    +
    1788 if (res == -1) {
    +
    1789 PERROR("mkdir");
    +
    1790 goto fail;
    +
    1791 }
    +
    1792
    +
    1793 errno = 0;
    +
    1794 res = rename(PATH("a/b"), PATH2("a/d"));
    +
    1795 if (res == 0 || (errno != ENOTEMPTY && errno != EEXIST)) {
    +
    1796 PERROR("rename");
    +
    1797 goto fail;
    +
    1798 }
    +
    1799
    +
    1800 rmdir(PATH("a/d/e"));
    +
    1801 rmdir(PATH("a/d"));
    +
    1802
    +
    1803 rmdir(PATH("a/b/c"));
    +
    1804 rmdir(PATH("a/b"));
    +
    1805 rmdir(PATH("a"));
    +
    1806
    +
    1807 err += cleanup_dir(testdir, testdir_files, 0);
    +
    1808 res = rmdir(testdir);
    +
    1809 if (res == -1) {
    +
    1810 PERROR("rmdir");
    +
    1811 goto fail;
    +
    1812 }
    +
    1813 res = check_nonexist(testdir);
    +
    1814 if (res == -1)
    +
    1815 return -1;
    +
    1816 if (err)
    +
    1817 return -1;
    +
    1818
    +
    1819 success();
    +
    1820 return 0;
    +
    1821
    +
    1822fail:
    +
    1823 unlink(PATH("a/bar"));
    +
    1824
    +
    1825 rmdir(PATH("a/d/e"));
    +
    1826 rmdir(PATH("a/d"));
    +
    1827
    +
    1828 rmdir(PATH("a/b/c"));
    +
    1829 rmdir(PATH("a/b"));
    +
    1830 rmdir(PATH("a"));
    +
    1831
    +
    1832 cleanup_dir(testdir, testdir_files, 1);
    +
    1833 rmdir(testdir);
    +
    1834
    +
    1835 return -1;
    +
    1836
    +
    1837#undef PATH2
    +
    1838#undef PATH
    +
    1839}
    +
    1840
    +
    1841static int test_mkfifo(void)
    +
    1842{
    +
    1843 int res;
    +
    1844 int err = 0;
    +
    1845
    +
    1846 start_test("mkfifo");
    +
    1847 unlink(testfile);
    +
    1848 res = mkfifo(testfile, 0644);
    +
    1849 if (res == -1) {
    +
    1850 PERROR("mkfifo");
    +
    1851 return -1;
    +
    1852 }
    +
    1853 res = check_type(testfile, S_IFIFO);
    +
    1854 if (res == -1)
    +
    1855 return -1;
    +
    1856 err += check_mode(testfile, 0644);
    +
    1857 err += check_nlink(testfile, 1);
    +
    1858 res = unlink(testfile);
    +
    1859 if (res == -1) {
    +
    1860 PERROR("unlink");
    +
    1861 return -1;
    +
    1862 }
    +
    1863 res = check_nonexist(testfile);
    +
    1864 if (res == -1)
    +
    1865 return -1;
    +
    1866 if (err)
    +
    1867 return -1;
    +
    1868
    +
    1869 success();
    +
    1870 return 0;
    +
    1871}
    +
    1872
    +
    1873static int test_mkdir(void)
    +
    1874{
    +
    1875 int res;
    +
    1876 int err = 0;
    +
    1877 const char *dir_contents[] = {NULL};
    +
    1878
    +
    1879 start_test("mkdir");
    +
    1880 rmdir(testdir);
    +
    1881 res = mkdir(testdir, 0755);
    +
    1882 if (res == -1) {
    +
    1883 PERROR("mkdir");
    +
    1884 return -1;
    +
    1885 }
    +
    1886 res = check_type(testdir, S_IFDIR);
    +
    1887 if (res == -1)
    +
    1888 return -1;
    +
    1889 err += check_mode(testdir, 0755);
    +
    1890 /* Some file systems (like btrfs) don't track link
    +
    1891 count for directories */
    +
    1892 //err += check_nlink(testdir, 2);
    +
    1893 err += check_dir_contents(testdir, dir_contents);
    +
    1894 res = rmdir(testdir);
    +
    1895 if (res == -1) {
    +
    1896 PERROR("rmdir");
    +
    1897 return -1;
    +
    1898 }
    +
    1899 res = check_nonexist(testdir);
    +
    1900 if (res == -1)
    +
    1901 return -1;
    +
    1902 if (err)
    +
    1903 return -1;
    +
    1904
    +
    1905 success();
    +
    1906 return 0;
    +
    1907}
    +
    1908
    +
    1909static int test_socket(void)
    +
    1910{
    +
    1911 struct sockaddr_un su;
    +
    1912 int fd;
    +
    1913 int res;
    +
    1914 int err = 0;
    +
    1915 const size_t test_sock_len = strlen(testsock) + 1;
    +
    1916
    +
    1917 start_test("socket");
    +
    1918 if (test_sock_len > sizeof(su.sun_path)) {
    +
    1919 fprintf(stderr, "Need to shorten mount point by %zu chars\n",
    +
    1920 strlen(testsock) + 1 - sizeof(su.sun_path));
    +
    1921 return -1;
    +
    1922 }
    +
    1923 unlink(testsock);
    +
    1924 fd = socket(AF_UNIX, SOCK_STREAM, 0);
    +
    1925 if (fd < 0) {
    +
    1926 PERROR("socket");
    +
    1927 return -1;
    +
    1928 }
    +
    1929 su.sun_family = AF_UNIX;
    +
    1930
    +
    1931 strncpy(su.sun_path, testsock, test_sock_len);
    +
    1932 su.sun_path[sizeof(su.sun_path) - 1] = '\0';
    +
    1933 res = bind(fd, (struct sockaddr*)&su, sizeof(su));
    +
    1934 if (res == -1) {
    +
    1935 PERROR("bind");
    +
    1936 return -1;
    +
    1937 }
    +
    1938
    +
    1939 res = check_type(testsock, S_IFSOCK);
    +
    1940 if (res == -1) {
    +
    1941 close(fd);
    +
    1942 return -1;
    +
    1943 }
    +
    1944 err += check_nlink(testsock, 1);
    +
    1945 close(fd);
    +
    1946 res = unlink(testsock);
    +
    1947 if (res == -1) {
    +
    1948 PERROR("unlink");
    +
    1949 return -1;
    +
    1950 }
    +
    1951 res = check_nonexist(testsock);
    +
    1952 if (res == -1)
    +
    1953 return -1;
    +
    1954 if (err)
    +
    1955 return -1;
    +
    1956
    +
    1957 success();
    +
    1958 return 0;
    +
    1959}
    +
    1960
    +
    1961#define test_create_ro_dir(flags) \
    +
    1962 do_test_create_ro_dir(flags, #flags)
    +
    1963
    +
    1964static int do_test_create_ro_dir(int flags, const char *flags_str)
    +
    1965{
    +
    1966 int res;
    +
    1967 int err = 0;
    +
    1968 int fd;
    +
    1969
    +
    1970 start_test("open(%s) in read-only directory", flags_str);
    +
    1971 rmdir(testdir);
    +
    1972 res = mkdir(testdir, 0555);
    +
    1973 if (res == -1) {
    +
    1974 PERROR("mkdir");
    +
    1975 return -1;
    +
    1976 }
    +
    1977 fd = open(subfile, flags, 0644);
    +
    1978 if (fd != -1) {
    +
    1979 close(fd);
    +
    1980 unlink(subfile);
    +
    1981 ERROR("open should have failed");
    +
    1982 err--;
    +
    1983 } else {
    +
    1984 res = check_nonexist(subfile);
    +
    1985 if (res == -1)
    +
    1986 err--;
    +
    1987 }
    +
    1988 unlink(subfile);
    +
    1989 res = rmdir(testdir);
    +
    1990 if (res == -1) {
    +
    1991 PERROR("rmdir");
    +
    1992 return -1;
    +
    1993 }
    +
    1994 res = check_nonexist(testdir);
    +
    1995 if (res == -1)
    +
    1996 return -1;
    +
    1997 if (err)
    +
    1998 return -1;
    +
    1999
    +
    2000 success();
    +
    2001 return 0;
    +
    2002}
    +
    2003
    +
    2004#ifndef __FreeBSD__
    +
    2005/* this tests open with O_TMPFILE
    +
    2006 note that this will only work with the fuse low level api
    +
    2007 you will get ENOTSUP with the high level api */
    +
    2008static int test_create_tmpfile(void)
    +
    2009{
    +
    2010 rmdir(testdir);
    +
    2011 int res = mkdir(testdir, 0777);
    +
    2012 if (res)
    +
    2013 return -1;
    +
    2014
    +
    2015 start_test("create tmpfile");
    +
    2016
    +
    2017 int fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
    +
    2018 if(fd == -1) {
    +
    2019 if (errno == ENOTSUP) {
    +
    2020 /* don't bother if we're working on an old kernel
    +
    2021 or on the high level API */
    +
    2022 return 0;
    +
    2023 }
    +
    2024
    +
    2025 PERROR("open O_TMPFILE | O_RDWR");
    +
    2026 return -1;
    +
    2027 }
    +
    2028 close(fd);
    +
    2029
    +
    2030 fd = open(testdir, O_TMPFILE | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
    +
    2031 if(fd == -1){
    +
    2032 PERROR("open with O_TMPFILE | O_WRONLY | O_EXCL");
    +
    2033 return -1;
    +
    2034 };
    +
    2035 close(fd);
    +
    2036
    +
    2037 fd = open(testdir, O_TMPFILE | O_RDONLY, S_IRUSR);
    +
    2038 if (fd != -1) {
    +
    2039 ERROR("open with O_TMPFILE | O_RDONLY succeeded");
    +
    2040 return -1;
    +
    2041 }
    +
    2042
    +
    2043 success();
    +
    2044 return 0;
    +
    2045}
    +
    2046
    +
    2047static int test_create_and_link_tmpfile(void)
    +
    2048{
    +
    2049 /* skip this test for now since the github runner will fail in the linkat call below */
    +
    2050 return 0;
    +
    2051
    +
    2052 rmdir(testdir);
    +
    2053 unlink(testfile);
    +
    2054
    +
    2055 int res = mkdir(testdir, 0777);
    +
    2056 if (res)
    +
    2057 return -1;
    +
    2058
    +
    2059 start_test("create and link tmpfile");
    +
    2060
    +
    2061 int fd = open(testdir, O_TMPFILE | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR);
    +
    2062 if(fd == -1) {
    +
    2063 if (errno == ENOTSUP) {
    +
    2064 /* don't bother if we're working on an old kernel
    +
    2065 or on the high level API */
    +
    2066 return 0;
    +
    2067 }
    +
    2068 PERROR("open with O_TMPFILE | O_RDWR | O_EXCL");
    +
    2069 return -1;
    +
    2070 }
    +
    2071
    +
    2072 if (!linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
    +
    2073 ERROR("linkat succeeded on a tmpfile opened with O_EXCL");
    +
    2074 return -1;
    +
    2075 }
    +
    2076 close(fd);
    +
    2077
    +
    2078 fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
    +
    2079 if(fd == -1) {
    +
    2080 PERROR("open O_TMPFILE");
    +
    2081 return -1;
    +
    2082 }
    +
    2083
    +
    2084 if (check_nonexist(testfile)) {
    +
    2085 return -1;
    +
    2086 }
    +
    2087
    +
    2088 if (linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
    +
    2089 PERROR("linkat tempfile");
    +
    2090 return -1;
    +
    2091 }
    +
    2092 close(fd);
    +
    2093
    +
    2094 if (check_nlink(testfile, 1)) {
    +
    2095 return -1;
    +
    2096 }
    +
    2097 unlink(testfile);
    +
    2098
    +
    2099 success();
    +
    2100 return 0;
    +
    2101}
    +
    2102#endif
    +
    2103
    +
    2104int main(int argc, char *argv[])
    +
    2105{
    +
    2106 int err = 0;
    +
    2107 int a;
    +
    2108 int is_root;
    +
    2109
    +
    2110 umask(0);
    +
    2111 if (argc < 2 || argc > 4) {
    +
    2112 fprintf(stderr, "usage: %s testdir [:realdir] [[-]test#] [-u]\n", argv[0]);
    +
    2113 return 1;
    +
    2114 }
    +
    2115 basepath = argv[1];
    +
    2116 basepath_r = basepath;
    +
    2117 for (a = 2; a < argc; a++) {
    +
    2118 char *endptr;
    +
    2119 char *arg = argv[a];
    +
    2120 if (arg[0] == ':') {
    +
    2121 basepath_r = arg + 1;
    +
    2122 } else {
    +
    2123 if (arg[0] == '-') {
    +
    2124 arg++;
    +
    2125 if (arg[0] == 'u') {
    +
    2126 unlinked_test = 1;
    +
    2127 endptr = arg + 1;
    +
    2128 } else {
    +
    2129 skip_test = strtoul(arg, &endptr, 10);
    +
    2130 }
    +
    2131 } else {
    +
    2132 select_test = strtoul(arg, &endptr, 10);
    +
    2133 }
    +
    2134 if (arg[0] == '\0' || *endptr != '\0') {
    +
    2135 fprintf(stderr, "invalid option: '%s'\n", argv[a]);
    +
    2136 return 1;
    +
    2137 }
    +
    2138 }
    +
    2139 }
    +
    2140 assert(strlen(basepath) < 512);
    +
    2141 assert(strlen(basepath_r) < 512);
    +
    2142 if (basepath[0] != '/') {
    +
    2143 fprintf(stderr, "testdir must be an absolute path\n");
    +
    2144 return 1;
    +
    2145 }
    +
    2146
    +
    2147 sprintf(testfile, "%s/testfile", basepath);
    +
    2148 sprintf(testfile2, "%s/testfile2", basepath);
    +
    2149 sprintf(testdir, "%s/testdir", basepath);
    +
    2150 sprintf(testdir2, "%s/testdir2", basepath);
    +
    2151 sprintf(subfile, "%s/subfile", testdir2);
    +
    2152 sprintf(testsock, "%s/testsock", basepath);
    +
    2153
    +
    2154 sprintf(testfile_r, "%s/testfile", basepath_r);
    +
    2155 sprintf(testfile2_r, "%s/testfile2", basepath_r);
    +
    2156 sprintf(testdir_r, "%s/testdir", basepath_r);
    +
    2157 sprintf(testdir2_r, "%s/testdir2", basepath_r);
    +
    2158 sprintf(subfile_r, "%s/subfile", testdir2_r);
    +
    2159
    +
    2160 is_root = (geteuid() == 0);
    +
    2161
    +
    2162 err += test_create();
    +
    2163 err += test_create_unlink();
    +
    2164 err += test_symlink();
    +
    2165 err += test_link();
    +
    2166 err += test_link2();
    +
    2167 err += test_mknod();
    +
    2168 err += test_mkfifo();
    +
    2169 err += test_mkdir();
    +
    2170 err += test_rename_file();
    +
    2171 err += test_rename_dir();
    +
    2172 err += test_rename_dir_loop();
    +
    2173 err += test_seekdir();
    +
    2174 err += test_socket();
    +
    2175 err += test_utime();
    +
    2176 err += test_truncate(0);
    +
    2177 err += test_truncate(testdatalen / 2);
    +
    2178 err += test_truncate(testdatalen);
    +
    2179 err += test_truncate(testdatalen + 100);
    +
    2180 err += test_ftruncate(0, 0600);
    +
    2181 err += test_ftruncate(testdatalen / 2, 0600);
    +
    2182 err += test_ftruncate(testdatalen, 0600);
    +
    2183 err += test_ftruncate(testdatalen + 100, 0600);
    +
    2184 err += test_ftruncate(0, 0400);
    +
    2185 err += test_ftruncate(0, 0200);
    +
    2186 err += test_ftruncate(0, 0000);
    +
    2187 err += test_open(0, O_RDONLY, 0);
    +
    2188 err += test_open(1, O_RDONLY, 0);
    +
    2189 err += test_open(1, O_RDWR, 0);
    +
    2190 err += test_open(1, O_WRONLY, 0);
    +
    2191 err += test_open(0, O_RDWR | O_CREAT, 0600);
    +
    2192 err += test_open(1, O_RDWR | O_CREAT, 0600);
    +
    2193 err += test_open(0, O_RDWR | O_CREAT | O_TRUNC, 0600);
    +
    2194 err += test_open(1, O_RDWR | O_CREAT | O_TRUNC, 0600);
    +
    2195 err += test_open(0, O_RDONLY | O_CREAT, 0600);
    +
    2196 err += test_open(0, O_RDONLY | O_CREAT, 0400);
    +
    2197 err += test_open(0, O_RDONLY | O_CREAT, 0200);
    +
    2198 err += test_open(0, O_RDONLY | O_CREAT, 0000);
    +
    2199 err += test_open(0, O_WRONLY | O_CREAT, 0600);
    +
    2200 err += test_open(0, O_WRONLY | O_CREAT, 0400);
    +
    2201 err += test_open(0, O_WRONLY | O_CREAT, 0200);
    +
    2202 err += test_open(0, O_WRONLY | O_CREAT, 0000);
    +
    2203 err += test_open(0, O_RDWR | O_CREAT, 0400);
    +
    2204 err += test_open(0, O_RDWR | O_CREAT, 0200);
    +
    2205 err += test_open(0, O_RDWR | O_CREAT, 0000);
    +
    2206 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0600);
    +
    2207 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0600);
    +
    2208 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0000);
    +
    2209 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0000);
    +
    2210 err += test_open_acc(O_RDONLY, 0600, 0);
    +
    2211 err += test_open_acc(O_WRONLY, 0600, 0);
    +
    2212 err += test_open_acc(O_RDWR, 0600, 0);
    +
    2213 err += test_open_acc(O_RDONLY, 0400, 0);
    +
    2214 err += test_open_acc(O_WRONLY, 0200, 0);
    +
    2215 if(!is_root) {
    +
    2216 err += test_open_acc(O_RDONLY | O_TRUNC, 0400, EACCES);
    +
    2217 err += test_open_acc(O_WRONLY, 0400, EACCES);
    +
    2218 err += test_open_acc(O_RDWR, 0400, EACCES);
    +
    2219 err += test_open_acc(O_RDONLY, 0200, EACCES);
    +
    2220 err += test_open_acc(O_RDWR, 0200, EACCES);
    +
    2221 err += test_open_acc(O_RDONLY, 0000, EACCES);
    +
    2222 err += test_open_acc(O_WRONLY, 0000, EACCES);
    +
    2223 err += test_open_acc(O_RDWR, 0000, EACCES);
    +
    2224 }
    +
    2225 err += test_create_ro_dir(O_CREAT);
    +
    2226 err += test_create_ro_dir(O_CREAT | O_EXCL);
    +
    2227 err += test_create_ro_dir(O_CREAT | O_WRONLY);
    +
    2228 err += test_create_ro_dir(O_CREAT | O_TRUNC);
    +
    2229 err += test_copy_file_range();
    +
    2230 err += test_statx();
    +
    2231#ifndef __FreeBSD__
    +
    2232 err += test_create_tmpfile();
    +
    2233 err += test_create_and_link_tmpfile();
    +
    2234#endif
    +
    2235
    +
    2236 unlink(testfile2);
    +
    2237 unlink(testsock);
    +
    2238 rmdir(testdir);
    +
    2239 rmdir(testdir2);
    +
    2240
    +
    2241 if (err) {
    +
    2242 fprintf(stderr, "%i tests failed\n", -err);
    +
    2243 return 1;
    +
    2244 }
    +
    2245
    +
    2246 return check_unlinked_testfiles();
    +
    2247}
    +
    + + + + diff --git a/doc/html/test_2test__want__conversion_8c_source.html b/doc/html/test_2test__want__conversion_8c_source.html new file mode 100644 index 0000000..c8eb50a --- /dev/null +++ b/doc/html/test_2test__want__conversion_8c_source.html @@ -0,0 +1,264 @@ + + + + + + + +libfuse: test/test_want_conversion.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    test_want_conversion.c
    +
    +
    +
    1#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17)
    +
    2
    +
    3#include "util.h"
    +
    4#include "fuse_i.h"
    +
    5#include "fuse_lowlevel.h"
    +
    6#include <stdio.h>
    +
    7#include <assert.h>
    +
    8#include <inttypes.h>
    +
    9#include <stdbool.h>
    +
    10#include <err.h>
    +
    11
    +
    12static void print_conn_info(const char *prefix, struct fuse_conn_info *conn)
    +
    13{
    +
    14 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
    +
    15
    +
    16 printf("%s: want=0x%" PRIx32 " want_ext=0x%" PRIx64
    +
    17 " want_default=0x%" PRIx32 " want_ext_default=0x%" PRIx64 "\n",
    +
    18 prefix, conn->want, conn->want_ext, se->conn_want,
    +
    19 se->conn_want_ext);
    +
    20}
    +
    21
    +
    22static void application_init_old_style(struct fuse_conn_info *conn)
    +
    23{
    +
    24 /* Simulate application init the old style */
    + +
    26 conn->want &= ~FUSE_CAP_SPLICE_READ;
    +
    27
    +
    28 /*
    +
    29 * Also use new style API, as that might happen through
    +
    30 * fuse_apply_conn_info_opts()
    +
    31 */
    + +
    33}
    +
    34
    +
    35static void application_init_new_style(struct fuse_conn_info *conn)
    +
    36{
    +
    37 /* Simulate application init the new style */
    + + + +
    41}
    +
    42
    +
    43static void test_fuse_fs_init(struct fuse_conn_info *conn, bool new_style)
    +
    44{
    +
    45 /* High-level init */
    + +
    47
    +
    48 if (new_style)
    +
    49 application_init_new_style(conn);
    +
    50 else
    +
    51 application_init_old_style(conn);
    +
    52}
    +
    53
    +
    54static void test_do_init(struct fuse_conn_info *conn, bool new_style)
    +
    55{
    +
    56 /* Initial setup */
    + + + + +
    61 conn->capable = fuse_lower_32_bits(conn->capable_ext);
    +
    62
    + + + +
    66
    +
    67 print_conn_info("Initial state", conn);
    +
    68
    +
    69 int rc;
    +
    70
    +
    71 test_fuse_fs_init(conn, new_style);
    +
    72 print_conn_info("After init", conn);
    +
    73
    + +
    75 assert(rc == 0);
    +
    76
    +
    77 /* Verify all expected flags are set */
    +
    78 assert(!(conn->want_ext & FUSE_CAP_SPLICE_READ));
    +
    79 assert(conn->want_ext & FUSE_CAP_SPLICE_WRITE);
    +
    80 assert(conn->want_ext & FUSE_CAP_SPLICE_MOVE);
    +
    81 assert(conn->want_ext & FUSE_CAP_EXPORT_SUPPORT);
    +
    82 assert(conn->want_ext & FUSE_CAP_ASYNC_READ);
    +
    83 assert(conn->want_ext & FUSE_CAP_IOCTL_DIR);
    +
    84
    +
    85 /* Verify no other flags are set */
    +
    86 assert(conn->want_ext ==
    + + + +
    90
    +
    91 print_conn_info("After init", conn);
    +
    92}
    +
    93
    +
    94static void test_want_conversion_basic(void)
    +
    95{
    +
    96 const struct fuse_lowlevel_ops ops = { 0 };
    +
    97 struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
    +
    98 struct fuse_session *se;
    +
    99 struct fuse_conn_info *conn;
    +
    100
    +
    101 /* Add the program name to arg[0] */
    +
    102 if (fuse_opt_add_arg(&args, "test_signals")) {
    +
    103 fprintf(stderr, "Failed to add argument\n");
    +
    104 errx(1, "Failed to add argument");
    +
    105 }
    +
    106
    +
    107
    +
    108 se = fuse_session_new(&args, &ops, sizeof(ops), NULL);
    +
    109 assert(se);
    +
    110 conn = &se->conn;
    +
    111 printf("\nTesting basic want conversion, old style:\n");
    +
    112 test_do_init(conn, false);
    + +
    114
    +
    115 se = fuse_session_new(&args, &ops, sizeof(ops), NULL);
    +
    116 assert(se);
    +
    117 conn = &se->conn;
    +
    118 printf("\nTesting basic want conversion, new style:\n");
    +
    119 test_do_init(conn, true);
    +
    120 print_conn_info("After init", conn);
    + +
    122
    +
    123 fuse_opt_free_args(&args);
    +
    124
    +
    125}
    +
    126
    +
    127static void test_want_conversion_conflict(void)
    +
    128{
    +
    129 struct fuse_conn_info conn = { 0 };
    +
    130 int rc;
    +
    131
    +
    132 printf("\nTesting want conversion conflict:\n");
    +
    133
    +
    134 /* Test conflicting values */
    +
    135 /* Initialize like fuse_lowlevel.c does */
    + + + +
    139 conn.capable = fuse_lower_32_bits(conn.capable_ext);
    +
    140 conn.want_ext = conn.capable_ext;
    +
    141 conn.want = fuse_lower_32_bits(conn.want_ext);
    +
    142 print_conn_info("Test conflict initial", &conn);
    +
    143
    +
    144 /* Simulate application init modifying capabilities */
    +
    145 conn.want_ext |= FUSE_CAP_ATOMIC_O_TRUNC; /* Add new capability */
    +
    146 conn.want &= ~FUSE_CAP_SPLICE_READ; /* Remove a capability */
    +
    147
    + +
    149 assert(rc == -EINVAL);
    +
    150 print_conn_info("Test conflict after", &conn);
    +
    151
    +
    152 printf("Want conversion conflict test passed\n");
    +
    153}
    +
    154
    +
    155static void test_want_conversion_high_bits(void)
    +
    156{
    +
    157 struct fuse_conn_info conn = { 0 };
    +
    158 int rc;
    +
    159
    +
    160 printf("\nTesting want conversion high bits preservation:\n");
    +
    161
    +
    162 /* Test high bits preservation */
    +
    163 conn.want_ext = (1ULL << 33) | FUSE_CAP_ASYNC_READ;
    +
    164 conn.want = fuse_lower_32_bits(conn.want_ext);
    +
    165 print_conn_info("Test high bits initial", &conn);
    +
    166
    + +
    168 assert(rc == 0);
    +
    169 assert(conn.want_ext == ((1ULL << 33) | FUSE_CAP_ASYNC_READ));
    +
    170 print_conn_info("Test high bits after", &conn);
    +
    171
    +
    172 printf("Want conversion high bits test passed\n");
    +
    173}
    +
    174
    +
    175int main(void)
    +
    176{
    +
    177 test_want_conversion_basic();
    +
    178 test_want_conversion_conflict();
    +
    179 test_want_conversion_high_bits();
    +
    180 return 0;
    +
    181}
    +
    #define FUSE_CAP_IOCTL_DIR
    +
    void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
    +
    int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
    +
    bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
    +
    #define FUSE_CAP_SPLICE_READ
    +
    #define FUSE_CAP_ATOMIC_O_TRUNC
    +
    #define FUSE_CAP_ASYNC_READ
    +
    #define FUSE_CAP_SPLICE_WRITE
    +
    #define FUSE_CAP_EXPORT_SUPPORT
    +
    #define FUSE_CAP_POSIX_LOCKS
    +
    #define FUSE_CAP_SPLICE_MOVE
    +
    #define FUSE_CAP_FLOCK_LOCKS
    +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    + + + +
    uint64_t capable_ext
    +
    uint32_t capable
    +
    uint64_t want_ext
    + +
    + + + + diff --git a/doc/html/test_2test__write__cache_8c_source.html b/doc/html/test_2test__write__cache_8c_source.html new file mode 100644 index 0000000..16c48dd --- /dev/null +++ b/doc/html/test_2test__write__cache_8c_source.html @@ -0,0 +1,412 @@ + + + + + + + +libfuse: test/test_write_cache.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    test_write_cache.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file GPL2.txt.
    +
    7*/
    +
    8
    +
    9#define FUSE_USE_VERSION 30
    +
    10
    +
    11/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
    +
    12#include <fuse.h>
    +
    13
    +
    14#include <fuse_config.h>
    +
    15#include <fuse_lowlevel.h>
    +
    16#include <stdio.h>
    +
    17#include <stdlib.h>
    +
    18#include <string.h>
    +
    19#include <errno.h>
    +
    20#include <fcntl.h>
    +
    21#include <assert.h>
    +
    22#include <stddef.h>
    +
    23#include <unistd.h>
    +
    24#include <sys/stat.h>
    +
    25#include <pthread.h>
    +
    26#include <stdatomic.h>
    +
    27
    +
    28#ifndef __linux__
    +
    29#include <limits.h>
    +
    30#else
    +
    31#include <linux/limits.h>
    +
    32#endif
    +
    33
    +
    34#define FILE_INO 2
    +
    35#define FILE_NAME "write_me"
    +
    36
    +
    37/* Command line parsing */
    +
    38struct options {
    +
    39 int writeback;
    +
    40 int data_size;
    +
    41 int delay_ms;
    +
    42} options = {
    +
    43 .writeback = 0,
    +
    44 .data_size = 2048,
    +
    45 .delay_ms = 0,
    +
    46};
    +
    47
    +
    48#define WRITE_SYSCALLS 64
    +
    49
    +
    50#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
    +
    51static const struct fuse_opt option_spec[] = {
    +
    52 OPTION("writeback_cache", writeback),
    +
    53 OPTION("--data-size=%d", data_size), OPTION("--delay_ms=%d", delay_ms),
    + +
    55};
    +
    56static int got_write;
    +
    57static atomic_int write_cnt;
    +
    58
    +
    59pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    +
    60pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    +
    61static int write_start, write_done;
    +
    62
    +
    63static void tfs_init(void *userdata, struct fuse_conn_info *conn)
    +
    64{
    +
    65 (void)userdata;
    +
    66
    +
    67 if (options.writeback) {
    + + +
    70 }
    +
    71}
    +
    72
    +
    73static int tfs_stat(fuse_ino_t ino, struct stat *stbuf)
    +
    74{
    +
    75 stbuf->st_ino = ino;
    +
    76 if (ino == FUSE_ROOT_ID) {
    +
    77 stbuf->st_mode = S_IFDIR | 0755;
    +
    78 stbuf->st_nlink = 1;
    +
    79 }
    +
    80
    +
    81 else if (ino == FILE_INO) {
    +
    82 stbuf->st_mode = S_IFREG | 0222;
    +
    83 stbuf->st_nlink = 1;
    +
    84 stbuf->st_size = 0;
    +
    85 }
    +
    86
    +
    87 else
    +
    88 return -1;
    +
    89
    +
    90 return 0;
    +
    91}
    +
    92
    +
    93static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    94{
    +
    95 struct fuse_entry_param e;
    +
    96
    +
    97 memset(&e, 0, sizeof(e));
    +
    98
    +
    99 if (parent != FUSE_ROOT_ID)
    +
    100 goto err_out;
    +
    101 else if (strcmp(name, FILE_NAME) == 0)
    +
    102 e.ino = FILE_INO;
    +
    103 else
    +
    104 goto err_out;
    +
    105
    +
    106 if (tfs_stat(e.ino, &e.attr) != 0)
    +
    107 goto err_out;
    +
    108 fuse_reply_entry(req, &e);
    +
    109 return;
    +
    110
    +
    111err_out:
    +
    112 fuse_reply_err(req, ENOENT);
    +
    113}
    +
    114
    +
    115static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    116 struct fuse_file_info *fi)
    +
    117{
    +
    118 struct stat stbuf;
    +
    119
    +
    120 (void)fi;
    +
    121
    +
    122 memset(&stbuf, 0, sizeof(stbuf));
    +
    123 if (tfs_stat(ino, &stbuf) != 0)
    +
    124 fuse_reply_err(req, ENOENT);
    +
    125 else
    +
    126 fuse_reply_attr(req, &stbuf, 5);
    +
    127}
    +
    128
    +
    129static void tfs_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    130{
    +
    131 if (ino == FUSE_ROOT_ID)
    +
    132 fuse_reply_err(req, EISDIR);
    +
    133 else {
    +
    134 assert(ino == FILE_INO);
    +
    135 /* Test close(rofd) does not block waiting for pending writes */
    +
    136 fi->noflush = !options.writeback && options.delay_ms &&
    +
    137 (fi->flags & O_ACCMODE) == O_RDONLY;
    +
    138 fuse_reply_open(req, fi);
    +
    139 }
    +
    140}
    +
    141
    +
    142static void tfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
    +
    143 size_t size, off_t off, struct fuse_file_info *fi)
    +
    144{
    +
    145 (void)fi;
    +
    146 (void)buf;
    +
    147 (void)off;
    +
    148 size_t expected;
    +
    149
    +
    150 assert(ino == FILE_INO);
    +
    151 expected = options.data_size;
    +
    152 if (options.writeback)
    +
    153 expected *= 2;
    +
    154
    +
    155 write_cnt++;
    +
    156
    +
    157 if (size != expected && !options.writeback)
    +
    158 fprintf(stderr, "ERROR: Expected %zd bytes, got %zd\n!",
    +
    159 expected, size);
    +
    160 else
    +
    161 got_write = 1;
    +
    162
    +
    163 /* Simulate waiting for pending writes */
    +
    164 if (options.delay_ms) {
    +
    165 pthread_mutex_lock(&lock);
    +
    166 write_start = 1;
    +
    167 pthread_cond_signal(&cond);
    +
    168 pthread_mutex_unlock(&lock);
    +
    169
    +
    170 usleep(options.delay_ms * 1000);
    +
    171
    +
    172 pthread_mutex_lock(&lock);
    +
    173 write_done = 1;
    +
    174 pthread_cond_signal(&cond);
    +
    175 pthread_mutex_unlock(&lock);
    +
    176 }
    +
    177
    +
    178 fuse_reply_write(req, size);
    +
    179}
    +
    180
    +
    181static struct fuse_lowlevel_ops tfs_oper = {
    +
    182 .init = tfs_init,
    +
    183 .lookup = tfs_lookup,
    +
    184 .getattr = tfs_getattr,
    +
    185 .open = tfs_open,
    +
    186 .write = tfs_write,
    +
    187};
    +
    188
    +
    189static void *close_rofd(void *data)
    +
    190{
    +
    191 int rofd = (int)(long)data;
    +
    192
    +
    193 /* Wait for first write to start */
    +
    194 pthread_mutex_lock(&lock);
    +
    195 while (!write_start && !write_done)
    +
    196 pthread_cond_wait(&cond, &lock);
    +
    197 pthread_mutex_unlock(&lock);
    +
    198
    +
    199 close(rofd);
    +
    200 printf("rofd closed. write_start: %d write_done: %d\n", write_start,
    +
    201 write_done);
    +
    202
    +
    203 /* First write should not have been completed */
    +
    204 if (write_done)
    +
    205 fprintf(stderr, "ERROR: close(rofd) blocked on write!\n");
    +
    206
    +
    207 return NULL;
    +
    208}
    +
    209
    +
    210static void *run_fs(void *data)
    +
    211{
    +
    212 struct fuse_session *se = (struct fuse_session *)data;
    +
    213
    +
    214 assert(fuse_session_loop(se) == 0);
    +
    215 return NULL;
    +
    216}
    +
    217
    +
    218static void test_fs(char *mountpoint)
    +
    219{
    +
    220 char fname[PATH_MAX];
    +
    221 char *buf;
    +
    222 const size_t iosize = options.data_size;
    +
    223 const size_t dsize = options.data_size * WRITE_SYSCALLS;
    +
    224 int fd, rofd;
    +
    225 pthread_t rofd_thread;
    +
    226 off_t off = 0;
    +
    227
    +
    228 buf = malloc(dsize);
    +
    229 assert(buf != NULL);
    +
    230 assert((fd = open("/dev/urandom", O_RDONLY)) != -1);
    +
    231 assert(read(fd, buf, dsize) == dsize);
    +
    232 close(fd);
    +
    233
    +
    234 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME, mountpoint) > 0);
    +
    235 fd = open(fname, O_WRONLY);
    +
    236 if (fd == -1) {
    +
    237 perror(fname);
    +
    238 assert(0);
    +
    239 }
    +
    240
    +
    241 if (options.delay_ms) {
    +
    242 /* Verify that close(rofd) does not block waiting for pending writes */
    +
    243 rofd = open(fname, O_RDONLY);
    +
    244 assert(pthread_create(&rofd_thread, NULL, close_rofd,
    +
    245 (void *)(long)rofd) == 0);
    +
    246 /* Give close_rofd time to start */
    +
    247 usleep(options.delay_ms * 1000);
    +
    248 }
    +
    249
    +
    250 for (int cnt = 0; cnt < WRITE_SYSCALLS; cnt++) {
    +
    251 assert(pwrite(fd, buf + off, iosize, off) == iosize);
    +
    252 off += iosize;
    +
    253 assert(off <= dsize);
    +
    254 }
    +
    255 free(buf);
    +
    256 close(fd);
    +
    257
    +
    258 if (options.delay_ms) {
    +
    259 printf("rwfd closed. write_start: %d write_done: %d\n",
    +
    260 write_start, write_done);
    +
    261 assert(pthread_join(rofd_thread, NULL) == 0);
    +
    262 }
    +
    263}
    +
    264
    +
    265int main(int argc, char *argv[])
    +
    266{
    +
    267 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    268 struct fuse_session *se;
    +
    269 struct fuse_cmdline_opts fuse_opts;
    +
    270 pthread_t fs_thread;
    +
    271
    +
    272 assert(fuse_opt_parse(&args, &options, option_spec, NULL) == 0);
    +
    273 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
    +
    274#ifndef __FreeBSD__
    +
    275 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
    +
    276#endif
    +
    277 se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), NULL);
    +
    278 fuse_opt_free_args(&args);
    +
    279 assert(se != NULL);
    +
    280 assert(fuse_set_signal_handlers(se) == 0);
    +
    281 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
    +
    282
    +
    283 /* Start file-system thread */
    +
    284 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
    +
    285
    +
    286 /* Write test data */
    +
    287 test_fs(fuse_opts.mountpoint);
    +
    288 free(fuse_opts.mountpoint);
    +
    289
    +
    290 /* Stop file system */
    + + +
    293 assert(pthread_join(fs_thread, NULL) == 0);
    +
    294
    +
    295 assert(got_write == 1);
    +
    296
    +
    297 /*
    +
    298 * when writeback cache is enabled, kernel side can merge requests, but
    +
    299 * memory pressure, system 'sync' might trigger data flushes before - flush
    +
    300 * might happen in between write syscalls - merging subpage writes into
    +
    301 * a single page and pages into large fuse requests might or might not work.
    +
    302 * Though we can expect that that at least some (but maybe all) write
    +
    303 * system calls can be merged.
    +
    304 */
    +
    305 if (options.writeback)
    +
    306 assert(write_cnt < WRITE_SYSCALLS);
    +
    307 else
    +
    308 assert(write_cnt == WRITE_SYSCALLS);
    +
    309
    + + +
    312
    +
    313 printf("Test completed successfully.\n");
    +
    314 return 0;
    +
    315}
    +
    316
    +
    bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    #define FUSE_CAP_WRITEBACK_CACHE
    +
    bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    void fuse_session_exit(struct fuse_session *se)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    int fuse_reply_write(fuse_req_t req, size_t count)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    uint64_t fuse_ino_t
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    +
    #define FUSE_ROOT_ID
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + + +
    +
    fuse_ino_t ino
    + +
    uint32_t noflush
    Definition fuse_common.h:93
    + + +
    void(* init)(void *userdata, struct fuse_conn_info *conn)
    + +
    + + + + diff --git a/doc/html/test_2wrong__command_8c_source.html b/doc/html/test_2wrong__command_8c_source.html new file mode 100644 index 0000000..2ce3098 --- /dev/null +++ b/doc/html/test_2wrong__command_8c_source.html @@ -0,0 +1,76 @@ + + + + + + + +libfuse: test/wrong_command.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    wrong_command.c
    +
    +
    +
    1#include <stdio.h>
    +
    2
    +
    3int main(void) {
    +
    4#ifdef MESON_IS_SUBPROJECT
    +
    5 fprintf(stderr, "libfuse tests were skipped because it's a meson subproject.\n"
    +
    6 "If you wish to run them try:\n"
    +
    7 "'cd <srcdir>/subprojects/libfuse && meson . build && cd build && python3 -m pytest test/' instead");
    +
    8 return 77; /* report as a skipped test */
    +
    9#else
    +
    10 fprintf(stderr, "\x1B[31m\e[1m"
    +
    11 "This is not the command you are looking for.\n"
    +
    12 "You probably want to run 'python3 -m pytest test/' instead"
    +
    13 "\e[0m\n");
    +
    14 return 1;
    +
    15#endif
    +
    16}
    +
    + + + + diff --git a/doc/html/util_2fusermount_8c_source.html b/doc/html/util_2fusermount_8c_source.html new file mode 100644 index 0000000..18b4cf0 --- /dev/null +++ b/doc/html/util_2fusermount_8c_source.html @@ -0,0 +1,1843 @@ + + + + + + + +libfuse: util/fusermount.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    fusermount.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file GPL2.txt.
    +
    7*/
    +
    8/* This program does the mounting and unmounting of FUSE filesystems */
    +
    9
    +
    10#define _GNU_SOURCE /* for clone,strchrnul and close_range */
    +
    11#include "fuse_config.h"
    +
    12#include "mount_util.h"
    +
    13#include "util.h"
    +
    14
    +
    15#include <stdio.h>
    +
    16#include <stdlib.h>
    +
    17#include <string.h>
    +
    18#include <ctype.h>
    +
    19#include <unistd.h>
    +
    20#include <getopt.h>
    +
    21#include <errno.h>
    +
    22#include <fcntl.h>
    +
    23#include <pwd.h>
    +
    24#include <paths.h>
    +
    25#include <mntent.h>
    +
    26#include <sys/wait.h>
    +
    27#include <sys/stat.h>
    +
    28#include <sys/param.h>
    +
    29
    +
    30#include "fuse_mount_compat.h"
    +
    31
    +
    32#include <sys/fsuid.h>
    +
    33#include <sys/socket.h>
    +
    34#include <sys/utsname.h>
    +
    35#include <sched.h>
    +
    36#include <stdbool.h>
    +
    37#include <sys/vfs.h>
    +
    38
    +
    39#if defined HAVE_CLOSE_RANGE && defined linux
    +
    40#include <linux/close_range.h>
    +
    41#endif
    +
    42
    +
    43#if defined HAVE_LISTMOUNT
    +
    44#include <linux/mount.h>
    +
    45#include <syscall.h>
    +
    46#include <stdint.h>
    +
    47#endif
    +
    48
    +
    49#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
    +
    50#define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE"
    +
    51
    +
    52#define FUSE_DEV "/dev/fuse"
    +
    53
    +
    54static const char *progname;
    +
    55
    +
    56static int user_allow_other = 0;
    +
    57static int mount_max = 1000;
    +
    58
    +
    59static int auto_unmount = 0;
    +
    60
    +
    61#ifdef GETMNTENT_NEEDS_UNESCAPING
    +
    62// Older versions of musl libc don't unescape entries in /etc/mtab
    +
    63
    +
    64// unescapes octal sequences like \040 in-place
    +
    65// That's ok, because unescaping can not extend the length of the string.
    +
    66static void unescape(char *buf) {
    +
    67 char *src = buf;
    +
    68 char *dest = buf;
    +
    69 while (1) {
    +
    70 char *next_src = strchrnul(src, '\\');
    +
    71 int offset = next_src - src;
    +
    72 memmove(dest, src, offset);
    +
    73 src = next_src;
    +
    74 dest += offset;
    +
    75
    +
    76 if(*src == '\0') {
    +
    77 *dest = *src;
    +
    78 return;
    +
    79 }
    +
    80 src++;
    +
    81
    +
    82 if('0' <= src[0] && src[0] < '2' &&
    +
    83 '0' <= src[1] && src[1] < '8' &&
    +
    84 '0' <= src[2] && src[2] < '8') {
    +
    85 *dest++ = (src[0] - '0') << 6
    +
    86 | (src[1] - '0') << 3
    +
    87 | (src[2] - '0') << 0;
    +
    88 src += 3;
    +
    89 } else if (src[0] == '\\') {
    +
    90 *dest++ = '\\';
    +
    91 src += 1;
    +
    92 } else {
    +
    93 *dest++ = '\\';
    +
    94 }
    +
    95 }
    +
    96}
    +
    97
    +
    98static struct mntent *GETMNTENT(FILE *stream)
    +
    99{
    +
    100 struct mntent *entp = getmntent(stream);
    +
    101 if(entp != NULL) {
    +
    102 unescape(entp->mnt_fsname);
    +
    103 unescape(entp->mnt_dir);
    +
    104 unescape(entp->mnt_type);
    +
    105 unescape(entp->mnt_opts);
    +
    106 }
    +
    107 return entp;
    +
    108}
    +
    109#else
    +
    110#define GETMNTENT getmntent
    +
    111#endif // GETMNTENT_NEEDS_UNESCAPING
    +
    112
    +
    113/*
    +
    114 * Take a ',' separated option string and extract "x-" options
    +
    115 */
    +
    116static int extract_x_options(const char *original, char **non_x_opts,
    +
    117 char **x_opts)
    +
    118{
    +
    119 size_t orig_len;
    +
    120 const char *opt, *opt_end;
    +
    121
    +
    122 orig_len = strlen(original) + 1;
    +
    123
    +
    124 *non_x_opts = calloc(1, orig_len);
    +
    125 *x_opts = calloc(1, orig_len);
    +
    126
    +
    127 size_t non_x_opts_len = orig_len;
    +
    128 size_t x_opts_len = orig_len;
    +
    129
    +
    130 if (*non_x_opts == NULL || *x_opts == NULL) {
    +
    131 fprintf(stderr, "%s: Failed to allocate %zuB.\n",
    +
    132 __func__, orig_len);
    +
    133 return -ENOMEM;
    +
    134 }
    +
    135
    +
    136 for (opt = original; opt < original + orig_len; opt = opt_end + 1) {
    +
    137 char *opt_buf;
    +
    138
    +
    139 opt_end = strchr(opt, ',');
    +
    140 if (opt_end == NULL)
    +
    141 opt_end = original + orig_len;
    +
    142
    +
    143 size_t opt_len = opt_end - opt;
    +
    144 size_t opt_len_left = orig_len - (opt - original);
    +
    145 size_t buf_len;
    +
    146 bool is_x_opts;
    +
    147
    +
    148 if (strncmp(opt, "x-", MIN(2, opt_len_left)) == 0) {
    +
    149 buf_len = x_opts_len;
    +
    150 is_x_opts = true;
    +
    151 opt_buf = *x_opts;
    +
    152 } else {
    +
    153 buf_len = non_x_opts_len;
    +
    154 is_x_opts = false;
    +
    155 opt_buf = *non_x_opts;
    +
    156 }
    +
    157
    +
    158 if (buf_len < orig_len) {
    +
    159 strncat(opt_buf, ",", 2);
    +
    160 buf_len -= 1;
    +
    161 }
    +
    162
    +
    163 /* omits ',' */
    +
    164 if ((ssize_t)(buf_len - opt_len) < 0) {
    +
    165 /* This would be a bug */
    +
    166 fprintf(stderr, "%s: no buf space left in copy, orig='%s'\n",
    +
    167 __func__, original);
    +
    168 return -EIO;
    +
    169 }
    +
    170
    +
    171 strncat(opt_buf, opt, opt_end - opt);
    +
    172 buf_len -= opt_len;
    +
    173
    +
    174 if (is_x_opts)
    +
    175 x_opts_len = buf_len;
    +
    176 else
    +
    177 non_x_opts_len = buf_len;
    +
    178 }
    +
    179
    +
    180 return 0;
    +
    181}
    +
    182
    +
    183static const char *get_user_name(void)
    +
    184{
    +
    185 struct passwd *pw = getpwuid(getuid());
    +
    186 if (pw != NULL && pw->pw_name != NULL)
    +
    187 return pw->pw_name;
    +
    188 else {
    +
    189 fprintf(stderr, "%s: could not determine username\n", progname);
    +
    190 return NULL;
    +
    191 }
    +
    192}
    +
    193
    +
    194static uid_t oldfsuid;
    +
    195static gid_t oldfsgid;
    +
    196
    +
    197static void drop_privs(void)
    +
    198{
    +
    199 if (getuid() != 0) {
    +
    200 oldfsuid = setfsuid(getuid());
    +
    201 oldfsgid = setfsgid(getgid());
    +
    202 }
    +
    203}
    +
    204
    +
    205static void restore_privs(void)
    +
    206{
    +
    207 if (getuid() != 0) {
    +
    208 setfsuid(oldfsuid);
    +
    209 setfsgid(oldfsgid);
    +
    210 }
    +
    211}
    +
    212
    +
    213#ifndef IGNORE_MTAB
    +
    214/*
    +
    215 * Make sure that /etc/mtab is checked and updated atomically
    +
    216 */
    +
    217static int lock_umount(void)
    +
    218{
    +
    219 const char *mtab_lock = _PATH_MOUNTED ".fuselock";
    +
    220 int mtablock;
    +
    221 int res;
    +
    222 struct stat mtab_stat;
    +
    223
    +
    224 /* /etc/mtab could be a symlink to /proc/mounts */
    +
    225 if (lstat(_PATH_MOUNTED, &mtab_stat) == 0 && S_ISLNK(mtab_stat.st_mode))
    +
    226 return -1;
    +
    227
    +
    228 mtablock = open(mtab_lock, O_RDWR | O_CREAT, 0600);
    +
    229 if (mtablock == -1) {
    +
    230 fprintf(stderr, "%s: unable to open fuse lock file: %s\n",
    +
    231 progname, strerror(errno));
    +
    232 return -1;
    +
    233 }
    +
    234 res = lockf(mtablock, F_LOCK, 0);
    +
    235 if (res < 0) {
    +
    236 fprintf(stderr, "%s: error getting lock: %s\n", progname,
    +
    237 strerror(errno));
    +
    238 close(mtablock);
    +
    239 return -1;
    +
    240 }
    +
    241
    +
    242 return mtablock;
    +
    243}
    +
    244
    +
    245static void unlock_umount(int mtablock)
    +
    246{
    +
    247 if (mtablock >= 0) {
    +
    248 int res;
    +
    249
    +
    250 res = lockf(mtablock, F_ULOCK, 0);
    +
    251 if (res < 0) {
    +
    252 fprintf(stderr, "%s: error releasing lock: %s\n",
    +
    253 progname, strerror(errno));
    +
    254 }
    +
    255 close(mtablock);
    +
    256 }
    +
    257}
    +
    258
    +
    259static int add_mount(const char *source, const char *mnt, const char *type,
    +
    260 const char *opts)
    +
    261{
    +
    262 return fuse_mnt_add_mount(progname, source, mnt, type, opts);
    +
    263}
    +
    264
    +
    265static int may_unmount(const char *mnt, int quiet)
    +
    266{
    +
    267 struct mntent *entp;
    +
    268 FILE *fp;
    +
    269 const char *user = NULL;
    +
    270 char uidstr[32];
    +
    271 unsigned uidlen = 0;
    +
    272 int found;
    +
    273 const char *mtab = _PATH_MOUNTED;
    +
    274
    +
    275 user = get_user_name();
    +
    276 if (user == NULL)
    +
    277 return -1;
    +
    278
    +
    279 fp = setmntent(mtab, "r");
    +
    280 if (fp == NULL) {
    +
    281 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
    +
    282 strerror(errno));
    +
    283 return -1;
    +
    284 }
    +
    285
    +
    286 uidlen = sprintf(uidstr, "%u", getuid());
    +
    287
    +
    288 found = 0;
    +
    289 while ((entp = GETMNTENT(fp)) != NULL) {
    +
    290 if (!found && strcmp(entp->mnt_dir, mnt) == 0 &&
    +
    291 (strcmp(entp->mnt_type, "fuse") == 0 ||
    +
    292 strcmp(entp->mnt_type, "fuseblk") == 0 ||
    +
    293 strncmp(entp->mnt_type, "fuse.", 5) == 0 ||
    +
    294 strncmp(entp->mnt_type, "fuseblk.", 8) == 0)) {
    +
    295 char *p = strstr(entp->mnt_opts, "user=");
    +
    296 if (p &&
    +
    297 (p == entp->mnt_opts || *(p-1) == ',') &&
    +
    298 strcmp(p + 5, user) == 0) {
    +
    299 found = 1;
    +
    300 break;
    +
    301 }
    +
    302 /* /etc/mtab is a link pointing to
    +
    303 /proc/mounts: */
    +
    304 else if ((p =
    +
    305 strstr(entp->mnt_opts, "user_id=")) &&
    +
    306 (p == entp->mnt_opts ||
    +
    307 *(p-1) == ',') &&
    +
    308 strncmp(p + 8, uidstr, uidlen) == 0 &&
    +
    309 (*(p+8+uidlen) == ',' ||
    +
    310 *(p+8+uidlen) == '\0')) {
    +
    311 found = 1;
    +
    312 break;
    +
    313 }
    +
    314 }
    +
    315 }
    +
    316 endmntent(fp);
    +
    317
    +
    318 if (!found) {
    +
    319 if (!quiet)
    +
    320 fprintf(stderr,
    +
    321 "%s: entry for %s not found in %s\n",
    +
    322 progname, mnt, mtab);
    +
    323 return -1;
    +
    324 }
    +
    325
    +
    326 return 0;
    +
    327}
    +
    328#endif
    +
    329
    +
    330/*
    +
    331 * Check whether the file specified in "fusermount3 -u" is really a
    +
    332 * mountpoint and not a symlink. This is necessary otherwise the user
    +
    333 * could move the mountpoint away and replace it with a symlink
    +
    334 * pointing to an arbitrary mount, thereby tricking fusermount3 into
    +
    335 * unmounting that (umount(2) will follow symlinks).
    +
    336 *
    +
    337 * This is the child process running in a separate mount namespace, so
    +
    338 * we don't mess with the global namespace and if the process is
    +
    339 * killed for any reason, mounts are automatically cleaned up.
    +
    340 *
    +
    341 * First make sure nothing is propagated back into the parent
    +
    342 * namespace by marking all mounts "private".
    +
    343 *
    +
    344 * Then bind mount parent onto a stable base where the user can't move
    +
    345 * it around.
    +
    346 *
    +
    347 * Finally check /proc/mounts for an entry matching the requested
    +
    348 * mountpoint. If it's found then we are OK, and the user can't move
    +
    349 * it around within the parent directory as rename() will return
    +
    350 * EBUSY. Be careful to ignore any mounts that existed before the
    +
    351 * bind.
    +
    352 */
    +
    353static int check_is_mount_child(void *p)
    +
    354{
    +
    355 const char **a = p;
    +
    356 const char *last = a[0];
    +
    357 const char *mnt = a[1];
    +
    358 const char *type = a[2];
    +
    359 int res;
    +
    360 const char *procmounts = "/proc/mounts";
    +
    361 int found;
    +
    362 FILE *fp;
    +
    363 struct mntent *entp;
    +
    364 int count;
    +
    365
    +
    366 res = mount("", "/", "", MS_PRIVATE | MS_REC, NULL);
    +
    367 if (res == -1) {
    +
    368 fprintf(stderr, "%s: failed to mark mounts private: %s\n",
    +
    369 progname, strerror(errno));
    +
    370 return 1;
    +
    371 }
    +
    372
    +
    373 fp = setmntent(procmounts, "r");
    +
    374 if (fp == NULL) {
    +
    375 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
    +
    376 procmounts, strerror(errno));
    +
    377 return 1;
    +
    378 }
    +
    379
    +
    380 count = 0;
    +
    381 while (GETMNTENT(fp) != NULL)
    +
    382 count++;
    +
    383 endmntent(fp);
    +
    384
    +
    385 fp = setmntent(procmounts, "r");
    +
    386 if (fp == NULL) {
    +
    387 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
    +
    388 procmounts, strerror(errno));
    +
    389 return 1;
    +
    390 }
    +
    391
    +
    392 res = mount(".", "/", "", MS_BIND | MS_REC, NULL);
    +
    393 if (res == -1) {
    +
    394 fprintf(stderr, "%s: failed to bind parent to /: %s\n",
    +
    395 progname, strerror(errno));
    +
    396 return 1;
    +
    397 }
    +
    398
    +
    399 found = 0;
    +
    400 while ((entp = GETMNTENT(fp)) != NULL) {
    +
    401 if (count > 0) {
    +
    402 count--;
    +
    403 continue;
    +
    404 }
    +
    405 if (entp->mnt_dir[0] == '/' &&
    +
    406 strcmp(entp->mnt_dir + 1, last) == 0 &&
    +
    407 (!type || strcmp(entp->mnt_type, type) == 0)) {
    +
    408 found = 1;
    +
    409 break;
    +
    410 }
    +
    411 }
    +
    412 endmntent(fp);
    +
    413
    +
    414 if (!found) {
    +
    415 fprintf(stderr, "%s: %s not mounted\n", progname, mnt);
    +
    416 return 1;
    +
    417 }
    +
    418
    +
    419 return 0;
    +
    420}
    +
    421
    +
    422static pid_t clone_newns(void *a)
    +
    423{
    +
    424 char buf[131072];
    +
    425 char *stack = buf + (sizeof(buf) / 2 - ((size_t) buf & 15));
    +
    426
    +
    427#ifdef __ia64__
    +
    428 extern int __clone2(int (*fn)(void *),
    +
    429 void *child_stack_base, size_t stack_size,
    +
    430 int flags, void *arg, pid_t *ptid,
    +
    431 void *tls, pid_t *ctid);
    +
    432
    +
    433 return __clone2(check_is_mount_child, stack, sizeof(buf) / 2,
    +
    434 CLONE_NEWNS, a, NULL, NULL, NULL);
    +
    435#else
    +
    436 return clone(check_is_mount_child, stack, CLONE_NEWNS, a);
    +
    437#endif
    +
    438}
    +
    439
    +
    440static int check_is_mount(const char *last, const char *mnt, const char *type)
    +
    441{
    +
    442 pid_t pid, p;
    +
    443 int status;
    +
    444 const char *a[3] = { last, mnt, type };
    +
    445
    +
    446 pid = clone_newns((void *) a);
    +
    447 if (pid == (pid_t) -1) {
    +
    448 fprintf(stderr, "%s: failed to clone namespace: %s\n",
    +
    449 progname, strerror(errno));
    +
    450 return -1;
    +
    451 }
    +
    452 p = waitpid(pid, &status, __WCLONE);
    +
    453 if (p == (pid_t) -1) {
    +
    454 fprintf(stderr, "%s: waitpid failed: %s\n",
    +
    455 progname, strerror(errno));
    +
    456 return -1;
    +
    457 }
    +
    458 if (!WIFEXITED(status)) {
    +
    459 fprintf(stderr, "%s: child terminated abnormally (status %i)\n",
    +
    460 progname, status);
    +
    461 return -1;
    +
    462 }
    +
    463 if (WEXITSTATUS(status) != 0)
    +
    464 return -1;
    +
    465
    +
    466 return 0;
    +
    467}
    +
    468
    +
    469static int chdir_to_parent(char *copy, const char **lastp)
    +
    470{
    +
    471 char *tmp;
    +
    472 const char *parent;
    +
    473 char buf[65536];
    +
    474 int res;
    +
    475
    +
    476 tmp = strrchr(copy, '/');
    +
    477 if (tmp == NULL || tmp[1] == '\0') {
    +
    478 fprintf(stderr, "%s: internal error: invalid abs path: <%s>\n",
    +
    479 progname, copy);
    +
    480 return -1;
    +
    481 }
    +
    482 if (tmp != copy) {
    +
    483 *tmp = '\0';
    +
    484 parent = copy;
    +
    485 *lastp = tmp + 1;
    +
    486 } else if (tmp[1] != '\0') {
    +
    487 *lastp = tmp + 1;
    +
    488 parent = "/";
    +
    489 } else {
    +
    490 *lastp = ".";
    +
    491 parent = "/";
    +
    492 }
    +
    493
    +
    494 res = chdir(parent);
    +
    495 if (res == -1) {
    +
    496 fprintf(stderr, "%s: failed to chdir to %s: %s\n",
    +
    497 progname, parent, strerror(errno));
    +
    498 return -1;
    +
    499 }
    +
    500
    +
    501 if (getcwd(buf, sizeof(buf)) == NULL) {
    +
    502 fprintf(stderr, "%s: failed to obtain current directory: %s\n",
    +
    503 progname, strerror(errno));
    +
    504 return -1;
    +
    505 }
    +
    506 if (strcmp(buf, parent) != 0) {
    +
    507 fprintf(stderr, "%s: mountpoint moved (%s -> %s)\n", progname,
    +
    508 parent, buf);
    +
    509 return -1;
    +
    510
    +
    511 }
    +
    512
    +
    513 return 0;
    +
    514}
    +
    515
    +
    516#ifndef IGNORE_MTAB
    +
    517static int unmount_fuse_locked(const char *mnt, int quiet, int lazy)
    +
    518{
    +
    519 int res;
    +
    520 char *copy;
    +
    521 const char *last;
    +
    522 int umount_flags = (lazy ? UMOUNT_DETACH : 0) | UMOUNT_NOFOLLOW;
    +
    523
    +
    524 if (getuid() != 0) {
    +
    525 res = may_unmount(mnt, quiet);
    +
    526 if (res == -1)
    +
    527 return -1;
    +
    528 }
    +
    529
    +
    530 copy = strdup(mnt);
    +
    531 if (copy == NULL) {
    +
    532 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    533 return -1;
    +
    534 }
    +
    535
    +
    536 drop_privs();
    +
    537 res = chdir_to_parent(copy, &last);
    +
    538 if (res == -1) {
    +
    539 restore_privs();
    +
    540 goto out;
    +
    541 }
    +
    542
    +
    543 res = umount2(last, umount_flags);
    +
    544 restore_privs();
    +
    545 if (res == -1 && !quiet) {
    +
    546 fprintf(stderr, "%s: failed to unmount %s: %s\n",
    +
    547 progname, mnt, strerror(errno));
    +
    548 }
    +
    549
    +
    550out:
    +
    551 free(copy);
    +
    552 if (res == -1)
    +
    553 return -1;
    +
    554
    +
    555 res = chdir("/");
    +
    556 if (res == -1) {
    +
    557 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
    +
    558 return -1;
    +
    559 }
    +
    560
    +
    561 return fuse_mnt_remove_mount(progname, mnt);
    +
    562}
    +
    563
    +
    564static int unmount_fuse(const char *mnt, int quiet, int lazy)
    +
    565{
    +
    566 int res;
    +
    567 int mtablock = lock_umount();
    +
    568
    +
    569 res = unmount_fuse_locked(mnt, quiet, lazy);
    +
    570 unlock_umount(mtablock);
    +
    571
    +
    572 return res;
    +
    573}
    +
    574
    +
    575static int count_fuse_fs_mtab(void)
    +
    576{
    +
    577 struct mntent *entp;
    +
    578 int count = 0;
    +
    579 const char *mtab = _PATH_MOUNTED;
    +
    580 FILE *fp = setmntent(mtab, "r");
    +
    581 if (fp == NULL) {
    +
    582 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
    +
    583 strerror(errno));
    +
    584 return -1;
    +
    585 }
    +
    586 while ((entp = GETMNTENT(fp)) != NULL) {
    +
    587 if (strcmp(entp->mnt_type, "fuse") == 0 ||
    +
    588 strncmp(entp->mnt_type, "fuse.", 5) == 0)
    +
    589 count ++;
    +
    590 }
    +
    591 endmntent(fp);
    +
    592 return count;
    +
    593}
    +
    594
    +
    595#ifdef HAVE_LISTMOUNT
    +
    596static int count_fuse_fs_ls_mnt(void)
    +
    597{
    +
    598 #define SMBUF_SIZE 1024
    +
    599 #define MNT_ID_LEN 128
    +
    600
    +
    601 int fuse_count = 0;
    +
    602 int n_mounts = 0;
    +
    603 int ret = 0;
    +
    604 uint64_t mnt_ids[MNT_ID_LEN];
    +
    605 unsigned char smbuf[SMBUF_SIZE];
    +
    606 struct mnt_id_req req = {
    +
    607 .size = sizeof(struct mnt_id_req),
    +
    608 };
    +
    609 struct statmount *sm;
    +
    610
    +
    611 for (;;) {
    +
    612 req.mnt_id = LSMT_ROOT;
    +
    613
    +
    614 n_mounts = syscall(SYS_listmount, &req, &mnt_ids, MNT_ID_LEN, 0);
    +
    615 if (n_mounts == -1) {
    +
    616 if (errno != ENOSYS) {
    +
    617 fprintf(stderr, "%s: failed to list mounts: %s\n", progname,
    +
    618 strerror(errno));
    +
    619 }
    +
    620 return -1;
    +
    621 }
    +
    622
    +
    623 for (int i = 0; i < n_mounts; i++) {
    +
    624 req.mnt_id = mnt_ids[i];
    +
    625 req.param = STATMOUNT_FS_TYPE;
    +
    626 ret = syscall(SYS_statmount, &req, &smbuf, SMBUF_SIZE, 0);
    +
    627 if (ret) {
    +
    628 if (errno == ENOENT)
    +
    629 continue;
    +
    630
    +
    631 fprintf(stderr, "%s: failed to stat mount %lld: %s\n", progname,
    +
    632 req.mnt_id, strerror(errno));
    +
    633 return -1;
    +
    634 }
    +
    635
    +
    636 sm = (struct statmount *)smbuf;
    +
    637 if (sm->mask & STATMOUNT_FS_TYPE &&
    +
    638 strcmp(&sm->str[sm->fs_type], "fuse") == 0)
    +
    639 fuse_count++;
    +
    640 }
    +
    641
    +
    642 if (n_mounts < MNT_ID_LEN)
    +
    643 break;
    +
    644 req.param = mnt_ids[MNT_ID_LEN - 1];
    +
    645 }
    +
    646 return fuse_count;
    +
    647}
    +
    648
    +
    649static int count_fuse_fs(void)
    +
    650{
    +
    651 int count = count_fuse_fs_ls_mnt();
    +
    652
    +
    653 return count >= 0 ? count : count_fuse_fs_mtab();
    +
    654}
    +
    655#else
    +
    656static int count_fuse_fs(void)
    +
    657{
    +
    658 return count_fuse_fs_mtab();
    +
    659}
    +
    660#endif
    +
    661
    +
    662#else /* IGNORE_MTAB */
    +
    663static int count_fuse_fs(void)
    +
    664{
    +
    665 return 0;
    +
    666}
    +
    667
    +
    668static int add_mount(const char *source, const char *mnt, const char *type,
    +
    669 const char *opts)
    +
    670{
    +
    671 (void) source;
    +
    672 (void) mnt;
    +
    673 (void) type;
    +
    674 (void) opts;
    +
    675 return 0;
    +
    676}
    +
    677
    +
    678static int unmount_fuse(const char *mnt, int quiet, int lazy)
    +
    679{
    +
    680 (void) quiet;
    +
    681 return fuse_mnt_umount(progname, mnt, mnt, lazy);
    +
    682}
    +
    683#endif /* IGNORE_MTAB */
    +
    684
    +
    685static void strip_line(char *line)
    +
    686{
    +
    687 char *s = strchr(line, '#');
    +
    688 if (s != NULL)
    +
    689 s[0] = '\0';
    +
    690 for (s = line + strlen(line) - 1;
    +
    691 s >= line && isspace((unsigned char) *s); s--);
    +
    692 s[1] = '\0';
    +
    693 for (s = line; isspace((unsigned char) *s); s++);
    +
    694 if (s != line)
    +
    695 memmove(line, s, strlen(s)+1);
    +
    696}
    +
    697
    +
    698static void parse_line(char *line, int linenum)
    +
    699{
    +
    700 int tmp;
    +
    701 if (strcmp(line, "user_allow_other") == 0)
    +
    702 user_allow_other = 1;
    +
    703 else if (sscanf(line, "mount_max = %i", &tmp) == 1)
    +
    704 mount_max = tmp;
    +
    705 else if(line[0])
    +
    706 fprintf(stderr,
    +
    707 "%s: unknown parameter in %s at line %i: '%s'\n",
    +
    708 progname, FUSE_CONF, linenum, line);
    +
    709}
    +
    710
    +
    711static void read_conf(void)
    +
    712{
    +
    713 FILE *fp = fopen(FUSE_CONF, "r");
    +
    714 if (fp != NULL) {
    +
    715 int linenum = 1;
    +
    716 char line[256];
    +
    717 int isnewline = 1;
    +
    718 while (fgets(line, sizeof(line), fp) != NULL) {
    +
    719 if (isnewline) {
    +
    720 if (line[strlen(line)-1] == '\n') {
    +
    721 strip_line(line);
    +
    722 parse_line(line, linenum);
    +
    723 } else {
    +
    724 isnewline = 0;
    +
    725 }
    +
    726 } else if(line[strlen(line)-1] == '\n') {
    +
    727 fprintf(stderr, "%s: reading %s: line %i too long\n", progname, FUSE_CONF, linenum);
    +
    728
    +
    729 isnewline = 1;
    +
    730 }
    +
    731 if (isnewline)
    +
    732 linenum ++;
    +
    733 }
    +
    734 if (!isnewline) {
    +
    735 fprintf(stderr, "%s: reading %s: missing newline at end of file\n", progname, FUSE_CONF);
    +
    736
    +
    737 }
    +
    738 if (ferror(fp)) {
    +
    739 fprintf(stderr, "%s: reading %s: read failed\n", progname, FUSE_CONF);
    +
    740 exit(1);
    +
    741 }
    +
    742 fclose(fp);
    +
    743 } else if (errno != ENOENT) {
    +
    744 bool fatal = (errno != EACCES && errno != ELOOP &&
    +
    745 errno != ENAMETOOLONG && errno != ENOTDIR &&
    +
    746 errno != EOVERFLOW);
    +
    747 fprintf(stderr, "%s: failed to open %s: %s\n",
    +
    748 progname, FUSE_CONF, strerror(errno));
    +
    749 if (fatal)
    +
    750 exit(1);
    +
    751 }
    +
    752}
    +
    753
    +
    754static int begins_with(const char *s, const char *beg)
    +
    755{
    +
    756 if (strncmp(s, beg, strlen(beg)) == 0)
    +
    757 return 1;
    +
    758 else
    +
    759 return 0;
    +
    760}
    +
    761
    +
    762struct mount_flags {
    +
    763 const char *opt;
    +
    764 unsigned long flag;
    +
    765 int on;
    +
    766 int safe;
    +
    767};
    +
    768
    +
    769static struct mount_flags mount_flags[] = {
    +
    770 {"rw", MS_RDONLY, 0, 1},
    +
    771 {"ro", MS_RDONLY, 1, 1},
    +
    772 {"suid", MS_NOSUID, 0, 0},
    +
    773 {"nosuid", MS_NOSUID, 1, 1},
    +
    774 {"dev", MS_NODEV, 0, 0},
    +
    775 {"nodev", MS_NODEV, 1, 1},
    +
    776 {"exec", MS_NOEXEC, 0, 1},
    +
    777 {"noexec", MS_NOEXEC, 1, 1},
    +
    778 {"async", MS_SYNCHRONOUS, 0, 1},
    +
    779 {"sync", MS_SYNCHRONOUS, 1, 1},
    +
    780 {"atime", MS_NOATIME, 0, 1},
    +
    781 {"noatime", MS_NOATIME, 1, 1},
    +
    782 {"diratime", MS_NODIRATIME, 0, 1},
    +
    783 {"nodiratime", MS_NODIRATIME, 1, 1},
    +
    784 {"lazytime", MS_LAZYTIME, 1, 1},
    +
    785 {"nolazytime", MS_LAZYTIME, 0, 1},
    +
    786 {"relatime", MS_RELATIME, 1, 1},
    +
    787 {"norelatime", MS_RELATIME, 0, 1},
    +
    788 {"strictatime", MS_STRICTATIME, 1, 1},
    +
    789 {"nostrictatime", MS_STRICTATIME, 0, 1},
    +
    790 {"dirsync", MS_DIRSYNC, 1, 1},
    +
    791 {"symfollow", MS_NOSYMFOLLOW, 0, 1},
    +
    792 {"nosymfollow", MS_NOSYMFOLLOW, 1, 1},
    +
    793 {NULL, 0, 0, 0}
    +
    794};
    +
    795
    +
    796static int find_mount_flag(const char *s, unsigned len, int *on, int *flag)
    +
    797{
    +
    798 int i;
    +
    799
    +
    800 for (i = 0; mount_flags[i].opt != NULL; i++) {
    +
    801 const char *opt = mount_flags[i].opt;
    +
    802 if (strlen(opt) == len && strncmp(opt, s, len) == 0) {
    +
    803 *on = mount_flags[i].on;
    +
    804 *flag = mount_flags[i].flag;
    +
    805 if (!mount_flags[i].safe && getuid() != 0) {
    +
    806 *flag = 0;
    +
    807 fprintf(stderr,
    +
    808 "%s: unsafe option %s ignored\n",
    +
    809 progname, opt);
    +
    810 }
    +
    811 return 1;
    +
    812 }
    +
    813 }
    +
    814 return 0;
    +
    815}
    +
    816
    +
    817static int add_option(char **optsp, const char *opt, unsigned expand)
    +
    818{
    +
    819 char *newopts;
    +
    820 if (*optsp == NULL)
    +
    821 newopts = strdup(opt);
    +
    822 else {
    +
    823 unsigned oldsize = strlen(*optsp);
    +
    824 unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1;
    +
    825 newopts = (char *) realloc(*optsp, newsize);
    +
    826 if (newopts)
    +
    827 sprintf(newopts + oldsize, ",%s", opt);
    +
    828 }
    +
    829 if (newopts == NULL) {
    +
    830 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    831 return -1;
    +
    832 }
    +
    833 *optsp = newopts;
    +
    834 return 0;
    +
    835}
    +
    836
    +
    837static int get_mnt_opts(int flags, char *opts, char **mnt_optsp)
    +
    838{
    +
    839 int i;
    +
    840 int l;
    +
    841
    +
    842 if (!(flags & MS_RDONLY) && add_option(mnt_optsp, "rw", 0) == -1)
    +
    843 return -1;
    +
    844
    +
    845 for (i = 0; mount_flags[i].opt != NULL; i++) {
    +
    846 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
    +
    847 add_option(mnt_optsp, mount_flags[i].opt, 0) == -1)
    +
    848 return -1;
    +
    849 }
    +
    850
    +
    851 if (add_option(mnt_optsp, opts, 0) == -1)
    +
    852 return -1;
    +
    853 /* remove comma from end of opts*/
    +
    854 l = strlen(*mnt_optsp);
    +
    855 if ((*mnt_optsp)[l-1] == ',')
    +
    856 (*mnt_optsp)[l-1] = '\0';
    +
    857 if (getuid() != 0) {
    +
    858 const char *user = get_user_name();
    +
    859 if (user == NULL)
    +
    860 return -1;
    +
    861
    +
    862 if (add_option(mnt_optsp, "user=", strlen(user)) == -1)
    +
    863 return -1;
    +
    864 strcat(*mnt_optsp, user);
    +
    865 }
    +
    866 return 0;
    +
    867}
    +
    868
    +
    869static int opt_eq(const char *s, unsigned len, const char *opt)
    +
    870{
    +
    871 if(strlen(opt) == len && strncmp(s, opt, len) == 0)
    +
    872 return 1;
    +
    873 else
    +
    874 return 0;
    +
    875}
    +
    876
    +
    877static int get_string_opt(const char *s, unsigned len, const char *opt,
    +
    878 char **val)
    +
    879{
    +
    880 int i;
    +
    881 unsigned opt_len = strlen(opt);
    +
    882 char *d;
    +
    883
    +
    884 if (*val)
    +
    885 free(*val);
    +
    886 *val = (char *) malloc(len - opt_len + 1);
    +
    887 if (!*val) {
    +
    888 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    889 return 0;
    +
    890 }
    +
    891
    +
    892 d = *val;
    +
    893 s += opt_len;
    +
    894 len -= opt_len;
    +
    895 for (i = 0; i < len; i++) {
    +
    896 if (s[i] == '\\' && i + 1 < len)
    +
    897 i++;
    +
    898 *d++ = s[i];
    +
    899 }
    +
    900 *d = '\0';
    +
    901 return 1;
    +
    902}
    +
    903
    +
    904/* The kernel silently truncates the "data" argument to PAGE_SIZE-1 characters.
    +
    905 * This can be dangerous if it e.g. truncates the option "group_id=1000" to
    +
    906 * "group_id=1".
    +
    907 * This wrapper detects this case and bails out with an error.
    +
    908 */
    +
    909static int mount_notrunc(const char *source, const char *target,
    +
    910 const char *filesystemtype, unsigned long mountflags,
    +
    911 const char *data) {
    +
    912 if (strlen(data) > sysconf(_SC_PAGESIZE) - 1) {
    +
    913 fprintf(stderr, "%s: mount options too long\n", progname);
    +
    914 errno = EINVAL;
    +
    915 return -1;
    +
    916 }
    +
    917 return mount(source, target, filesystemtype, mountflags, data);
    +
    918}
    +
    919
    +
    920
    +
    921static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
    +
    922 int fd, const char *opts, const char *dev, char **sourcep,
    +
    923 char **mnt_optsp)
    +
    924{
    +
    925 int res;
    +
    926 int flags = MS_NOSUID | MS_NODEV;
    +
    927 char *optbuf;
    +
    928 char *mnt_opts = NULL;
    +
    929 const char *s;
    +
    930 char *d;
    +
    931 char *fsname = NULL;
    +
    932 char *subtype = NULL;
    +
    933 char *source = NULL;
    +
    934 char *type = NULL;
    +
    935 int blkdev = 0;
    +
    936
    +
    937 optbuf = (char *) malloc(strlen(opts) + 128);
    +
    938 if (!optbuf) {
    +
    939 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    940 return -1;
    +
    941 }
    +
    942
    +
    943 for (s = opts, d = optbuf; *s;) {
    +
    944 unsigned len;
    +
    945 const char *fsname_str = "fsname=";
    +
    946 const char *subtype_str = "subtype=";
    +
    947 bool escape_ok = begins_with(s, fsname_str) ||
    +
    948 begins_with(s, subtype_str);
    +
    949 for (len = 0; s[len]; len++) {
    +
    950 if (escape_ok && s[len] == '\\' && s[len + 1])
    +
    951 len++;
    +
    952 else if (s[len] == ',')
    +
    953 break;
    +
    954 }
    +
    955 if (begins_with(s, fsname_str)) {
    +
    956 if (!get_string_opt(s, len, fsname_str, &fsname))
    +
    957 goto err;
    +
    958 } else if (begins_with(s, subtype_str)) {
    +
    959 if (!get_string_opt(s, len, subtype_str, &subtype))
    +
    960 goto err;
    +
    961 } else if (opt_eq(s, len, "blkdev")) {
    +
    962 if (getuid() != 0) {
    +
    963 fprintf(stderr,
    +
    964 "%s: option blkdev is privileged\n",
    +
    965 progname);
    +
    966 goto err;
    +
    967 }
    +
    968 blkdev = 1;
    +
    969 } else if (opt_eq(s, len, "auto_unmount")) {
    +
    970 auto_unmount = 1;
    +
    971 } else if (!opt_eq(s, len, "nonempty") &&
    +
    972 !begins_with(s, "fd=") &&
    +
    973 !begins_with(s, "rootmode=") &&
    +
    974 !begins_with(s, "user_id=") &&
    +
    975 !begins_with(s, "group_id=")) {
    +
    976 int on;
    +
    977 int flag;
    +
    978 int skip_option = 0;
    +
    979 if (opt_eq(s, len, "large_read")) {
    +
    980 struct utsname utsname;
    +
    981 unsigned kmaj, kmin;
    +
    982 res = uname(&utsname);
    +
    983 if (res == 0 &&
    +
    984 sscanf(utsname.release, "%u.%u",
    +
    985 &kmaj, &kmin) == 2 &&
    +
    986 (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
    +
    987 fprintf(stderr, "%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n", progname, kmaj, kmin);
    +
    988 skip_option = 1;
    +
    989 }
    +
    990 }
    +
    991 if (getuid() != 0 && !user_allow_other &&
    +
    992 (opt_eq(s, len, "allow_other") ||
    +
    993 opt_eq(s, len, "allow_root"))) {
    +
    994 fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n", progname, len, s, FUSE_CONF);
    +
    995 goto err;
    +
    996 }
    +
    997 if (!skip_option) {
    +
    998 if (find_mount_flag(s, len, &on, &flag)) {
    +
    999 if (on)
    +
    1000 flags |= flag;
    +
    1001 else
    +
    1002 flags &= ~flag;
    +
    1003 } else if (opt_eq(s, len, "default_permissions") ||
    +
    1004 opt_eq(s, len, "allow_other") ||
    +
    1005 begins_with(s, "max_read=") ||
    +
    1006 begins_with(s, "blksize=")) {
    +
    1007 memcpy(d, s, len);
    +
    1008 d += len;
    +
    1009 *d++ = ',';
    +
    1010 } else {
    +
    1011 fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, s);
    +
    1012 exit(1);
    +
    1013 }
    +
    1014 }
    +
    1015 }
    +
    1016 s += len;
    +
    1017 if (*s)
    +
    1018 s++;
    +
    1019 }
    +
    1020 *d = '\0';
    +
    1021 res = get_mnt_opts(flags, optbuf, &mnt_opts);
    +
    1022 if (res == -1)
    +
    1023 goto err;
    +
    1024
    +
    1025 sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
    +
    1026 fd, rootmode, getuid(), getgid());
    +
    1027
    +
    1028 source = malloc((fsname ? strlen(fsname) : 0) +
    +
    1029 (subtype ? strlen(subtype) : 0) + strlen(dev) + 32);
    +
    1030
    +
    1031 type = malloc((subtype ? strlen(subtype) : 0) + 32);
    +
    1032 if (!type || !source) {
    +
    1033 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    1034 goto err;
    +
    1035 }
    +
    1036
    +
    1037 if (subtype)
    +
    1038 sprintf(type, "%s.%s", blkdev ? "fuseblk" : "fuse", subtype);
    +
    1039 else
    +
    1040 strcpy(type, blkdev ? "fuseblk" : "fuse");
    +
    1041
    +
    1042 if (fsname)
    +
    1043 strcpy(source, fsname);
    +
    1044 else
    +
    1045 strcpy(source, subtype ? subtype : dev);
    +
    1046
    +
    1047 res = mount_notrunc(source, mnt, type, flags, optbuf);
    +
    1048 if (res == -1 && errno == ENODEV && subtype) {
    +
    1049 /* Probably missing subtype support */
    +
    1050 strcpy(type, blkdev ? "fuseblk" : "fuse");
    +
    1051 if (fsname) {
    +
    1052 if (!blkdev)
    +
    1053 sprintf(source, "%s#%s", subtype, fsname);
    +
    1054 } else {
    +
    1055 strcpy(source, type);
    +
    1056 }
    +
    1057
    +
    1058 res = mount_notrunc(source, mnt, type, flags, optbuf);
    +
    1059 }
    +
    1060 if (res == -1 && errno == EINVAL) {
    +
    1061 /* It could be an old version not supporting group_id */
    +
    1062 sprintf(d, "fd=%i,rootmode=%o,user_id=%u",
    +
    1063 fd, rootmode, getuid());
    +
    1064 res = mount_notrunc(source, mnt, type, flags, optbuf);
    +
    1065 }
    +
    1066 if (res == -1) {
    +
    1067 int errno_save = errno;
    +
    1068 if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
    +
    1069 fprintf(stderr, "%s: 'fuseblk' support missing\n",
    +
    1070 progname);
    +
    1071 else
    +
    1072 fprintf(stderr, "%s: mount failed: %s\n", progname,
    +
    1073 strerror(errno_save));
    +
    1074 goto err;
    +
    1075 }
    +
    1076 *sourcep = source;
    +
    1077 *typep = type;
    +
    1078 *mnt_optsp = mnt_opts;
    +
    1079 free(fsname);
    +
    1080 free(optbuf);
    +
    1081
    +
    1082 return 0;
    +
    1083
    +
    1084err:
    +
    1085 free(fsname);
    +
    1086 free(subtype);
    +
    1087 free(source);
    +
    1088 free(type);
    +
    1089 free(mnt_opts);
    +
    1090 free(optbuf);
    +
    1091 return -1;
    +
    1092}
    +
    1093
    +
    1094static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)
    +
    1095{
    +
    1096 int res;
    +
    1097 const char *mnt = *mntp;
    +
    1098 const char *origmnt = mnt;
    +
    1099 struct statfs fs_buf;
    +
    1100 size_t i;
    +
    1101
    +
    1102 res = lstat(mnt, stbuf);
    +
    1103 if (res == -1) {
    +
    1104 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
    +
    1105 progname, mnt, strerror(errno));
    +
    1106 return -1;
    +
    1107 }
    +
    1108
    +
    1109 /* No permission checking is done for root */
    +
    1110 if (getuid() == 0)
    +
    1111 return 0;
    +
    1112
    +
    1113 if (S_ISDIR(stbuf->st_mode)) {
    +
    1114 res = chdir(mnt);
    +
    1115 if (res == -1) {
    +
    1116 fprintf(stderr,
    +
    1117 "%s: failed to chdir to mountpoint: %s\n",
    +
    1118 progname, strerror(errno));
    +
    1119 return -1;
    +
    1120 }
    +
    1121 mnt = *mntp = ".";
    +
    1122 res = lstat(mnt, stbuf);
    +
    1123 if (res == -1) {
    +
    1124 fprintf(stderr,
    +
    1125 "%s: failed to access mountpoint %s: %s\n",
    +
    1126 progname, origmnt, strerror(errno));
    +
    1127 return -1;
    +
    1128 }
    +
    1129
    +
    1130 if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) {
    +
    1131 fprintf(stderr, "%s: mountpoint %s not owned by user\n",
    +
    1132 progname, origmnt);
    +
    1133 return -1;
    +
    1134 }
    +
    1135
    +
    1136 res = access(mnt, W_OK);
    +
    1137 if (res == -1) {
    +
    1138 fprintf(stderr, "%s: user has no write access to mountpoint %s\n",
    +
    1139 progname, origmnt);
    +
    1140 return -1;
    +
    1141 }
    +
    1142 } else if (S_ISREG(stbuf->st_mode)) {
    +
    1143 static char procfile[256];
    +
    1144 *mountpoint_fd = open(mnt, O_WRONLY);
    +
    1145 if (*mountpoint_fd == -1) {
    +
    1146 fprintf(stderr, "%s: failed to open %s: %s\n",
    +
    1147 progname, mnt, strerror(errno));
    +
    1148 return -1;
    +
    1149 }
    +
    1150 res = fstat(*mountpoint_fd, stbuf);
    +
    1151 if (res == -1) {
    +
    1152 fprintf(stderr,
    +
    1153 "%s: failed to access mountpoint %s: %s\n",
    +
    1154 progname, mnt, strerror(errno));
    +
    1155 return -1;
    +
    1156 }
    +
    1157 if (!S_ISREG(stbuf->st_mode)) {
    +
    1158 fprintf(stderr,
    +
    1159 "%s: mountpoint %s is no longer a regular file\n",
    +
    1160 progname, mnt);
    +
    1161 return -1;
    +
    1162 }
    +
    1163
    +
    1164 sprintf(procfile, "/proc/self/fd/%i", *mountpoint_fd);
    +
    1165 *mntp = procfile;
    +
    1166 } else {
    +
    1167 fprintf(stderr,
    +
    1168 "%s: mountpoint %s is not a directory or a regular file\n",
    +
    1169 progname, mnt);
    +
    1170 return -1;
    +
    1171 }
    +
    1172
    +
    1173 /* Do not permit mounting over anything in procfs - it has a couple
    +
    1174 * places to which we have "write access" without being supposed to be
    +
    1175 * able to just put anything we want there.
    +
    1176 * Luckily, without allow_other, we can't get other users to actually
    +
    1177 * use any fake information we try to put there anyway.
    +
    1178 * Use a whitelist to be safe. */
    +
    1179 if (statfs(*mntp, &fs_buf)) {
    +
    1180 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
    +
    1181 progname, mnt, strerror(errno));
    +
    1182 return -1;
    +
    1183 }
    +
    1184
    +
    1185 /* Define permitted filesystems for the mount target. This was
    +
    1186 * originally the same list as used by the ecryptfs mount helper
    +
    1187 * (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225)
    +
    1188 * but got expanded as we found more filesystems that needed to be
    +
    1189 * overlaid. */
    +
    1190 typeof(fs_buf.f_type) f_type_whitelist[] = {
    +
    1191 0x61756673 /* AUFS_SUPER_MAGIC */,
    +
    1192 0x00000187 /* AUTOFS_SUPER_MAGIC */,
    +
    1193 0xCA451A4E /* BCACHEFS_STATFS_MAGIC */,
    +
    1194 0x9123683E /* BTRFS_SUPER_MAGIC */,
    +
    1195 0x00C36400 /* CEPH_SUPER_MAGIC */,
    +
    1196 0xFF534D42 /* CIFS_MAGIC_NUMBER */,
    +
    1197 0x0000F15F /* ECRYPTFS_SUPER_MAGIC */,
    +
    1198 0X2011BAB0 /* EXFAT_SUPER_MAGIC */,
    +
    1199 0x0000EF53 /* EXT[234]_SUPER_MAGIC */,
    +
    1200 0xF2F52010 /* F2FS_SUPER_MAGIC */,
    +
    1201 0x65735546 /* FUSE_SUPER_MAGIC */,
    +
    1202 0x01161970 /* GFS2_MAGIC */,
    +
    1203 0x47504653 /* GPFS_SUPER_MAGIC */,
    +
    1204 0x0000482b /* HFSPLUS_SUPER_MAGIC */,
    +
    1205 0x000072B6 /* JFFS2_SUPER_MAGIC */,
    +
    1206 0x3153464A /* JFS_SUPER_MAGIC */,
    +
    1207 0x0BD00BD0 /* LL_SUPER_MAGIC */,
    +
    1208 0X00004D44 /* MSDOS_SUPER_MAGIC */,
    +
    1209 0x0000564C /* NCP_SUPER_MAGIC */,
    +
    1210 0x00006969 /* NFS_SUPER_MAGIC */,
    +
    1211 0x00003434 /* NILFS_SUPER_MAGIC */,
    +
    1212 0x5346544E /* NTFS_SB_MAGIC */,
    +
    1213 0x7366746E /* NTFS3_SUPER_MAGIC */,
    +
    1214 0x5346414f /* OPENAFS_SUPER_MAGIC */,
    +
    1215 0x794C7630 /* OVERLAYFS_SUPER_MAGIC */,
    +
    1216 0xAAD7AAEA /* PANFS_SUPER_MAGIC */,
    +
    1217 0x52654973 /* REISERFS_SUPER_MAGIC */,
    +
    1218 0xFE534D42 /* SMB2_SUPER_MAGIC */,
    +
    1219 0x73717368 /* SQUASHFS_MAGIC */,
    +
    1220 0x01021994 /* TMPFS_MAGIC */,
    +
    1221 0x24051905 /* UBIFS_SUPER_MAGIC */,
    +
    1222 0x18031977 /* WEKAFS_SUPER_MAGIC */,
    +
    1223#if __SIZEOF_LONG__ > 4
    +
    1224 0x736675005346544e /* UFSD */,
    +
    1225#endif
    +
    1226 0x58465342 /* XFS_SB_MAGIC */,
    +
    1227 0x2FC12FC1 /* ZFS_SUPER_MAGIC */,
    +
    1228 0x858458f6 /* RAMFS_MAGIC */,
    +
    1229 };
    +
    1230 for (i = 0; i < sizeof(f_type_whitelist)/sizeof(f_type_whitelist[0]); i++) {
    +
    1231 if (f_type_whitelist[i] == fs_buf.f_type)
    +
    1232 return 0;
    +
    1233 }
    +
    1234
    +
    1235 fprintf(stderr, "%s: mounting over filesystem type %#010lx is forbidden\n",
    +
    1236 progname, (unsigned long)fs_buf.f_type);
    +
    1237 return -1;
    +
    1238}
    +
    1239
    +
    1240static int open_fuse_device(const char *dev)
    +
    1241{
    +
    1242 int fd;
    +
    1243
    +
    1244 drop_privs();
    +
    1245 fd = open(dev, O_RDWR);
    +
    1246 if (fd == -1) {
    +
    1247 if (errno == ENODEV || errno == ENOENT)/* check for ENOENT too, for the udev case */
    +
    1248 fprintf(stderr,
    +
    1249 "%s: fuse device %s not found. Kernel module not loaded?\n",
    +
    1250 progname, dev);
    +
    1251 else
    +
    1252 fprintf(stderr,
    +
    1253 "%s: failed to open %s: %s\n", progname, dev, strerror(errno));
    +
    1254 }
    +
    1255 restore_privs();
    +
    1256 return fd;
    +
    1257}
    +
    1258
    +
    1259static int mount_fuse(const char *mnt, const char *opts, const char **type)
    +
    1260{
    +
    1261 int res;
    +
    1262 int fd;
    +
    1263 const char *dev = getenv(FUSE_KERN_DEVICE_ENV) ?: FUSE_DEV;
    +
    1264 struct stat stbuf;
    +
    1265 char *source = NULL;
    +
    1266 char *mnt_opts = NULL;
    +
    1267 const char *real_mnt = mnt;
    +
    1268 int mountpoint_fd = -1;
    +
    1269 char *do_mount_opts = NULL;
    +
    1270 char *x_opts = NULL;
    +
    1271
    +
    1272 fd = open_fuse_device(dev);
    +
    1273 if (fd == -1)
    +
    1274 return -1;
    +
    1275
    +
    1276 drop_privs();
    +
    1277 read_conf();
    +
    1278
    +
    1279 if (getuid() != 0 && mount_max != -1) {
    +
    1280 int mount_count = count_fuse_fs();
    +
    1281 if (mount_count >= mount_max) {
    +
    1282 fprintf(stderr, "%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n", progname, FUSE_CONF);
    +
    1283 goto fail_close_fd;
    +
    1284 }
    +
    1285 }
    +
    1286
    +
    1287 // Extract any options starting with "x-"
    +
    1288 res= extract_x_options(opts, &do_mount_opts, &x_opts);
    +
    1289 if (res)
    +
    1290 goto fail_close_fd;
    +
    1291
    +
    1292 res = check_perm(&real_mnt, &stbuf, &mountpoint_fd);
    +
    1293 restore_privs();
    +
    1294 if (res != -1)
    +
    1295 res = do_mount(real_mnt, type, stbuf.st_mode & S_IFMT,
    +
    1296 fd, do_mount_opts, dev, &source, &mnt_opts);
    +
    1297
    +
    1298 if (mountpoint_fd != -1)
    +
    1299 close(mountpoint_fd);
    +
    1300
    +
    1301 if (res == -1)
    +
    1302 goto fail_close_fd;
    +
    1303
    +
    1304 res = chdir("/");
    +
    1305 if (res == -1) {
    +
    1306 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
    +
    1307 goto fail_close_fd;
    +
    1308 }
    +
    1309
    +
    1310 if (geteuid() == 0) {
    +
    1311 if (x_opts && strlen(x_opts) > 0) {
    +
    1312 /*
    +
    1313 * Add back the options starting with "x-" to opts from
    +
    1314 * do_mount. +2 for ',' and '\0'
    +
    1315 */
    +
    1316 size_t mnt_opts_len = strlen(mnt_opts);
    +
    1317 size_t x_mnt_opts_len = mnt_opts_len+
    +
    1318 strlen(x_opts) + 2;
    +
    1319 char *x_mnt_opts = calloc(1, x_mnt_opts_len);
    +
    1320
    +
    1321 if (mnt_opts_len) {
    +
    1322 strcpy(x_mnt_opts, mnt_opts);
    +
    1323 strncat(x_mnt_opts, ",", 2);
    +
    1324 }
    +
    1325
    +
    1326 strncat(x_mnt_opts, x_opts,
    +
    1327 x_mnt_opts_len - mnt_opts_len - 2);
    +
    1328
    +
    1329 free(mnt_opts);
    +
    1330 mnt_opts = x_mnt_opts;
    +
    1331 }
    +
    1332
    +
    1333 res = add_mount(source, mnt, *type, mnt_opts);
    +
    1334 if (res == -1) {
    +
    1335 /* Can't clean up mount in a non-racy way */
    +
    1336 goto fail_close_fd;
    +
    1337 }
    +
    1338 }
    +
    1339
    +
    1340out_free:
    +
    1341 free(source);
    +
    1342 free(mnt_opts);
    +
    1343 free(x_opts);
    +
    1344 free(do_mount_opts);
    +
    1345
    +
    1346 return fd;
    +
    1347
    +
    1348fail_close_fd:
    +
    1349 close(fd);
    +
    1350 fd = -1;
    +
    1351 goto out_free;
    +
    1352}
    +
    1353
    +
    1354static int send_fd(int sock_fd, int fd)
    +
    1355{
    +
    1356 int retval;
    +
    1357 struct msghdr msg;
    +
    1358 struct cmsghdr *p_cmsg;
    +
    1359 struct iovec vec;
    +
    1360 size_t cmsgbuf[CMSG_SPACE(sizeof(fd)) / sizeof(size_t)];
    +
    1361 int *p_fds;
    +
    1362 char sendchar = 0;
    +
    1363
    +
    1364 msg.msg_control = cmsgbuf;
    +
    1365 msg.msg_controllen = sizeof(cmsgbuf);
    +
    1366 p_cmsg = CMSG_FIRSTHDR(&msg);
    +
    1367 p_cmsg->cmsg_level = SOL_SOCKET;
    +
    1368 p_cmsg->cmsg_type = SCM_RIGHTS;
    +
    1369 p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
    +
    1370 p_fds = (int *) CMSG_DATA(p_cmsg);
    +
    1371 *p_fds = fd;
    +
    1372 msg.msg_controllen = p_cmsg->cmsg_len;
    +
    1373 msg.msg_name = NULL;
    +
    1374 msg.msg_namelen = 0;
    +
    1375 msg.msg_iov = &vec;
    +
    1376 msg.msg_iovlen = 1;
    +
    1377 msg.msg_flags = 0;
    +
    1378 /* "To pass file descriptors or credentials you need to send/read at
    +
    1379 * least one byte" (man 7 unix) */
    +
    1380 vec.iov_base = &sendchar;
    +
    1381 vec.iov_len = sizeof(sendchar);
    +
    1382 while ((retval = sendmsg(sock_fd, &msg, 0)) == -1 && errno == EINTR);
    +
    1383 if (retval != 1) {
    +
    1384 perror("sending file descriptor");
    +
    1385 return -1;
    +
    1386 }
    +
    1387 return 0;
    +
    1388}
    +
    1389
    +
    1390/* Helper for should_auto_unmount
    +
    1391 *
    +
    1392 * fusermount typically has the s-bit set - initial open of `mnt` was as root
    +
    1393 * and got EACCESS as 'allow_other' was not specified.
    +
    1394 * Try opening `mnt` again with uid and guid of the calling process.
    +
    1395 */
    +
    1396static int recheck_ENOTCONN_as_owner(const char *mnt)
    +
    1397{
    +
    1398 int pid = fork();
    +
    1399 if(pid == -1) {
    +
    1400 perror("fuse: recheck_ENOTCONN_as_owner can't fork");
    +
    1401 _exit(EXIT_FAILURE);
    +
    1402 } else if(pid == 0) {
    +
    1403 uid_t uid = getuid();
    +
    1404 gid_t gid = getgid();
    +
    1405 if(setresgid(gid, gid, gid) == -1) {
    +
    1406 perror("fuse: can't set resgid");
    +
    1407 _exit(EXIT_FAILURE);
    +
    1408 }
    +
    1409 if(setresuid(uid, uid, uid) == -1) {
    +
    1410 perror("fuse: can't set resuid");
    +
    1411 _exit(EXIT_FAILURE);
    +
    1412 }
    +
    1413
    +
    1414 int fd = open(mnt, O_RDONLY);
    +
    1415 if(fd == -1 && errno == ENOTCONN)
    +
    1416 _exit(EXIT_SUCCESS);
    +
    1417 else
    +
    1418 _exit(EXIT_FAILURE);
    +
    1419 } else {
    +
    1420 int status;
    +
    1421 int res = waitpid(pid, &status, 0);
    +
    1422 if (res == -1) {
    +
    1423 perror("fuse: waiting for child failed");
    +
    1424 _exit(EXIT_FAILURE);
    +
    1425 }
    +
    1426 return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS;
    +
    1427 }
    +
    1428}
    +
    1429
    +
    1430/* The parent fuse process has died: decide whether to auto_unmount.
    +
    1431 *
    +
    1432 * In the normal case (umount or fusermount -u), the filesystem
    +
    1433 * has already been unmounted. If we simply unmount again we can
    +
    1434 * cause problems with stacked mounts (e.g. autofs).
    +
    1435 *
    +
    1436 * So we unmount here only in abnormal case where fuse process has
    +
    1437 * died without unmount happening. To detect this, we first look in
    +
    1438 * the mount table to make sure the mountpoint is still mounted and
    +
    1439 * has proper type. If so, we then see if opening the mount dir is
    +
    1440 * returning 'Transport endpoint is not connected'.
    +
    1441 *
    +
    1442 * The order of these is important, because if autofs is in use,
    +
    1443 * opening the dir to check for ENOTCONN will cause a new mount
    +
    1444 * in the normal case where filesystem has been unmounted cleanly.
    +
    1445 */
    +
    1446static int should_auto_unmount(const char *mnt, const char *type)
    +
    1447{
    +
    1448 char *copy;
    +
    1449 const char *last;
    +
    1450 int result = 0;
    +
    1451 int fd;
    +
    1452
    +
    1453 copy = strdup(mnt);
    +
    1454 if (copy == NULL) {
    +
    1455 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    1456 return 0;
    +
    1457 }
    +
    1458
    +
    1459 if (chdir_to_parent(copy, &last) == -1)
    +
    1460 goto out;
    +
    1461 if (check_is_mount(last, mnt, type) == -1)
    +
    1462 goto out;
    +
    1463
    +
    1464 fd = open(mnt, O_RDONLY);
    +
    1465
    +
    1466 if (fd != -1) {
    +
    1467 close(fd);
    +
    1468 } else {
    +
    1469 switch(errno) {
    +
    1470 case ENOTCONN:
    +
    1471 result = 1;
    +
    1472 break;
    +
    1473 case EACCES:
    +
    1474 result = recheck_ENOTCONN_as_owner(mnt);
    +
    1475 break;
    +
    1476 default:
    +
    1477 result = 0;
    +
    1478 break;
    +
    1479 }
    +
    1480 }
    +
    1481out:
    +
    1482 free(copy);
    +
    1483 return result;
    +
    1484}
    +
    1485
    +
    1486static void usage(void)
    +
    1487{
    +
    1488 printf("%s: [options] mountpoint\n"
    +
    1489 "Options:\n"
    +
    1490 " -h print help\n"
    +
    1491 " -V print version\n"
    +
    1492 " -o opt[,opt...] mount options\n"
    +
    1493 " -u unmount\n"
    +
    1494 " -q quiet\n"
    +
    1495 " -z lazy unmount\n",
    +
    1496 progname);
    +
    1497 exit(1);
    +
    1498}
    +
    1499
    +
    1500static void show_version(void)
    +
    1501{
    +
    1502 printf("fusermount3 version: %s\n", PACKAGE_VERSION);
    +
    1503 exit(0);
    +
    1504}
    +
    1505
    +
    1506static void close_range_loop(int min_fd, int max_fd, int cfd)
    +
    1507{
    +
    1508 for (int fd = min_fd; fd <= max_fd; fd++)
    +
    1509 if (fd != cfd)
    +
    1510 close(fd);
    +
    1511}
    +
    1512
    +
    1513/*
    +
    1514 * Close all inherited fds that are not needed
    +
    1515 * Ideally these wouldn't come up at all, applications should better
    +
    1516 * use FD_CLOEXEC / O_CLOEXEC
    +
    1517 */
    +
    1518static int close_inherited_fds(int cfd)
    +
    1519{
    +
    1520 int rc = -1;
    +
    1521 int nullfd;
    +
    1522
    +
    1523 /* We can't even report an error */
    +
    1524 if (cfd <= STDERR_FILENO)
    +
    1525 return -EINVAL;
    +
    1526
    +
    1527#ifdef HAVE_CLOSE_RANGE
    +
    1528 if (cfd < STDERR_FILENO + 2) {
    +
    1529 close_range_loop(STDERR_FILENO + 1, cfd - 1, cfd);
    +
    1530 } else {
    +
    1531 rc = close_range(STDERR_FILENO + 1, cfd - 1, 0);
    +
    1532 if (rc < 0)
    +
    1533 goto fallback;
    +
    1534 }
    +
    1535
    +
    1536 /* Close high range */
    +
    1537 rc = close_range(cfd + 1, ~0U, 0);
    +
    1538#else
    +
    1539 goto fallback; /* make use of fallback to avoid compiler warnings */
    +
    1540#endif
    +
    1541
    +
    1542fallback:
    +
    1543 if (rc < 0) {
    +
    1544 int max_fd = sysconf(_SC_OPEN_MAX) - 1;
    +
    1545
    +
    1546 close_range_loop(STDERR_FILENO + 1, max_fd, cfd);
    +
    1547 }
    +
    1548
    +
    1549 nullfd = open("/dev/null", O_RDWR);
    +
    1550 if (nullfd < 0) {
    +
    1551 perror("fusermount: cannot open /dev/null");
    +
    1552 return -errno;
    +
    1553 }
    +
    1554
    +
    1555 /* Redirect stdin, stdout, stderr to /dev/null */
    +
    1556 dup2(nullfd, STDIN_FILENO);
    +
    1557 dup2(nullfd, STDOUT_FILENO);
    +
    1558 dup2(nullfd, STDERR_FILENO);
    +
    1559 if (nullfd > STDERR_FILENO)
    +
    1560 close(nullfd);
    +
    1561
    +
    1562 return 0;
    +
    1563}
    +
    1564
    +
    1565int main(int argc, char *argv[])
    +
    1566{
    +
    1567 sigset_t sigset;
    +
    1568 int ch;
    +
    1569 int fd;
    +
    1570 int res;
    +
    1571 char *origmnt;
    +
    1572 char *mnt;
    +
    1573 static int unmount = 0;
    +
    1574 static int lazy = 0;
    +
    1575 static int quiet = 0;
    +
    1576 char *commfd = NULL;
    +
    1577 long cfd;
    +
    1578 const char *opts = "";
    +
    1579 const char *type = NULL;
    +
    1580 int setup_auto_unmount_only = 0;
    +
    1581
    +
    1582 static const struct option long_opts[] = {
    +
    1583 {"unmount", no_argument, NULL, 'u'},
    +
    1584 {"lazy", no_argument, NULL, 'z'},
    +
    1585 {"quiet", no_argument, NULL, 'q'},
    +
    1586 {"help", no_argument, NULL, 'h'},
    +
    1587 {"version", no_argument, NULL, 'V'},
    +
    1588 {"options", required_argument, NULL, 'o'},
    +
    1589 // Note: auto-unmount and comm-fd don't have short versions.
    +
    1590 // They'ne meant for internal use by mount.c
    +
    1591 {"auto-unmount", no_argument, NULL, 'U'},
    +
    1592 {"comm-fd", required_argument, NULL, 'c'},
    +
    1593 {0, 0, 0, 0}};
    +
    1594
    +
    1595 progname = strdup(argc > 0 ? argv[0] : "fusermount");
    +
    1596 if (progname == NULL) {
    +
    1597 fprintf(stderr, "%s: failed to allocate memory\n", argv[0]);
    +
    1598 exit(1);
    +
    1599 }
    +
    1600
    +
    1601 while ((ch = getopt_long(argc, argv, "hVo:uzq", long_opts,
    +
    1602 NULL)) != -1) {
    +
    1603 switch (ch) {
    +
    1604 case 'h':
    +
    1605 usage();
    +
    1606 break;
    +
    1607
    +
    1608 case 'V':
    +
    1609 show_version();
    +
    1610 break;
    +
    1611
    +
    1612 case 'o':
    +
    1613 opts = optarg;
    +
    1614 break;
    +
    1615
    +
    1616 case 'u':
    +
    1617 unmount = 1;
    +
    1618 break;
    +
    1619 case 'U':
    +
    1620 unmount = 1;
    +
    1621 auto_unmount = 1;
    +
    1622 setup_auto_unmount_only = 1;
    +
    1623 break;
    +
    1624 case 'c':
    +
    1625 commfd = optarg;
    +
    1626 break;
    +
    1627 case 'z':
    +
    1628 lazy = 1;
    +
    1629 break;
    +
    1630
    +
    1631 case 'q':
    +
    1632 quiet = 1;
    +
    1633 break;
    +
    1634
    +
    1635 default:
    +
    1636 exit(1);
    +
    1637 }
    +
    1638 }
    +
    1639
    +
    1640 if (lazy && !unmount) {
    +
    1641 fprintf(stderr, "%s: -z can only be used with -u\n", progname);
    +
    1642 exit(1);
    +
    1643 }
    +
    1644
    +
    1645 if (optind >= argc) {
    +
    1646 fprintf(stderr, "%s: missing mountpoint argument\n", progname);
    +
    1647 exit(1);
    +
    1648 } else if (argc > optind + 1) {
    +
    1649 fprintf(stderr, "%s: extra arguments after the mountpoint\n",
    +
    1650 progname);
    +
    1651 exit(1);
    +
    1652 }
    +
    1653
    +
    1654 origmnt = argv[optind];
    +
    1655
    +
    1656 drop_privs();
    +
    1657 mnt = fuse_mnt_resolve_path(progname, origmnt);
    +
    1658 if (mnt != NULL) {
    +
    1659 res = chdir("/");
    +
    1660 if (res == -1) {
    +
    1661 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
    +
    1662 goto err_out;
    +
    1663 }
    +
    1664 }
    +
    1665 restore_privs();
    +
    1666 if (mnt == NULL)
    +
    1667 exit(1);
    +
    1668
    +
    1669 umask(033);
    +
    1670 if (!setup_auto_unmount_only && unmount)
    +
    1671 goto do_unmount;
    +
    1672
    +
    1673 if(commfd == NULL)
    +
    1674 commfd = getenv(FUSE_COMMFD_ENV);
    +
    1675 if (commfd == NULL) {
    +
    1676 fprintf(stderr, "%s: old style mounting not supported\n",
    +
    1677 progname);
    +
    1678 goto err_out;
    +
    1679 }
    +
    1680
    +
    1681 res = libfuse_strtol(commfd, &cfd);
    +
    1682 if (res) {
    +
    1683 fprintf(stderr,
    +
    1684 "%s: invalid _FUSE_COMMFD: %s\n",
    +
    1685 progname, commfd);
    +
    1686 goto err_out;
    +
    1687
    +
    1688 }
    +
    1689
    +
    1690 {
    +
    1691 struct stat statbuf;
    +
    1692 fstat(cfd, &statbuf);
    +
    1693 if(!S_ISSOCK(statbuf.st_mode)) {
    +
    1694 fprintf(stderr,
    +
    1695 "%s: file descriptor %li is not a socket, can't send fuse fd\n",
    +
    1696 progname, cfd);
    +
    1697 goto err_out;
    +
    1698 }
    +
    1699 }
    +
    1700
    +
    1701 if (setup_auto_unmount_only)
    +
    1702 goto wait_for_auto_unmount;
    +
    1703
    +
    1704 fd = mount_fuse(mnt, opts, &type);
    +
    1705 if (fd == -1)
    +
    1706 goto err_out;
    +
    1707
    +
    1708 res = send_fd(cfd, fd);
    +
    1709 if (res != 0) {
    +
    1710 umount2(mnt, MNT_DETACH); /* lazy umount */
    +
    1711 goto err_out;
    +
    1712 }
    +
    1713 close(fd);
    +
    1714
    +
    1715 if (!auto_unmount) {
    +
    1716 free(mnt);
    +
    1717 free((void*) type);
    +
    1718 return 0;
    +
    1719 }
    +
    1720
    +
    1721wait_for_auto_unmount:
    +
    1722 /* Become a daemon and wait for the parent to exit or die.
    +
    1723 ie For the control socket to get closed.
    +
    1724 Btw, we don't want to use daemon() function here because
    +
    1725 it forks and messes with the file descriptors. */
    +
    1726
    +
    1727 res = close_inherited_fds(cfd);
    +
    1728 if (res < 0)
    +
    1729 exit(EXIT_FAILURE);
    +
    1730
    +
    1731 setsid();
    +
    1732 res = chdir("/");
    +
    1733 if (res == -1) {
    +
    1734 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
    +
    1735 goto err_out;
    +
    1736 }
    +
    1737
    +
    1738 sigfillset(&sigset);
    +
    1739 sigprocmask(SIG_BLOCK, &sigset, NULL);
    +
    1740
    +
    1741 lazy = 1;
    +
    1742 quiet = 1;
    +
    1743
    +
    1744 while (1) {
    +
    1745 unsigned char buf[16];
    +
    1746 int n = recv(cfd, buf, sizeof(buf), 0);
    +
    1747 if (!n)
    +
    1748 break;
    +
    1749
    +
    1750 if (n < 0) {
    +
    1751 if (errno == EINTR)
    +
    1752 continue;
    +
    1753 break;
    +
    1754 }
    +
    1755 }
    +
    1756
    +
    1757 if (!should_auto_unmount(mnt, type)) {
    +
    1758 goto success_out;
    +
    1759 }
    +
    1760
    +
    1761do_unmount:
    +
    1762 if (geteuid() == 0)
    +
    1763 res = unmount_fuse(mnt, quiet, lazy);
    +
    1764 else {
    +
    1765 res = umount2(mnt, lazy ? UMOUNT_DETACH : 0);
    +
    1766 if (res == -1 && !quiet)
    +
    1767 fprintf(stderr,
    +
    1768 "%s: failed to unmount %s: %s\n",
    +
    1769 progname, mnt, strerror(errno));
    +
    1770 }
    +
    1771 if (res == -1)
    +
    1772 goto err_out;
    +
    1773
    +
    1774success_out:
    +
    1775 free((void*) type);
    +
    1776 free(mnt);
    +
    1777 return 0;
    +
    1778
    +
    1779err_out:
    +
    1780 free((void*) type);
    +
    1781 free(mnt);
    +
    1782 exit(1);
    +
    1783}
    +
    + + + + diff --git a/doc/html/util_2mount_8fuse_8c_source.html b/doc/html/util_2mount_8fuse_8c_source.html new file mode 100644 index 0000000..a932047 --- /dev/null +++ b/doc/html/util_2mount_8fuse_8c_source.html @@ -0,0 +1,515 @@ + + + + + + + +libfuse: util/mount.fuse.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    mount.fuse.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file GPL2.txt.
    +
    7*/
    +
    8
    +
    9#include "fuse_config.h"
    +
    10
    +
    11#include <stdio.h>
    +
    12#include <stdlib.h>
    +
    13#include <string.h>
    +
    14#include <unistd.h>
    +
    15#include <errno.h>
    +
    16#include <stdint.h>
    +
    17#include <fcntl.h>
    +
    18#include <pwd.h>
    +
    19#include <sys/wait.h>
    +
    20
    +
    21#ifdef linux
    +
    22#include <sys/prctl.h>
    +
    23#include <sys/syscall.h>
    +
    24#include <linux/capability.h>
    +
    25#include <linux/securebits.h>
    +
    26/* for 2.6 kernels */
    +
    27#if !defined(SECBIT_KEEP_CAPS) && defined(SECURE_KEEP_CAPS)
    +
    28#define SECBIT_KEEP_CAPS (issecure_mask(SECURE_KEEP_CAPS))
    +
    29#endif
    +
    30#if !defined(SECBIT_KEEP_CAPS_LOCKED) && defined(SECURE_KEEP_CAPS_LOCKED)
    +
    31#define SECBIT_KEEP_CAPS_LOCKED (issecure_mask(SECURE_KEEP_CAPS_LOCKED))
    +
    32#endif
    +
    33#if !defined(SECBIT_NO_SETUID_FIXUP) && defined(SECURE_NO_SETUID_FIXUP)
    +
    34#define SECBIT_NO_SETUID_FIXUP (issecure_mask(SECURE_NO_SETUID_FIXUP))
    +
    35#endif
    +
    36#if !defined(SECBIT_NO_SETUID_FIXUP_LOCKED) && defined(SECURE_NO_SETUID_FIXUP_LOCKED)
    +
    37#define SECBIT_NO_SETUID_FIXUP_LOCKED (issecure_mask(SECURE_NO_SETUID_FIXUP_LOCKED))
    +
    38#endif
    +
    39#if !defined(SECBIT_NOROOT) && defined(SECURE_NOROOT)
    +
    40#define SECBIT_NOROOT (issecure_mask(SECURE_NOROOT))
    +
    41#endif
    +
    42#if !defined(SECBIT_NOROOT_LOCKED) && defined(SECURE_NOROOT_LOCKED)
    +
    43#define SECBIT_NOROOT_LOCKED (issecure_mask(SECURE_NOROOT_LOCKED))
    +
    44#endif
    +
    45#endif
    +
    46/* linux < 3.5 */
    +
    47#ifndef PR_SET_NO_NEW_PRIVS
    +
    48#define PR_SET_NO_NEW_PRIVS 38
    +
    49#endif
    +
    50
    +
    51#include "fuse.h"
    +
    52
    +
    53static char *progname;
    +
    54
    +
    55static char *xstrdup(const char *s)
    +
    56{
    +
    57 char *t = strdup(s);
    +
    58 if (!t) {
    +
    59 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    60 exit(1);
    +
    61 }
    +
    62 return t;
    +
    63}
    +
    64
    +
    65static void *xrealloc(void *oldptr, size_t size)
    +
    66{
    +
    67 void *ptr = realloc(oldptr, size);
    +
    68 if (!ptr) {
    +
    69 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    70 exit(1);
    +
    71 }
    +
    72 return ptr;
    +
    73}
    +
    74
    +
    75static void add_arg(char **cmdp, const char *opt)
    +
    76{
    +
    77 size_t optlen = strlen(opt);
    +
    78 size_t cmdlen = *cmdp ? strlen(*cmdp) : 0;
    +
    79 if (optlen >= (SIZE_MAX - cmdlen - 4)/4) {
    +
    80 fprintf(stderr, "%s: argument too long\n", progname);
    +
    81 exit(1);
    +
    82 }
    +
    83 char *cmd = xrealloc(*cmdp, cmdlen + optlen * 4 + 4);
    +
    84 char *s;
    +
    85 s = cmd + cmdlen;
    +
    86 if (*cmdp)
    +
    87 *s++ = ' ';
    +
    88
    +
    89 *s++ = '\'';
    +
    90 for (; *opt; opt++) {
    +
    91 if (*opt == '\'') {
    +
    92 *s++ = '\'';
    +
    93 *s++ = '\\';
    +
    94 *s++ = '\'';
    +
    95 *s++ = '\'';
    +
    96 } else
    +
    97 *s++ = *opt;
    +
    98 }
    +
    99 *s++ = '\'';
    +
    100 *s = '\0';
    +
    101 *cmdp = cmd;
    +
    102}
    +
    103
    +
    104static char *add_option(const char *opt, char *options)
    +
    105{
    +
    106 int oldlen = options ? strlen(options) : 0;
    +
    107
    +
    108 options = xrealloc(options, oldlen + 1 + strlen(opt) + 1);
    +
    109 if (!oldlen)
    +
    110 strcpy(options, opt);
    +
    111 else {
    +
    112 strcat(options, ",");
    +
    113 strcat(options, opt);
    +
    114 }
    +
    115 return options;
    +
    116}
    +
    117
    +
    118static int prepare_fuse_fd(const char *mountpoint, const char* subtype,
    +
    119 const char *options)
    +
    120{
    +
    121 int fuse_fd = -1;
    +
    122 int flags = -1;
    +
    123 int subtype_len = strlen(subtype) + 9;
    +
    124 char* options_copy = xrealloc(NULL, subtype_len);
    +
    125
    +
    126 snprintf(options_copy, subtype_len, "subtype=%s", subtype);
    +
    127 options_copy = add_option(options, options_copy);
    +
    128 fuse_fd = fuse_open_channel(mountpoint, options_copy);
    +
    129 if (fuse_fd == -1) {
    +
    130 exit(1);
    +
    131 }
    +
    132
    +
    133 flags = fcntl(fuse_fd, F_GETFD);
    +
    134 if (flags == -1 || fcntl(fuse_fd, F_SETFD, flags & ~FD_CLOEXEC) == 1) {
    +
    135 fprintf(stderr, "%s: Failed to clear CLOEXEC: %s\n",
    +
    136 progname, strerror(errno));
    +
    137 exit(1);
    +
    138 }
    +
    139
    +
    140 return fuse_fd;
    +
    141}
    +
    142
    +
    143#ifdef linux
    +
    144static uint64_t get_capabilities(void)
    +
    145{
    +
    146 /*
    +
    147 * This invokes the capset syscall directly to avoid the libcap
    +
    148 * dependency, which isn't really justified just for this.
    +
    149 */
    +
    150 struct __user_cap_header_struct header = {
    +
    151 .version = _LINUX_CAPABILITY_VERSION_3,
    +
    152 .pid = 0,
    +
    153 };
    +
    154 struct __user_cap_data_struct data[2];
    +
    155 memset(data, 0, sizeof(data));
    +
    156 if (syscall(SYS_capget, &header, data) == -1) {
    +
    157 fprintf(stderr, "%s: Failed to get capabilities: %s\n",
    +
    158 progname, strerror(errno));
    +
    159 exit(1);
    +
    160 }
    +
    161
    +
    162 return data[0].effective | ((uint64_t) data[1].effective << 32);
    +
    163}
    +
    164
    +
    165static void set_capabilities(uint64_t caps)
    +
    166{
    +
    167 /*
    +
    168 * This invokes the capset syscall directly to avoid the libcap
    +
    169 * dependency, which isn't really justified just for this.
    +
    170 */
    +
    171 struct __user_cap_header_struct header = {
    +
    172 .version = _LINUX_CAPABILITY_VERSION_3,
    +
    173 .pid = 0,
    +
    174 };
    +
    175 struct __user_cap_data_struct data[2];
    +
    176 memset(data, 0, sizeof(data));
    +
    177 data[0].effective = data[0].permitted = caps;
    +
    178 data[1].effective = data[1].permitted = caps >> 32;
    +
    179 if (syscall(SYS_capset, &header, data) == -1) {
    +
    180 fprintf(stderr, "%s: Failed to set capabilities: %s\n",
    +
    181 progname, strerror(errno));
    +
    182 exit(1);
    +
    183 }
    +
    184}
    +
    185
    +
    186static void drop_and_lock_capabilities(void)
    +
    187{
    +
    188 /* Set and lock securebits. */
    +
    189 if (prctl(PR_SET_SECUREBITS,
    +
    190 SECBIT_KEEP_CAPS_LOCKED |
    +
    191 SECBIT_NO_SETUID_FIXUP |
    +
    192 SECBIT_NO_SETUID_FIXUP_LOCKED |
    +
    193 SECBIT_NOROOT |
    +
    194 SECBIT_NOROOT_LOCKED) == -1) {
    +
    195 fprintf(stderr, "%s: Failed to set securebits %s\n",
    +
    196 progname, strerror(errno));
    +
    197 exit(1);
    +
    198 }
    +
    199
    +
    200 /* Clear the capability bounding set. */
    +
    201 int cap;
    +
    202 for (cap = 0; ; cap++) {
    +
    203 int cap_status = prctl(PR_CAPBSET_READ, cap);
    +
    204 if (cap_status == 0) {
    +
    205 continue;
    +
    206 }
    +
    207 if (cap_status == -1 && errno == EINVAL) {
    +
    208 break;
    +
    209 }
    +
    210
    +
    211 if (cap_status != 1) {
    +
    212 fprintf(stderr,
    +
    213 "%s: Failed to get capability %u: %s\n",
    +
    214 progname, cap, strerror(errno));
    +
    215 exit(1);
    +
    216 }
    +
    217 if (prctl(PR_CAPBSET_DROP, cap) == -1) {
    +
    218 fprintf(stderr,
    +
    219 "%s: Failed to drop capability %u: %s\n",
    +
    220 progname, cap, strerror(errno));
    +
    221 }
    +
    222 }
    +
    223
    +
    224 /* Drop capabilities. */
    +
    225 set_capabilities(0);
    +
    226
    +
    227 /* Prevent re-acquisition of privileges. */
    +
    228 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
    +
    229 fprintf(stderr, "%s: Failed to set no_new_privs: %s\n",
    +
    230 progname, strerror(errno));
    +
    231 exit(1);
    +
    232 }
    +
    233}
    +
    234#endif
    +
    235
    +
    236int main(int argc, char *argv[])
    +
    237{
    +
    238 char *type = NULL;
    +
    239 char *source;
    +
    240 char *dup_source = NULL;
    +
    241 const char *mountpoint;
    +
    242 char *basename;
    +
    243 char *options = NULL;
    +
    244 char *command = NULL;
    +
    245 char *setuid_name = NULL;
    +
    246 int i;
    +
    247 int dev = 1;
    +
    248 int suid = 1;
    +
    249 int pass_fuse_fd = 0;
    +
    250 int fuse_fd = 0;
    +
    251 int drop_privileges = 0;
    +
    252 char *dev_fd_mountpoint = NULL;
    +
    253
    +
    254 progname = argv[0];
    +
    255 basename = strrchr(argv[0], '/');
    +
    256 if (basename)
    +
    257 basename++;
    +
    258 else
    +
    259 basename = argv[0];
    +
    260
    +
    261 if (strncmp(basename, "mount.fuse.", 11) == 0)
    +
    262 type = basename + 11;
    +
    263 if (strncmp(basename, "mount.fuseblk.", 14) == 0)
    +
    264 type = basename + 14;
    +
    265
    +
    266 if (type && !type[0])
    +
    267 type = NULL;
    +
    268
    +
    269 if (argc < 3) {
    +
    270 fprintf(stderr,
    +
    271 "usage: %s %s destination [-t type] [-o opt[,opts...]]\n",
    +
    272 progname, type ? "source" : "type#[source]");
    +
    273 exit(1);
    +
    274 }
    +
    275
    +
    276 source = argv[1];
    +
    277 if (!source[0])
    +
    278 source = NULL;
    +
    279
    +
    280 mountpoint = argv[2];
    +
    281
    +
    282 for (i = 3; i < argc; i++) {
    +
    283 if (strcmp(argv[i], "-v") == 0) {
    +
    284 continue;
    +
    285 } else if (strcmp(argv[i], "-t") == 0) {
    +
    286 i++;
    +
    287
    +
    288 if (i == argc) {
    +
    289 fprintf(stderr,
    +
    290 "%s: missing argument to option '-t'\n",
    +
    291 progname);
    +
    292 exit(1);
    +
    293 }
    +
    294 type = argv[i];
    +
    295 if (strncmp(type, "fuse.", 5) == 0)
    +
    296 type += 5;
    +
    297 else if (strncmp(type, "fuseblk.", 8) == 0)
    +
    298 type += 8;
    +
    299
    +
    300 if (!type[0]) {
    +
    301 fprintf(stderr,
    +
    302 "%s: empty type given as argument to option '-t'\n",
    +
    303 progname);
    +
    304 exit(1);
    +
    305 }
    +
    306 } else if (strcmp(argv[i], "-o") == 0) {
    +
    307 char *opts;
    +
    308 char *opt;
    +
    309 i++;
    +
    310 if (i == argc)
    +
    311 break;
    +
    312
    +
    313 opts = xstrdup(argv[i]);
    +
    314 opt = strtok(opts, ",");
    +
    315 while (opt) {
    +
    316 int j;
    +
    317 int ignore = 0;
    +
    318 const char *ignore_opts[] = { "",
    +
    319 "user",
    +
    320 "nofail",
    +
    321 "nouser",
    +
    322 "users",
    +
    323 "auto",
    +
    324 "noauto",
    +
    325 "_netdev",
    +
    326 NULL};
    +
    327 if (strncmp(opt, "setuid=", 7) == 0) {
    +
    328 setuid_name = xstrdup(opt + 7);
    +
    329 ignore = 1;
    +
    330 } else if (strcmp(opt,
    +
    331 "drop_privileges") == 0) {
    +
    332 pass_fuse_fd = 1;
    +
    333 drop_privileges = 1;
    +
    334 ignore = 1;
    +
    335 }
    +
    336 for (j = 0; ignore_opts[j]; j++)
    +
    337 if (strcmp(opt, ignore_opts[j]) == 0)
    +
    338 ignore = 1;
    +
    339
    +
    340 if (!ignore) {
    +
    341 if (strcmp(opt, "nodev") == 0)
    +
    342 dev = 0;
    +
    343 else if (strcmp(opt, "nosuid") == 0)
    +
    344 suid = 0;
    +
    345
    +
    346 options = add_option(opt, options);
    +
    347 }
    +
    348 opt = strtok(NULL, ",");
    +
    349 }
    +
    350 free(opts);
    +
    351 }
    +
    352 }
    +
    353
    +
    354 if (drop_privileges) {
    +
    355 uint64_t required_caps = CAP_TO_MASK(CAP_SETPCAP) |
    +
    356 CAP_TO_MASK(CAP_SYS_ADMIN);
    +
    357 if ((get_capabilities() & required_caps) != required_caps) {
    +
    358 fprintf(stderr, "%s: drop_privileges was requested, which launches the FUSE file system fully unprivileged. In order to do so %s must be run with privileges, please invoke with CAP_SYS_ADMIN and CAP_SETPCAP (e.g. as root).\n",
    +
    359 progname, progname);
    +
    360 exit(1);
    +
    361 }
    +
    362 }
    +
    363
    +
    364 if (dev)
    +
    365 options = add_option("dev", options);
    +
    366 if (suid)
    +
    367 options = add_option("suid", options);
    +
    368
    +
    369 if (!type) {
    +
    370 if (source) {
    +
    371 dup_source = xstrdup(source);
    +
    372 type = dup_source;
    +
    373 source = strchr(type, '#');
    +
    374 if (source)
    +
    375 *source++ = '\0';
    +
    376 if (!type[0]) {
    +
    377 fprintf(stderr, "%s: empty filesystem type\n",
    +
    378 progname);
    +
    379 exit(1);
    +
    380 }
    +
    381 } else {
    +
    382 fprintf(stderr, "%s: empty source\n", progname);
    +
    383 exit(1);
    +
    384 }
    +
    385 }
    +
    386
    +
    387 if (setuid_name && setuid_name[0]) {
    +
    388#ifdef linux
    +
    389 if (drop_privileges) {
    +
    390 /*
    +
    391 * Make securebits more permissive before calling
    +
    392 * setuid(). Specifically, if SECBIT_KEEP_CAPS and
    +
    393 * SECBIT_NO_SETUID_FIXUP weren't set, setuid() would
    +
    394 * have the side effect of dropping all capabilities,
    +
    395 * and we need to retain CAP_SETPCAP in order to drop
    +
    396 * all privileges before exec().
    +
    397 */
    +
    398 if (prctl(PR_SET_SECUREBITS,
    +
    399 SECBIT_KEEP_CAPS |
    +
    400 SECBIT_NO_SETUID_FIXUP) == -1) {
    +
    401 fprintf(stderr,
    +
    402 "%s: Failed to set securebits %s\n",
    +
    403 progname, strerror(errno));
    +
    404 exit(1);
    +
    405 }
    +
    406 }
    +
    407#endif
    +
    408
    +
    409 struct passwd *pwd = getpwnam(setuid_name);
    +
    410 if (!pwd || setgid(pwd->pw_gid) == -1 || setuid(pwd->pw_uid) == -1) {
    +
    411 fprintf(stderr, "%s: Failed to setuid to %s: %s\n",
    +
    412 progname, setuid_name, strerror(errno));
    +
    413 exit(1);
    +
    414 }
    +
    415 } else if (!getenv("HOME")) {
    +
    416 /* Hack to make filesystems work in the boot environment */
    +
    417 setenv("HOME", "/root", 0);
    +
    418 }
    +
    419
    +
    420 if (pass_fuse_fd) {
    +
    421 fuse_fd = prepare_fuse_fd(mountpoint, type, options);
    +
    422 dev_fd_mountpoint = xrealloc(NULL, 20);
    +
    423 snprintf(dev_fd_mountpoint, 20, "/dev/fd/%u", fuse_fd);
    +
    424 mountpoint = dev_fd_mountpoint;
    +
    425 }
    +
    426
    +
    427#ifdef linux
    +
    428 if (drop_privileges) {
    +
    429 drop_and_lock_capabilities();
    +
    430 }
    +
    431#endif
    +
    432 add_arg(&command, type);
    +
    433 if (source)
    +
    434 add_arg(&command, source);
    +
    435 add_arg(&command, mountpoint);
    +
    436 if (options) {
    +
    437 add_arg(&command, "-o");
    +
    438 add_arg(&command, options);
    +
    439 }
    +
    440
    +
    441 free(options);
    +
    442 free(dev_fd_mountpoint);
    +
    443 free(dup_source);
    +
    444 free(setuid_name);
    +
    445
    +
    446 execl("/bin/sh", "/bin/sh", "-c", command, NULL);
    +
    447 fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
    +
    448 strerror(errno));
    +
    449
    +
    450 if (pass_fuse_fd)
    +
    451 close(fuse_fd);
    +
    452 free(command);
    +
    453 return 1;
    +
    454}
    +
    int fuse_open_channel(const char *mountpoint, const char *options)
    Definition helper.c:482
    +
    + + + + diff --git a/doc/kernel.txt b/doc/kernel.txt new file mode 100644 index 0000000..6e7c7a1 --- /dev/null +++ b/doc/kernel.txt @@ -0,0 +1,380 @@ +Definitions +~~~~~~~~~~~ + +Userspace filesystem: + + A filesystem in which data and metadata are provided by an ordinary + userspace process. The filesystem can be accessed normally through + the kernel interface. + +Filesystem daemon: + + The process(es) providing the data and metadata of the filesystem. + +Non-privileged mount (or user mount): + + A userspace filesystem mounted by a non-privileged (non-root) user. + The filesystem daemon is running with the privileges of the mounting + user. NOTE: this is not the same as mounts allowed with the "user" + option in /etc/fstab, which is not discussed here. + +Filesystem connection: + + A connection between the filesystem daemon and the kernel. The + connection exists until either the daemon dies, or the filesystem is + umounted. Note that detaching (or lazy umounting) the filesystem + does _not_ break the connection, in this case it will exist until + the last reference to the filesystem is released. + +Mount owner: + + The user who does the mounting. + +User: + + The user who is performing filesystem operations. + +What is FUSE? +~~~~~~~~~~~~~ + +FUSE is a userspace filesystem framework. It consists of a kernel +module (fuse.ko), a userspace library (libfuse.*) and a mount utility +(fusermount3). + +One of the most important features of FUSE is allowing secure, +non-privileged mounts. This opens up new possibilities for the use of +filesystems. A good example is sshfs: a secure network filesystem +using the sftp protocol. + +The userspace library and utilities are available from the FUSE +homepage: + + https://github.com/libfuse/libfuse/ + +Filesystem type +~~~~~~~~~~~~~~~ + +The filesystem type given to mount(2) can be one of the following: + +'fuse' + + This is the usual way to mount a FUSE filesystem. The first + argument of the mount system call may contain an arbitrary string, + which is not interpreted by the kernel. + +'fuseblk' + + The filesystem is block device based. The first argument of the + mount system call is interpreted as the name of the device. + +Mount options +~~~~~~~~~~~~~ + +See mount.fuse3(8). + +Control filesystem +~~~~~~~~~~~~~~~~~~ + +There's a control filesystem for FUSE, which can be mounted by: + + mount -t fusectl none /sys/fs/fuse/connections + +Mounting it under the '/sys/fs/fuse/connections' directory makes it +backwards compatible with versions before 2.6.0. + +Under the fuse control filesystem each connection has a directory +named by a unique number. + +For each connection the following files exist within this directory: + + 'waiting' + + The number of requests which are waiting to be transferred to + userspace or being processed by the filesystem daemon. If there is + no filesystem activity and 'waiting' is non-zero, then the + filesystem is hung or deadlocked. + + 'abort' + + Writing anything into this file will abort the filesystem + connection. This means that all waiting requests will be aborted an + error returned for all aborted and new requests. + +Only the owner of the mount may read or write these files. + +Interrupting filesystem operations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If a process issuing a FUSE filesystem request is interrupted, the +following will happen: + + 1) If the request is not yet sent to userspace AND the signal is + fatal (SIGKILL or unhandled fatal signal), then the request is + dequeued and returns immediately. + + 2) If the request is not yet sent to userspace AND the signal is not + fatal, then an 'interrupted' flag is set for the request. When + the request has been successfully transferred to userspace and + this flag is set, an INTERRUPT request is queued. + + 3) If the request is already sent to userspace, then an INTERRUPT + request is queued. + +INTERRUPT requests take precedence over other requests, so the +userspace filesystem will receive queued INTERRUPTs before any others. + +The userspace filesystem may ignore the INTERRUPT requests entirely, +or may honor them by sending a reply to the _original_ request, with +the error set to EINTR. + +It is also possible that there's a race between processing the +original request and it's INTERRUPT request. There are two possibilities: + + 1) The INTERRUPT request is processed before the original request is + processed + + 2) The INTERRUPT request is processed after the original request has + been answered + +If the filesystem cannot find the original request, it should wait for +some timeout and/or a number of new requests to arrive, after which it +should reply to the INTERRUPT request with an EAGAIN error. In case +1) the INTERRUPT request will be requeued. In case 2) the INTERRUPT +reply will be ignored. + +Aborting a filesystem connection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is possible to get into certain situations where the filesystem is +not responding. Reasons for this may be: + + a) Broken userspace filesystem implementation + + b) Network connection down + + c) Accidental deadlock + + d) Malicious deadlock + +(For more on c) and d) see later sections) + +In either of these cases it may be useful to abort the connection to +the filesystem. There are several ways to do this: + + - Kill the filesystem daemon. Works in case of a) and b) + + - Kill the filesystem daemon and all users of the filesystem. Works + in all cases except some malicious deadlocks + + - Use forced umount (umount -f). Works in all cases but only if + filesystem is still attached (it hasn't been lazy unmounted) + + - Abort filesystem through the FUSE control filesystem. Most + powerful method, always works. + +How do non-privileged mounts work? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Since the mount() system call is a privileged operation, a helper +program (fusermount3) is needed, which is installed setuid root. + +The implication of providing non-privileged mounts is that the mount +owner must not be able to use this capability to compromise the +system. Obvious requirements arising from this are: + + A) mount owner should not be able to get elevated privileges with the + help of the mounted filesystem + + B) mount owner should not get illegitimate access to information from + other users' and the super user's processes + + C) mount owner should not be able to induce undesired behavior in + other users' or the super user's processes + +How are requirements fulfilled? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + A) The mount owner could gain elevated privileges by either: + + 1) creating a filesystem containing a device file, then opening + this device + + 2) creating a filesystem containing a suid or sgid application, + then executing this application + + The solution is not to allow opening device files and ignore + setuid and setgid bits when executing programs. To ensure this + fusermount3 always adds "nosuid" and "nodev" to the mount options + for non-privileged mounts. + + B) If another user is accessing files or directories in the + filesystem, the filesystem daemon serving requests can record the + exact sequence and timing of operations performed. This + information is otherwise inaccessible to the mount owner, so this + counts as an information leak. + + The solution to this problem will be presented in point 2) of C). + + C) There are several ways in which the mount owner can induce + undesired behavior in other users' processes, such as: + + 1) mounting a filesystem over a file or directory which the mount + owner could otherwise not be able to modify (or could only + make limited modifications). + + This is solved in fusermount3, by checking the access + permissions on the mountpoint and only allowing the mount if + the mount owner can do unlimited modification (has write + access to the mountpoint, and mountpoint is not a "sticky" + directory) + + 2) Even if 1) is solved the mount owner can change the behavior + of other users' processes. + + i) It can slow down or indefinitely delay the execution of a + filesystem operation creating a DoS against the user or the + whole system. For example a suid application locking a + system file, and then accessing a file on the mount owner's + filesystem could be stopped, and thus causing the system + file to be locked forever. + + ii) It can present files or directories of unlimited length, or + directory structures of unlimited depth, possibly causing a + system process to eat up diskspace, memory or other + resources, again causing DoS. + + The solution to this as well as B) is not to allow processes + to access the filesystem, which could otherwise not be + monitored or manipulated by the mount owner. Since if the + mount owner can ptrace a process, it can do all of the above + without using a FUSE mount, the same criteria as used in + ptrace can be used to check if a process is allowed to access + the filesystem or not. + + Note that the ptrace check is not strictly necessary to + prevent B/2/i, it is enough to check if mount owner has enough + privilege to send signal to the process accessing the + filesystem, since SIGSTOP can be used to get a similar effect. + +I think these limitations are unacceptable? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If a sysadmin trusts the users enough, or can ensure through other +measures, that system processes will never enter non-privileged +mounts, it can relax the last limitation with a "user_allow_other" +config option. If this config option is set, the mounting user can +add the "allow_other" mount option which disables the check for other +users' processes. + +Kernel - userspace interface +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following diagram shows how a filesystem operation (in this +example unlink) is performed in FUSE. + +NOTE: everything in this description is greatly simplified + + | "rm /mnt/fuse/file" | FUSE filesystem daemon + | | + | | >sys_read() + | | >fuse_dev_read() + | | >request_wait() + | | [sleep on fc->waitq] + | | + | >sys_unlink() | + | >fuse_unlink() | + | [get request from | + | fc->unused_list] | + | >request_send() | + | [queue req on fc->pending] | + | [wake up fc->waitq] | [woken up] + | >request_wait_answer() | + | [sleep on req->waitq] | + | | pending] + | | [copy req to read buffer] + | | [add req to fc->processing] + | | sys_write() + | | >fuse_dev_write() + | | [look up req in fc->processing] + | | [remove from fc->processing] + | | [copy write buffer to req] + | [woken up] | [wake up req->waitq] + | | unused_list] | + | sys_unlink("/mnt/fuse/file") | + | [acquire inode semaphore | + | for "file"] | + | >fuse_unlink() | + | [sleep on req->waitq] | + | | sys_unlink("/mnt/fuse/file") + | | [acquire inode semaphore + | | for "file"] + | | *DEADLOCK* + +The solution for this is to allow the filesystem to be aborted. + +Scenario 2 - Tricky deadlock +---------------------------- + +This one needs a carefully crafted filesystem. It's a variation on +the above, only the call back to the filesystem is not explicit, +but is caused by a pagefault. + + | Kamikaze filesystem thread 1 | Kamikaze filesystem thread 2 + | | + | [fd = open("/mnt/fuse/file")] | [request served normally] + | [mmap fd to 'addr'] | + | [close fd] | [FLUSH triggers 'magic' flag] + | [read a byte from addr] | + | >do_page_fault() | + | [find or create page] | + | [lock page] | + | >fuse_readpage() | + | [queue READ request] | + | [sleep on req->waitq] | + | | [read request to buffer] + | | [create reply header before addr] + | | >sys_write(addr - headerlength) + | | >fuse_dev_write() + | | [look up req in fc->processing] + | | [remove from fc->processing] + | | [copy write buffer to req] + | | >do_page_fault() + | | [find or create page] + | | [lock page] + | | * DEADLOCK * + +Solution is basically the same as above. + +An additional problem is that while the write buffer is being copied +to the request, the request must not be interrupted/aborted. This is +because the destination address of the copy may not be valid after the +request has returned. + +This is solved with doing the copy atomically, and allowing abort +while the page(s) belonging to the write buffer are faulted with +get_user_pages(). The 'req->locked' flag indicates when the copy is +taking place, and abort is delayed until this flag is unset. diff --git a/doc/libfuse-operations.txt b/doc/libfuse-operations.txt new file mode 100644 index 0000000..457ff2c --- /dev/null +++ b/doc/libfuse-operations.txt @@ -0,0 +1,426 @@ +List of libfuse operations with their in/out arguments, created with +help of chatgpt. As of kernel 6.18 (protocol 7.45). The list +was only partly human verified - use with care. + +1. FUSE_LOOKUP (1) + - in_args[0]: Variable (up to PATH_MAX for the file name) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +2. FUSE_FORGET (2) + - in_args[0]: Size of fuse_forget_in (typically 8 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +3. FUSE_GETATTR (3) + - in_args[0]: Size of fuse_getattr_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_attr_out (typically 96 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +4. FUSE_SETATTR (4) + - in_args[0]: Size of fuse_setattr_in (typically 32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_attr_out (typically 96 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +5. FUSE_READLINK (5) + - in_args[0]: Not used + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Variable (size of the link target path) + - out_args[1]: Not used + - out_args[2]: Not used + +6. FUSE_SYMLINK (6) + - in_args[0]: Variable (new link file name, up to PATH_MAX) + - in_args[1]: Variable (target path for the symlink, up to PATH_MAX) + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +7. Unused (7) + +8. FUSE_MKNOD (8) + - in_args[0]: Size of fuse_mknod_in (24 bytes) + - in_args[1]: Variable (file name, up to PATH_MAX) + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +9. FUSE_MKDIR (9) + - in_args[0]: Size of fuse_mkdir_in (16 bytes) + - in_args[1]: Variable (directory name, up to PATH_MAX) + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +10. FUSE_UNLINK (10) + - in_args[0]: Variable (file name, up to PATH_MAX) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +11. FUSE_RMDIR (11) + - in_args[0]: Variable (directory name, up to PATH_MAX) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +12. FUSE_RENAME (12) + - in_args[0]: Size of fuse_rename_in (24 bytes) + - in_args[1]: Variable (old path, up to PATH_MAX) + - in_args[2]: Variable (new path, up to PATH_MAX) + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +13. FUSE_LINK (13) + - in_args[0]: Size of fuse_link_in (16 bytes) + - in_args[1]: Variable (new link path, up to PATH_MAX) + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +14. FUSE_OPEN (14) + - in_args[0]: Size of fuse_open_in (8 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_open_out (24 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +15. FUSE_READ (15) + - in_args[0]: Size of fuse_read_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Variable (data read from the file) + - out_args[1]: Not used + - out_args[2]: Not used + +16. FUSE_WRITE (16) + - in_args[0]: Size of fuse_write_in (32 bytes) + - in_args[1]: Variable (data to write) + - in_args[2]: Not used + - out_args[0]: Size of fuse_write_out (24 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +17. FUSE_STATFS (17) + - in_args[0]: Not used + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_statfs_out (typically 96 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +18. FUSE_RELEASE (18) + - in_args[0]: Size of fuse_release_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +19. Unused (19) + +20. FUSE_FSYNC (20) + - in_args[0]: Size of fuse_fsync_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +21. FUSE_SETXATTR (21) + - in_args[0]: Size of fuse_setxattr_in (24 bytes) + - in_args[1]: Variable (name of attribute, up to 255 bytes) + - in_args[2]: Variable (value of attribute) + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + - When FUSE_SETXATTR_EXT is not set: Size of fuse_setxattr_in is 16 bytes (FUSE_COMPAT_SETXATTR_IN_SIZE). + +22. FUSE_GETXATTR (22) + - in_args[0]: Size of fuse_getxattr_in (24 bytes) + - in_args[1]: Variable (name of attribute, up to 255 bytes) + - in_args[2]: Not used + - out_args[0]: Variable (value of the attribute) + - out_args[1]: Not used + - out_args[2]: Not used + +23. FUSE_LISTXATTR (23) + - in_args[0]: Size of fuse_getxattr_in (24 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Variable (list of attribute names, each up to 255 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +24. FUSE_REMOVEXATTR (24) + - in_args[0]: Variable (name of attribute, up to 255 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +25. FUSE_FLUSH (25) + - in_args[0]: Size of fuse_flush_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +26. FUSE_INIT (26) + - in_args[0]: Size of fuse_init_in (64 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_init_out (typically 80 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + - Flags: + - FUSE_SECURITY_CTX + - FUSE_CREATE_SUPP_GROUP + - FUSE_SETXATTR_EXT + - FUSE_COMPAT_SETXATTR_IN_SIZE + - Additional flags may include FUSE_ASYNC_READ, FUSE_POSIX_LOCKS, FUSE_FILE_OPS, etc. + +27. FUSE_OPENDIR (27) + - in_args[0]: Size of fuse_open_in (8 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_open_out (24 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +28. FUSE_READDIR (28) + - in_args[0]: Size of fuse_read_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Variable (directory entries) + - out_args[1]: Not used + - out_args[2]: Not used + +29. FUSE_RELEASEDIR (29) + - in_args[0]: Size of fuse_release_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +30. FUSE_FSYNCDIR (30) + - in_args[0]: Size of fuse_fsync_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +31. FUSE_GETLK (31) + - in_args[0]: Size of fuse_lk_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_lk_out (typically 32 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +32. FUSE_SETLK (32) + - in_args[0]: Size of fuse_lk_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +33. FUSE_SETLKW (33) + - in_args[0]: Size of fuse_lk_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +34. FUSE_ACCESS (34) + - in_args[0]: Size of fuse_access_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +35. FUSE_CREATE (35) + - in_args[0]: Size of fuse_create_in (16 bytes) + - in_args[1]: Variable (file name, up to PATH_MAX) + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Size of fuse_open_out (24 bytes) + - out_args[2]: Not used + - When FUSE_SECURITY_CTX is set (fc->init_security): Additional security context included in in_args and/or out_args. + - When FUSE_CREATE_SUPP_GROUP is set (fc->create_supp_group): Additional supplementary group information included in in_args. + +36. FUSE_INTERRUPT (36) + - in_args[0]: Size of fuse_interrupt_in (8 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +37. FUSE_BMAP (37) + - in_args[0]: Size of fuse_bmap_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_bmap_out (16 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +38. FUSE_DESTROY (38) + - in_args[0]: Not used + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +39. FUSE_IOCTL (39) + - in_args[0]: Size of fuse_ioctl_in (64 bytes) + - in_args[1]: Variable (ioctl command data) + - in_args[2]: Not used + - out_args[0]: Size of fuse_ioctl_out (32 bytes) + - out_args[1]: Variable (data returned by the ioctl command) + - out_args[2]: Not used + +40. FUSE_POLL (40) + - in_args[0]: Size of fuse_poll_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_poll_out (16 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +41. FUSE_NOTIFY_REPLY (41) + - in_args[0]: Variable (reply data) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +42. FUSE_BATCH_FORGET (42) + - in_args[0]: Variable (size depends on the number of forget requests) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +43. FUSE_FALLOCATE (43) + - in_args[0]: Size of fuse_fallocate_in (24 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +44. FUSE_READDIRPLUS (44) + - in_args[0]: Size of fuse_read_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Variable (directory entries) + - out_args[1]: Not used + - out_args[2]: Not used + +45. FUSE_RENAME2 (45) + - in_args[0]: Size of fuse_rename2_in (32 bytes) + - in_args[1]: Variable (old path, up to PATH_MAX) + - in_args[2]: Variable (new path, up to PATH_MAX) + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +46. FUSE_LSEEK (46) + - in_args[0]: Size of fuse_lseek_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_lseek_out (16 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +47. FUSE_COPY_FILE_RANGE (47) + - in_args[0]: Size of fuse_copy_file_range_in (56 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_write_out (24 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +48. FUSE_SETUPMAPPING (48) + - in_args[0]: Size of fuse_setupmapping_in (32 bytes) + - in_args[1]: Variable (data to map) + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +49. FUSE_REMOVEMAPPING (49) + - in_args[0]: Variable (size depends on the number of mappings to remove) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +50. FUSE_SYNCFS (50) + - in_args[0]: Size of fuse_syncfs_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +51. FUSE_TMPFILE (51) + - in_args[0]: Size of fuse_create_in (16 bytes) + - in_args[1]: Variable (file name, up to PATH_MAX) + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Size of fuse_open_out (24 bytes) + - out_args[2]: Not used + - When FUSE_SECURITY_CTX is set (fc->init_security): Additional security context included in in_args and/or out_args. + - When FUSE_CREATE_SUPP_GROUP is set (fc->create_supp_group): Additional supplementary group information included in in_args. + +52. FUSE_STATX (52) + - in_args[0]: Size of fuse_statx_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_statx_out (typically 256 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +53. FUSE_COPY_FILE_RANGE_64 (53) + - in_args[0]: Size of fuse_copy_file_range_in (56 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_copy_file_range_out (8 bytes) + - out_args[1]: Not used + - out_args[2]: Not used diff --git a/doc/mainpage.dox b/doc/mainpage.dox new file mode 100644 index 0000000..36ba3bc --- /dev/null +++ b/doc/mainpage.dox @@ -0,0 +1,54 @@ +/*! +\mainpage libfuse API documentation + +FUSE (Filesystem in Userspace) is an interface for userspace programs +to export a filesystem to the Linux kernel. The FUSE project consists +of two components: the *fuse* kernel module (maintained in the regular +kernel repositories) and the *libfuse* userspace library. libfuse +provides the reference implementation for communicating with the FUSE +kernel module. + +A FUSE file system is typically implemented as a standalone +application that links with libfuse. libfuse provides functions to +mount the file system, unmount it, read requests from the kernel, and +send responses back. + + +## Getting started ## + +libfuse offers two APIs: a "high-level", synchronous API, and a +"low-level" asynchronous API. In both cases, incoming requests from +the kernel are passed to the main program using callbacks. When using +the high-level API, the callbacks may work with file names and paths +instead of inodes, and processing of a request finishes when the +callback function returns. When using the low-level API, the callbacks +must work with inodes and responses must be sent explicitly using a +separate set of API functions. + +The high-level API that is primarily specified in fuse.h. The +low-level API that is primarily documented in fuse_lowlevel.h. + +## Examples ## + +FUSE comes with several examples in the examples directory. A good starting point are +hello.c (for the high-level API) and hello_ll.c (for the low-level +API). + +## FUSE internals ## + +The authoritative source of information about libfuse internals +(including the protocol used for communication with the FUSE kernel +module) is the source code. + +However, some people have kindly documented different aspects of FUSE +in a more beginner friendly way. While this information is +increasingly out of date, it still provides a good overview: + +- Bharat Vangoor et al have included an overview of the FUSE internals + in a paper evaluating FUSE performance. + +- Some documentation of the kernel-userspace protocol is available on + the libfuse wiki. + +*/ diff --git a/doc/meson.build b/doc/meson.build new file mode 100644 index 0000000..db3e0b2 --- /dev/null +++ b/doc/meson.build @@ -0,0 +1,4 @@ +if not platform.endswith('bsd') and platform != 'dragonfly' + install_man('fusermount3.1', 'mount.fuse3.8') +endif + diff --git a/doc/mount.fuse3.8 b/doc/mount.fuse3.8 new file mode 100644 index 0000000..32862fc --- /dev/null +++ b/doc/mount.fuse3.8 @@ -0,0 +1,273 @@ +.TH fuse "8" +.SH NAME +fuse \- configuration and mount options for FUSE file systems +.SH DESCRIPTION +FUSE (Filesystem in Userspace) is a simple interface for userspace programs to export a virtual filesystem to the Linux kernel. FUSE also aims to provide a secure method for non privileged users to create and mount their own filesystem implementations. +.SH DEFINITIONS +.TP +\fBFUSE\fP +The in-kernel filesystem that forwards requests to a user-space +process. +.TP +\fBfilesystem\fP +The user-space process that responds to requests received from the +kernel. +.TP +\fBlibfuse\fP +The shared library that most (user-space) filesystems use to +communicate with FUSE (the kernel filesystem). libfuse also provides +the \fBfusermount3\fP (or \fBfusermount\fP if you have older version of +libfuse) helper to allow non-privileged users to mount filesystems. +.TP +\fBfilesystem owner\fP +The user that starts the filesystem and instructs the kernel to +associate it with a particular mountpoint. The latter is typically done +by the filesystem itself on start-up. When using libfuse, this is done +by calling the \fBfusermount3\fP utility. +.TP +\fBclient\fP +Any process that interacts with the mountpoint. +.SH CONFIGURATION +Some options regarding mount policy can be set in the file \fI/etc/fuse.conf\fP. Currently these options are: +.TP +\fBmount_max = NNN\fP +Set the maximum number of FUSE mounts allowed to non-root users. The default is 1000. +.TP +\fBuser_allow_other\fP +Allow non-root users to specify the \fBallow_other\fP or +\fBallow_root\fP mount options (see below). +.TP +These limits are enforced by the \fBfusermount3\fP helper, so they can be avoided by filesystems that run as root. +.SH OPTIONS +Most of the generic mount options described in \fBmount\fP are +supported (\fBro\fP, \fBrw\fP, \fBsuid\fP, \fBnosuid\fP, \fBdev\fP, +\fBnodev\fP, \fBexec\fP, \fBnoexec\fP, \fBatime\fP, \fBnoatime\fP, +\fBsync\fP, \fBasync\fP, \fBdirsync\fP). Filesystems are mounted with +\fBnodev,nosuid\fP by default, which can only be overridden by a +privileged user. +.SS "General mount options:" +These are FUSE specific mount options that can be specified for all filesystems: +.TP +\fBdefault_permissions\fP +This option instructs the kernel to perform its own permission check +instead of deferring all permission checking to the +filesystem. The check by the kernel is done in addition to any +permission checks by the filesystem, and both have to succeed for an +operation to be allowed. The kernel performs a standard UNIX permission +check (based on mode bits and ownership of the directory entry, and +uid/gid of the client). + +This mount option is activated implicitly if the filesystem enables +ACL support during the initial feature negotiation when opening the +device fd. In this case, the kernel performs both ACL and standard +unix permission checking. + +Filesystems that do not implement any permission checking should +generally add this option internally. +.TP +\fBallow_other\fP +This option overrides the security measure +restricting file access to the filesystem owner, so that all users +(including root) can access the files. +.TP +\fBrootmode=M\fP +Specifies the file mode of the filesystem's root (in octal +representation). +.TP +\fBblkdev\fP +Mount a filesystem backed by a block device. This is a privileged +option. The device must be specified with the \fBfsname=NAME\fP +option. +.TP +\fBblksize=N\fP +Set the block size for the filesystem. This option is only valid +for 'fuseblk' type mounts. The default is 512. + +In most cases, this option should not be specified by +the filesystem owner but set internally by the filesystem. +.TP +\fBmax_read=N\fP +With this option the maximum size of read operations can be set. The +default is infinite, but typically the kernel enforces its own limit +in addition to this one. A value of zero corresponds to no limit. + +This option should not be specified by the filesystem owner. The +correct (or optimum) value depends on the filesystem implementation +and should thus be set by the filesystem internally. + +This mount option is deprecated in favor of direct negotiation over +the device fd (as done for e.g. the maximum size of write +operations). For the time being, libfuse-using filesystems that want +to limit the read size must therefore use this mount option \fIand\fP +set the same value again in the init() handler. +.TP +\fBfd=N\fP +The file descriptor to use for communication between the userspace +filesystem and the kernel. The file descriptor must have been +obtained by opening the FUSE device (/dev/fuse). + +This option should not be specified by the filesystem owner. It is set +by libfuse (or, if libfuse is not used, must be set by the filesystem +itself). +.TP +\fBuser_id=N\fP +\fBgroup_id=N\fP +Specifies the numeric uid/gid of the mount owner. + +This option should not be specified by the filesystem owner. It is set +by libfuse (or, if libfuse is not used, must be set by the filesystem +itself). +.TP +\fBfsname=NAME\fP +Sets the filesystem source (first field in \fI/etc/mtab\fP). The +default is the name of the filesystem process. +.TP +\fBsubtype=TYPE\fP +Sets the filesystem type (third field in \fI/etc/mtab\fP). The default +is the name of the filesystem process. If the kernel supports it, \fI/etc/mtab\fP and \fI/proc/mounts\fP will show the filesystem type as \fBfuse.TYPE\fP + +If the kernel doesn't support subtypes, the source field will be +\fBTYPE#NAME\fP, or if \fBfsname\fP option is not specified, just +\fBTYPE\fP. + +.SS "libfuse-specific mount options:" +These following options are not actually passed to the kernel but +interpreted by libfuse. They can be specified for all filesystems +that use libfuse: +.TP +\fBallow_root\fP +This option is similar to \fBallow_other\fP but file access is limited +to the filesystem owner and root. This option and \fBallow_other\fP are mutually exclusive. +.TP +\fBauto_unmount\fP +This option enables automatic release of the mountpoint if filesystem +terminates for any reason. Normally the filesystem is +responsible for releasing the mountpoint, which means that the +mountpoint becomes inaccessible if the filesystem terminates +without first unmounting. + +This option is dangerous and should only be used after careful consideration of the +risks. + +Automatically unmounting the filesystem means that if the filesystem crashes the +mountpoint may suddenly appear empty, which may have unintended consequences. For example, +a running backup and mirroring program may conclude that all the data in the filesystem +has been deleted and proceed to propagate this deletion to the backup / remote system. If +the mountpoint instead becomes inaccessible (the default), most programs will behave +correctly (report an error). + +This feature may also accidentally unmount the wrong filesystem due to race +conditions. For example, if another filesystem was mounted underneath the same mountpoint, +or if a new filesystem is mounted after the FUSE process has crashed, it may accidentally +get unmounted. + +At the moment, this option implies that the filesystem will also be +mounted with \fBnodev\fP and \fBnosuid\fP (even when mounted by +root). This restriction may be lifted in the future. + +.SS "High-level mount options:" +These following options are not actually passed to the kernel but +interpreted by libfuse. They can only be specified for filesystems +that use the high-level libfuse API: +.TP +\fBkernel_cache\fP +This option disables flushing the cache of the file contents on every \fBopen\fP(2). This should only be enabled on filesystems, where the file data is never changed externally (not through the mounted FUSE filesystem). Thus it is not suitable for network filesystems and other "intermediate" filesystems. + +\fBNOTE\fP: if this option is not specified (and neither \fBdirect_io\fP) data is still cached after the \fBopen\fP(2), so a \fBread\fP(2) system call will not always initiate a read operation. +.TP +\fBauto_cache\fP +This option is an alternative to +\fBkernel_cache\fP. Instead of unconditionally keeping cached data, the +cached data is invalidated on \fBopen\fP(2) if the modification +time or the size of the file has changed since it was last opened. +.TP +\fBumask=M fmask=M dmask=M\fP +Override the permission bits set by the filesystem in \fIst_mode\fP. The resulting permission bits are the ones missing from the mask value, which is given in octal representation. \fBfmask\fP and \fBdmask\fP (respectively) may be used to control the permission bits of files and directories separately. umask is overridden by the individual fmask and dmask options. +.TP +\fBuid=N\fP +Override the \fIst_uid\fP field set by the filesystem (N is numeric). +.TP +\fBgid=N\fP +Override the \fIst_gid\fP field set by the filesystem (N is numeric). +.TP +\fBentry_timeout=T\fP +The timeout in seconds for which name lookups will be cached. The default is 1.0 second. For all the timeout options, it is possible to give fractions of a second as well (e.g. \fBentry_timeout=2.8\fP) +.TP +\fBnegative_timeout=T\fP +The timeout in seconds for which a negative lookup will be cached. This means, that if file did not exist (lookup returned \fBENOENT\fP), the lookup will only be redone after the timeout, and the file/directory will be assumed to not exist until then. The default is 0.0 second, meaning that caching negative lookups are disabled. +.TP +\fBattr_timeout=T\fP +The timeout in seconds for which file/directory attributes are cached. The default is 1.0 second. +.TP +\fBac_attr_timeout=T\fP +The timeout in seconds for which file attributes are cached for the purpose of checking if \fBauto_cache\fP should flush the file data on open. The default is the value of \fBattr_timeout\fP +.TP +\fBnoforget\fP +.TP +\fBremember=T\fP +Normally, libfuse assigns inodes to paths only for as long as the kernel +is aware of them. With this option inodes are instead assigned +for at least \fBT\fP seconds (or, in the case of \fBnoforget\fP, +the life-time of the filesystem). This will require more +memory, but may be necessary when using applications that make use of +inode numbers. +.TP +\fBmodules=M1[:M2...]\fP +Add modules to the filesystem stack. Modules are pushed in the order they are specified, with the original filesystem being on the bottom of the stack. + +.SS "\fBmount.fuse3\fP options:" +These options are interpreted by \fBmount.fuse3\fP and are thus only available when mounting a file system via \fBmount.fuse3\fP (such as when mounting via the generic \fBmount\fP(1) command or \fI/etc/fstab\fP). Supported options are: +.TP +\fBsetuid=USER\fP +Switch to \fBUSER\fP and its primary group before launching the FUSE file system process. mount.fuse3 must be run as root or with \fBCAP_SETUID\fP and \fBCAP_SETGID\fP for this to work. +.TP +\fBdrop_privileges\fP +Perform setup of the FUSE file descriptor and mounting the file system before launching the FUSE file system process. \fBmount.fuse3\fP requires privilege to do so, i.e. must be run as root or at least with \fBCAP_SYS_ADMIN\fP and \fBCAP_SETPCAP\fP. It will launch the file system process fully unprivileged, i.e. without \fBcapabilities\fP(7) and \fBprctl\fP(2) flags set up such that privileges can't be reacquired (e.g. via setuid or fscaps binaries). This reduces risk in the event of the FUSE file system process getting compromised by malicious file system data. + +.SH FUSE MODULES (STACKING) +Modules are filesystem stacking support to high level API. Filesystem modules can be built into libfuse or loaded from shared object +.SS "iconv" +Perform file name character set conversion. Options are: +.TP +\fBfrom_code=CHARSET\fP +Character set to convert from (see \fBiconv -l\fP for a list of possible values). Default is \fBUTF-8\fP. +.TP +\fBto_code=CHARSET\fP +Character set to convert to. Default is determined by the current locale. +.SS "subdir" +Prepend a given directory to each path. Options are: +.TP +\fBsubdir=DIR\fP +Directory to prepend to all paths. This option is \fImandatory\fP. +.TP +\fBrellinks\fP +Transform absolute symlinks into relative +.TP +\fBnorellinks\fP +Do not transform absolute symlinks into relative. This is the default. +.SH SECURITY +The fusermount3 program is installed set-user-gid to fuse. This is done to allow users from fuse group to mount +their own filesystem implementations. +There must however be some limitations, in order to prevent Bad User from +doing nasty things. Currently those limitations are: +.IP 1. +The user can only mount on a mountpoint, for which it has write permission +.IP 2. +The mountpoint is not a sticky directory which isn't owned by the user (like \fI/tmp\fP usually is) +.IP 3. +No other user (including root) can access the contents of the mounted filesystem. +.SH NOTE +FUSE filesystems are unmounted using the \fBfusermount3\fP(1) command (\fBfusermount3 -u mountpoint\fP). +.SH "AUTHORS" +.LP +FUSE is currently maintained by Nikolaus Rath +.LP +The original author of FUSE is Miklos Szeredi . +.LP +This man page was originally written by Bastien Roucaries for the +Debian GNU/Linux distribution. +.SH SEE ALSO +.BR fusermount3 (1) +.BR fusermount (1) +.BR mount (8) +.BR fuse (4) diff --git a/example/README.compile b/example/README.compile new file mode 100644 index 0000000..a7b6819 --- /dev/null +++ b/example/README.compile @@ -0,0 +1,4 @@ +Note: + * If the pkg-config command fails due to the absence of the fuse3.pc file, + you should configure the path to the fuse3.pc file in the PKG_CONFIG_PATH + variable. \ No newline at end of file diff --git a/example/cuse.c b/example/cuse.c new file mode 100644 index 0000000..92b6b09 --- /dev/null +++ b/example/cuse.c @@ -0,0 +1,335 @@ +/* + CUSE example: Character device in Userspace + Copyright (C) 2008-2009 SUSE Linux Products GmbH + Copyright (C) 2008-2009 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. + +*/ + +/** @file + * + * This example demonstrates how to implement a character device in + * userspace ("CUSE"). This is only allowed for root. The character + * device should appear in /dev under the specified name. It can be + * tested with the cuse_client.c program. + * + * Mount the file system with: + * + * cuse -f --name=mydevice + * + * You should now have a new /dev/mydevice character device. To "unmount" it, + * kill the "cuse" process. + * + * To compile this example, run + * + * gcc -Wall cuse.c `pkg-config fuse3 --cflags --libs` -o cuse + * + * ## Source code ## + * \include cuse.c + */ + + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ioctl.h" + +static void *cusexmp_buf; +static size_t cusexmp_size; + +static const char *usage = +"usage: cusexmp [options]\n" +"\n" +"options:\n" +" --help|-h print this help message\n" +" --maj=MAJ|-M MAJ device major number\n" +" --min=MIN|-m MIN device minor number\n" +" --name=NAME|-n NAME device name (mandatory)\n" +" -d -o debug enable debug output (implies -f)\n" +" -f foreground operation\n" +" -s disable multi-threaded operation\n" +"\n"; + +static int cusexmp_resize(size_t new_size) +{ + void *new_buf; + + if (new_size == cusexmp_size) + return 0; + + new_buf = realloc(cusexmp_buf, new_size); + if (!new_buf && new_size) + return -ENOMEM; + + if (new_size > cusexmp_size) + memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size); + + cusexmp_buf = new_buf; + cusexmp_size = new_size; + + return 0; +} + +static int cusexmp_expand(size_t new_size) +{ + if (new_size > cusexmp_size) + return cusexmp_resize(new_size); + return 0; +} + +static void cusexmp_init(void *userdata, struct fuse_conn_info *conn) +{ + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; +} + +static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi) +{ + fuse_reply_open(req, fi); +} + +static void cusexmp_read(fuse_req_t req, size_t size, off_t off, + struct fuse_file_info *fi) +{ + (void)fi; + + if (off >= cusexmp_size) + off = cusexmp_size; + if (size > cusexmp_size - off) + size = cusexmp_size - off; + + fuse_reply_buf(req, cusexmp_buf + off, size); +} + +static void cusexmp_write(fuse_req_t req, const char *buf, size_t size, + off_t off, struct fuse_file_info *fi) +{ + (void)fi; + + if (cusexmp_expand(off + size)) { + fuse_reply_err(req, ENOMEM); + return; + } + + memcpy(cusexmp_buf + off, buf, size); + fuse_reply_write(req, size); +} + +static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf, + size_t in_bufsz, size_t out_bufsz, int is_read) +{ + const struct fioc_rw_arg *arg; + struct iovec in_iov[2], out_iov[3], iov[3]; + size_t cur_size; + + /* read in arg */ + in_iov[0].iov_base = addr; + in_iov[0].iov_len = sizeof(*arg); + if (!in_bufsz) { + fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0); + return; + } + arg = in_buf; + in_buf += sizeof(*arg); + in_bufsz -= sizeof(*arg); + + /* prepare size outputs */ + out_iov[0].iov_base = + addr + offsetof(struct fioc_rw_arg, prev_size); + out_iov[0].iov_len = sizeof(arg->prev_size); + + out_iov[1].iov_base = + addr + offsetof(struct fioc_rw_arg, new_size); + out_iov[1].iov_len = sizeof(arg->new_size); + + /* prepare client buf */ + if (is_read) { + out_iov[2].iov_base = arg->buf; + out_iov[2].iov_len = arg->size; + if (!out_bufsz) { + fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3); + return; + } + } else { + in_iov[1].iov_base = arg->buf; + in_iov[1].iov_len = arg->size; + if (arg->size && !in_bufsz) { + fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2); + return; + } + } + + /* we're all set */ + cur_size = cusexmp_size; + iov[0].iov_base = &cur_size; + iov[0].iov_len = sizeof(cur_size); + + iov[1].iov_base = &cusexmp_size; + iov[1].iov_len = sizeof(cusexmp_size); + + if (is_read) { + size_t off = arg->offset; + size_t size = arg->size; + + if (off >= cusexmp_size) + off = cusexmp_size; + if (size > cusexmp_size - off) + size = cusexmp_size - off; + + iov[2].iov_base = cusexmp_buf + off; + iov[2].iov_len = size; + fuse_reply_ioctl_iov(req, size, iov, 3); + } else { + if (cusexmp_expand(arg->offset + in_bufsz)) { + fuse_reply_err(req, ENOMEM); + return; + } + + memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz); + fuse_reply_ioctl_iov(req, in_bufsz, iov, 2); + } +} + +static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg, + struct fuse_file_info *fi, unsigned flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz) +{ + int is_read = 0; + + (void)fi; + + if (flags & FUSE_IOCTL_COMPAT) { + fuse_reply_err(req, ENOSYS); + return; + } + + switch (cmd) { + case FIOC_GET_SIZE: + if (!out_bufsz) { + struct iovec iov = { arg, sizeof(size_t) }; + + fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1); + } else + fuse_reply_ioctl(req, 0, &cusexmp_size, + sizeof(cusexmp_size)); + break; + + case FIOC_SET_SIZE: + if (!in_bufsz) { + struct iovec iov = { arg, sizeof(size_t) }; + + fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0); + } else { + cusexmp_resize(*(size_t *)in_buf); + fuse_reply_ioctl(req, 0, NULL, 0); + } + break; + + case FIOC_READ: + is_read = 1; + /* fall through */ + case FIOC_WRITE: + fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read); + break; + + default: + fuse_reply_err(req, EINVAL); + } +} + +struct cusexmp_param { + unsigned major; + unsigned minor; + char *dev_name; + int is_help; +}; + +#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 } + +static const struct fuse_opt cusexmp_opts[] = { + CUSEXMP_OPT("-M %u", major), + CUSEXMP_OPT("--maj=%u", major), + CUSEXMP_OPT("-m %u", minor), + CUSEXMP_OPT("--min=%u", minor), + CUSEXMP_OPT("-n %s", dev_name), + CUSEXMP_OPT("--name=%s", dev_name), + FUSE_OPT_KEY("-h", 0), + FUSE_OPT_KEY("--help", 0), + FUSE_OPT_END +}; + +static int cusexmp_process_arg(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + struct cusexmp_param *param = data; + + (void)outargs; + (void)arg; + + switch (key) { + case 0: + param->is_help = 1; + fprintf(stderr, "%s", usage); + return fuse_opt_add_arg(outargs, "-ho"); + default: + return 1; + } +} + +static const struct cuse_lowlevel_ops cusexmp_clop = { + .init = cusexmp_init, + .open = cusexmp_open, + .read = cusexmp_read, + .write = cusexmp_write, + .ioctl = cusexmp_ioctl, +}; + +int main(int argc, char **argv) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct cusexmp_param param = { 0, 0, NULL, 0 }; + char dev_name[128] = "DEVNAME="; + const char *dev_info_argv[] = { dev_name }; + struct cuse_info ci; + int ret = 1; + + if (fuse_opt_parse(&args, ¶m, cusexmp_opts, cusexmp_process_arg)) { + printf("failed to parse option\n"); + free(param.dev_name); + goto out; + } + + if (!param.is_help) { + if (!param.dev_name) { + fprintf(stderr, "Error: device name missing\n"); + goto out; + } + strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME=")); + free(param.dev_name); + } + + memset(&ci, 0, sizeof(ci)); + ci.dev_major = param.major; + ci.dev_minor = param.minor; + ci.dev_info_argc = 1; + ci.dev_info_argv = dev_info_argv; + ci.flags = CUSE_UNRESTRICTED_IOCTL; + + ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL); + +out: + fuse_opt_free_args(&args); + return ret; +} diff --git a/example/cuse_client.c b/example/cuse_client.c new file mode 100644 index 0000000..de82444 --- /dev/null +++ b/example/cuse_client.c @@ -0,0 +1,157 @@ +/* + FUSE fioclient: FUSE ioctl example client + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This program tests the cuse.c example file system. + * + * Example usage (assuming that /dev/foobar is a CUSE device provided + * by the cuse.c example file system): + * + * $ cuse_client /dev/foobar s + * 0 + * + * $ echo "hello" | cuse_client /dev/foobar w 6 + * Writing 6 bytes + * transferred 6 bytes (0 -> 6) + * + * $ cuse_client /dev/foobar s + * 6 + * + * $ cuse_client /dev/foobar r 10 + * hello + * transferred 6 bytes (6 -> 6) + * + * Compiling this example + * + * gcc -Wall cuse_client.c -o cuse_client + * + * ## Source Code ## + * \include cuse_client.c + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ioctl.h" + +const char *usage = +"Usage: cuse_client FIOC_FILE COMMAND\n" +"\n" +"COMMANDS\n" +" s [SIZE] : get size if SIZE is omitted, set size otherwise\n" +" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n" +" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n" +"\n"; + +static int do_rw(int fd, int is_read, size_t size, off_t offset, + size_t *prev_size, size_t *new_size) +{ + struct fioc_rw_arg arg = { .offset = offset }; + ssize_t ret; + + arg.buf = calloc(1, size); + if (!arg.buf) { + fprintf(stderr, "failed to allocated %zu bytes\n", size); + return -1; + } + + if (is_read) { + arg.size = size; + ret = ioctl(fd, FIOC_READ, &arg); + if (ret >= 0) + fwrite(arg.buf, 1, ret, stdout); + } else { + arg.size = fread(arg.buf, 1, size, stdin); + fprintf(stderr, "Writing %zu bytes\n", arg.size); + ret = ioctl(fd, FIOC_WRITE, &arg); + } + + if (ret >= 0) { + *prev_size = arg.prev_size; + *new_size = arg.new_size; + } else + perror("ioctl"); + + free(arg.buf); + return ret; +} + +int main(int argc, char **argv) +{ + size_t param[2] = { }; + size_t size, prev_size = 0, new_size = 0; + char cmd; + int fd, i, rc; + + if (argc < 3) + goto usage; + + fd = open(argv[1], O_RDWR); + if (fd < 0) { + perror("open"); + return 1; + } + + cmd = tolower(argv[2][0]); + argc -= 3; + argv += 3; + + for (i = 0; i < argc; i++) { + char *endp; + param[i] = strtoul(argv[i], &endp, 0); + if (endp == argv[i] || *endp != '\0') + goto usage; + } + + switch (cmd) { + case 's': + if (!argc) { + if (ioctl(fd, FIOC_GET_SIZE, &size)) { + perror("ioctl"); + goto error; + } + printf("%zu\n", size); + } else { + size = param[0]; + if (ioctl(fd, FIOC_SET_SIZE, &size)) { + perror("ioctl"); + goto error; + } + } + close(fd); + return 0; + + case 'r': + case 'w': + rc = do_rw(fd, cmd == 'r', param[0], param[1], + &prev_size, &new_size); + if (rc < 0) + goto error; + fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n", + rc, prev_size, new_size); + close(fd); + return 0; + } + +usage: + fprintf(stderr, "%s", usage); + return 1; + +error: + close(fd); + return 1; +} diff --git a/example/cxxopts.hpp b/example/cxxopts.hpp new file mode 100644 index 0000000..80b695a --- /dev/null +++ b/example/cxxopts.hpp @@ -0,0 +1,2114 @@ +/* + +Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +#ifndef CXXOPTS_HPP_INCLUDED +#define CXXOPTS_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cpp_lib_optional +#include +#define CXXOPTS_HAS_OPTIONAL +#endif + +#ifndef CXXOPTS_VECTOR_DELIMITER +#define CXXOPTS_VECTOR_DELIMITER ',' +#endif + +#define CXXOPTS__VERSION_MAJOR 2 +#define CXXOPTS__VERSION_MINOR 2 +#define CXXOPTS__VERSION_PATCH 1 + +namespace cxxopts +{ + static constexpr struct { + uint8_t major, minor, patch; + } version = { + CXXOPTS__VERSION_MAJOR, + CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH + }; +} + +//when we ask cxxopts to use Unicode, help strings are processed using ICU, +//which results in the correct lengths being computed for strings when they +//are formatted for the help output +//it is necessary to make sure that can be found by the +//compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE +#include + +namespace cxxopts +{ + typedef icu::UnicodeString String; + + inline + String + toLocalString(std::string s) + { + return icu::UnicodeString::fromUTF8(std::move(s)); + } + + class UnicodeStringIterator : public + std::iterator + { + public: + + UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) + : s(string) + , i(pos) + { + } + + value_type + operator*() const + { + return s->char32At(i); + } + + bool + operator==(const UnicodeStringIterator& rhs) const + { + return s == rhs.s && i == rhs.i; + } + + bool + operator!=(const UnicodeStringIterator& rhs) const + { + return !(*this == rhs); + } + + UnicodeStringIterator& + operator++() + { + ++i; + return *this; + } + + UnicodeStringIterator + operator+(int32_t v) + { + return UnicodeStringIterator(s, i + v); + } + + private: + const icu::UnicodeString* s; + int32_t i; + }; + + inline + String& + stringAppend(String&s, String a) + { + return s.append(std::move(a)); + } + + inline + String& + stringAppend(String& s, int n, UChar32 c) + { + for (int i = 0; i != n; ++i) + { + s.append(c); + } + + return s; + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + while (begin != end) + { + s.append(*begin); + ++begin; + } + + return s; + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + std::string + toUTF8String(const String& s) + { + std::string result; + s.toUTF8String(result); + + return result; + } + + inline + bool + empty(const String& s) + { + return s.isEmpty(); + } +} + +namespace std +{ + inline + cxxopts::UnicodeStringIterator + begin(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, 0); + } + + inline + cxxopts::UnicodeStringIterator + end(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, s.length()); + } +} + +//ifdef CXXOPTS_USE_UNICODE +#else + +namespace cxxopts +{ + typedef std::string String; + + template + T + toLocalString(T&& t) + { + return std::forward(t); + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + String& + stringAppend(String&s, String a) + { + return s.append(std::move(a)); + } + + inline + String& + stringAppend(String& s, size_t n, char c) + { + return s.append(n, c); + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + return s.append(begin, end); + } + + template + std::string + toUTF8String(T&& t) + { + return std::forward(t); + } + + inline + bool + empty(const std::string& s) + { + return s.empty(); + } +} + +//ifdef CXXOPTS_USE_UNICODE +#endif + +namespace cxxopts +{ + namespace + { +#ifdef _WIN32 + const std::string LQUOTE("\'"); + const std::string RQUOTE("\'"); +#else + const std::string LQUOTE("‘"); + const std::string RQUOTE("’"); +#endif + } + + class Value : public std::enable_shared_from_this + { + public: + + virtual ~Value() = default; + + virtual + std::shared_ptr + clone() const = 0; + + virtual void + parse(const std::string& text) const = 0; + + virtual void + parse() const = 0; + + virtual bool + has_default() const = 0; + + virtual bool + is_container() const = 0; + + virtual bool + has_implicit() const = 0; + + virtual std::string + get_default_value() const = 0; + + virtual std::string + get_implicit_value() const = 0; + + virtual std::shared_ptr + default_value(const std::string& value) = 0; + + virtual std::shared_ptr + implicit_value(const std::string& value) = 0; + + virtual std::shared_ptr + no_implicit_value() = 0; + + virtual bool + is_boolean() const = 0; + }; + + class OptionException : public std::exception + { + public: + OptionException(const std::string& message) + : m_message(message) + { + } + + virtual const char* + what() const noexcept + { + return m_message.c_str(); + } + + private: + std::string m_message; + }; + + class OptionSpecException : public OptionException + { + public: + + OptionSpecException(const std::string& message) + : OptionException(message) + { + } + }; + + class OptionParseException : public OptionException + { + public: + OptionParseException(const std::string& message) + : OptionException(message) + { + } + }; + + class option_exists_error : public OptionSpecException + { + public: + option_exists_error(const std::string& option) + : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") + { + } + }; + + class invalid_option_format_error : public OptionSpecException + { + public: + invalid_option_format_error(const std::string& format) + : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) + { + } + }; + + class option_syntax_exception : public OptionParseException { + public: + option_syntax_exception(const std::string& text) + : OptionParseException("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") + { + } + }; + + class option_not_exists_exception : public OptionParseException + { + public: + option_not_exists_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") + { + } + }; + + class missing_argument_exception : public OptionParseException + { + public: + missing_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is missing an argument" + ) + { + } + }; + + class option_requires_argument_exception : public OptionParseException + { + public: + option_requires_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " requires an argument" + ) + { + } + }; + + class option_not_has_argument_exception : public OptionParseException + { + public: + option_not_has_argument_exception + ( + const std::string& option, + const std::string& arg + ) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + + " does not take an argument, but argument " + + LQUOTE + arg + RQUOTE + " given" + ) + { + } + }; + + class option_not_present_exception : public OptionParseException + { + public: + option_not_present_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") + { + } + }; + + class argument_incorrect_type : public OptionParseException + { + public: + argument_incorrect_type + ( + const std::string& arg + ) + : OptionParseException( + "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + ) + { + } + }; + + class option_required_exception : public OptionParseException + { + public: + option_required_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is required but not present" + ) + { + } + }; + + namespace values + { + namespace + { + std::basic_regex integer_pattern + ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); + std::basic_regex truthy_pattern + ("(t|T)(rue)?|1"); + std::basic_regex falsy_pattern + ("(f|F)(alse)?|0"); + } + + namespace detail + { + template + struct SignedCheck; + + template + struct SignedCheck + { + template + void + operator()(bool negative, U u, const std::string& text) + { + if (negative) + { + if (u > static_cast((std::numeric_limits::min)())) + { + throw argument_incorrect_type(text); + } + } + else + { + if (u > static_cast((std::numeric_limits::max)())) + { + throw argument_incorrect_type(text); + } + } + } + }; + + template + struct SignedCheck + { + template + void + operator()(bool, U, const std::string&) {} + }; + + template + void + check_signed_range(bool negative, U value, const std::string& text) + { + SignedCheck::is_signed>()(negative, value, text); + } + } + + template + R + checked_negate(T&& t, const std::string&, std::true_type) + { + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + return -static_cast(t-1)-1; + } + + template + T + checked_negate(T&&, const std::string& text, std::false_type) + { + throw argument_incorrect_type(text); + } + + template + void + integer_parser(const std::string& text, T& value) + { + std::smatch match; + std::regex_match(text, match, integer_pattern); + + if (match.length() == 0) + { + throw argument_incorrect_type(text); + } + + if (match.length(4) > 0) + { + value = 0; + return; + } + + using US = typename std::make_unsigned::type; + + constexpr bool is_signed = std::numeric_limits::is_signed; + const bool negative = match.length(1) > 0; + const uint8_t base = match.length(2) > 0 ? 16 : 10; + + auto value_match = match[3]; + + US result = 0; + + for (auto iter = value_match.first; iter != value_match.second; ++iter) + { + US digit = 0; + + if (*iter >= '0' && *iter <= '9') + { + digit = static_cast(*iter - '0'); + } + else if (base == 16 && *iter >= 'a' && *iter <= 'f') + { + digit = static_cast(*iter - 'a' + 10); + } + else if (base == 16 && *iter >= 'A' && *iter <= 'F') + { + digit = static_cast(*iter - 'A' + 10); + } + else + { + throw argument_incorrect_type(text); + } + + US next = result * base + digit; + if (result > next) + { + throw argument_incorrect_type(text); + } + + result = next; + } + + detail::check_signed_range(negative, result, text); + + if (negative) + { + value = checked_negate(result, + text, + std::integral_constant()); + } + else + { + value = static_cast(result); + } + } + + template + void stringstream_parser(const std::string& text, T& value) + { + std::stringstream in(text); + in >> value; + if (!in) { + throw argument_incorrect_type(text); + } + } + + inline + void + parse_value(const std::string& text, uint8_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int8_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint16_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int16_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint32_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int32_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint64_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int64_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, bool& value) + { + std::smatch result; + std::regex_match(text, result, truthy_pattern); + + if (!result.empty()) + { + value = true; + return; + } + + std::regex_match(text, result, falsy_pattern); + if (!result.empty()) + { + value = false; + return; + } + + throw argument_incorrect_type(text); + } + + inline + void + parse_value(const std::string& text, std::string& value) + { + value = text; + } + + // The fallback parser. It uses the stringstream parser to parse all types + // that have not been overloaded explicitly. It has to be placed in the + // source code before all other more specialized templates. + template + void + parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); + } + + template + void + parse_value(const std::string& text, std::vector& value) + { + std::stringstream in(text); + std::string token; + while(in.eof() == false && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { + T v; + parse_value(token, v); + value.emplace_back(std::move(v)); + } + } + +#ifdef CXXOPTS_HAS_OPTIONAL + template + void + parse_value(const std::string& text, std::optional& value) + { + T result; + parse_value(text, result); + value = std::move(result); + } +#endif + + template + struct type_is_container + { + static constexpr bool value = false; + }; + + template + struct type_is_container> + { + static constexpr bool value = true; + }; + + template + class abstract_value : public Value + { + using Self = abstract_value; + + public: + abstract_value() + : m_result(std::make_shared()) + , m_store(m_result.get()) + { + } + + abstract_value(T* t) + : m_store(t) + { + } + + virtual ~abstract_value() = default; + + abstract_value(const abstract_value& rhs) + { + if (rhs.m_result) + { + m_result = std::make_shared(); + m_store = m_result.get(); + } + else + { + m_store = rhs.m_store; + } + + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } + + void + parse(const std::string& text) const + { + parse_value(text, *m_store); + } + + bool + is_container() const + { + return type_is_container::value; + } + + void + parse() const + { + parse_value(m_default_value, *m_store); + } + + bool + has_default() const + { + return m_default; + } + + bool + has_implicit() const + { + return m_implicit; + } + + std::shared_ptr + default_value(const std::string& value) + { + m_default = true; + m_default_value = value; + return shared_from_this(); + } + + std::shared_ptr + implicit_value(const std::string& value) + { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } + + std::shared_ptr + no_implicit_value() + { + m_implicit = false; + return shared_from_this(); + } + + std::string + get_default_value() const + { + return m_default_value; + } + + std::string + get_implicit_value() const + { + return m_implicit_value; + } + + bool + is_boolean() const + { + return std::is_same::value; + } + + const T& + get() const + { + if (m_store == nullptr) + { + return *m_result; + } + else + { + return *m_store; + } + } + + protected: + std::shared_ptr m_result; + T* m_store; + + bool m_default = false; + bool m_implicit = false; + + std::string m_default_value; + std::string m_implicit_value; + }; + + template + class standard_value : public abstract_value + { + public: + using abstract_value::abstract_value; + + std::shared_ptr + clone() const + { + return std::make_shared>(*this); + } + }; + + template <> + class standard_value : public abstract_value + { + public: + ~standard_value() = default; + + standard_value() + { + set_default_and_implicit(); + } + + standard_value(bool* b) + : abstract_value(b) + { + set_default_and_implicit(); + } + + std::shared_ptr + clone() const + { + return std::make_shared>(*this); + } + + private: + + void + set_default_and_implicit() + { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } + }; + } + + template + std::shared_ptr + value() + { + return std::make_shared>(); + } + + template + std::shared_ptr + value(T& t) + { + return std::make_shared>(&t); + } + + class OptionAdder; + + class OptionDetails + { + public: + OptionDetails + ( + const std::string& short_, + const std::string& long_, + const String& desc, + std::shared_ptr val + ) + : m_short(short_) + , m_long(long_) + , m_desc(desc) + , m_value(val) + , m_count(0) + { + } + + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_count(rhs.m_count) + { + m_value = rhs.m_value->clone(); + } + + OptionDetails(OptionDetails&& rhs) = default; + + const String& + description() const + { + return m_desc; + } + + const Value& value() const { + return *m_value; + } + + std::shared_ptr + make_storage() const + { + return m_value->clone(); + } + + const std::string& + short_name() const + { + return m_short; + } + + const std::string& + long_name() const + { + return m_long; + } + + private: + std::string m_short; + std::string m_long; + String m_desc; + std::shared_ptr m_value; + int m_count; + }; + + struct HelpOptionDetails + { + std::string s; + std::string l; + String desc; + bool has_default; + std::string default_value; + bool has_implicit; + std::string implicit_value; + std::string arg_help; + bool is_container; + bool is_boolean; + }; + + struct HelpGroupDetails + { + std::string name; + std::string description; + std::vector options; + }; + + class OptionValue + { + public: + void + parse + ( + std::shared_ptr details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->parse(text); + } + + void + parse_default(std::shared_ptr details) + { + ensure_value(details); + m_default = true; + m_value->parse(); + } + + size_t + count() const noexcept + { + return m_count; + } + + // TODO: maybe default options should count towards the number of arguments + bool + has_default() const noexcept + { + return m_default; + } + + template + const T& + as() const + { + if (m_value == nullptr) { + throw std::domain_error("No value"); + } + +#ifdef CXXOPTS_NO_RTTI + return static_cast&>(*m_value).get(); +#else + return dynamic_cast&>(*m_value).get(); +#endif + } + + private: + void + ensure_value(std::shared_ptr details) + { + if (m_value == nullptr) + { + m_value = details->make_storage(); + } + } + + std::shared_ptr m_value; + size_t m_count = 0; + bool m_default = false; + }; + + class KeyValue + { + public: + KeyValue(std::string key_, std::string value_) + : m_key(std::move(key_)) + , m_value(std::move(value_)) + { + } + + const + std::string& + key() const + { + return m_key; + } + + const + std::string& + value() const + { + return m_value; + } + + template + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } + + private: + std::string m_key; + std::string m_value; + }; + + class ParseResult + { + public: + + ParseResult( + const std::shared_ptr< + std::unordered_map> + >, + std::vector, + bool allow_unrecognised, + int&, char**&); + + size_t + count(const std::string& o) const + { + auto iter = m_options->find(o); + if (iter == m_options->end()) + { + return 0; + } + + auto riter = m_results.find(iter->second); + + return riter->second.count(); + } + + const OptionValue& + operator[](const std::string& option) const + { + auto iter = m_options->find(option); + + if (iter == m_options->end()) + { + throw option_not_present_exception(option); + } + + auto riter = m_results.find(iter->second); + + return riter->second; + } + + const std::vector& + arguments() const + { + return m_sequential; + } + + private: + + void + parse(int& argc, char**& argv); + + void + add_to_option(const std::string& option, const std::string& arg); + + bool + consume_positional(std::string a); + + void + parse_option + ( + std::shared_ptr value, + const std::string& name, + const std::string& arg = "" + ); + + void + parse_default(std::shared_ptr details); + + void + checked_parse_arg + ( + int argc, + char* argv[], + int& current, + std::shared_ptr value, + const std::string& name + ); + + const std::shared_ptr< + std::unordered_map> + > m_options; + std::vector m_positional; + std::vector::iterator m_next_positional; + std::unordered_set m_positional_set; + std::unordered_map, OptionValue> m_results; + + bool m_allow_unrecognised; + + std::vector m_sequential; + }; + + class Options + { + typedef std::unordered_map> + OptionMap; + public: + + Options(std::string program, std::string help_string = "") + : m_program(std::move(program)) + , m_help_string(toLocalString(std::move(help_string))) + , m_custom_help("[OPTION...]") + , m_positional_help("positional parameters") + , m_show_positional(false) + , m_allow_unrecognised(false) + , m_options(std::make_shared()) + , m_next_positional(m_positional.end()) + { + } + + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } + + Options& + custom_help(std::string help_text) + { + m_custom_help = std::move(help_text); + return *this; + } + + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } + + Options& + allow_unrecognised_options() + { + m_allow_unrecognised = true; + return *this; + } + + ParseResult + parse(int& argc, char**& argv); + + OptionAdder + add_options(std::string group = ""); + + void + add_option + ( + const std::string& group, + const std::string& s, + const std::string& l, + std::string desc, + std::shared_ptr value, + std::string arg_help + ); + + //parse positional arguments into the given option + void + parse_positional(std::string option); + + void + parse_positional(std::vector options); + + void + parse_positional(std::initializer_list options); + + template + void + parse_positional(Iterator begin, Iterator end) { + parse_positional(std::vector{begin, end}); + } + + std::string + help(const std::vector& groups = {}) const; + + const std::vector + groups() const; + + const HelpGroupDetails& + group_help(const std::string& group) const; + + private: + + void + add_one_option + ( + const std::string& option, + std::shared_ptr details + ); + + String + help_one_group(const std::string& group) const; + + void + generate_group_help + ( + String& result, + const std::vector& groups + ) const; + + void + generate_all_groups_help(String& result) const; + + std::string m_program; + String m_help_string; + std::string m_custom_help; + std::string m_positional_help; + bool m_show_positional; + bool m_allow_unrecognised; + + std::shared_ptr m_options; + std::vector m_positional; + std::vector::iterator m_next_positional; + std::unordered_set m_positional_set; + + //mapping from groups to help options + std::map m_help; + }; + + class OptionAdder + { + public: + + OptionAdder(Options& options, std::string group) + : m_options(options), m_group(std::move(group)) + { + } + + OptionAdder& + operator() + ( + const std::string& opts, + const std::string& desc, + std::shared_ptr value + = ::cxxopts::value(), + std::string arg_help = "" + ); + + private: + Options& m_options; + std::string m_group; + }; + + namespace + { + constexpr int OPTION_LONGEST = 30; + constexpr int OPTION_DESC_GAP = 2; + + std::basic_regex option_matcher + ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); + + std::basic_regex option_specifier + ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); + + String + format_option + ( + const HelpOptionDetails& o + ) + { + auto& s = o.s; + auto& l = o.l; + + String result = " "; + + if (s.size() > 0) + { + result += "-" + toLocalString(s) + ","; + } + else + { + result += " "; + } + + if (l.size() > 0) + { + result += " --" + toLocalString(l); + } + + auto arg = o.arg_help.size() > 0 ? toLocalString(o.arg_help) : "arg"; + + if (!o.is_boolean) + { + if (o.has_implicit) + { + result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]"; + } + else + { + result += " " + arg; + } + } + + return result; + } + + String + format_description + ( + const HelpOptionDetails& o, + size_t start, + size_t width + ) + { + auto desc = o.desc; + + if (o.has_default && (!o.is_boolean || o.default_value != "false")) + { + desc += toLocalString(" (default: " + o.default_value + ")"); + } + + String result; + + auto current = std::begin(desc); + auto startLine = current; + auto lastSpace = current; + + auto size = size_t{}; + + while (current != std::end(desc)) + { + if (*current == ' ') + { + lastSpace = current; + } + + if (*current == '\n') + { + startLine = current + 1; + lastSpace = startLine; + } + else if (size > width) + { + if (lastSpace == startLine) + { + stringAppend(result, startLine, current + 1); + stringAppend(result, "\n"); + stringAppend(result, start, ' '); + startLine = current + 1; + lastSpace = startLine; + } + else + { + stringAppend(result, startLine, lastSpace); + stringAppend(result, "\n"); + stringAppend(result, start, ' '); + startLine = lastSpace + 1; + lastSpace = startLine; + } + size = 0; + } + else + { + ++size; + } + + ++current; + } + + //append whatever is left + stringAppend(result, startLine, current); + + return result; + } + } + +inline +ParseResult::ParseResult +( + const std::shared_ptr< + std::unordered_map> + > options, + std::vector positional, + bool allow_unrecognised, + int& argc, char**& argv +) +: m_options(options) +, m_positional(std::move(positional)) +, m_next_positional(m_positional.begin()) +, m_allow_unrecognised(allow_unrecognised) +{ + parse(argc, argv); +} + +inline +OptionAdder +Options::add_options(std::string group) +{ + return OptionAdder(*this, std::move(group)); +} + +inline +OptionAdder& +OptionAdder::operator() +( + const std::string& opts, + const std::string& desc, + std::shared_ptr value, + std::string arg_help +) +{ + std::match_results result; + std::regex_match(opts.c_str(), result, option_specifier); + + if (result.empty()) + { + throw invalid_option_format_error(opts); + } + + const auto& short_match = result[2]; + const auto& long_match = result[3]; + + if (!short_match.length() && !long_match.length()) + { + throw invalid_option_format_error(opts); + } else if (long_match.length() == 1 && short_match.length()) + { + throw invalid_option_format_error(opts); + } + + auto option_names = [] + ( + const std::sub_match& short_, + const std::sub_match& long_ + ) + { + if (long_.length() == 1) + { + return std::make_tuple(long_.str(), short_.str()); + } + else + { + return std::make_tuple(short_.str(), long_.str()); + } + }(short_match, long_match); + + m_options.add_option + ( + m_group, + std::get<0>(option_names), + std::get<1>(option_names), + desc, + value, + std::move(arg_help) + ); + + return *this; +} + +inline +void +ParseResult::parse_default(std::shared_ptr details) +{ + m_results[details].parse_default(details); +} + +inline +void +ParseResult::parse_option +( + std::shared_ptr value, + const std::string& /*name*/, + const std::string& arg +) +{ + auto& result = m_results[value]; + result.parse(value, arg); + + m_sequential.emplace_back(value->long_name(), arg); +} + +inline +void +ParseResult::checked_parse_arg +( + int argc, + char* argv[], + int& current, + std::shared_ptr value, + const std::string& name +) +{ + if (current + 1 >= argc) + { + if (value->value().has_implicit()) + { + parse_option(value, name, value->value().get_implicit_value()); + } + else + { + throw missing_argument_exception(name); + } + } + else + { + if (value->value().has_implicit()) + { + parse_option(value, name, value->value().get_implicit_value()); + } + else + { + parse_option(value, name, argv[current + 1]); + ++current; + } + } +} + +inline +void +ParseResult::add_to_option(const std::string& option, const std::string& arg) +{ + auto iter = m_options->find(option); + + if (iter == m_options->end()) + { + throw option_not_exists_exception(option); + } + + parse_option(iter->second, option, arg); +} + +inline +bool +ParseResult::consume_positional(std::string a) +{ + while (m_next_positional != m_positional.end()) + { + auto iter = m_options->find(*m_next_positional); + if (iter != m_options->end()) + { + auto& result = m_results[iter->second]; + if (!iter->second->value().is_container()) + { + if (result.count() == 0) + { + add_to_option(*m_next_positional, a); + ++m_next_positional; + return true; + } + else + { + ++m_next_positional; + continue; + } + } + else + { + add_to_option(*m_next_positional, a); + return true; + } + } + else + { + throw option_not_exists_exception(*m_next_positional); + } + } + + return false; +} + +inline +void +Options::parse_positional(std::string option) +{ + parse_positional(std::vector{std::move(option)}); +} + +inline +void +Options::parse_positional(std::vector options) +{ + m_positional = std::move(options); + m_next_positional = m_positional.begin(); + + m_positional_set.insert(m_positional.begin(), m_positional.end()); +} + +inline +void +Options::parse_positional(std::initializer_list options) +{ + parse_positional(std::vector(std::move(options))); +} + +inline +ParseResult +Options::parse(int& argc, char**& argv) +{ + ParseResult result(m_options, m_positional, m_allow_unrecognised, argc, argv); + return result; +} + +inline +void +ParseResult::parse(int& argc, char**& argv) +{ + int current = 1; + + int nextKeep = 1; + + bool consume_remaining = false; + + while (current != argc) + { + if (strcmp(argv[current], "--") == 0) + { + consume_remaining = true; + ++current; + break; + } + + std::match_results result; + std::regex_match(argv[current], result, option_matcher); + + if (result.empty()) + { + //not a flag + + // but if it starts with a `-`, then it's an error + if (argv[current][0] == '-' && argv[current][1] != '\0') { + if (!m_allow_unrecognised) { + throw option_syntax_exception(argv[current]); + } + } + + //if true is returned here then it was consumed, otherwise it is + //ignored + if (consume_positional(argv[current])) + { + } + else + { + argv[nextKeep] = argv[current]; + ++nextKeep; + } + //if we return from here then it was parsed successfully, so continue + } + else + { + //short or long option? + if (result[4].length() != 0) + { + const std::string& s = result[4]; + + for (std::size_t i = 0; i != s.size(); ++i) + { + std::string name(1, s[i]); + auto iter = m_options->find(name); + + if (iter == m_options->end()) + { + if (m_allow_unrecognised) + { + continue; + } + else + { + //error + throw option_not_exists_exception(name); + } + } + + auto value = iter->second; + + if (i + 1 == s.size()) + { + //it must be the last argument + checked_parse_arg(argc, argv, current, value, name); + } + else if (value->value().has_implicit()) + { + parse_option(value, name, value->value().get_implicit_value()); + } + else + { + //error + throw option_requires_argument_exception(name); + } + } + } + else if (result[1].length() != 0) + { + const std::string& name = result[1]; + + auto iter = m_options->find(name); + + if (iter == m_options->end()) + { + if (m_allow_unrecognised) + { + // keep unrecognised options in argument list, skip to next argument + argv[nextKeep] = argv[current]; + ++nextKeep; + ++current; + continue; + } + else + { + //error + throw option_not_exists_exception(name); + } + } + + auto opt = iter->second; + + //equals provided for long option? + if (result[2].length() != 0) + { + //parse the option given + + parse_option(opt, name, result[3]); + } + else + { + //parse the next argument + checked_parse_arg(argc, argv, current, opt, name); + } + } + + } + + ++current; + } + + for (auto& opt : *m_options) + { + auto& detail = opt.second; + auto& value = detail->value(); + + auto& store = m_results[detail]; + + if(value.has_default() && !store.count() && !store.has_default()){ + parse_default(detail); + } + } + + if (consume_remaining) + { + while (current < argc) + { + if (!consume_positional(argv[current])) { + break; + } + ++current; + } + + //adjust argv for any that couldn't be swallowed + while (current != argc) { + argv[nextKeep] = argv[current]; + ++nextKeep; + ++current; + } + } + + argc = nextKeep; + +} + +inline +void +Options::add_option +( + const std::string& group, + const std::string& s, + const std::string& l, + std::string desc, + std::shared_ptr value, + std::string arg_help +) +{ + auto stringDesc = toLocalString(std::move(desc)); + auto option = std::make_shared(s, l, stringDesc, value); + + if (s.size() > 0) + { + add_one_option(s, option); + } + + if (l.size() > 0) + { + add_one_option(l, option); + } + + //add the help details + auto& options = m_help[group]; + + options.options.emplace_back(HelpOptionDetails{s, l, stringDesc, + value->has_default(), value->get_default_value(), + value->has_implicit(), value->get_implicit_value(), + std::move(arg_help), + value->is_container(), + value->is_boolean()}); +} + +inline +void +Options::add_one_option +( + const std::string& option, + std::shared_ptr details +) +{ + auto in = m_options->emplace(option, details); + + if (!in.second) + { + throw option_exists_error(option); + } +} + +inline +String +Options::help_one_group(const std::string& g) const +{ + typedef std::vector> OptionHelp; + + auto group = m_help.find(g); + if (group == m_help.end()) + { + return ""; + } + + OptionHelp format; + + size_t longest = 0; + + String result; + + if (!g.empty()) + { + result += toLocalString(" " + g + " options:\n"); + } + + for (const auto& o : group->second.options) + { + if (m_positional_set.find(o.l) != m_positional_set.end() && + !m_show_positional) + { + continue; + } + + auto s = format_option(o); + longest = (std::max)(longest, stringLength(s)); + format.push_back(std::make_pair(s, String())); + } + + longest = (std::min)(longest, static_cast(OPTION_LONGEST)); + + //widest allowed description + auto allowed = size_t{76} - longest - OPTION_DESC_GAP; + + auto fiter = format.begin(); + for (const auto& o : group->second.options) + { + if (m_positional_set.find(o.l) != m_positional_set.end() && + !m_show_positional) + { + continue; + } + + auto d = format_description(o, longest + OPTION_DESC_GAP, allowed); + + result += fiter->first; + if (stringLength(fiter->first) > longest) + { + result += '\n'; + result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' ')); + } + else + { + result += toLocalString(std::string(longest + OPTION_DESC_GAP - + stringLength(fiter->first), + ' ')); + } + result += d; + result += '\n'; + + ++fiter; + } + + return result; +} + +inline +void +Options::generate_group_help +( + String& result, + const std::vector& print_groups +) const +{ + for (size_t i = 0; i != print_groups.size(); ++i) + { + const String& group_help_text = help_one_group(print_groups[i]); + if (empty(group_help_text)) + { + continue; + } + result += group_help_text; + if (i < print_groups.size() - 1) + { + result += '\n'; + } + } +} + +inline +void +Options::generate_all_groups_help(String& result) const +{ + std::vector all_groups; + all_groups.reserve(m_help.size()); + + for (auto& group : m_help) + { + all_groups.push_back(group.first); + } + + generate_group_help(result, all_groups); +} + +inline +std::string +Options::help(const std::vector& help_groups) const +{ + String result = m_help_string + "\nUsage:\n " + + toLocalString(m_program) + " " + toLocalString(m_custom_help); + + if (m_positional.size() > 0 && m_positional_help.size() > 0) { + result += " " + toLocalString(m_positional_help); + } + + result += "\n\n"; + + if (help_groups.size() == 0) + { + generate_all_groups_help(result); + } + else + { + generate_group_help(result, help_groups); + } + + return toUTF8String(result); +} + +inline +const std::vector +Options::groups() const +{ + std::vector g; + + std::transform( + m_help.begin(), + m_help.end(), + std::back_inserter(g), + [] (const std::map::value_type& pair) + { + return pair.first; + } + ); + + return g; +} + +inline +const HelpGroupDetails& +Options::group_help(const std::string& group) const +{ + return m_help.at(group); +} + +} + +#endif //CXXOPTS_HPP_INCLUDED diff --git a/example/hello.c b/example/hello.c new file mode 100644 index 0000000..6288162 --- /dev/null +++ b/example/hello.c @@ -0,0 +1,184 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * minimal example filesystem using high-level API + * + * Compile with: + * + * gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello + * + * ## Source code ## + * \include hello.c + */ + + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include +#include +#include + +/* + * Command line options + * + * We can't set default values for the char* fields here because + * fuse_opt_parse would attempt to free() them when the user specifies + * different values on the command line. + */ +static struct options { + const char *filename; + const char *contents; + int show_help; +} options; + +#define OPTION(t, p) \ + { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--name=%s", filename), + OPTION("--contents=%s", contents), + OPTION("-h", show_help), + OPTION("--help", show_help), + FUSE_OPT_END +}; + +static void *hello_init(struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + (void) conn; + cfg->kernel_cache = 1; + + /* Test setting flags the old way */ + fuse_set_feature_flag(conn, FUSE_CAP_ASYNC_READ); + fuse_unset_feature_flag(conn, FUSE_CAP_ASYNC_READ); + + return NULL; +} + +static int hello_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void) fi; + int res = 0; + + memset(stbuf, 0, sizeof(struct stat)); + if (strcmp(path, "/") == 0) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + } else if (strcmp(path+1, options.filename) == 0) { + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = strlen(options.contents); + } else + res = -ENOENT; + + return res; +} + +static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + (void) offset; + (void) fi; + (void) flags; + + if (strcmp(path, "/") != 0) + return -ENOENT; + + filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS); + + return 0; +} + +static int hello_open(const char *path, struct fuse_file_info *fi) +{ + if (strcmp(path+1, options.filename) != 0) + return -ENOENT; + + if ((fi->flags & O_ACCMODE) != O_RDONLY) + return -EACCES; + + return 0; +} + +static int hello_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + size_t len; + (void) fi; + if(strcmp(path+1, options.filename) != 0) + return -ENOENT; + + len = strlen(options.contents); + if (offset < len) { + if (offset + size > len) + size = len - offset; + memcpy(buf, options.contents + offset, size); + } else + size = 0; + + return size; +} + +static const struct fuse_operations hello_oper = { + .init = hello_init, + .getattr = hello_getattr, + .readdir = hello_readdir, + .open = hello_open, + .read = hello_read, +}; + +static void show_help(const char *progname) +{ + printf("usage: %s [options] \n\n", progname); + printf("File-system specific options:\n" + " --name= Name of the \"hello\" file\n" + " (default: \"hello\")\n" + " --contents= Contents \"hello\" file\n" + " (default \"Hello, World!\\n\")\n" + "\n"); +} + +int main(int argc, char *argv[]) +{ + int ret; + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + + /* Set defaults -- we have to use strdup so that + fuse_opt_parse can free the defaults if other + values are specified */ + options.filename = strdup("hello"); + options.contents = strdup("Hello World!\n"); + + /* Parse options */ + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + /* When --help is specified, first print our own file-system + specific help text, then signal fuse_main to show + additional help (by adding `--help` to the options again) + without usage: line (by setting argv[0] to the empty + string) */ + if (options.show_help) { + show_help(argv[0]); + assert(fuse_opt_add_arg(&args, "--help") == 0); + args.argv[0][0] = '\0'; + } + + ret = fuse_main(args.argc, args.argv, &hello_oper, NULL); + fuse_opt_free_args(&args); + return ret; +} diff --git a/example/hello_ll.c b/example/hello_ll.c new file mode 100644 index 0000000..bd5fecd --- /dev/null +++ b/example/hello_ll.c @@ -0,0 +1,297 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * minimal example filesystem using low-level API + * + * Compile with: + * + * gcc -Wall hello_ll.c `pkg-config fuse3 --cflags --libs` -o hello_ll + * + * Note: If the pkg-config command fails due to the absence of the fuse3.pc + * file, you should configure the path to the fuse3.pc file in the + * PKG_CONFIG_PATH variable. + * + * ## Source code ## + * \include hello_ll.c + */ + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12) + +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *hello_str = "Hello World!\n"; +static const char *hello_name = "hello"; + +static int hello_stat(fuse_ino_t ino, struct stat *stbuf) +{ + stbuf->st_ino = ino; + switch (ino) { + case 1: + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + break; + + case 2: + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = strlen(hello_str); + break; + + default: + return -1; + } + return 0; +} + +static void hello_ll_init(void *userdata, struct fuse_conn_info *conn) +{ + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; + + /* Test setting flags the old way */ + conn->want = FUSE_CAP_ASYNC_READ; + conn->want &= ~FUSE_CAP_ASYNC_READ; +} + +static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct stat stbuf; + + (void) fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (hello_stat(ino, &stbuf) == -1) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, 1.0); +} + +static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e; + + if (parent != 1 || strcmp(name, hello_name) != 0) + fuse_reply_err(req, ENOENT); + else { + memset(&e, 0, sizeof(e)); + e.ino = 2; + e.attr_timeout = 1.0; + e.entry_timeout = 1.0; + hello_stat(e.ino, &e.attr); + + fuse_reply_entry(req, &e); + } +} + +struct dirbuf { + char *p; + size_t size; +}; + +static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name, + fuse_ino_t ino) +{ + struct stat stbuf; + size_t oldsize = b->size; + b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); + b->p = (char *) realloc(b->p, b->size); + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = ino; + fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, + b->size); +} + +#define min(x, y) ((x) < (y) ? (x) : (y)) + +static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, + off_t off, size_t maxsize) +{ + if (off < bufsize) + return fuse_reply_buf(req, buf + off, + min(bufsize - off, maxsize)); + else + return fuse_reply_buf(req, NULL, 0); +} + +static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + (void) fi; + + if (ino != 1) + fuse_reply_err(req, ENOTDIR); + else { + struct dirbuf b; + + memset(&b, 0, sizeof(b)); + dirbuf_add(req, &b, ".", 1); + dirbuf_add(req, &b, "..", 1); + dirbuf_add(req, &b, hello_name, 2); + reply_buf_limited(req, b.p, b.size, off, size); + free(b.p); + } +} + +static void hello_ll_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + if (ino != 2) + fuse_reply_err(req, EISDIR); + else if ((fi->flags & O_ACCMODE) != O_RDONLY) + fuse_reply_err(req, EACCES); + else + fuse_reply_open(req, fi); +} + +static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + (void) fi; + + assert(ino == 2); + reply_buf_limited(req, hello_str, strlen(hello_str), off, size); +} + +static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size) +{ + (void)size; + assert(ino == 1 || ino == 2); + if (strcmp(name, "hello_ll_getxattr_name") == 0) + { + const char *buf = "hello_ll_getxattr_value"; + fuse_reply_buf(req, buf, strlen(buf)); + } + else + { + fuse_reply_err(req, ENOTSUP); + } +} + +static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags) +{ + (void)flags; + (void)size; + assert(ino == 1 || ino == 2); + const char* exp_val = "hello_ll_setxattr_value"; + if (strcmp(name, "hello_ll_setxattr_name") == 0 && + strlen(exp_val) == size && + strncmp(value, exp_val, size) == 0) + { + fuse_reply_err(req, 0); + } + else + { + fuse_reply_err(req, ENOTSUP); + } +} + +static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name) +{ + assert(ino == 1 || ino == 2); + if (strcmp(name, "hello_ll_removexattr_name") == 0) + { + fuse_reply_err(req, 0); + } + else + { + fuse_reply_err(req, ENOTSUP); + } +} + +static const struct fuse_lowlevel_ops hello_ll_oper = { + .init = hello_ll_init, + .lookup = hello_ll_lookup, + .getattr = hello_ll_getattr, + .readdir = hello_ll_readdir, + .open = hello_ll_open, + .read = hello_ll_read, + .setxattr = hello_ll_setxattr, + .getxattr = hello_ll_getxattr, + .removexattr = hello_ll_removexattr, +}; + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + struct fuse_loop_config *config; + int ret = -1; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + if (opts.show_help) { + printf("usage: %s [options] \n\n", argv[0]); + fuse_cmdline_help(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + if(opts.mountpoint == NULL) { + printf("usage: %s [options] \n", argv[0]); + printf(" %s --help\n", argv[0]); + ret = 1; + goto err_out1; + } + + se = fuse_session_new(&args, &hello_ll_oper, + sizeof(hello_ll_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + /* Block until ctrl+c or fusermount -u */ + if (opts.singlethread) + ret = fuse_session_loop(se); + else { + config = fuse_loop_cfg_create(); + fuse_loop_cfg_set_clone_fd(config, opts.clone_fd); + fuse_loop_cfg_set_max_threads(config, opts.max_threads); + ret = fuse_session_loop_mt(se, config); + fuse_loop_cfg_destroy(config); + config = NULL; + } + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} diff --git a/example/hello_ll_uds.c b/example/hello_ll_uds.c new file mode 100644 index 0000000..b0a6e9c --- /dev/null +++ b/example/hello_ll_uds.c @@ -0,0 +1,367 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + Copyright (C) 2022 Tofik Sonono + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * minimal example filesystem using low-level API and a custom io. This custom + * io is implemented using UNIX domain sockets (of type SOCK_STREAM) + * + * Compile with: + * + * gcc -Wall hello_ll_uds.c `pkg-config fuse3 --cflags --libs` -o hello_ll_uds + * + * ## Source code ## + * \include hello_ll.c + */ + +#define FUSE_USE_VERSION 34 + + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *hello_str = "Hello World!\n"; +static const char *hello_name = "hello"; + +static int hello_stat(fuse_ino_t ino, struct stat *stbuf) +{ + stbuf->st_ino = ino; + switch (ino) { + case 1: + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + break; + + case 2: + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = strlen(hello_str); + break; + + default: + return -1; + } + return 0; +} + +static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct stat stbuf; + + (void) fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (hello_stat(ino, &stbuf) == -1) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, 1.0); +} + +static void hello_ll_init(void *userdata, struct fuse_conn_info *conn) +{ + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; +} + +static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e; + + if (parent != 1 || strcmp(name, hello_name) != 0) + fuse_reply_err(req, ENOENT); + else { + memset(&e, 0, sizeof(e)); + e.ino = 2; + e.attr_timeout = 1.0; + e.entry_timeout = 1.0; + hello_stat(e.ino, &e.attr); + + fuse_reply_entry(req, &e); + } +} + +struct dirbuf { + char *p; + size_t size; +}; + +static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name, + fuse_ino_t ino) +{ + struct stat stbuf; + size_t oldsize = b->size; + b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); + b->p = (char *) realloc(b->p, b->size); + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = ino; + fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, + b->size); +} + +#define min(x, y) ((x) < (y) ? (x) : (y)) + +static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, + off_t off, size_t maxsize) +{ + if (off < bufsize) + return fuse_reply_buf(req, buf + off, + min(bufsize - off, maxsize)); + else + return fuse_reply_buf(req, NULL, 0); +} + +static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + (void) fi; + + if (ino != 1) + fuse_reply_err(req, ENOTDIR); + else { + struct dirbuf b; + + memset(&b, 0, sizeof(b)); + dirbuf_add(req, &b, ".", 1); + dirbuf_add(req, &b, "..", 1); + dirbuf_add(req, &b, hello_name, 2); + reply_buf_limited(req, b.p, b.size, off, size); + free(b.p); + } +} + +static void hello_ll_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + if (ino != 2) + fuse_reply_err(req, EISDIR); + else if ((fi->flags & O_ACCMODE) != O_RDONLY) + fuse_reply_err(req, EACCES); + else + fuse_reply_open(req, fi); +} + +static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + (void) fi; + + assert(ino == 2); + reply_buf_limited(req, hello_str, strlen(hello_str), off, size); +} + +static const struct fuse_lowlevel_ops hello_ll_oper = { + .init = hello_ll_init, + .lookup = hello_ll_lookup, + .getattr = hello_ll_getattr, + .readdir = hello_ll_readdir, + .open = hello_ll_open, + .read = hello_ll_read, +}; + +static int create_socket(const char *socket_path) { + struct sockaddr_un addr; + + if (strnlen(socket_path, sizeof(addr.sun_path)) >= + sizeof(addr.sun_path)) { + printf("Socket path may not be longer than %zu characters\n", + sizeof(addr.sun_path) - 1); + return -1; + } + + if (remove(socket_path) == -1 && errno != ENOENT) { + printf("Could not delete previous socket file entry at %s. Error: " + "%s\n", socket_path, strerror(errno)); + return -1; + } + + memset(&addr, 0, sizeof(struct sockaddr_un)); + strcpy(addr.sun_path, socket_path); + + int sfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sfd == -1) { + printf("Could not create socket. Error: %s\n", strerror(errno)); + return -1; + } + + addr.sun_family = AF_UNIX; + if (bind(sfd, (struct sockaddr *) &addr, + sizeof(struct sockaddr_un)) == -1) { + printf("Could not bind socket. Error: %s\n", strerror(errno)); + return -1; + } + + if (listen(sfd, 1) == -1) + return -1; + + printf("Awaiting connection on socket at %s...\n", socket_path); + int cfd = accept(sfd, NULL, NULL); + if (cfd == -1) { + printf("Could not accept connection. Error: %s\n", + strerror(errno)); + return -1; + } else { + printf("Accepted connection!\n"); + } + return cfd; +} + +static ssize_t stream_writev(int fd, struct iovec *iov, int count, + void *userdata) { + (void)userdata; + + ssize_t written = 0; + int cur = 0; + for (;;) { + written = writev(fd, iov+cur, count-cur); + if (written < 0) + return written; + + while (cur < count && written >= iov[cur].iov_len) + written -= iov[cur++].iov_len; + if (cur == count) + break; + + iov[cur].iov_base = (char *)iov[cur].iov_base + written; + iov[cur].iov_len -= written; + } + return written; +} + + +static ssize_t readall(int fd, void *buf, size_t len) { + size_t count = 0; + + while (count < len) { + int i = read(fd, (char *)buf + count, len - count); + if (!i) + break; + + if (i < 0) + return i; + + count += i; + } + return count; +} + +static ssize_t stream_read(int fd, void *buf, size_t buf_len, void *userdata) { + (void)userdata; + + int res = readall(fd, buf, sizeof(struct fuse_in_header)); + if (res == -1) + return res; + + + uint32_t packet_len = ((struct fuse_in_header *)buf)->len; + if (packet_len > buf_len) + return -1; + + int prev_res = res; + + res = readall(fd, (char *)buf + sizeof(struct fuse_in_header), + packet_len - sizeof(struct fuse_in_header)); + + return (res == -1) ? res : (res + prev_res); +} + +static ssize_t stream_splice_send(int fdin, off_t *offin, int fdout, + off_t *offout, size_t len, + unsigned int flags, void *userdata) { + (void)userdata; + + size_t count = 0; + while (count < len) { + int i = splice(fdin, offin, fdout, offout, len - count, flags); + if (i < 1) + return i; + + count += i; + } + return count; +} + +static void fuse_cmdline_help_uds(void) +{ + printf(" -h --help print help\n" + " -V --version print version\n" + " -d -o debug enable debug output (implies -f)\n"); +} + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + const struct fuse_custom_io io = { + .writev = stream_writev, + .read = stream_read, + .splice_receive = NULL, + .splice_send = stream_splice_send, + }; + int cfd = -1; + int ret = -1; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + if (opts.show_help) { + printf("usage: %s [options]\n\n", argv[0]); + fuse_cmdline_help_uds(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + se = fuse_session_new(&args, &hello_ll_oper, + sizeof(hello_ll_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + cfd = create_socket("/tmp/libfuse-hello-ll.sock"); + if (cfd == -1) + goto err_out3; + + if (fuse_session_custom_io(se, &io, cfd) != 0) + goto err_out3; + + /* Block until ctrl+c */ + ret = fuse_session_loop(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} diff --git a/example/invalidate_path.c b/example/invalidate_path.c new file mode 100644 index 0000000..fad43ea --- /dev/null +++ b/example/invalidate_path.c @@ -0,0 +1,292 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2016 Nikolaus Rath + (C) 2017 EditShare LLC + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. + */ + +/** @file + * + * This example implements a file system with two files: + * * 'current-time', whose contents change dynamically: + * it always contains the current time (same as in + * notify_inval_inode.c). + * * 'growing', whose size changes dynamically, growing + * by 1 byte after each update. This aims to check + * if cached file metadata is also invalidated. + * + * ## Compilation ## + * + * gcc -Wall invalidate_path.c `pkg-config fuse3 --cflags --libs` -o invalidate_path + * + * ## Source code ## + * \include invalidate_path.c + */ + +#define FUSE_USE_VERSION 34 + +#include +#include /* for fuse_cmdline_opts */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* We can't actually tell the kernel that there is no + timeout, so we just send a big value */ +#define NO_TIMEOUT 500000 + +#define MAX_STR_LEN 128 +#define TIME_FILE_NAME "current_time" +#define TIME_FILE_INO 2 +#define GROW_FILE_NAME "growing" +#define GROW_FILE_INO 3 + +static char time_file_contents[MAX_STR_LEN]; +static size_t grow_file_size; + +/* Command line parsing */ +struct options { + int no_notify; + int update_interval; +}; +static struct options options = { + .no_notify = 0, + .update_interval = 1, +}; + +#define OPTION(t, p) { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--no-notify", no_notify), + OPTION("--update-interval=%d", update_interval), + FUSE_OPT_END +}; + +static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) +{ + (void) conn; + cfg->entry_timeout = NO_TIMEOUT; + cfg->attr_timeout = NO_TIMEOUT; + cfg->negative_timeout = 0; + + return NULL; +} + +static int xmp_getattr(const char *path, + struct stat *stbuf, struct fuse_file_info* fi) { + (void) fi; + if (strcmp(path, "/") == 0) { + stbuf->st_ino = 1; + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } else if (strcmp(path, "/" TIME_FILE_NAME) == 0) { + stbuf->st_ino = TIME_FILE_INO; + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = strlen(time_file_contents); + } else if (strcmp(path, "/" GROW_FILE_NAME) == 0) { + stbuf->st_ino = GROW_FILE_INO; + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = grow_file_size; + } else { + return -ENOENT; + } + + return 0; +} + +static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) { + (void) fi; + (void) offset; + (void) flags; + if (strcmp(path, "/") != 0) { + return -ENOTDIR; + } else { + (void) filler; + (void) buf; + struct stat file_stat; + xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL); + filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS); + xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL); + filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS); + return 0; + } +} + +static int xmp_open(const char *path, struct fuse_file_info *fi) { + (void) path; + /* Make cache persistent even if file is closed, + this makes it easier to see the effects */ + fi->keep_cache = 1; + return 0; +} + +static int xmp_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) { + (void) fi; + (void) offset; + if (strcmp(path, "/" TIME_FILE_NAME) == 0) { + int file_length = strlen(time_file_contents); + int to_copy = offset + size <= file_length + ? size + : file_length - offset; + memcpy(buf, time_file_contents, to_copy); + return to_copy; + } else { + assert(strcmp(path, "/" GROW_FILE_NAME) == 0); + int to_copy = offset + size <= grow_file_size + ? size + : grow_file_size - offset; + memset(buf, 'x', to_copy); + return to_copy; + } +} + +static const struct fuse_operations xmp_oper = { + .init = xmp_init, + .getattr = xmp_getattr, + .readdir = xmp_readdir, + .open = xmp_open, + .read = xmp_read, +}; + +static void update_fs(void) { + static int count = 0; + struct tm *now; + time_t t; + t = time(NULL); + now = localtime(&t); + assert(now != NULL); + + int time_file_size = strftime(time_file_contents, MAX_STR_LEN, + "The current time is %H:%M:%S\n", now); + assert(time_file_size != 0); + + grow_file_size = count++; +} + +static int invalidate(struct fuse *fuse, const char *path) { + int status = fuse_invalidate_path(fuse, path); + if (status == -ENOENT) { + return 0; + } else { + return status; + } +} + +static void* update_fs_loop(void *data) { + struct fuse *fuse = (struct fuse*) data; + + while (1) { + update_fs(); + if (!options.no_notify) { + assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0); + assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0); + } + sleep(options.update_interval); + } + return NULL; +} + +static void show_help(const char *progname) +{ + printf("usage: %s [options] \n\n", progname); + printf("File-system specific options:\n" + " --update-interval= Update-rate of file system contents\n" + " --no-notify Disable kernel notifications\n" + "\n"); +} + +int main(int argc, char *argv[]) { + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse *fuse; + struct fuse_cmdline_opts opts; + struct fuse_loop_config config; + int res; + + /* Initialize the files */ + update_fs(); + + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + + if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + res = 0; + goto out1; + } else if (opts.show_help) { + show_help(argv[0]); + fuse_cmdline_help(); + fuse_lib_help(&args); + res = 0; + goto out1; + } else if (!opts.mountpoint) { + fprintf(stderr, "error: no mountpoint specified\n"); + res = 1; + goto out1; + } + + fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL); + if (fuse == NULL) { + res = 1; + goto out1; + } + + if (fuse_mount(fuse,opts.mountpoint) != 0) { + res = 1; + goto out2; + } + + if (fuse_daemonize(opts.foreground) != 0) { + res = 1; + goto out3; + } + + pthread_t updater; /* Start thread to update file contents */ + int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse); + if (ret != 0) { + fprintf(stderr, "pthread_create failed with %s\n", strerror(ret)); + return 1; + }; + + struct fuse_session *se = fuse_get_session(fuse); + if (fuse_set_signal_handlers(se) != 0) { + res = 1; + goto out3; + } + + if (opts.singlethread) + res = fuse_loop(fuse); + else { + config.clone_fd = opts.clone_fd; + config.max_idle_threads = opts.max_idle_threads; + res = fuse_loop_mt(fuse, &config); + } + if (res) + res = 1; + + fuse_remove_signal_handlers(se); +out3: + fuse_unmount(fuse); +out2: + fuse_destroy(fuse); +out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + return res; +} diff --git a/example/ioctl.c b/example/ioctl.c new file mode 100644 index 0000000..2548fd3 --- /dev/null +++ b/example/ioctl.c @@ -0,0 +1,230 @@ +/* + FUSE fioc: FUSE ioctl example + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * @tableofcontents + * + * This example illustrates how to write a FUSE file system that can + * process (a restricted set of) ioctls. It can be tested with the + * ioctl_client.c program. + * + * Compile with: + * + * gcc -Wall ioctl.c `pkg-config fuse3 --cflags --libs` -o ioctl + * + * ## Source code ## + * \include ioctl.c + */ + +#define FUSE_USE_VERSION 35 + +#include +#include +#include +#include +#include +#include +#include + +#include "ioctl.h" + +#define FIOC_NAME "fioc" + +enum { + FIOC_NONE, + FIOC_ROOT, + FIOC_FILE, +}; + +static void *fioc_buf; +static size_t fioc_size; + +static int fioc_resize(size_t new_size) +{ + void *new_buf; + + if (new_size == fioc_size) + return 0; + + new_buf = realloc(fioc_buf, new_size); + if (!new_buf && new_size) + return -ENOMEM; + + if (new_size > fioc_size) + memset(new_buf + fioc_size, 0, new_size - fioc_size); + + fioc_buf = new_buf; + fioc_size = new_size; + + return 0; +} + +static int fioc_expand(size_t new_size) +{ + if (new_size > fioc_size) + return fioc_resize(new_size); + return 0; +} + +static int fioc_file_type(const char *path) +{ + if (strcmp(path, "/") == 0) + return FIOC_ROOT; + if (strcmp(path, "/" FIOC_NAME) == 0) + return FIOC_FILE; + return FIOC_NONE; +} + +static int fioc_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void) fi; + stbuf->st_uid = getuid(); + stbuf->st_gid = getgid(); + stbuf->st_atime = stbuf->st_mtime = time(NULL); + + switch (fioc_file_type(path)) { + case FIOC_ROOT: + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + break; + case FIOC_FILE: + stbuf->st_mode = S_IFREG | 0644; + stbuf->st_nlink = 1; + stbuf->st_size = fioc_size; + break; + case FIOC_NONE: + return -ENOENT; + } + + return 0; +} + +static int fioc_open(const char *path, struct fuse_file_info *fi) +{ + (void) fi; + + if (fioc_file_type(path) != FIOC_NONE) + return 0; + return -ENOENT; +} + +static int fioc_do_read(char *buf, size_t size, off_t offset) +{ + if (offset >= fioc_size) + return 0; + + if (size > fioc_size - offset) + size = fioc_size - offset; + + memcpy(buf, fioc_buf + offset, size); + + return size; +} + +static int fioc_read(const char *path, char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + (void) fi; + + if (fioc_file_type(path) != FIOC_FILE) + return -EINVAL; + + return fioc_do_read(buf, size, offset); +} + +static int fioc_do_write(const char *buf, size_t size, off_t offset) +{ + if (fioc_expand(offset + size)) + return -ENOMEM; + + memcpy(fioc_buf + offset, buf, size); + + return size; +} + +static int fioc_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + (void) fi; + + if (fioc_file_type(path) != FIOC_FILE) + return -EINVAL; + + return fioc_do_write(buf, size, offset); +} + +static int fioc_truncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + (void) fi; + if (fioc_file_type(path) != FIOC_FILE) + return -EINVAL; + + return fioc_resize(size); +} + +static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + (void) fi; + (void) offset; + (void) flags; + + if (fioc_file_type(path) != FIOC_ROOT) + return -ENOENT; + + filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS); + + return 0; +} + +static int fioc_ioctl(const char *path, unsigned int cmd, void *arg, + struct fuse_file_info *fi, unsigned int flags, void *data) +{ + (void) arg; + (void) fi; + (void) flags; + + if (fioc_file_type(path) != FIOC_FILE) + return -EINVAL; + + if (flags & FUSE_IOCTL_COMPAT) + return -ENOSYS; + + switch (cmd) { + case FIOC_GET_SIZE: + *(size_t *)data = fioc_size; + return 0; + + case FIOC_SET_SIZE: + fioc_resize(*(size_t *)data); + return 0; + } + + return -EINVAL; +} + +static const struct fuse_operations fioc_oper = { + .getattr = fioc_getattr, + .readdir = fioc_readdir, + .truncate = fioc_truncate, + .open = fioc_open, + .read = fioc_read, + .write = fioc_write, + .ioctl = fioc_ioctl, +}; + +int main(int argc, char *argv[]) +{ + return fuse_main(argc, argv, &fioc_oper, NULL); +} diff --git a/example/ioctl.h b/example/ioctl.h new file mode 100644 index 0000000..ca7e975 --- /dev/null +++ b/example/ioctl.h @@ -0,0 +1,42 @@ +/* + FUSE-ioctl: ioctl support for FUSE + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * @tableofcontents + * + * Header file to share definitions between the ioctl.c example file + * system and the ioctl_client.c test program. + * + * \include ioctl.h + */ + + +#include +#include +#include + +enum { + FIOC_GET_SIZE = _IOR('E', 0, size_t), + FIOC_SET_SIZE = _IOW('E', 1, size_t), + + /* + * The following two ioctls don't follow usual encoding rules + * and transfer variable amount of data. + */ + FIOC_READ = _IO('E', 2), + FIOC_WRITE = _IO('E', 3), +}; + +struct fioc_rw_arg { + off_t offset; + void *buf; + size_t size; + size_t prev_size; /* out param for previous total size */ + size_t new_size; /* out param for new total size */ +}; diff --git a/example/ioctl_client.c b/example/ioctl_client.c new file mode 100644 index 0000000..99e2750 --- /dev/null +++ b/example/ioctl_client.c @@ -0,0 +1,74 @@ +/* + FUSE fioclient: FUSE ioctl example client + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This program tests the ioctl.c example file systsem. + * + * Compile with: + * + * gcc -Wall ioctl_client.c -o ioctl_client + * + * ## Source code ## + * \include ioctl_client.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ioctl.h" + +const char *usage = +"Usage: fioclient FIOC_FILE [size]\n" +"\n" +"Get size if is omitted, set size otherwise\n" +"\n"; + +int main(int argc, char **argv) +{ + size_t size; + int fd; + int ret = 0; + + if (argc < 2) { + fprintf(stderr, "%s", usage); + return 1; + } + + fd = open(argv[1], O_RDWR); + if (fd < 0) { + perror("open"); + return 1; + } + + if (argc == 2) { + if (ioctl(fd, FIOC_GET_SIZE, &size)) { + perror("ioctl"); + ret = 1; + goto out; + } + printf("%zu\n", size); + } else { + size = strtoul(argv[2], NULL, 0); + if (ioctl(fd, FIOC_SET_SIZE, &size)) { + perror("ioctl"); + ret = 1; + goto out; + } + } +out: + close(fd); + return ret; +} diff --git a/example/memfs_ll.cc b/example/memfs_ll.cc new file mode 100644 index 0000000..7055a43 --- /dev/null +++ b/example/memfs_ll.cc @@ -0,0 +1,1139 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2024 DataDirect Networks. + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LINUX_LIMITS_H +#include +#endif + +#define MEMFS_ATTR_TIMEOUT 0.0 +#define MEMFS_ENTRY_TIMEOUT 0.0 + +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) + +class Inodes; +class Inode; +class Dentry; + +static void memfs_panic(std::string_view message); + +struct DirHandle { + std::vector > entries; + size_t offset; + + DirHandle(const std::vector > + &entries) + : entries(entries) + , offset(0) + { + } +}; + +class Inode { + private: + uint64_t ino; // Unique inode number + std::string name; + bool is_dir_; + time_t ctime; + time_t mtime; + time_t atime; + mode_t mode; + std::vector content; + std::vector dentries; + mutable std::mutex mutex; + uint64_t nlookup; + mutable std::mutex attr_mutex; + std::atomic nlink; + uid_t uid; + gid_t gid; + + friend class Inodes; + + public: + Inode(uint64_t ino, const std::string &n, bool dir) + : ino(ino) + , name(n) + , is_dir_(dir) + , ctime(time(NULL)) + , mtime(ctime) + , atime(ctime) + , mode(dir ? S_IFDIR | 0755 : S_IFREG | 0644) + , nlookup(1) + , nlink(dir ? 2 : 1) + , uid(0) + , gid(0) + { + } + + uint64_t get_ino() const + { + return ino; + } + + // Method to lock the mutex + void lock() const + { + mutex.lock(); + } + + // Method to unlock the mutex + void unlock() const + { + mutex.unlock(); + } + + void inc_lookup() + { + std::lock_guard lock(mutex); + nlookup++; + } + + uint64_t dec_lookup(uint64_t count) + { + std::unique_lock lock(mutex); + if (nlookup < count) { + lock.unlock(); + memfs_panic("Lookup count mismatch detected"); + } + nlookup -= count; + return nlookup; + } + + const std::string &get_name() const + { + return name; + } + bool is_dir() const + { + return is_dir_; + } + time_t get_ctime() const + { + return ctime; + } + time_t get_mtime() const + { + return mtime; + } + mode_t get_mode() const + { + return mode; + } + + size_t content_size() const + { + return content.size(); + } + + void read_content(char *buf, size_t size, off_t offset) const + { + size_t bytes_to_read = std::min(size, content.size() - (size_t)offset); + std::copy(content.begin() + offset, + content.begin() + offset + bytes_to_read, buf); + } + + void write_content(const char *buf, size_t size, off_t offset) + { + std::lock_guard lock(mutex); + if (offset + size > content.size()) { + content.resize(offset + size); + } + std::copy(buf, buf + size, content.begin() + offset); + mtime = time(NULL); + } + + void set_uid(uid_t _uid) + { + std::lock_guard lock(attr_mutex); + uid = _uid; + } + + void set_gid(gid_t _gid) + { + std::lock_guard lock(attr_mutex); + gid = _gid; + } + + void set_mode(mode_t new_mode) + { + std::lock_guard lock(attr_mutex); + mode = new_mode; + } + + void set_atime(const struct timespec &_atime) + { + std::lock_guard lock(attr_mutex); + atime = _atime.tv_sec; + } + + void set_mtime(const struct timespec &_mtime) + { + std::lock_guard lock(attr_mutex); + mtime = _mtime.tv_sec; + } + + void truncate(off_t size) + { + std::lock_guard attr_lock(attr_mutex); + if (size < content.size()) { + content.resize(size); + } else if (size > content.size()) { + content.resize(size, 0); + } + mtime = time(NULL); + } + + void get_attr(struct stat *stbuf) const + { + std::lock_guard lock(attr_mutex); + stbuf->st_ino = ino; + stbuf->st_mode = mode; + stbuf->st_nlink = nlink; + stbuf->st_uid = uid; + stbuf->st_gid = gid; + stbuf->st_size = content.size(); + stbuf->st_blocks = DIV_ROUND_UP(content.size(), 512); + stbuf->st_atime = atime; + stbuf->st_mtime = mtime; + stbuf->st_ctime = ctime; + } + + bool is_empty() const + { + return dentries.empty(); + } + + void inc_nlink() + { + nlink++; + } + + nlink_t dec_nlink() + { + nlink_t old_value = + nlink.fetch_sub(1, std::memory_order_relaxed); + if (old_value == 0) { + memfs_panic("Attempting to decrement nlink below zero"); + } + return old_value - 1; + } + + /** + * Methods that need Dentry knowledge + */ + int add_child_locked(const std::string &name, Dentry *child_dentry); + int add_child(const std::string &name, Dentry *child_dentry); + int remove_child(const std::string &name); + std::vector > + get_children() const; + Dentry *find_child_locked(const std::string &name) const; + Dentry *find_child(const std::string &name) const; +}; + +class Dentry { + public: + std::string name; + Inode *inode; + + Dentry(const std::string &n, Inode *i) + : name(n) + , inode(i) + { + } + + uint64_t get_ino() const + { + return inode->get_ino(); + } + bool is_dir() const + { + return inode->is_dir(); + } + const std::string &get_name() const + { + return name; + } + + time_t get_ctime() const + { + return inode->get_ctime(); + } + time_t get_mtime() const + { + return inode->get_mtime(); + } + mode_t get_mode() const + { + return inode->get_mode(); + } + size_t content_size() const + { + return inode->content_size(); + } + + Inode *get_inode() const + { + return inode; + } + + void inc_lookup() + { + inode->inc_lookup(); + } +}; + +class Inodes { + private: + std::unordered_map > inodes; + mutable std::shared_mutex inodes_mutex; + std::atomic next_ino{ FUSE_ROOT_ID + 1 }; + std::mutex mutex; + + public: + Inodes() + { + auto root = std::make_unique(FUSE_ROOT_ID, "/", true); + root->mode = S_IFDIR | 0755; + root->nlink = 2; // . and .. + inodes[FUSE_ROOT_ID] = std::move(root); + } + + // New lock method + void lock() + { + inodes_mutex.lock(); + } + + // New unlock method + void unlock() + { + inodes_mutex.unlock(); + } + + void erase_locked(Inode *inode) + { + if (inode) { + inodes.erase(inode->get_ino()); + } + } + + void erase(Inode *inode) + { + std::unique_lock lock(inodes_mutex); + erase_locked(inode); + } + + Inode *find_locked(fuse_ino_t ino) + { + auto it = inodes.find(ino); + if (it == inodes.end()) { + return nullptr; + } + return it->second.get(); + } + + Inode *find(fuse_ino_t ino) + { + std::shared_lock lock(inodes_mutex); + return find_locked(ino); + } + + Inode *create(const std::string &name, bool is_dir, mode_t mode) + { + std::unique_lock lock(inodes_mutex); + + uint64_t ino = next_ino.fetch_add(1, std::memory_order_relaxed); + auto new_inode = std::make_unique(ino, name, is_dir); + new_inode->set_mode(mode); + + auto [it, inserted] = inodes.emplace(ino, std::move(new_inode)); + + if (!inserted) { + // This should never happen, but let's handle it just in case + return nullptr; + } + + return it->second.get(); + } + + size_t size() + { + std::lock_guard lock(mutex); + return inodes.size(); + } +}; + +int Inode::add_child_locked(const std::string &name, Dentry *child_dentry) +{ + if (!is_dir_) { + return ENOTDIR; + } + + // Check if a child with this name already exists + auto it = std::find_if(dentries.begin(), dentries.end(), + [&name](const Dentry *dentry) { + return dentry->get_name() == name; + }); + + if (it != dentries.end()) { + return EEXIST; + } + + dentries.push_back(child_dentry); + if (child_dentry->is_dir()) { + nlink++; + } + return 0; +} + +int Inode::add_child(const std::string &name, Dentry *child_dentry) +{ + std::lock_guard lock(mutex); + return add_child_locked(name, child_dentry); +} + +int Inode::remove_child(const std::string &name) +{ + if (!is_dir_) { + return ENOTDIR; + } + + auto it = std::find_if(dentries.begin(), dentries.end(), + [&name](const Dentry *dentry) { + return dentry->get_name() == name; + }); + + if (it == dentries.end()) { + return ENOENT; + } + + Dentry *child_dentry = *it; + dentries.erase(it); + + if (child_dentry->is_dir()) { + nlink--; + } + + delete child_dentry; + return 0; +} +Dentry *Inode::find_child_locked(const std::string &name) const +{ + if (!is_dir_) { + return nullptr; + } + + auto it = std::find_if(dentries.begin(), dentries.end(), + [&name](const Dentry *dentry) { + return dentry->get_name() == name; + }); + + return (it != dentries.end()) ? *it : nullptr; +} + +Dentry *Inode::find_child(const std::string &name) const +{ + std::lock_guard lock(mutex); + return find_child_locked(name); +} + +std::vector > Inode::get_children() const +{ + if (!is_dir_) { + return {}; // Return an empty vector if this is not a directory + } + + std::vector > children; + children.reserve(dentries.size()); + + for (size_t i = 0; i < dentries.size(); ++i) { + const Dentry *dentry = dentries[i]; + std::string name = dentry->get_name(); + children.emplace_back(name, dentry); + } + + return children; +} +static Inodes Inodes; + +static void memfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + auto *parentInode = Inodes.find(parent); + + if (!parentInode) { + fuse_reply_err(req, ENOENT); + return; + } + + if (!parentInode->is_dir()) { + fuse_reply_err(req, ENOTDIR); + return; + } + + Dentry *child = parentInode->find_child(name); + if (!child) { + fuse_reply_err(req, ENOENT); + return; + } + + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + e.ino = child->get_ino(); + e.attr_timeout = MEMFS_ATTR_TIMEOUT; + e.entry_timeout = MEMFS_ENTRY_TIMEOUT; + e.attr.st_ino = child->get_ino(); + e.attr.st_mode = child->get_mode(); + e.attr.st_nlink = child->is_dir() ? 2 : 1; + + child->inc_lookup(); + + fuse_reply_entry(req, &e); +} + +static void memfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + fuse_ino_t actual_ino = fi ? fi->fh : ino; + if (actual_ino == 0) { + fuse_reply_err(req, EBADF); + return; + } + + auto *inode_data = Inodes.find(actual_ino); + if (!inode_data) { + fuse_reply_err(req, ENOENT); + return; + } + + struct stat stbuf; + inode_data->get_attr(&stbuf); + stbuf.st_ino = actual_ino; // Ensure the correct inode number is set + + fuse_reply_attr(req, &stbuf, MEMFS_ATTR_TIMEOUT); +} + +static void memfs_create(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, struct fuse_file_info *fi) +{ + auto *parentInode = Inodes.find(parent); + if (!parentInode || !parentInode->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + if (parentInode->find_child(name)) { + fuse_reply_err(req, EEXIST); + return; + } + + Inode *new_inode = Inodes.create(name, false, mode); + if (!new_inode) { + fuse_reply_err(req, EIO); + return; + } + + // Create a new Dentry and add it to the parent + Dentry *new_dentry = new Dentry(name, new_inode); + + //std::cout << "Debug: Created new Dentry at address " + // << (void *)new_dentry << ", name: '" << name + // << "', inode address: " << (void *)new_inode << std::endl; + + parentInode->add_child(name, new_dentry); + + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + e.ino = new_inode->get_ino(); + e.attr_timeout = MEMFS_ATTR_TIMEOUT; + e.entry_timeout = MEMFS_ENTRY_TIMEOUT; + new_inode->get_attr(&e.attr); + + fi->fh = e.ino; + fuse_reply_create(req, &e, fi); +} + +static void memfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf, + size_t size, off_t offset, + [[maybe_unused]] struct fuse_file_info *fi) +{ + Inode *inode = Inodes.find(ino); + if (!inode) { + fuse_reply_err(req, ENOENT); + return; + } + + if (inode->is_dir()) { + fuse_reply_err(req, EISDIR); + return; + } + + inode->write_content(buf, size, offset); + fuse_reply_write(req, size); +} + +static void memfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, [[maybe_unused]] struct fuse_file_info *fi) +{ + Inode *inode = Inodes.find(ino); + if (!inode || inode->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + inode->lock(); + + if (offset >= inode->content_size()) { + fuse_reply_buf(req, nullptr, 0); + inode->unlock(); + return; + } + + std::vector content( + std::min(size, inode->content_size() - (size_t)offset)); + inode->read_content(content.data(), content.size(), offset); + + inode->unlock(); + + fuse_reply_buf(req, content.data(), content.size()); +} + +static void memfs_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + auto *inode_data = Inodes.find(ino); + if (!inode_data || inode_data->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + // Use the inode number as the file handle + fi->fh = ino; + fuse_reply_open(req, fi); +} + +static void memfs_opendir(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + auto *inode = Inodes.find(ino); + if (!inode || !inode->is_dir()) { + fuse_reply_err(req, ENOTDIR); + return; + } + + // Create a new DirHandle + auto dir_handle = new DirHandle(inode->get_children()); + + // Store the pointer to the DirHandle in fi->fh + fi->fh = reinterpret_cast(dir_handle); + + fuse_reply_open(req, fi); +} + +static void memfs_readdir(fuse_req_t req, [[maybe_unused]] fuse_ino_t ino, + size_t size, off_t offset, struct fuse_file_info *fi) +{ + auto *dir_handle = reinterpret_cast(fi->fh); + if (!dir_handle) { + fuse_reply_err(req, EBADF); + return; + } + + char *buffer = new char[size]; + size_t buf_size = 0; + + for (off_t i = offset; + i < static_cast(dir_handle->entries.size()); ++i) { + const auto &entry = dir_handle->entries[i]; + const std::string &name = entry.first; + const Dentry *dentry = entry.second; + + struct stat stbuf; + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = dentry->get_inode()->get_ino(); + dentry->get_inode()->get_attr(&stbuf); + + size_t entry_size = fuse_add_direntry(req, nullptr, 0, + name.c_str(), nullptr, 0); + if (buf_size + entry_size > size) { + break; + } + + fuse_add_direntry(req, buffer + buf_size, size - buf_size, + name.c_str(), &stbuf, i + 1); + buf_size += entry_size; + } + + fuse_reply_buf(req, buffer, buf_size); + delete[] buffer; +} + +static void memfs_release(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + // No need to remove file handle + (void)fi; + (void)ino; + fuse_reply_err(req, 0); +} + +static void memfs_releasedir(fuse_req_t req, [[maybe_unused]] fuse_ino_t ino, + struct fuse_file_info *fi) +{ + auto *dir_handle = reinterpret_cast(fi->fh); + delete dir_handle; + fuse_reply_err(req, 0); +} + +static void memfs_panic(std::string_view message) +{ + std::cerr << "MEMFS PANIC: " << message << std::endl; + std::abort(); +} + +static void memfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + Inodes.lock(); + Inode *inode = Inodes.find_locked(ino); + uint64_t res; + if (inode) { + res = inode->dec_lookup(nlookup); + if (res == 0) + Inodes.erase_locked(inode); + } + Inodes.unlock(); + fuse_reply_none(req); +} + +static void memfs_forget_multi(fuse_req_t req, size_t count, + struct fuse_forget_data *forgets) +{ + for (size_t i = 0; i < count; i++) { + fuse_ino_t ino = forgets[i].ino; + uint64_t nlookup = forgets[i].nlookup; + auto *inode_data = Inodes.find(ino); + if (inode_data) { + inode_data->dec_lookup(nlookup); + } + } + fuse_reply_none(req); +} + +static void memfs_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int to_set, struct fuse_file_info *fi) +{ + fuse_ino_t actual_ino = fi ? fi->fh : ino; + if (actual_ino == 0) { + fuse_reply_err(req, EBADF); + return; + } + + auto *inode_data = Inodes.find(actual_ino); + if (!inode_data) { + fuse_reply_err(req, ENOENT); + return; + } + + inode_data->lock(); + + if (to_set & FUSE_SET_ATTR_MODE) + inode_data->set_mode(attr->st_mode); + if (to_set & FUSE_SET_ATTR_UID) + inode_data->set_uid(attr->st_uid); + if (to_set & FUSE_SET_ATTR_GID) + inode_data->set_gid(attr->st_gid); + if (to_set & FUSE_SET_ATTR_SIZE) + inode_data->truncate(attr->st_size); + if (to_set & FUSE_SET_ATTR_ATIME) + inode_data->set_atime(attr->st_atim); + if (to_set & FUSE_SET_ATTR_MTIME) + inode_data->set_mtime(attr->st_mtim); + + struct stat st; + inode_data->get_attr(&st); + inode_data->unlock(); + + fuse_reply_attr(req, &st, MEMFS_ATTR_TIMEOUT); +} + +static void memfs_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode) +{ + int error = 0; + Inode *parentInode = nullptr; + Inode *new_inode = nullptr; + Dentry *new_dentry = nullptr; + struct fuse_entry_param e; + + parentInode = Inodes.find(parent); + if (!parentInode || !parentInode->is_dir()) { + error = ENOENT; + goto out; + } + + new_inode = Inodes.create(name, true, mode | S_IFDIR); + if (!new_inode) { + error = EIO; + goto out; + } + + new_dentry = new Dentry(name, new_inode); + error = parentInode->add_child(name, new_dentry); + if (error != 0) { + goto out_cleanup; + } + + memset(&e, 0, sizeof(e)); + e.ino = new_inode->get_ino(); + e.attr_timeout = MEMFS_ATTR_TIMEOUT; + e.entry_timeout = MEMFS_ENTRY_TIMEOUT; + new_inode->get_attr(&e.attr); + +out: + if (error == 0) { + fuse_reply_entry(req, &e); + } else { + fuse_reply_err(req, error); + } + return; + +out_cleanup: + delete new_dentry; + Inodes.erase_locked(new_inode); + goto out; +} + +static void memfs_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + auto *parentInode = Inodes.find(parent); + if (!parentInode || !parentInode->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + parentInode->lock(); + + auto child_dentry = parentInode->find_child_locked(name); + if (child_dentry == nullptr) { + parentInode->unlock(); + fuse_reply_err(req, ENOENT); + return; + } + + Inode *child = child_dentry->get_inode(); + if (!child || !child->is_dir() || !child->is_empty()) { + parentInode->unlock(); + fuse_reply_err(req, child ? (child->is_empty() ? ENOTDIR : + ENOTEMPTY) : + ENOENT); + return; + } + + parentInode->remove_child(name); + child->dec_nlink(); // This should handle removal if nlink reaches 0 + + parentInode->unlock(); + + fuse_reply_err(req, 0); +} + +static void memfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + auto *parentInode = Inodes.find(parent); + if (!parentInode || !parentInode->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + parentInode->lock(); + + auto child_dentry = parentInode->find_child_locked(name); + if (child_dentry == nullptr) { + parentInode->unlock(); + fuse_reply_err(req, ENOENT); + return; + } + + Inode *child = child_dentry->get_inode(); + if (!child || child->is_dir()) { + parentInode->unlock(); + fuse_reply_err(req, child ? EISDIR : ENOENT); + return; + } + + parentInode->remove_child(name); + child->dec_nlink(); + + parentInode->unlock(); + + fuse_reply_err(req, 0); +} + +static void memfs_rename(fuse_req_t req, fuse_ino_t parent, const char *name, + fuse_ino_t newparent, const char *newname, + unsigned int flags) +{ + int error = 0; + Inode *parentInode = nullptr; + Inode *newparentInode = nullptr; + Dentry *child_dentry = nullptr; + Dentry *child_dentry_copy = nullptr; + Dentry *existing_dentry = nullptr; + +#if defined(RENAME_EXCHANGE) && defined(RENAME_NOREPLACE) + if (flags & (RENAME_EXCHANGE | RENAME_NOREPLACE)) { + fuse_reply_err(req, EINVAL); + return; + } +#else + (void)flags; +#endif + + Inodes.lock(); + + parentInode = Inodes.find_locked(parent); + newparentInode = Inodes.find_locked(newparent); + if (!parentInode || !parentInode->is_dir() || !newparentInode || + !newparentInode->is_dir()) { + error = ENOENT; + goto out_unlock_global; + } + + parentInode->lock(); + if (parent != newparent) { + newparentInode->lock(); + } + + child_dentry = parentInode->find_child_locked(name); + if (child_dentry == nullptr) { + error = ENOENT; + goto out_unlock; + } + + existing_dentry = newparentInode->find_child_locked(newname); + if (existing_dentry) { + if (existing_dentry->is_dir()) { + if (!existing_dentry->get_inode()->is_empty()) { + error = ENOTEMPTY; + goto out_unlock; + } + newparentInode->dec_nlink(); + } + newparentInode->remove_child(newname); + existing_dentry->get_inode()->dec_nlink(); + } + + child_dentry_copy = new Dentry(newname, child_dentry->get_inode()); + parentInode->remove_child(name); + newparentInode->add_child_locked(newname, child_dentry_copy); + +out_unlock: + parentInode->unlock(); + if (parent != newparent) { + newparentInode->unlock(); + } + +out_unlock_global: + Inodes.unlock(); + fuse_reply_err(req, error); +} + +static void memfs_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, + const char *newname) +{ + int error = 0; + Inode *src_inode = nullptr; + Inode *parent_inode = nullptr; + struct fuse_entry_param e; + std::unique_ptr new_dentry; + + Inodes.lock(); + + src_inode = Inodes.find(ino); + if (!src_inode) { + error = ENOENT; + goto out_unlock_global; + } + + parent_inode = Inodes.find(newparent); + if (!parent_inode || !parent_inode->is_dir()) { + error = ENOENT; + goto out_unlock_global; + } + + parent_inode->lock(); + + // Check if the new name already exists in the parent directory + if (parent_inode->find_child_locked(newname) != nullptr) { + error = EEXIST; + goto out_unlock_parent; + } + + src_inode->inc_nlink(); + + new_dentry = std::make_unique(newname, src_inode); + parent_inode->add_child(newname, new_dentry.get()); + + memset(&e, 0, sizeof(e)); + e.ino = ino; + e.attr_timeout = MEMFS_ATTR_TIMEOUT; + e.entry_timeout = MEMFS_ENTRY_TIMEOUT; + src_inode->get_attr(&e.attr); + +out_unlock_parent: + parent_inode->unlock(); + +out_unlock_global: + Inodes.unlock(); + + if (error == 0) { + fuse_reply_entry(req, &e); + } else { + fuse_reply_err(req, error); + } +} + +static void memfs_statfs(fuse_req_t req, [[maybe_unused]] fuse_ino_t ino) +{ + struct statvfs stbuf; + memset(&stbuf, 0, sizeof(stbuf)); + + stbuf.f_bsize = 4096; + stbuf.f_frsize = 4096; + stbuf.f_namemax = PATH_MAX; // Maximum filename length + + stbuf.f_files = Inodes.size(); // Total inodes (files + directories) + + stbuf.f_ffree = std::numeric_limits::max() - + stbuf.f_files; // Free inodes + + // Set total and free blocks + // For simplicity, we'll set a fixed total number of blocks and calculate free blocks based on used inodes + stbuf.f_blocks = 1000000; // arbitrary number, needs to be a parameter + stbuf.f_bfree = stbuf.f_blocks - + (stbuf.f_files * + 10); // Assume each file uses 10 blocks on average + stbuf.f_bavail = stbuf.f_bfree; + + stbuf.f_fsid = 0; + + // Set flags + stbuf.f_flag = ST_NOSUID; + + fuse_reply_statfs(req, &stbuf); +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +static const struct fuse_lowlevel_ops memfs_oper = { + .lookup = memfs_lookup, + .forget = memfs_forget, + .getattr = memfs_getattr, + .setattr = memfs_setattr, + .mkdir = memfs_mkdir, + .unlink = memfs_unlink, + .rmdir = memfs_rmdir, + .rename = memfs_rename, + .link = memfs_link, + .open = memfs_open, + .read = memfs_read, + .write = memfs_write, + .release = memfs_release, + .opendir = memfs_opendir, + .readdir = memfs_readdir, + .releasedir = memfs_releasedir, + .statfs = memfs_statfs, + .create = memfs_create, + .forget_multi = memfs_forget_multi, +}; +#pragma GCC diagnostic pop + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + int ret = -1; + struct fuse_loop_config *config = fuse_loop_cfg_create(); + + if (config == NULL) { + std::cerr << "fuse_loop_cfg_create failed" << std::endl; + exit(EXIT_FAILURE); + } + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + + if (opts.show_help) { + printf("usage: %s [options] \n\n", argv[0]); + printf("File-system specific options:\n" + " -o opt,[opt...] mount options\n" + " -h --help print help\n" + "\n"); + fuse_cmdline_help(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + if (opts.mountpoint == NULL) { + printf("usage: %s [options] \n", argv[0]); + printf(" %s --help\n", argv[0]); + ret = 1; + goto err_out1; + } + + se = fuse_session_new(&args, &memfs_oper, sizeof(memfs_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + ret = fuse_session_loop_mt(se, config); + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} diff --git a/example/meson.build b/example/meson.build new file mode 100644 index 0000000..21e8317 --- /dev/null +++ b/example/meson.build @@ -0,0 +1,44 @@ +examples = [ 'passthrough', 'passthrough_fh', + 'hello', 'hello_ll', + 'printcap', 'ioctl_client', 'poll_client', + 'ioctl', 'cuse', 'cuse_client' ] + +if not platform.endswith('bsd') and platform != 'dragonfly' + examples += [ 'passthrough_ll', 'hello_ll_uds' ] + + # According to Conrad Meyer , FreeBSD doesn't + # support mounting files, This is enforced in vfs_domount_first() + # with the v_type != VDIR check. + examples += [ 'null' ] +endif + +threaded_examples = [ 'notify_inval_inode', + 'invalidate_path', + 'notify_store_retrieve', + 'notify_inval_entry', + 'poll' ] + +foreach ex : examples + executable(ex, ex + '.c', + dependencies: [ libfuse_dep ], + install: false) +endforeach + + +foreach ex : threaded_examples + executable(ex, ex + '.c', + dependencies: [ thread_dep, libfuse_dep ], + install: false) +endforeach + +if platform != 'dragonfly' and add_languages('cpp', required : false) + executable('passthrough_hp', 'passthrough_hp.cc', + dependencies: [ thread_dep, libfuse_dep ], + install: false) + executable('memfs_ll', 'memfs_ll.cc', + dependencies: [ thread_dep, libfuse_dep ], + cpp_args : '-std=c++20', + install: false) +endif + +# TODO: Link passthrough_fh with ulockmgr if available diff --git a/example/notify_inval_entry.c b/example/notify_inval_entry.c new file mode 100644 index 0000000..5ab3c32 --- /dev/null +++ b/example/notify_inval_entry.c @@ -0,0 +1,427 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2016 Nikolaus Rath + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This example implements a file system with a single file whose + * file name changes dynamically to reflect the current time. + * + * It illustrates the use of the fuse_lowlevel_notify_inval_entry() and + * fuse_lowlevel_notify_expire_entry() functions. + * + * To see the effect, first start the file system with the + * ``--no-notify`` + * + * $ notify_inval_entry --update-interval=1 --timeout=30 --no-notify mnt/ + * + * Observe that `ls` always prints the correct directory contents + * (since `readdir` output is not cached):: + * + * $ ls mnt; sleep 1; ls mnt; sleep 1; ls mnt + * Time_is_15h_48m_33s current_time + * Time_is_15h_48m_34s current_time + * Time_is_15h_48m_35s current_time + * + * However, if you try to access a file by name the kernel will + * report that it still exists: + * + * $ file=$(ls mnt/); echo $file + * Time_is_15h_50m_09s + * $ sleep 5; stat mnt/$file + * File: ‘mnt/Time_is_15h_50m_09s’ + * Size: 32 Blocks: 0 IO Block: 4096 regular file + * Device: 2ah/42d Inode: 3 Links: 1 + * Access: (0444/-r--r--r--) Uid: ( 0/ root) Gid: ( 0/ root) + * Access: 1969-12-31 16:00:00.000000000 -0800 + * Modify: 1969-12-31 16:00:00.000000000 -0800 + * Change: 1969-12-31 16:00:00.000000000 -0800 + * Birth: - + * + * Only once the kernel cache timeout has been reached will the stat + * call fail: + * + * $ sleep 30; stat mnt/$file + * stat: cannot stat ‘mnt/Time_is_15h_50m_09s’: No such file or directory + * + * In contrast, if you enable notifications you will be unable to stat + * the file as soon as the file system updates its name: + * + * $ notify_inval_entry --update-interval=1 --timeout=30 mnt/ + * $ file=$(ls mnt/); stat mnt/$file + * File: ‘mnt/Time_is_20h_42m_11s’ + * Size: 0 Blocks: 0 IO Block: 4096 regular empty file + * Device: 2ah/42d Inode: 2 Links: 1 + * Access: (0000/----------) Uid: ( 0/ root) Gid: ( 0/ root) + * Access: 1969-12-31 16:00:00.000000000 -0800 + * Modify: 1969-12-31 16:00:00.000000000 -0800 + * Change: 1969-12-31 16:00:00.000000000 -0800 + * Birth: - + * $ sleep 1; stat mnt/$file + * stat: cannot stat ‘mnt/Time_is_20h_42m_11s’: No such file or directory + * + * To use the function fuse_lowlevel_notify_expire_entry() instead of + * fuse_lowlevel_notify_inval_entry(), use the command line option --only-expire + * + * Another possible command-line option is --inc-epoch, which will use the FUSE + * low-level function fuse_lowlevel_notify_increment_epoch() instead. This will + * function will force the invalidation of all dentries next time they are + * revalidated. Note that --inc-epoch and --only-expire options are mutually + * exclusive. + * + * ## Compilation ## + * + * gcc -Wall notify_inval_entry.c `pkg-config fuse3 --cflags --libs` -o notify_inval_entry + * + * ## Source code ## + * \include notify_inval_entry.c + */ + + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_STR_LEN 128 +static char file_name[MAX_STR_LEN]; +static fuse_ino_t file_ino = 2; +static int lookup_cnt = 0; +static pthread_t main_thread; + +/* Command line parsing */ +struct options { + int no_notify; + float timeout; + int update_interval; + int only_expire; + int inc_epoch; +}; +static struct options options = { + .timeout = 5, + .no_notify = 0, + .update_interval = 1, + .only_expire = 0, + .inc_epoch = 0, +}; + +#define OPTION(t, p) \ + { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--no-notify", no_notify), + OPTION("--update-interval=%d", update_interval), + OPTION("--timeout=%f", timeout), + OPTION("--only-expire", only_expire), + OPTION("--inc-epoch", inc_epoch), + FUSE_OPT_END +}; + +static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) { + stbuf->st_ino = ino; + if (ino == FUSE_ROOT_ID) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } + + else if (ino == file_ino) { + stbuf->st_mode = S_IFREG | 0000; + stbuf->st_nlink = 1; + stbuf->st_size = 0; + } + + else + return -1; + + return 0; +} + +static void tfs_init(void *userdata, struct fuse_conn_info *conn) { + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; +} + +static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, + const char *name) { + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + + if (parent != FUSE_ROOT_ID) + goto err_out; + else if (strcmp(name, file_name) == 0) { + e.ino = file_ino; + lookup_cnt++; + } else + goto err_out; + + e.attr_timeout = options.timeout; + e.entry_timeout = options.timeout; + if (tfs_stat(e.ino, &e.attr) != 0) + goto err_out; + fuse_reply_entry(req, &e); + return; + +err_out: + fuse_reply_err(req, ENOENT); +} + +static void tfs_forget (fuse_req_t req, fuse_ino_t ino, + uint64_t nlookup) { + (void) req; + if(ino == file_ino) + lookup_cnt -= nlookup; + else + assert(ino == FUSE_ROOT_ID); + fuse_reply_none(req); +} + +static void tfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) { + struct stat stbuf; + + (void) fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (tfs_stat(ino, &stbuf) != 0) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, options.timeout); +} + +struct dirbuf { + char *p; + size_t size; +}; + +static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name, + fuse_ino_t ino) { + struct stat stbuf; + size_t oldsize = b->size; + b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); + b->p = (char *) realloc(b->p, b->size); + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = ino; + fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, + b->size); +} + +#define min(x, y) ((x) < (y) ? (x) : (y)) + +static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, + off_t off, size_t maxsize) { + if (off < bufsize) + return fuse_reply_buf(req, buf + off, + min(bufsize - off, maxsize)); + else + return fuse_reply_buf(req, NULL, 0); +} + +static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) { + (void) fi; + + if (ino != FUSE_ROOT_ID) + fuse_reply_err(req, ENOTDIR); + else { + struct dirbuf b; + + memset(&b, 0, sizeof(b)); + dirbuf_add(req, &b, file_name, file_ino); + reply_buf_limited(req, b.p, b.size, off, size); + free(b.p); + } +} + +static const struct fuse_lowlevel_ops tfs_oper = { + .init = tfs_init, + .lookup = tfs_lookup, + .getattr = tfs_getattr, + .readdir = tfs_readdir, + .forget = tfs_forget, +}; + +static void update_fs(void) { + time_t t; + struct tm *now; + ssize_t ret; + + t = time(NULL); + now = localtime(&t); + assert(now != NULL); + + ret = strftime(file_name, MAX_STR_LEN, + "Time_is_%Hh_%Mm_%Ss", now); + assert(ret != 0); +} + +static void* update_fs_loop(void *data) { + struct fuse_session *se = (struct fuse_session*) data; + char *old_name; + int ret = 0; + + while(!fuse_session_exited(se)) { + old_name = strdup(file_name); + update_fs(); + + if (!options.no_notify && lookup_cnt) { + if(options.only_expire) { // expire entry + ret = fuse_lowlevel_notify_expire_entry + (se, FUSE_ROOT_ID, old_name, strlen(old_name)); + + // no kernel support + if (ret == -ENOSYS) { + printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n"); + break; + } + + // 1) ret == 0: successful expire of an existing entry + // 2) ret == -ENOENT: kernel has already expired the entry / + // entry does not exist anymore in the kernel + assert(ret == 0 || ret == -ENOENT); + } else if (options.inc_epoch) { // increment epoch + ret = fuse_lowlevel_notify_increment_epoch(se); + + if (ret == -ENOSYS) { + printf("fuse_lowlevel_notify_increment_epoch not supported by kernel\n"); + break; + } + assert(ret == 0); + } else { // invalidate entry + assert(fuse_lowlevel_notify_inval_entry + (se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0); + } + } + free(old_name); + sleep(options.update_interval); + } + + if (ret == -ENOSYS) { + printf("Exiting...\n"); + + fuse_session_exit(se); + // Make sure to exit now, rather than on next request from userspace + pthread_kill(main_thread, SIGPIPE); + } + + return NULL; +} + +static void show_help(const char *progname) +{ + printf("usage: %s [options] \n\n", progname); + printf("File-system specific options:\n" + " --timeout= Timeout for kernel caches\n" + " --update-interval= Update-rate of file system contents\n" + " --no-notify Disable kernel notifications\n" + " --only-expire Expire entries instead of invalidating them\n" + " --inc-epoch Increment epoch, invalidating all dentries\n" + "\n"); +} + +int main(int argc, char *argv[]) { + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + struct fuse_loop_config *config; + pthread_t updater; + int ret = -1; + + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + if (opts.show_help) { + show_help(argv[0]); + fuse_cmdline_help(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + if (options.only_expire && options.inc_epoch) { + printf("'only-expire' and 'inc-epoch' options are exclusive\n"); + ret = 0; + goto err_out1; + } + + /* Initial contents */ + update_fs(); + + se = fuse_session_new(&args, &tfs_oper, + sizeof(tfs_oper), &se); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + // Needed to ensure that the main thread continues/restarts processing as soon + // as the fuse session ends (immediately after calling fuse_session_exit() ) + // and not only on the next request from userspace + main_thread = pthread_self(); + + /* Start thread to update file contents */ + ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se); + if (ret != 0) { + fprintf(stderr, "pthread_create failed with %s\n", + strerror(ret)); + goto err_out3; + } + + /* Block until ctrl+c or fusermount -u */ + if (opts.singlethread) { + ret = fuse_session_loop(se); + } else { + config = fuse_loop_cfg_create(); + fuse_loop_cfg_set_clone_fd(config, opts.clone_fd); + fuse_loop_cfg_set_max_threads(config, opts.max_threads); + ret = fuse_session_loop_mt(se, config); + fuse_loop_cfg_destroy(config); + config = NULL; + } + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} + + +/** + * Local Variables: + * mode: c + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */ diff --git a/example/notify_inval_inode.c b/example/notify_inval_inode.c new file mode 100644 index 0000000..1dcecee --- /dev/null +++ b/example/notify_inval_inode.c @@ -0,0 +1,402 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2016 Nikolaus Rath + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This example implements a file system with a single file whose + * contents change dynamically: it always contains the current time. + * + * While notify_store_retrieve.c uses fuse_lowlevel_notify_store() to + * actively push the updated data into the kernel cache, this example + * uses fuse_lowlevel_notify_inval_inode() to notify the kernel that + * the cache has to be invalidated - but the kernel still has to + * explicitly request the updated data on the next read. + * + * To see the effect, first start the file system with the + * ``--no-notify`` option: + * + * $ notify_inval_inode --update-interval=1 --no-notify mnt/ + * + * Observe that the output never changes, even though the file system + * updates it once per second. This is because the contents are cached + * in the kernel: + * + * $ for i in 1 2 3 4 5; do + * > cat mnt/current_time + * > sleep 1 + * > done + * The current time is 15:58:18 + * The current time is 15:58:18 + * The current time is 15:58:18 + * The current time is 15:58:18 + * The current time is 15:58:18 + * + * If you instead enable the notification functions, the changes become + * visible: + * + * $ notify_inval_inode --update-interval=1 mnt/ + * $ for i in 1 2 3 4 5; do + * > cat mnt/current_time + * > sleep 1 + * > done + * The current time is 15:58:40 + * The current time is 15:58:41 + * The current time is 15:58:42 + * The current time is 15:58:43 + * The current time is 15:58:44 + * + * ## Compilation ## + * + * gcc -Wall notify_inval_inode.c `pkg-config fuse3 --cflags --libs` -o notify_inval_inode + * + * ## Source code ## + * \include notify_inval_inode.c + */ + + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* We can't actually tell the kernel that there is no + timeout, so we just send a big value */ +#define NO_TIMEOUT 500000 + +#define MAX_STR_LEN 128 +#define FILE_INO 2 +#define FILE_NAME "current_time" +static char file_contents[MAX_STR_LEN]; +static int lookup_cnt = 0; +static size_t file_size; +static _Atomic bool is_stop = false; + +/* Command line parsing */ +struct options { + int no_notify; + int update_interval; +}; +static struct options options = { + .no_notify = 0, + .update_interval = 1, +}; + +#define OPTION(t, p) \ + { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--no-notify", no_notify), + OPTION("--update-interval=%d", update_interval), + FUSE_OPT_END +}; + +static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) { + stbuf->st_ino = ino; + if (ino == FUSE_ROOT_ID) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } + + else if (ino == FILE_INO) { + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = file_size; + } + + else + return -1; + + return 0; +} + +static void tfs_init(void *userdata, struct fuse_conn_info *conn) { + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; +} + +static void tfs_destroy(void *userarg) +{ + (void)userarg; + + is_stop = true; +} + +static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, + const char *name) { + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + + if (parent != FUSE_ROOT_ID) + goto err_out; + else if (strcmp(name, FILE_NAME) == 0) { + e.ino = FILE_INO; + lookup_cnt++; + } else + goto err_out; + + e.attr_timeout = NO_TIMEOUT; + e.entry_timeout = NO_TIMEOUT; + if (tfs_stat(e.ino, &e.attr) != 0) + goto err_out; + fuse_reply_entry(req, &e); + return; + +err_out: + fuse_reply_err(req, ENOENT); +} + +static void tfs_forget (fuse_req_t req, fuse_ino_t ino, + uint64_t nlookup) { + (void) req; + if(ino == FILE_INO) + lookup_cnt -= nlookup; + else + assert(ino == FUSE_ROOT_ID); + fuse_reply_none(req); +} + +static void tfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) { + struct stat stbuf; + + (void) fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (tfs_stat(ino, &stbuf) != 0) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, NO_TIMEOUT); +} + +struct dirbuf { + char *p; + size_t size; +}; + +static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name, + fuse_ino_t ino) { + struct stat stbuf; + size_t oldsize = b->size; + b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); + b->p = (char *) realloc(b->p, b->size); + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = ino; + fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, + b->size); +} + +#define min(x, y) ((x) < (y) ? (x) : (y)) + +static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, + off_t off, size_t maxsize) { + if (off < bufsize) + return fuse_reply_buf(req, buf + off, + min(bufsize - off, maxsize)); + else + return fuse_reply_buf(req, NULL, 0); +} + +static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) { + (void) fi; + + if (ino != FUSE_ROOT_ID) + fuse_reply_err(req, ENOTDIR); + else { + struct dirbuf b; + + memset(&b, 0, sizeof(b)); + dirbuf_add(req, &b, FILE_NAME, FILE_INO); + reply_buf_limited(req, b.p, b.size, off, size); + free(b.p); + } +} + +static void tfs_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) { + + /* Make cache persistent even if file is closed, + this makes it easier to see the effects */ + fi->keep_cache = 1; + + if (ino == FUSE_ROOT_ID) + fuse_reply_err(req, EISDIR); + else if ((fi->flags & O_ACCMODE) != O_RDONLY) + fuse_reply_err(req, EACCES); + else if (ino == FILE_INO) + fuse_reply_open(req, fi); + else { + // This should not happen + fprintf(stderr, "Got open for non-existing inode!\n"); + fuse_reply_err(req, ENOENT); + } +} + +static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) { + (void) fi; + + assert(ino == FILE_INO); + reply_buf_limited(req, file_contents, file_size, off, size); +} + +static const struct fuse_lowlevel_ops tfs_oper = { + .init = tfs_init, + .destroy = tfs_destroy, + .lookup = tfs_lookup, + .getattr = tfs_getattr, + .readdir = tfs_readdir, + .open = tfs_open, + .read = tfs_read, + .forget = tfs_forget, +}; + +static void update_fs(void) { + struct tm *now; + time_t t; + t = time(NULL); + now = localtime(&t); + assert(now != NULL); + + file_size = strftime(file_contents, MAX_STR_LEN, + "The current time is %H:%M:%S\n", now); + assert(file_size != 0); +} + +static void* update_fs_loop(void *data) { + struct fuse_session *se = (struct fuse_session*) data; + + while(!is_stop) { + update_fs(); + if (!options.no_notify && lookup_cnt) { + /* Only send notification if the kernel is aware of the inode */ + + /* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they + * might come up during umount, when kernel side already releases + * all inodes, but does not send FUSE_DESTROY yet. + */ + int ret = + fuse_lowlevel_notify_inval_inode(se, FILE_INO, 0, 0); + if ((ret != 0 && !is_stop) && + ret != -ENOENT && ret != -EBADF && ret != -ENODEV) { + fprintf(stderr, + "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n", + strerror(-ret), -ret); + abort(); + } + } + sleep(options.update_interval); + } + return NULL; +} + +static void show_help(const char *progname) +{ + printf("usage: %s [options] \n\n", progname); + printf("File-system specific options:\n" + " --update-interval= Update-rate of file system contents\n" + " --no-notify Disable kernel notifications\n" + "\n"); +} + +int main(int argc, char *argv[]) { + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + struct fuse_loop_config *config; + pthread_t updater; + int ret = -1; + + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + if (fuse_parse_cmdline(&args, &opts) != 0) { + ret = 1; + goto err_out1; + } + + if (opts.show_help) { + show_help(argv[0]); + fuse_cmdline_help(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + /* Initial contents */ + update_fs(); + + se = fuse_session_new(&args, &tfs_oper, + sizeof(tfs_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + /* Start thread to update file contents */ + ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se); + if (ret != 0) { + fprintf(stderr, "pthread_create failed with %s\n", + strerror(ret)); + goto err_out3; + } + + /* Block until ctrl+c or fusermount -u */ + if (opts.singlethread) + ret = fuse_session_loop(se); + else { + config = fuse_loop_cfg_create(); + fuse_loop_cfg_set_clone_fd(config, opts.clone_fd); + fuse_loop_cfg_set_max_threads(config, opts.max_threads); + ret = fuse_session_loop_mt(se, config); + fuse_loop_cfg_destroy(config); + config = NULL; + } + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + fuse_opt_free_args(&args); + free(opts.mountpoint); + + return ret ? 1 : 0; +} + + +/** + * Local Variables: + * mode: c + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */ diff --git a/example/notify_store_retrieve.c b/example/notify_store_retrieve.c new file mode 100644 index 0000000..23fdecc --- /dev/null +++ b/example/notify_store_retrieve.c @@ -0,0 +1,475 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2016 Nikolaus Rath + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This example implements a file system with a single file whose + * contents change dynamically: it always contains the current time. + * + * While notify_inval_inode.c uses fuse_lowlevel_notify_inval_inode() + * to let the kernel know that it has to invalidate the cache, this + * example actively pushes the updated data into the kernel cache + * using fuse_lowlevel_notify_store(). + * + * To see the effect, first start the file system with the + * ``--no-notify`` option: + * + * $ notify_store_retrieve --update-interval=1 --no-notify mnt/ + * + * Observe that the output never changes, even though the file system + * updates it once per second. This is because the contents are cached + * in the kernel: + * + * $ for i in 1 2 3 4 5; do + * > cat mnt/current_time + * > sleep 1 + * > done + * The current time is 15:58:18 + * The current time is 15:58:18 + * The current time is 15:58:18 + * The current time is 15:58:18 + * The current time is 15:58:18 + * + * If you instead enable the notification functions, the changes become + * visible: + * + * $ notify_store_retrieve --update-interval=1 mnt/ + * $ for i in 1 2 3 4 5; do + * > cat mnt/current_time + * > sleep 1 + * > done + * The current time is 15:58:40 + * The current time is 15:58:41 + * The current time is 15:58:42 + * The current time is 15:58:43 + * The current time is 15:58:44 + * + * ## Compilation ## + * + * gcc -Wall notify_store_retrieve.c `pkg-config fuse3 --cflags --libs` -o notify_store_retrieve + * + * ## Source code ## + * \include notify_store_retrieve.c + */ + + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* We can't actually tell the kernel that there is no + timeout, so we just send a big value */ +#define NO_TIMEOUT 500000 + +#define MAX_STR_LEN 128 +#define FILE_INO 2 +#define FILE_NAME "current_time" +static char file_contents[MAX_STR_LEN]; +static int lookup_cnt = 0; +static int open_cnt = 0; +static size_t file_size; +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +/* Keep track if we ever stored data (==1), and + received it back correctly (==2) */ +static int retrieve_status = 0; + +static bool is_umount = false; + +/* updater thread tid */ +static pthread_t updater; + + +/* Command line parsing */ +struct options { + int no_notify; + int update_interval; +}; +static struct options options = { + .no_notify = 0, + .update_interval = 1, +}; + +#define OPTION(t, p) \ + { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--no-notify", no_notify), + OPTION("--update-interval=%d", update_interval), + FUSE_OPT_END +}; + +static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) { + stbuf->st_ino = ino; + if (ino == FUSE_ROOT_ID) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } + + else if (ino == FILE_INO) { + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = file_size; + } + + else + return -1; + + return 0; +} + +static void tfs_init(void *userdata, struct fuse_conn_info *conn) { + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; +} + +static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, + const char *name) { + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + + if (parent != FUSE_ROOT_ID) + goto err_out; + else if (strcmp(name, FILE_NAME) == 0) { + e.ino = FILE_INO; + } else + goto err_out; + + e.attr_timeout = NO_TIMEOUT; + e.entry_timeout = NO_TIMEOUT; + if (tfs_stat(e.ino, &e.attr) != 0) + goto err_out; + fuse_reply_entry(req, &e); + + /* + * must only be set when the kernel knows about the entry, + * otherwise update_fs_loop() might see a positive count, but kernel + * would not have the entry yet + */ + if (e.ino == FILE_INO) { + pthread_mutex_lock(&lock); + lookup_cnt++; + pthread_mutex_unlock(&lock); + } + + return; + +err_out: + fuse_reply_err(req, ENOENT); +} + +static void tfs_forget (fuse_req_t req, fuse_ino_t ino, + uint64_t nlookup) { + (void) req; + if(ino == FILE_INO) { + pthread_mutex_lock(&lock); + lookup_cnt -= nlookup; + pthread_mutex_unlock(&lock); + } else + assert(ino == FUSE_ROOT_ID); + fuse_reply_none(req); +} + +static void tfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) { + struct stat stbuf; + + (void) fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (tfs_stat(ino, &stbuf) != 0) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, NO_TIMEOUT); +} + +struct dirbuf { + char *p; + size_t size; +}; + +static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name, + fuse_ino_t ino) { + struct stat stbuf; + size_t oldsize = b->size; + b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); + b->p = (char *) realloc(b->p, b->size); + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = ino; + fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, + b->size); +} + +#define min(x, y) ((x) < (y) ? (x) : (y)) + +static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, + off_t off, size_t maxsize) { + if (off < bufsize) + return fuse_reply_buf(req, buf + off, + min(bufsize - off, maxsize)); + else + return fuse_reply_buf(req, NULL, 0); +} + +static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) { + (void) fi; + + if (ino != FUSE_ROOT_ID) + fuse_reply_err(req, ENOTDIR); + else { + struct dirbuf b; + + memset(&b, 0, sizeof(b)); + dirbuf_add(req, &b, FILE_NAME, FILE_INO); + reply_buf_limited(req, b.p, b.size, off, size); + free(b.p); + } +} + +static void tfs_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) { + + /* Make cache persistent even if file is closed, + this makes it easier to see the effects */ + fi->keep_cache = 1; + + if (ino == FUSE_ROOT_ID) + fuse_reply_err(req, EISDIR); + else if ((fi->flags & O_ACCMODE) != O_RDONLY) + fuse_reply_err(req, EACCES); + else if (ino == FILE_INO) { + fuse_reply_open(req, fi); + pthread_mutex_lock(&lock); + open_cnt++; + pthread_mutex_unlock(&lock); + } else { + // This should not happen + fprintf(stderr, "Got open for non-existing inode!\n"); + fuse_reply_err(req, ENOENT); + } +} + +static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) { + (void) fi; + + assert(ino == FILE_INO); + reply_buf_limited(req, file_contents, file_size, off, size); +} + +static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino, + off_t offset, struct fuse_bufvec *data) { + struct fuse_bufvec bufv; + char buf[MAX_STR_LEN]; + char *expected; + ssize_t ret; + + assert(ino == FILE_INO); + assert(offset == 0); + expected = (char*) cookie; + + bufv.count = 1; + bufv.idx = 0; + bufv.off = 0; + bufv.buf[0].size = MAX_STR_LEN; + bufv.buf[0].mem = buf; + bufv.buf[0].flags = 0; + + ret = fuse_buf_copy(&bufv, data, 0); + assert(ret > 0); + assert(strncmp(buf, expected, ret) == 0); + free(expected); + retrieve_status = 2; + fuse_reply_none(req); +} + +static void tfs_destroy(void *userdata) +{ + (void)userdata; + + is_umount = true; + + pthread_join(updater, NULL); +} + + +static const struct fuse_lowlevel_ops tfs_oper = { + .init = tfs_init, + .lookup = tfs_lookup, + .getattr = tfs_getattr, + .readdir = tfs_readdir, + .open = tfs_open, + .read = tfs_read, + .forget = tfs_forget, + .retrieve_reply = tfs_retrieve_reply, + .destroy = tfs_destroy, +}; + +static void update_fs(void) { + struct tm *now; + time_t t; + t = time(NULL); + now = localtime(&t); + assert(now != NULL); + + file_size = strftime(file_contents, MAX_STR_LEN, + "The current time is %H:%M:%S\n", now); + assert(file_size != 0); +} + +static void* update_fs_loop(void *data) { + struct fuse_session *se = (struct fuse_session*) data; + struct fuse_bufvec bufv; + int ret; + + while(!is_umount) { + update_fs(); + pthread_mutex_lock(&lock); + if (!options.no_notify && open_cnt && lookup_cnt) { + /* Only send notification if the kernel + is aware of the inode */ + bufv.count = 1; + bufv.idx = 0; + bufv.off = 0; + bufv.buf[0].size = file_size; + bufv.buf[0].mem = file_contents; + bufv.buf[0].flags = 0; + + /* + * Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they + * might come up during umount, when kernel side already releases + * all inodes, but does not send FUSE_DESTROY yet. + */ + + ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0); + if ((ret != 0 && !is_umount) && + ret != -ENOENT && ret != -EBADF && ret != -ENODEV) { + fprintf(stderr, + "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n", + strerror(-ret), -ret); + abort(); + } + + /* To make sure that everything worked correctly, ask the + kernel to send us back the stored data */ + ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN, + 0, (void*) strdup(file_contents)); + assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF || + ret != -ENODEV); + if(retrieve_status == 0) + retrieve_status = 1; + } + pthread_mutex_unlock(&lock); + sleep(options.update_interval); + } + return NULL; +} + +static void show_help(const char *progname) +{ + printf("usage: %s [options] \n\n", progname); + printf("File-system specific options:\n" + " --update-interval= Update-rate of file system contents\n" + " --no-notify Disable kernel notifications\n" + "\n"); +} + +int main(int argc, char *argv[]) { + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + struct fuse_loop_config *config; + int ret = -1; + + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + if (opts.show_help) { + show_help(argv[0]); + fuse_cmdline_help(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + /* Initial contents */ + update_fs(); + + se = fuse_session_new(&args, &tfs_oper, + sizeof(tfs_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + /* Start thread to update file contents */ + ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se); + if (ret != 0) { + fprintf(stderr, "pthread_create failed with %s\n", + strerror(ret)); + goto err_out3; + } + + /* Block until ctrl+c or fusermount -u */ + if (opts.singlethread) + ret = fuse_session_loop(se); + else { + config = fuse_loop_cfg_create(); + fuse_loop_cfg_set_clone_fd(config, opts.clone_fd); + fuse_loop_cfg_set_max_threads(config, opts.max_threads); + ret = fuse_session_loop_mt(se, config); + fuse_loop_cfg_destroy(config); + config = NULL; + } + + assert(retrieve_status != 1); + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} + + +/** + * Local Variables: + * mode: c + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */ diff --git a/example/null.c b/example/null.c new file mode 100644 index 0000000..ec41def --- /dev/null +++ b/example/null.c @@ -0,0 +1,143 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This "filesystem" provides only a single file. The mountpoint + * needs to be a file rather than a directory. All writes to the + * file will be discarded, and reading the file always returns + * \0. + * + * Compile with: + * + * gcc -Wall null.c `pkg-config fuse3 --cflags --libs` -o null + * + * ## Source code ## + * \include passthrough_fh.c + */ + + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include +#include +#include +#include + +static int null_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void) fi; + + if(strcmp(path, "/") != 0) + return -ENOENT; + + stbuf->st_mode = S_IFREG | 0644; + stbuf->st_nlink = 1; + stbuf->st_uid = getuid(); + stbuf->st_gid = getgid(); + stbuf->st_size = (1ULL << 32); /* 4G */ + stbuf->st_blocks = 0; + stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = time(NULL); + + return 0; +} + +static int null_truncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + (void) size; + (void) fi; + + if(strcmp(path, "/") != 0) + return -ENOENT; + + return 0; +} + +static int null_open(const char *path, struct fuse_file_info *fi) +{ + (void) fi; + + if(strcmp(path, "/") != 0) + return -ENOENT; + + return 0; +} + +static int null_read(const char *path, char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + (void) buf; + (void) offset; + (void) fi; + + if(strcmp(path, "/") != 0) + return -ENOENT; + + if (offset >= (1ULL << 32)) + return 0; + + memset(buf, 0, size); + return size; +} + +static int null_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + (void) buf; + (void) offset; + (void) fi; + + if(strcmp(path, "/") != 0) + return -ENOENT; + + return size; +} + +static const struct fuse_operations null_oper = { + .getattr = null_getattr, + .truncate = null_truncate, + .open = null_open, + .read = null_read, + .write = null_write, +}; + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_cmdline_opts opts; + struct stat stbuf; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + fuse_opt_free_args(&args); + + if (!opts.mountpoint) { + fprintf(stderr, "missing mountpoint parameter\n"); + return 1; + } + + if (stat(opts.mountpoint, &stbuf) == -1) { + fprintf(stderr ,"failed to access mountpoint %s: %s\n", + opts.mountpoint, strerror(errno)); + free(opts.mountpoint); + return 1; + } + free(opts.mountpoint); + if (!S_ISREG(stbuf.st_mode)) { + fprintf(stderr, "mountpoint is not a regular file\n"); + return 1; + } + + return fuse_main(argc, argv, &null_oper, NULL); +} diff --git a/example/passthrough.c b/example/passthrough.c new file mode 100644 index 0000000..81a265a --- /dev/null +++ b/example/passthrough.c @@ -0,0 +1,604 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + Copyright (C) 2011 Sebastian Pipping + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This file system mirrors the existing file system hierarchy of the + * system, starting at the root file system. This is implemented by + * just "passing through" all requests to the corresponding user-space + * libc functions. Its performance is terrible. + * + * Compile with + * + * gcc -Wall passthrough.c `pkg-config fuse3 --cflags --libs` -o passthrough + * + * ## Source code ## + * \include passthrough.c + */ + + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18) + +#define _GNU_SOURCE + +#ifdef linux +/* For pread()/pwrite()/utimensat() */ +#define _XOPEN_SOURCE 700 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SETXATTR +#include +#endif + +#include "passthrough_helpers.h" + +static int fill_dir_plus = 0; +static int readdir_zero_ino; + +static void *xmp_init(struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + (void) conn; + cfg->use_ino = !readdir_zero_ino; + + /* parallel_direct_writes feature depends on direct_io features. + To make parallel_direct_writes valid, need either set cfg->direct_io + in current function (recommended in high level API) or set fi->direct_io + in xmp_create() or xmp_open(). */ + // cfg->direct_io = 1; + cfg->parallel_direct_writes = 1; + + /* Pick up changes from lower filesystem right away. This is + also necessary for better hardlink support. When the kernel + calls the unlink() handler, it does not know the inode of + the to-be-removed entry and can therefore not invalidate + the cache of the associated inode - resulting in an + incorrect st_nlink value being reported for any remaining + hardlinks to this inode. */ + if (!cfg->auto_cache) { + cfg->entry_timeout = 0; + cfg->attr_timeout = 0; + cfg->negative_timeout = 0; + } + + return NULL; +} + +static int xmp_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void) fi; + int res; + + res = lstat(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_access(const char *path, int mask) +{ + int res; + + res = access(path, mask); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_readlink(const char *path, char *buf, size_t size) +{ + int res; + + res = readlink(path, buf, size - 1); + if (res == -1) + return -errno; + + buf[res] = '\0'; + return 0; +} + + +static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + DIR *dp; + struct dirent *de; + + (void) offset; + (void) fi; + (void) flags; + + dp = opendir(path); + if (dp == NULL) + return -errno; + + while ((de = readdir(dp)) != NULL) { + struct stat st; + if (fill_dir_plus) { + fstatat(dirfd(dp), de->d_name, &st, + AT_SYMLINK_NOFOLLOW); + } else { + memset(&st, 0, sizeof(st)); + st.st_ino = de->d_ino; + st.st_mode = de->d_type << 12; + } + if (readdir_zero_ino) + st.st_ino = 0; + if (filler(buf, de->d_name, &st, 0, fill_dir_plus)) + break; + } + + closedir(dp); + return 0; +} + +static int xmp_mknod(const char *path, mode_t mode, dev_t rdev) +{ + int res; + + res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_mkdir(const char *path, mode_t mode) +{ + int res; + + res = mkdir(path, mode); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_unlink(const char *path) +{ + int res; + + res = unlink(path); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_rmdir(const char *path) +{ + int res; + + res = rmdir(path); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_symlink(const char *from, const char *to) +{ + int res; + + res = symlink(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_rename(const char *from, const char *to, unsigned int flags) +{ + int res; + + if (flags) + return -EINVAL; + + res = rename(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_link(const char *from, const char *to) +{ + int res; + + res = link(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_chmod(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + (void) fi; + int res; + + res = chmod(path, mode); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_chown(const char *path, uid_t uid, gid_t gid, + struct fuse_file_info *fi) +{ + (void) fi; + int res; + + res = lchown(path, uid, gid); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_truncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + int res; + + if (fi != NULL) + res = ftruncate(fi->fh, size); + else + res = truncate(path, size); + if (res == -1) + return -errno; + + return 0; +} + +#ifdef HAVE_UTIMENSAT +static int xmp_utimens(const char *path, const struct timespec ts[2], + struct fuse_file_info *fi) +{ + (void) fi; + int res; + + /* don't use utime/utimes since they follow symlinks */ + res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW); + if (res == -1) + return -errno; + + return 0; +} +#endif + +static int xmp_create(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + int res; + + res = open(path, fi->flags, mode); + if (res == -1) + return -errno; + + fi->fh = res; + return 0; +} + +static int xmp_open(const char *path, struct fuse_file_info *fi) +{ + int res; + + res = open(path, fi->flags); + if (res == -1) + return -errno; + + /* Enable direct_io when open has flags O_DIRECT to enjoy the feature + parallel_direct_writes (i.e., to get a shared lock, not exclusive lock, + for writes to the same file). */ + if (fi->flags & O_DIRECT) { + fi->direct_io = 1; + fi->parallel_direct_writes = 1; + } + + fi->fh = res; + return 0; +} + +static int xmp_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + int fd; + int res; + + if(fi == NULL) + fd = open(path, O_RDONLY); + else + fd = fi->fh; + + if (fd == -1) + return -errno; + + res = pread(fd, buf, size, offset); + if (res == -1) + res = -errno; + + if(fi == NULL) + close(fd); + return res; +} + +static int xmp_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + int fd; + int res; + + (void) fi; + if(fi == NULL) + fd = open(path, O_WRONLY); + else + fd = fi->fh; + + if (fd == -1) + return -errno; + + res = pwrite(fd, buf, size, offset); + if (res == -1) + res = -errno; + + if(fi == NULL) + close(fd); + return res; +} + +static int xmp_statfs(const char *path, struct statvfs *stbuf) +{ + int res; + + res = statvfs(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_release(const char *path, struct fuse_file_info *fi) +{ + (void) path; + close(fi->fh); + return 0; +} + +static int xmp_fsync(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + /* Just a stub. This method is optional and can safely be left + unimplemented */ + + (void) path; + (void) isdatasync; + (void) fi; + return 0; +} + +static int xmp_fallocate(const char *path, int mode, + off_t offset, off_t length, struct fuse_file_info *fi) +{ + int fd; + int res; + + (void) fi; + + if(fi == NULL) + fd = open(path, O_WRONLY); + else + fd = fi->fh; + + if (fd == -1) + return -errno; + + res = do_fallocate(fd, mode, offset, length); + + if(fi == NULL) + close(fd); + return res; +} + +#ifdef HAVE_SETXATTR +/* xattr operations are optional and can safely be left unimplemented */ +static int xmp_setxattr(const char *path, const char *name, const char *value, + size_t size, int flags) +{ + int res = lsetxattr(path, name, value, size, flags); + if (res == -1) + return -errno; + return 0; +} + +static int xmp_getxattr(const char *path, const char *name, char *value, + size_t size) +{ + int res = lgetxattr(path, name, value, size); + if (res == -1) + return -errno; + return res; +} + +static int xmp_listxattr(const char *path, char *list, size_t size) +{ + int res = llistxattr(path, list, size); + if (res == -1) + return -errno; + return res; +} + +static int xmp_removexattr(const char *path, const char *name) +{ + int res = lremovexattr(path, name); + if (res == -1) + return -errno; + return 0; +} +#endif /* HAVE_SETXATTR */ + +#ifdef HAVE_COPY_FILE_RANGE +static ssize_t xmp_copy_file_range(const char *path_in, + struct fuse_file_info *fi_in, + off_t offset_in, const char *path_out, + struct fuse_file_info *fi_out, + off_t offset_out, size_t len, int flags) +{ + int fd_in, fd_out; + ssize_t res; + + if(fi_in == NULL) + fd_in = open(path_in, O_RDONLY); + else + fd_in = fi_in->fh; + + if (fd_in == -1) + return -errno; + + if(fi_out == NULL) + fd_out = open(path_out, O_WRONLY); + else + fd_out = fi_out->fh; + + if (fd_out == -1) { + close(fd_in); + return -errno; + } + + res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len, + flags); + if (res == -1) + res = -errno; + + if (fi_out == NULL) + close(fd_out); + if (fi_in == NULL) + close(fd_in); + + return res; +} +#endif + +static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi) +{ + int fd; + off_t res; + + if (fi == NULL) + fd = open(path, O_RDONLY); + else + fd = fi->fh; + + if (fd == -1) + return -errno; + + res = lseek(fd, off, whence); + if (res == -1) + res = -errno; + + if (fi == NULL) + close(fd); + return res; +} + +#ifdef HAVE_STATX +static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi) +{ + int fd = -1; + int res; + + if (fi) + fd = fi->fh; + + res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf); + if (res == -1) + return -errno; + + return 0; +} +#endif + +static const struct fuse_operations xmp_oper = { + .init = xmp_init, + .getattr = xmp_getattr, + .access = xmp_access, + .readlink = xmp_readlink, + .readdir = xmp_readdir, + .mknod = xmp_mknod, + .mkdir = xmp_mkdir, + .symlink = xmp_symlink, + .unlink = xmp_unlink, + .rmdir = xmp_rmdir, + .rename = xmp_rename, + .link = xmp_link, + .chmod = xmp_chmod, + .chown = xmp_chown, + .truncate = xmp_truncate, +#ifdef HAVE_UTIMENSAT + .utimens = xmp_utimens, +#endif + .open = xmp_open, + .create = xmp_create, + .read = xmp_read, + .write = xmp_write, + .statfs = xmp_statfs, + .release = xmp_release, + .fsync = xmp_fsync, + .fallocate = xmp_fallocate, +#ifdef HAVE_SETXATTR + .setxattr = xmp_setxattr, + .getxattr = xmp_getxattr, + .listxattr = xmp_listxattr, + .removexattr = xmp_removexattr, +#endif +#ifdef HAVE_COPY_FILE_RANGE + .copy_file_range = xmp_copy_file_range, +#endif + .lseek = xmp_lseek, +#ifdef HAVE_STATX + .statx = xmp_statx, +#endif +}; + +int main(int argc, char *argv[]) +{ + enum { MAX_ARGS = 10 }; + int i,new_argc; + char *new_argv[MAX_ARGS]; + + umask(0); + /* Process the "--plus" option apart */ + for (i=0, new_argc=0; (i + Copyright (C) 2011 Sebastian Pipping + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This file system mirrors the existing file system hierarchy of the + * system, starting at the root file system. This is implemented by + * just "passing through" all requests to the corresponding user-space + * libc functions. This implementation is a little more sophisticated + * than the one in passthrough.c, so performance is not quite as bad. + * + * Compile with: + * + * gcc -Wall passthrough_fh.c `pkg-config fuse3 --cflags --libs` -lulockmgr -o passthrough_fh + * + * ## Source code ## + * \include passthrough_fh.c + */ + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18) + +#define _GNU_SOURCE + +#include + +#ifdef HAVE_LIBULOCKMGR +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SETXATTR +#include +#endif +#include /* flock(2) */ + +#include "passthrough_helpers.h" + +static void *xmp_init(struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + (void) conn; + cfg->use_ino = 1; + cfg->nullpath_ok = 1; + + /* parallel_direct_writes feature depends on direct_io features. + To make parallel_direct_writes valid, need either set cfg->direct_io + in current function (recommended in high level API) or set fi->direct_io + in xmp_create() or xmp_open(). */ + // cfg->direct_io = 1; + cfg->parallel_direct_writes = 1; + + /* Pick up changes from lower filesystem right away. This is + also necessary for better hardlink support. When the kernel + calls the unlink() handler, it does not know the inode of + the to-be-removed entry and can therefore not invalidate + the cache of the associated inode - resulting in an + incorrect st_nlink value being reported for any remaining + hardlinks to this inode. */ + cfg->entry_timeout = 0; + cfg->attr_timeout = 0; + cfg->negative_timeout = 0; + + return NULL; +} + +static int xmp_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + int res; + + (void) path; + + if(fi) + res = fstat(fi->fh, stbuf); + else + res = lstat(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_access(const char *path, int mask) +{ + int res; + + res = access(path, mask); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_readlink(const char *path, char *buf, size_t size) +{ + int res; + + res = readlink(path, buf, size - 1); + if (res == -1) + return -errno; + + buf[res] = '\0'; + return 0; +} + +struct xmp_dirp { + DIR *dp; + struct dirent *entry; + off_t offset; +}; + +static int xmp_opendir(const char *path, struct fuse_file_info *fi) +{ + int res; + struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp)); + if (d == NULL) + return -ENOMEM; + + d->dp = opendir(path); + if (d->dp == NULL) { + res = -errno; + free(d); + return res; + } + d->offset = 0; + d->entry = NULL; + + fi->fh = (unsigned long) d; + return 0; +} + +static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi) +{ + return (struct xmp_dirp *) (uintptr_t) fi->fh; +} + +static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + struct xmp_dirp *d = get_dirp(fi); + + (void) path; + if (offset != d->offset) { +#ifndef __FreeBSD__ + seekdir(d->dp, offset); +#else + /* Subtract the one that we add when calling + telldir() below */ + seekdir(d->dp, offset-1); +#endif + d->entry = NULL; + d->offset = offset; + } + while (1) { + struct stat st; + off_t nextoff; + enum fuse_fill_dir_flags fill_flags = FUSE_FILL_DIR_DEFAULTS; + + if (!d->entry) { + d->entry = readdir(d->dp); + if (!d->entry) + break; + } +#ifdef HAVE_FSTATAT + if (flags & FUSE_READDIR_PLUS) { + int res; + + res = fstatat(dirfd(d->dp), d->entry->d_name, &st, + AT_SYMLINK_NOFOLLOW); + if (res != -1) + fill_flags |= FUSE_FILL_DIR_PLUS; + } +#endif + if (!(fill_flags & FUSE_FILL_DIR_PLUS)) { + memset(&st, 0, sizeof(st)); + st.st_ino = d->entry->d_ino; + st.st_mode = d->entry->d_type << 12; + } + nextoff = telldir(d->dp); +#ifdef __FreeBSD__ + /* Under FreeBSD, telldir() may return 0 the first time + it is called. But for libfuse, an offset of zero + means that offsets are not supported, so we shift + everything by one. */ + nextoff++; +#endif + if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags)) + break; + + d->entry = NULL; + d->offset = nextoff; + } + + return 0; +} + +static int xmp_releasedir(const char *path, struct fuse_file_info *fi) +{ + struct xmp_dirp *d = get_dirp(fi); + (void) path; + closedir(d->dp); + free(d); + return 0; +} + +static int xmp_mknod(const char *path, mode_t mode, dev_t rdev) +{ + int res; + + if (S_ISFIFO(mode)) + res = mkfifo(path, mode); + else + res = mknod(path, mode, rdev); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_mkdir(const char *path, mode_t mode) +{ + int res; + + res = mkdir(path, mode); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_unlink(const char *path) +{ + int res; + + res = unlink(path); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_rmdir(const char *path) +{ + int res; + + res = rmdir(path); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_symlink(const char *from, const char *to) +{ + int res; + + res = symlink(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_rename(const char *from, const char *to, unsigned int flags) +{ + int res; + + /* When we have renameat2() in libc, then we can implement flags */ + if (flags) + return -EINVAL; + + res = rename(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_link(const char *from, const char *to) +{ + int res; + + res = link(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_chmod(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + int res; + + if(fi) + res = fchmod(fi->fh, mode); + else + res = chmod(path, mode); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_chown(const char *path, uid_t uid, gid_t gid, + struct fuse_file_info *fi) +{ + int res; + + if (fi) + res = fchown(fi->fh, uid, gid); + else + res = lchown(path, uid, gid); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_truncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + int res; + + if(fi) + res = ftruncate(fi->fh, size); + else + res = truncate(path, size); + + if (res == -1) + return -errno; + + return 0; +} + +#ifdef HAVE_UTIMENSAT +static int xmp_utimens(const char *path, const struct timespec ts[2], + struct fuse_file_info *fi) +{ + int res; + + /* don't use utime/utimes since they follow symlinks */ + if (fi) + res = futimens(fi->fh, ts); + else + res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW); + if (res == -1) + return -errno; + + return 0; +} +#endif + +static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi) +{ + int fd; + + fd = open(path, fi->flags, mode); + if (fd == -1) + return -errno; + + fi->fh = fd; + return 0; +} + +static int xmp_open(const char *path, struct fuse_file_info *fi) +{ + int fd; + + fd = open(path, fi->flags); + if (fd == -1) + return -errno; + + /* Enable direct_io when open has flags O_DIRECT to enjoy the feature + parallel_direct_writes (i.e., to get a shared lock, not exclusive lock, + for writes to the same file). */ + if (fi->flags & O_DIRECT) { + fi->direct_io = 1; + fi->parallel_direct_writes = 1; + } + + fi->fh = fd; + return 0; +} + +static int xmp_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + int res; + + (void) path; + res = pread(fi->fh, buf, size, offset); + if (res == -1) + res = -errno; + + return res; +} + +static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp, + size_t size, off_t offset, struct fuse_file_info *fi) +{ + struct fuse_bufvec *src; + + (void) path; + + src = malloc(sizeof(struct fuse_bufvec)); + if (src == NULL) + return -ENOMEM; + + *src = FUSE_BUFVEC_INIT(size); + + src->buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; + src->buf[0].fd = fi->fh; + src->buf[0].pos = offset; + + *bufp = src; + + return 0; +} + +static int xmp_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + int res; + + (void) path; + res = pwrite(fi->fh, buf, size, offset); + if (res == -1) + res = -errno; + + return res; +} + +static int xmp_write_buf(const char *path, struct fuse_bufvec *buf, + off_t offset, struct fuse_file_info *fi) +{ + struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf)); + + (void) path; + + dst.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; + dst.buf[0].fd = fi->fh; + dst.buf[0].pos = offset; + + return fuse_buf_copy(&dst, buf, FUSE_BUF_SPLICE_NONBLOCK); +} + +static int xmp_statfs(const char *path, struct statvfs *stbuf) +{ + int res; + + res = statvfs(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_flush(const char *path, struct fuse_file_info *fi) +{ + int res; + + (void) path; + /* This is called from every close on an open file, so call the + close on the underlying filesystem. But since flush may be + called multiple times for an open file, this must not really + close the file. This is important if used on a network + filesystem like NFS which flush the data/metadata on close() */ + res = close(dup(fi->fh)); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_release(const char *path, struct fuse_file_info *fi) +{ + (void) path; + close(fi->fh); + + return 0; +} + +static int xmp_fsync(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + int res; + (void) path; + +#ifndef HAVE_FDATASYNC + (void) isdatasync; +#else + if (isdatasync) + res = fdatasync(fi->fh); + else +#endif + res = fsync(fi->fh); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_fallocate(const char *path, int mode, + off_t offset, off_t length, struct fuse_file_info *fi) +{ + (void) path; + + return do_fallocate(fi->fh, mode, offset, length); +} + +#ifdef HAVE_SETXATTR +/* xattr operations are optional and can safely be left unimplemented */ +static int xmp_setxattr(const char *path, const char *name, const char *value, + size_t size, int flags) +{ + int res = lsetxattr(path, name, value, size, flags); + if (res == -1) + return -errno; + return 0; +} + +static int xmp_getxattr(const char *path, const char *name, char *value, + size_t size) +{ + int res = lgetxattr(path, name, value, size); + if (res == -1) + return -errno; + return res; +} + +static int xmp_listxattr(const char *path, char *list, size_t size) +{ + int res = llistxattr(path, list, size); + if (res == -1) + return -errno; + return res; +} + +static int xmp_removexattr(const char *path, const char *name) +{ + int res = lremovexattr(path, name); + if (res == -1) + return -errno; + return 0; +} +#endif /* HAVE_SETXATTR */ + +#ifdef HAVE_LIBULOCKMGR +static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd, + struct flock *lock) +{ + (void) path; + + return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner, + sizeof(fi->lock_owner)); +} +#endif + +static int xmp_flock(const char *path, struct fuse_file_info *fi, int op) +{ + int res; + (void) path; + + res = flock(fi->fh, op); + if (res == -1) + return -errno; + + return 0; +} + +#ifdef HAVE_COPY_FILE_RANGE +static ssize_t xmp_copy_file_range(const char *path_in, + struct fuse_file_info *fi_in, + off_t off_in, const char *path_out, + struct fuse_file_info *fi_out, + off_t off_out, size_t len, int flags) +{ + ssize_t res; + (void) path_in; + (void) path_out; + + res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len, + flags); + if (res == -1) + return -errno; + + return res; +} +#endif + +static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi) +{ + off_t res; + (void) path; + + res = lseek(fi->fh, off, whence); + if (res == -1) + return -errno; + + return res; +} + +#ifdef HAVE_STATX +static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi) +{ + int fd = -1; + int res; + + if (fi) + fd = fi->fh; + + res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf); + if (res == -1) + return -errno; + + return 0; +} +#endif + +static const struct fuse_operations xmp_oper = { + .init = xmp_init, + .getattr = xmp_getattr, + .access = xmp_access, + .readlink = xmp_readlink, + .opendir = xmp_opendir, + .readdir = xmp_readdir, + .releasedir = xmp_releasedir, + .mknod = xmp_mknod, + .mkdir = xmp_mkdir, + .symlink = xmp_symlink, + .unlink = xmp_unlink, + .rmdir = xmp_rmdir, + .rename = xmp_rename, + .link = xmp_link, + .chmod = xmp_chmod, + .chown = xmp_chown, + .truncate = xmp_truncate, +#ifdef HAVE_UTIMENSAT + .utimens = xmp_utimens, +#endif + .create = xmp_create, + .open = xmp_open, + .read = xmp_read, + .read_buf = xmp_read_buf, + .write = xmp_write, + .write_buf = xmp_write_buf, + .statfs = xmp_statfs, + .flush = xmp_flush, + .release = xmp_release, + .fsync = xmp_fsync, + .fallocate = xmp_fallocate, +#ifdef HAVE_SETXATTR + .setxattr = xmp_setxattr, + .getxattr = xmp_getxattr, + .listxattr = xmp_listxattr, + .removexattr = xmp_removexattr, +#endif +#ifdef HAVE_LIBULOCKMGR + .lock = xmp_lock, +#endif + .flock = xmp_flock, +#ifdef HAVE_COPY_FILE_RANGE + .copy_file_range = xmp_copy_file_range, +#endif + .lseek = xmp_lseek, +#ifdef HAVE_STATX + .statx = xmp_statx, +#endif +}; + +int main(int argc, char *argv[]) +{ + umask(0); + return fuse_main(argc, argv, &xmp_oper, NULL); +} diff --git a/example/passthrough_helpers.h b/example/passthrough_helpers.h new file mode 100644 index 0000000..326a5c7 --- /dev/null +++ b/example/passthrough_helpers.h @@ -0,0 +1,122 @@ +/* + * FUSE: Filesystem in Userspace + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE + */ + +#ifndef FUSE_EXAMPLE_PASSTHROUGH_HELPERS_H_ +#define FUSE_EXAMPLE_PASSTHROUGH_HELPERS_H_ + +#include +#include +#include +#include +#include + +#ifdef __FreeBSD__ +#include +#include +#endif + +static inline int do_fallocate(int fd, int mode, off_t offset, off_t length) +{ +#ifdef HAVE_FALLOCATE + if (fallocate(fd, mode, offset, length) == -1) + return -errno; + return 0; +#else // HAVE_FALLOCATE + +#ifdef HAVE_POSIX_FALLOCATE + if (mode == 0) + return -posix_fallocate(fd, offset, length); +#endif + +#ifdef HAVE_FSPACECTL + // 0x3 == FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE + if (mode == 0x3) { + struct spacectl_range sr; + + sr.r_offset = offset; + sr.r_len = length; + if (fspacectl(fd, SPACECTL_DEALLOC, &sr, 0, NULL) == -1) + return -errno; + return 0; + } +#endif + + return -EOPNOTSUPP; +#endif // HAVE_FALLOCATE +} + +/* + * Creates files on the underlying file system in response to a FUSE_MKNOD + * operation + */ +static inline int mknod_wrapper(int dirfd, const char *path, const char *link, + int mode, dev_t rdev) +{ + int res; + + if (S_ISREG(mode)) { + res = openat(dirfd, path, O_CREAT | O_EXCL | O_WRONLY, mode); + if (res >= 0) + res = close(res); + } else if (S_ISDIR(mode)) { + res = mkdirat(dirfd, path, mode); + } else if (S_ISLNK(mode) && link != NULL) { + res = symlinkat(link, dirfd, path); + } else if (S_ISFIFO(mode)) { + res = mkfifoat(dirfd, path, mode); +#ifdef __FreeBSD__ + } else if (S_ISSOCK(mode)) { + struct sockaddr_un su; + int fd; + + if (strlen(path) >= sizeof(su.sun_path)) { + errno = ENAMETOOLONG; + return -1; + } + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd >= 0) { + /* + * We must bind the socket to the underlying file + * system to create the socket file, even though + * we'll never listen on this socket. + */ + su.sun_family = AF_UNIX; + strncpy(su.sun_path, path, sizeof(su.sun_path)); + res = bindat(dirfd, fd, (struct sockaddr*)&su, + sizeof(su)); + if (res == 0) + close(fd); + } else { + res = -1; + } +#endif + } else { + res = mknodat(dirfd, path, mode, rdev); + } + + return res; +} + +#endif // FUSE_PASSTHROUGH_HELPERS_H_ diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc new file mode 100644 index 0000000..2dc0f95 --- /dev/null +++ b/example/passthrough_hp.cc @@ -0,0 +1,1666 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + Copyright (C) 2017 Nikolaus Rath + Copyright (C) 2018 Valve, Inc + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This is a "high-performance" version of passthrough_ll.c. While + * passthrough_ll.c is designed to be as simple as possible, this + * example intended to be as efficient and correct as possible. + * + * passthrough_hp.cc mirrors a specified "source" directory under a + * specified the mountpoint with as much fidelity and performance as + * possible. + * + * If --nocache is specified, the source directory may be changed + * directly even while mounted and the filesystem will continue + * to work correctly. + * + * Without --nocache, the source directory is assumed to be modified + * only through the passthrough filesystem. This enables much better + * performance, but if changes are made directly to the source, they + * may not be immediately visible under the mountpoint and further + * access to the mountpoint may result in incorrect behavior, + * including data-loss. + * + * On its own, this filesystem fulfills no practical purpose. It is + * intended as a template upon which additional functionality can be + * built. + * + * Unless --nocache is specified, is only possible to write to files + * for which the mounting user has read permissions. This is because + * the writeback cache requires the kernel to be able to issue read + * requests for all files (which the passthrough filesystem cannot + * satisfy if it can't read the file in the underlying filesystem). + * + * ## Source code ## + * \include passthrough_hp.cc + */ + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12) + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +// C includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_XATTR_H +#include +#endif +#include +#include +#include +#include +#ifdef __FreeBSD__ +#include +#include +#endif + +// C++ includes +#include +#include +#include +#include "cxxopts.hpp" +#include +#include +#include + +#include "passthrough_helpers.h" + +using namespace std; + +#define SFS_DEFAULT_THREADS "-1" // take libfuse value as default +#define SFS_DEFAULT_CLONE_FD "0" + +/* We are re-using pointers to our `struct sfs_inode` and `struct + sfs_dirp` elements as inodes and file handles. This means that we + must be able to store pointer a pointer in both a fuse_ino_t + variable and a uint64_t variable (used for file handles). */ +static_assert(sizeof(fuse_ino_t) >= sizeof(void *), + "void* must fit into fuse_ino_t"); +static_assert(sizeof(fuse_ino_t) >= sizeof(uint64_t), + "fuse_ino_t must be at least 64 bits"); + +/* Forward declarations */ +struct Inode; +static Inode &get_inode(fuse_ino_t ino); +static void forget_one(fuse_ino_t ino, uint64_t n); + +// Uniquely identifies a file in the source directory tree. This could +// be simplified to just ino_t since we require the source directory +// not to contain any mountpoints. This hasn't been done yet in case +// we need to reconsider this constraint (but relaxing this would have +// the drawback that we can no longer reuse inode numbers, and thus +// readdir() would need to do a full lookup() in order to report the +// right inode number). +typedef std::pair SrcId; + +// Define a hash function for SrcId +namespace std +{ +template <> struct hash { + size_t operator()(const SrcId &id) const + { + return hash{}(id.first) ^ hash{}(id.second); + } +}; +} + +// Maps files in the source directory tree to inodes +typedef std::unordered_map InodeMap; + +struct Inode { + int fd{ -1 }; + dev_t src_dev{ 0 }; + ino_t src_ino{ 0 }; + int generation{ 0 }; + int backing_id{ 0 }; + uint64_t nopen{ 0 }; + std::atomic nlookup{ 0 }; + std::mutex m; + + // Delete copy constructor and assignments. We could implement + // move if we need it. + Inode() = default; + Inode(const Inode &) = delete; + Inode(Inode &&inode) = delete; + Inode &operator=(Inode &&inode) = delete; + Inode &operator=(const Inode &) = delete; + + ~Inode() + { + if (fd > 0) + close(fd); + } +}; + +struct Fs { + // Must be acquired *after* any Inode.m locks. + std::mutex mutex; + InodeMap inodes; // protected by mutex + Inode root; + double timeout; + bool debug; + bool debug_fuse; + bool foreground; + std::string source; + size_t blocksize; + dev_t src_dev; + bool nosplice; + bool nocache; + size_t num_threads; + bool clone_fd; + + std::string fuse_mount_options; + bool direct_io; + bool passthrough; +}; +static Fs fs{}; + +#define FUSE_BUF_COPY_FLAGS \ + (fs.nosplice ? FUSE_BUF_NO_SPLICE : \ + static_cast(FUSE_BUF_SPLICE_MOVE)) + +static Inode &get_inode(fuse_ino_t ino) +{ + if (ino == FUSE_ROOT_ID) + return fs.root; + + Inode *inode = reinterpret_cast(ino); + if (inode->fd == -1) { + cerr << "INTERNAL ERROR: Unknown inode " << ino << endl; + abort(); + } + return *inode; +} + +static int get_fs_fd(fuse_ino_t ino) +{ + int fd = get_inode(ino).fd; + return fd; +} + +static void sfs_init(void *userdata, fuse_conn_info *conn) +{ + (void)userdata; + + if (!fuse_set_feature_flag(conn, FUSE_CAP_PASSTHROUGH)) + fs.passthrough = false; + + /* Passthrough and writeback cache are conflicting modes */ + if (fs.timeout && !fs.passthrough) + fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); + + fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); + + if (fs.nosplice) { + // FUSE_CAP_SPLICE_READ is enabled in libfuse3 by default, + // see do_init() in fuse_lowlevel.c + // Just unset all, in case FUSE_CAP_SPLICE_WRITE or + // FUSE_CAP_SPLICE_MOVE would also get enabled by default. + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ); + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_WRITE); + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_MOVE); + } else { + fuse_set_feature_flag(conn, FUSE_CAP_SPLICE_WRITE); + fuse_set_feature_flag(conn, FUSE_CAP_SPLICE_READ); + fuse_set_feature_flag(conn, FUSE_CAP_SPLICE_MOVE); + } + + /* This is a local file system - no network coherency needed */ + fuse_set_feature_flag(conn, FUSE_CAP_DIRECT_IO_ALLOW_MMAP); + + /* Disable NFS export support, which also disabled name_to_handle_at. + * Goal is to make xfstests that test name_to_handle_at to fail with + * the right error code (EOPNOTSUPP) than to open_by_handle_at to fail with + * ESTALE and let those test fail. + * Perfect NFS export support is not possible with this FUSE filesystem needs + * more kernel work, in order to passthrough nfs handle encode/decode to + * fuse-server/daemon. + */ + fuse_set_feature_flag(conn, FUSE_CAP_NO_EXPORT_SUPPORT); + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; + + /* Try a large IO by default */ + conn->max_write = 4 * 1024 * 1024; +} + +static void sfs_getattr(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) +{ + struct stat attr; + int fd = fi ? fi->fh : get_inode(ino).fd; + + auto res = fstatat(fd, "", &attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) { + fuse_reply_err(req, errno); + return; + } + fuse_reply_attr(req, &attr, fs.timeout); +} + +static int with_fd_path(int fd, const std::function &f) +{ +#ifdef __FreeBSD__ + struct kinfo_file kf; + kf.kf_structsize = sizeof(kf); + int ret = fcntl(fd, F_KINFO, &kf); + if (ret == -1) + return ret; + return f(kf.kf_path); +#else // Linux + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", fd); + return f(procname); +#endif +} +static void do_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int valid, struct fuse_file_info *fi) +{ + Inode &inode = get_inode(ino); + int ifd = inode.fd; + int res; + + if (valid & FUSE_SET_ATTR_MODE) { + if (fi) { + res = fchmod(fi->fh, attr->st_mode); + } else { + res = with_fd_path(ifd, [attr](const char *procname) { + return chmod(procname, attr->st_mode); + }); + } + if (res == -1) + goto out_err; + } + if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) { + uid_t uid = (valid & FUSE_SET_ATTR_UID) ? + attr->st_uid : + static_cast(-1); + gid_t gid = (valid & FUSE_SET_ATTR_GID) ? + attr->st_gid : + static_cast(-1); + + res = fchownat(ifd, "", uid, gid, + AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + goto out_err; + } + if (valid & FUSE_SET_ATTR_SIZE) { + if (fi) { + res = ftruncate(fi->fh, attr->st_size); + } else { + res = with_fd_path(ifd, [attr](const char *procname) { + return truncate(procname, attr->st_size); + }); + } + if (res == -1) + goto out_err; + } + if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) { + struct timespec tv[2]; + + tv[0].tv_sec = 0; + tv[1].tv_sec = 0; + tv[0].tv_nsec = UTIME_OMIT; + tv[1].tv_nsec = UTIME_OMIT; + + if (valid & FUSE_SET_ATTR_ATIME_NOW) + tv[0].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_ATIME) + tv[0] = attr->st_atim; + + if (valid & FUSE_SET_ATTR_MTIME_NOW) + tv[1].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_MTIME) + tv[1] = attr->st_mtim; + + if (fi) + res = futimens(fi->fh, tv); + else { +#ifdef HAVE_UTIMENSAT + res = with_fd_path(ifd, [&tv](const char *procname) { + return utimensat(AT_FDCWD, procname, tv, 0); + }); +#else + res = -1; + errno = EOPNOTSUPP; +#endif + } + if (res == -1) + goto out_err; + } + return sfs_getattr(req, ino, fi); + +out_err: + fuse_reply_err(req, errno); +} + +static void sfs_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int valid, fuse_file_info *fi) +{ + (void)ino; + do_setattr(req, ino, attr, valid, fi); +} + +static int do_lookup(fuse_ino_t parent, const char *name, fuse_entry_param *e) +{ + if (fs.debug) + cerr << "DEBUG: lookup(): name=" << name + << ", parent=" << parent << endl; + memset(e, 0, sizeof(*e)); + e->attr_timeout = fs.timeout; + e->entry_timeout = fs.timeout; + + auto newfd = openat(get_fs_fd(parent), name, O_PATH | O_NOFOLLOW); + if (newfd == -1) + return errno; + + auto res = fstatat(newfd, "", &e->attr, + AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) { + auto saveerr = errno; + close(newfd); + if (fs.debug) + cerr << "DEBUG: lookup(): fstatat failed" << endl; + return saveerr; + } + + if (e->attr.st_dev != fs.src_dev) { + cerr << "WARNING: Mountpoints in the source directory tree will be hidden." + << endl; + return ENOTSUP; + } else if (e->attr.st_ino == FUSE_ROOT_ID) { + cerr << "ERROR: Source directory tree must not include inode " + << FUSE_ROOT_ID << endl; + return EIO; + } + + SrcId id{ e->attr.st_ino, e->attr.st_dev }; + unique_lock fs_lock{ fs.mutex }; + Inode *inode_p; + try { + inode_p = &fs.inodes[id]; + } catch (std::bad_alloc &) { + return ENOMEM; + } + e->ino = reinterpret_cast(inode_p); + Inode &inode{ *inode_p }; + e->generation = inode.generation; + + if (inode.fd == -ENOENT) { // found unlinked inode + if (fs.debug) + cerr << "DEBUG: lookup(): inode " << e->attr.st_ino + << " recycled; generation=" << inode.generation + << endl; + /* fallthrough to new inode but keep existing inode.nlookup */ + } + + if (inode.fd > 0) { // found existing inode + if (fs.debug) + cerr << "DEBUG: lookup(): inode " << e->attr.st_ino + << " (userspace) already known; fd = " << inode.fd + << endl; + + inode.nlookup++; + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << inode.src_ino << " count " + << inode.nlookup << endl; + + fs_lock.unlock(); + close(newfd); + } else { // no existing inode + /* This is just here to make Helgrind happy. It violates the + * lock ordering requirement (inode.m must be acquired before + * fs.mutex), but this is of no consequence because at this + * point no other thread has access to the inode mutex + */ + lock_guard g{ inode.m }; + inode.src_ino = e->attr.st_ino; + inode.src_dev = e->attr.st_dev; + + inode.nlookup++; + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << inode.src_ino << " count " + << inode.nlookup << endl; + + inode.fd = newfd; + fs_lock.unlock(); + + if (fs.debug) + cerr << "DEBUG: lookup(): created userspace inode " + << e->attr.st_ino << "; fd = " << inode.fd << endl; + } + + return 0; +} + +static void sfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + fuse_entry_param e{}; + auto err = do_lookup(parent, name, &e); + if (err == ENOENT) { + e.attr_timeout = fs.timeout; + e.entry_timeout = fs.timeout; + e.ino = e.attr.st_ino = 0; + fuse_reply_entry(req, &e); + } else if (err) { + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, err); + } else { + fuse_reply_entry(req, &e); + } +} + +static void mknod_symlink(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, dev_t rdev, const char *link) +{ + int res; + Inode &inode_p = get_inode(parent); + auto saverr = ENOMEM; + + if (S_ISDIR(mode)) + res = mkdirat(inode_p.fd, name, mode); + else if (S_ISLNK(mode)) + res = symlinkat(link, inode_p.fd, name); + else + res = mknodat(inode_p.fd, name, mode, rdev); + saverr = errno; + if (res == -1) + goto out; + + fuse_entry_param e; + saverr = do_lookup(parent, name, &e); + if (saverr) + goto out; + + fuse_reply_entry(req, &e); + return; + +out: + if (saverr == ENFILE || saverr == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, saverr); +} + +static void sfs_mknod(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, dev_t rdev) +{ + mknod_symlink(req, parent, name, mode, rdev, nullptr); +} + +static void sfs_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode) +{ + mknod_symlink(req, parent, name, S_IFDIR | mode, 0, nullptr); +} + +static void sfs_symlink(fuse_req_t req, const char *link, fuse_ino_t parent, + const char *name) +{ + mknod_symlink(req, parent, name, S_IFLNK, 0, link); +} + +static void sfs_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent, + const char *name) +{ + Inode &inode = get_inode(ino); + Inode &inode_p = get_inode(parent); + fuse_entry_param e{}; + + e.attr_timeout = fs.timeout; + e.entry_timeout = fs.timeout; + + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", inode.fd); + auto res = + linkat(AT_FDCWD, procname, inode_p.fd, name, AT_SYMLINK_FOLLOW); + if (res == -1) { + fuse_reply_err(req, errno); + return; + } + + res = fstatat(inode.fd, "", &e.attr, + AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) { + fuse_reply_err(req, errno); + return; + } + e.ino = reinterpret_cast(&inode); + { + inode.nlookup++; + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << inode.src_ino << " count " + << inode.nlookup << endl; + } + + fuse_reply_entry(req, &e); + return; +} + +static void sfs_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + Inode &inode_p = get_inode(parent); + lock_guard g{ inode_p.m }; + auto res = unlinkat(inode_p.fd, name, AT_REMOVEDIR); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void sfs_rename(fuse_req_t req, fuse_ino_t parent, const char *name, + fuse_ino_t newparent, const char *newname, + unsigned int flags) +{ + Inode &inode_p = get_inode(parent); + Inode &inode_np = get_inode(newparent); + if (flags) { + fuse_reply_err(req, EINVAL); + return; + } + + auto res = renameat(inode_p.fd, name, inode_np.fd, newname); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void sfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + Inode &inode_p = get_inode(parent); + // Release inode.fd before last unlink like nfsd EXPORT_OP_CLOSE_BEFORE_UNLINK + // to test reused inode numbers. + // Skip this when inode has an open file and when writeback cache is enabled. + if (!fs.timeout) { + fuse_entry_param e; + auto err = do_lookup(parent, name, &e); + if (err) { + fuse_reply_err(req, err); + return; + } + if (e.attr.st_nlink == 1) { + Inode &inode = get_inode(e.ino); + lock_guard g{ inode.m }; + if (inode.fd > 0 && !inode.nopen) { + if (fs.debug) + cerr << "DEBUG: unlink: release inode " + << e.attr.st_ino + << "; fd=" << inode.fd << endl; + lock_guard g_fs{ fs.mutex }; + close(inode.fd); + inode.fd = -ENOENT; + inode.generation++; + } + } + + // decrease the ref which lookup above had increased + forget_one(e.ino, 1); + } + auto res = unlinkat(inode_p.fd, name, 0); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void forget_one(fuse_ino_t ino, uint64_t n) +{ + Inode &inode = get_inode(ino); + unique_lock l{ inode.m }; + + if (n > inode.nlookup) { + cerr << "INTERNAL ERROR: Negative lookup count for inode " + << inode.src_ino << endl; + abort(); + } + inode.nlookup -= n; + + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << inode.src_ino << " count " << inode.nlookup + << endl; + + if (!inode.nlookup) { + lock_guard g_fs{ fs.mutex }; + l.unlock(); + if (!inode.nlookup) { + if (fs.debug) + cerr << "DEBUG: forget: cleaning up inode " + << inode.src_ino << endl; + fs.inodes.erase({ inode.src_ino, inode.src_dev }); + } + } else if (fs.debug) + cerr << "DEBUG: forget: inode " << inode.src_ino + << " lookup count now " << inode.nlookup << endl; +} + +static void sfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + forget_one(ino, nlookup); + fuse_reply_none(req); +} + +static void sfs_forget_multi(fuse_req_t req, size_t count, + fuse_forget_data *forgets) +{ + for (int i = 0; i < count; i++) + forget_one(forgets[i].ino, forgets[i].nlookup); + fuse_reply_none(req); +} + +static void sfs_readlink(fuse_req_t req, fuse_ino_t ino) +{ + Inode &inode = get_inode(ino); + char buf[PATH_MAX + 1]; + auto res = readlinkat(inode.fd, "", buf, sizeof(buf)); + if (res == -1) + fuse_reply_err(req, errno); + else if (res == sizeof(buf)) + fuse_reply_err(req, ENAMETOOLONG); + else { + buf[res] = '\0'; + fuse_reply_readlink(req, buf); + } +} + +struct DirHandle { + DIR *dp{ nullptr }; + off_t offset; + + DirHandle() = default; + DirHandle(const DirHandle &) = delete; + DirHandle &operator=(const DirHandle &) = delete; + + ~DirHandle() + { + if (dp) + closedir(dp); + } +}; + +static DirHandle *get_dir_handle(fuse_file_info *fi) +{ + return reinterpret_cast(fi->fh); +} + +static void sfs_opendir(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) +{ + Inode &inode = get_inode(ino); + auto d = new (nothrow) DirHandle; + if (d == nullptr) { + fuse_reply_err(req, ENOMEM); + return; + } + + // Make Helgrind happy - it can't know that there's an implicit + // synchronization due to the fact that other threads cannot + // access d until we've called fuse_reply_*. + lock_guard g{ inode.m }; + + auto fd = openat(inode.fd, ".", O_RDONLY); + if (fd == -1) + goto out_errno; + + // On success, dir stream takes ownership of fd, so we + // do not have to close it. + d->dp = fdopendir(fd); + if (d->dp == nullptr) + goto out_errno; + + d->offset = 0; + + fi->fh = reinterpret_cast(d); + if (fs.timeout) { + fi->keep_cache = 1; + fi->cache_readdir = 1; + } + fuse_reply_open(req, fi); + return; + +out_errno: + auto error = errno; + delete d; + if (error == ENFILE || error == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, error); +} + +static bool is_dot_or_dotdot(const char *name) +{ + return name[0] == '.' && + (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); +} + +static void do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, fuse_file_info *fi, const int plus) +{ + auto d = get_dir_handle(fi); + Inode &inode = get_inode(ino); + lock_guard g{ inode.m }; + char *p; + auto rem = size; + int err = 0, count = 0; + + if (fs.debug) + cerr << "DEBUG: readdir(): started with offset " << offset + << endl; + + auto buf = new (nothrow) char[size]; + if (!buf) { + fuse_reply_err(req, ENOMEM); + return; + } + p = buf; + + if (offset != d->offset) { + if (fs.debug) + cerr << "DEBUG: readdir(): seeking to " << offset + << endl; + seekdir(d->dp, offset); + d->offset = offset; + } + + while (1) { + bool did_lookup = false; + struct dirent *entry; + errno = 0; + entry = readdir(d->dp); + if (!entry) { + if (errno) { + err = errno; + if (fs.debug) + warn("DEBUG: readdir(): readdir failed with"); + goto error; + } + break; // End of stream + } + d->offset = entry->d_off; + + fuse_entry_param e{}; + size_t entsize; + if (plus) { + if (is_dot_or_dotdot(entry->d_name)) { + /* fuse kernel ignores attributes for these and also does + * not increase lookup count (see fuse_direntplus_link) + */ + e.attr.st_ino = entry->d_ino; + e.attr.st_mode = entry->d_type << 12; + } else { + err = do_lookup(ino, entry->d_name, &e); + if (err) + goto error; + did_lookup = true; + } + entsize = fuse_add_direntry_plus( + req, p, rem, entry->d_name, &e, entry->d_off); + } else { + e.attr.st_ino = entry->d_ino; + e.attr.st_mode = entry->d_type << 12; + entsize = fuse_add_direntry(req, p, rem, entry->d_name, + &e.attr, entry->d_off); + } + + if (entsize > rem) { + if (fs.debug) + cerr << "DEBUG: readdir(): buffer full, returning data. " + << endl; + if (did_lookup) + forget_one(e.ino, 1); + break; + } + + p += entsize; + rem -= entsize; + count++; + if (fs.debug) { + cerr << "DEBUG: readdir(): added to buffer: " + << entry->d_name << ", ino " << e.attr.st_ino + << ", offset " << entry->d_off << endl; + } + } + err = 0; +error: + + // If there's an error, we can only signal it if we haven't stored + // any entries yet - otherwise we'd end up with wrong lookup + // counts for the entries that are already in the buffer. So we + // return what we've collected until that point. + if (err && rem == size) { + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, err); + } else { + if (fs.debug) + cerr << "DEBUG: readdir(): returning " << count + << " entries, curr offset " << d->offset << endl; + fuse_reply_buf(req, buf, size - rem); + } + delete[] buf; + return; +} + +static void sfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, fuse_file_info *fi) +{ + // operation logging is done in readdir to reduce code duplication + do_readdir(req, ino, size, offset, fi, 0); +} + +static void sfs_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, fuse_file_info *fi) +{ + // operation logging is done in readdir to reduce code duplication + do_readdir(req, ino, size, offset, fi, 1); +} + +static void sfs_releasedir(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) +{ + (void)ino; + auto d = get_dir_handle(fi); + delete d; + fuse_reply_err(req, 0); +} + +static void do_passthrough_open(fuse_req_t req, fuse_ino_t ino, int fd, + fuse_file_info *fi) +{ + Inode &inode = get_inode(ino); + /* Setup a shared backing file on first open of an inode */ + if (inode.backing_id) { + if (fs.debug) + cerr << "DEBUG: reusing shared backing file " + << inode.backing_id << " for inode " << ino + << endl; + fi->backing_id = inode.backing_id; + } else if (!(inode.backing_id = fuse_passthrough_open(req, fd))) { + cerr << "DEBUG: fuse_passthrough_open failed for inode " << ino + << ", disabling rw passthrough." << endl; + fs.passthrough = false; + } else { + if (fs.debug) + cerr << "DEBUG: setup shared backing file " + << inode.backing_id << " for inode " << ino + << endl; + fi->backing_id = inode.backing_id; + } + /* open in passthrough mode must drop old page cache */ + if (fi->backing_id) + fi->keep_cache = false; +} + +static void sfs_create_open_flags(fuse_file_info *fi) +{ + if (fs.direct_io) + fi->direct_io = 1; + + /* + * fi->direct_io (FOPEN_DIRECT_IO) is set to benefit from + * parallel_direct_writes, which kernel cannot do for plain O_DIRECT. + * However, passthrough is preferred, but which is not possible when + * FOPEN_DIRECT_IO is set. + */ + if (!fs.passthrough) { + if (fi->flags & O_DIRECT) + fi->direct_io = 1; + } + + /* parallel_direct_writes feature depends on direct_io features. + * To make parallel_direct_writes valid, need set fi->direct_io + * in current function. + */ + fi->parallel_direct_writes = 1; + + fi->keep_cache = (fs.timeout != 0); + fi->noflush = (fs.timeout == 0 && (fi->flags & O_ACCMODE) == O_RDONLY); +} + +static void sfs_create(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, fuse_file_info *fi) +{ + Inode &inode_p = get_inode(parent); + + auto fd = openat(inode_p.fd, name, (fi->flags | O_CREAT) & ~O_NOFOLLOW, + mode); + if (fd == -1) { + auto err = errno; + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, err); + return; + } + + fi->fh = fd; + fuse_entry_param e; + auto err = do_lookup(parent, name, &e); + if (err) { + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, err); + return; + } + + Inode &inode = get_inode(e.ino); + lock_guard g{ inode.m }; + inode.nopen++; + + sfs_create_open_flags(fi); + + if (fs.passthrough) + do_passthrough_open(req, e.ino, fd, fi); + fuse_reply_create(req, &e, fi); +} + +#ifdef O_TMPFILE +static Inode *create_new_inode(int fd, fuse_entry_param *e) +{ + memset(e, 0, sizeof(*e)); + e->attr_timeout = fs.timeout; + e->entry_timeout = fs.timeout; + + auto res = + fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) { + if (fs.debug) + cerr << "DEBUG: lookup(): fstatat failed" << endl; + return NULL; + } + + SrcId id{ e->attr.st_ino, e->attr.st_dev }; + unique_lock fs_lock{ fs.mutex }; + Inode *p_inode; + try { + p_inode = &fs.inodes[id]; + } catch (std::bad_alloc &) { + return NULL; + } + + e->ino = reinterpret_cast(p_inode); + e->generation = p_inode->generation; + + lock_guard g{ p_inode->m }; + p_inode->src_ino = e->attr.st_ino; + p_inode->src_dev = e->attr.st_dev; + + p_inode->nlookup++; + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << p_inode->src_ino << " count " + << p_inode->nlookup << endl; + + p_inode->fd = fd; + fs_lock.unlock(); + + if (fs.debug) + cerr << "DEBUG: lookup(): created userspace inode " + << e->attr.st_ino << "; fd = " << p_inode->fd << endl; + return p_inode; +} + +static void sfs_tmpfile(fuse_req_t req, fuse_ino_t parent, mode_t mode, + fuse_file_info *fi) +{ + Inode &parent_inode = get_inode(parent); + + auto fd = openat(parent_inode.fd, ".", + (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode); + if (fd == -1) { + auto err = errno; + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, err); + return; + } + + fi->fh = fd; + fuse_entry_param e; + + Inode *inode = create_new_inode(dup(fd), &e); + if (inode == NULL) { + auto err = errno; + cerr << "ERROR: could not create new inode." << endl; + close(fd); + fuse_reply_err(req, err); + return; + } + + lock_guard g{ inode->m }; + + sfs_create_open_flags(fi); + + if (fs.passthrough) + do_passthrough_open(req, e.ino, fd, fi); + + fuse_reply_create(req, &e, fi); +} +#endif +static void sfs_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync, + fuse_file_info *fi) +{ + (void)ino; + int res; + int fd = dirfd(get_dir_handle(fi)->dp); + if (datasync) + res = fdatasync(fd); + else + res = fsync(fd); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void sfs_open(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) +{ + Inode &inode = get_inode(ino); + + /* With writeback cache, kernel may send read requests even + when userspace opened write-only */ + if (fs.timeout && (fi->flags & O_ACCMODE) == O_WRONLY) { + fi->flags &= ~O_ACCMODE; + fi->flags |= O_RDWR; + } + + /* With writeback cache, O_APPEND is handled by the kernel. This + * breaks atomicity (since the file may change in the underlying + * filesystem, so that the kernel's idea of the end of the file + * isn't accurate anymore). However, no process should modify the + * file in the underlying filesystem once it has been read, so + * this is not a problem. + */ + if (fs.timeout && fi->flags & O_APPEND) + fi->flags &= ~O_APPEND; + + /* Unfortunately we cannot use inode.fd, because this was opened + with O_PATH (so it doesn't allow read/write access). */ + auto fd = with_fd_path(inode.fd, [fi](const char *buf) { + return open(buf, fi->flags & ~O_NOFOLLOW); + }); + if (fd == -1) { + auto err = errno; + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, err); + return; + } + + lock_guard g{ inode.m }; + inode.nopen++; + + sfs_create_open_flags(fi); + + fi->fh = fd; + if (fs.passthrough) + do_passthrough_open(req, ino, fd, fi); + fuse_reply_open(req, fi); +} + +static void sfs_release(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) +{ + Inode &inode = get_inode(ino); + lock_guard g{ inode.m }; + inode.nopen--; + + /* Close the shared backing file on last file close of an inode */ + if (inode.backing_id && !inode.nopen) { + if (fuse_passthrough_close(req, inode.backing_id) < 0) { + cerr << "DEBUG: fuse_passthrough_close failed for inode " + << ino << " backing file " << inode.backing_id + << endl; + } else if (fs.debug) { + cerr << "DEBUG: closed backing file " + << inode.backing_id << " for inode " << ino + << endl; + } + inode.backing_id = 0; + } + + close(fi->fh); + fuse_reply_err(req, 0); +} + +static void sfs_flush(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) +{ + (void)ino; + auto res = close(dup(fi->fh)); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void sfs_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, + fuse_file_info *fi) +{ + (void)ino; + int res; + if (datasync) + res = fdatasync(fi->fh); + else + res = fsync(fi->fh); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void do_read(fuse_req_t req, size_t size, off_t off, fuse_file_info *fi) +{ + fuse_bufvec buf = FUSE_BUFVEC_INIT(size); + char *payload = NULL; + size_t payload_size = 0; + int res = fuse_req_get_payload(req, &payload, &payload_size, NULL); + + /* + * This is a demonstration how to use io-uring payload. For FUSE_BUF_IS_FD + * it shouldn't make much of a difference because fuse_reply_data() -> + * fuse_reply_data_uring() also has access to the payload and will + * read directly from the FD into the payload. + * It is more useful for file systems that need a buffer for decryption, + * decompression, etc. + */ + if (res == 0) { + /* This is an io-uring request - write directly to the payload */ + assert(payload_size >= size); + + buf.buf[0].mem = payload; + buf.buf[0].size = payload_size; + + res = pread(fi->fh, payload, size, off); + if (res < 0) { + fuse_reply_err(req, errno); + return; + } + + buf.buf[0].size = res; + } else { + buf.buf[0].flags = static_cast( + FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK); + buf.buf[0].fd = fi->fh; + buf.buf[0].pos = off; + } + + fuse_reply_data(req, &buf, FUSE_BUF_COPY_FLAGS); +} + +static void sfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + fuse_file_info *fi) +{ + (void)ino; + if (fs.passthrough && !fs.direct_io) { + cerr << "ERROR: fuse_passthrough read failed." << endl; + fuse_reply_err(req, EIO); + return; + } + do_read(req, size, off, fi); +} + +static void do_write_buf(fuse_req_t req, size_t size, off_t off, + fuse_bufvec *in_buf, fuse_file_info *fi) +{ + fuse_bufvec out_buf = FUSE_BUFVEC_INIT(size); + out_buf.buf[0].flags = + static_cast(FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK); + out_buf.buf[0].fd = fi->fh; + out_buf.buf[0].pos = off; + + auto res = fuse_buf_copy(&out_buf, in_buf, FUSE_BUF_COPY_FLAGS); + if (res < 0) + fuse_reply_err(req, -res); + else + fuse_reply_write(req, (size_t)res); +} + +static void sfs_write_buf(fuse_req_t req, fuse_ino_t ino, fuse_bufvec *in_buf, + off_t off, fuse_file_info *fi) +{ + (void)ino; + if (fs.passthrough && !fs.direct_io) { + cerr << "ERROR: fuse_passthrough write failed." << endl; + fuse_reply_err(req, EIO); + return; + } + auto size{ fuse_buf_size(in_buf) }; + do_write_buf(req, size, off, in_buf, fi); +} + +static void sfs_statfs(fuse_req_t req, fuse_ino_t ino) +{ + struct statvfs stbuf; + + auto res = fstatvfs(get_fs_fd(ino), &stbuf); + if (res == -1) + fuse_reply_err(req, errno); + else + fuse_reply_statfs(req, &stbuf); +} + +static void sfs_fallocate(fuse_req_t req, fuse_ino_t ino, int mode, + off_t offset, off_t length, fuse_file_info *fi) +{ + (void)ino; + + auto err = -do_fallocate(fi->fh, mode, offset, length); + + fuse_reply_err(req, err); +} + +static void sfs_flock(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi, + int op) +{ + (void)ino; + auto res = flock(fi->fh, op); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +#ifdef HAVE_SETXATTR +static void sfs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size) +{ + char *value = nullptr; + Inode &inode = get_inode(ino); + ssize_t ret; + int saverr; + + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", inode.fd); + + if (size) { + value = new (nothrow) char[size]; + if (value == nullptr) { + saverr = ENOMEM; + goto out; + } + + ret = getxattr(procname, name, value, size); + if (ret == -1) + goto out_err; + saverr = 0; + if (ret == 0) + goto out; + + fuse_reply_buf(req, value, ret); + } else { + ret = getxattr(procname, name, nullptr, 0); + if (ret == -1) + goto out_err; + + fuse_reply_xattr(req, ret); + } +out_free: + delete[] value; + return; + +out_err: + saverr = errno; +out: + fuse_reply_err(req, saverr); + goto out_free; +} + +static void sfs_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) +{ + char *value = nullptr; + Inode &inode = get_inode(ino); + ssize_t ret; + int saverr; + + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", inode.fd); + + if (size) { + value = new (nothrow) char[size]; + if (value == nullptr) { + saverr = ENOMEM; + goto out; + } + + ret = listxattr(procname, value, size); + if (ret == -1) + goto out_err; + saverr = 0; + if (ret == 0) + goto out; + + fuse_reply_buf(req, value, ret); + } else { + ret = listxattr(procname, nullptr, 0); + if (ret == -1) + goto out_err; + + fuse_reply_xattr(req, ret); + } +out_free: + delete[] value; + return; +out_err: + saverr = errno; +out: + fuse_reply_err(req, saverr); + goto out_free; +} + +static void sfs_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags) +{ + Inode &inode = get_inode(ino); + ssize_t ret; + int saverr; + + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", inode.fd); + + ret = setxattr(procname, name, value, size, flags); + saverr = ret == -1 ? errno : 0; + + fuse_reply_err(req, saverr); +} + +static void sfs_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name) +{ + char procname[64]; + Inode &inode = get_inode(ino); + ssize_t ret; + int saverr; + + sprintf(procname, "/proc/self/fd/%i", inode.fd); + ret = removexattr(procname, name); + saverr = ret == -1 ? errno : 0; + + fuse_reply_err(req, saverr); +} +#endif + +static void assign_operations(fuse_lowlevel_ops &sfs_oper) +{ + sfs_oper.init = sfs_init; + sfs_oper.lookup = sfs_lookup; + sfs_oper.mkdir = sfs_mkdir; + sfs_oper.mknod = sfs_mknod; + sfs_oper.symlink = sfs_symlink; + sfs_oper.link = sfs_link; + sfs_oper.unlink = sfs_unlink; + sfs_oper.rmdir = sfs_rmdir; + sfs_oper.rename = sfs_rename; + sfs_oper.forget = sfs_forget; + sfs_oper.forget_multi = sfs_forget_multi; + sfs_oper.getattr = sfs_getattr; + sfs_oper.setattr = sfs_setattr; + sfs_oper.readlink = sfs_readlink; + sfs_oper.opendir = sfs_opendir; + sfs_oper.readdir = sfs_readdir; + sfs_oper.readdirplus = sfs_readdirplus; + sfs_oper.releasedir = sfs_releasedir; + sfs_oper.fsyncdir = sfs_fsyncdir; + sfs_oper.create = sfs_create; +#ifdef O_TMPFILE + sfs_oper.tmpfile = sfs_tmpfile; +#endif + sfs_oper.open = sfs_open; + sfs_oper.release = sfs_release; + sfs_oper.flush = sfs_flush; + sfs_oper.fsync = sfs_fsync; + sfs_oper.read = sfs_read; + sfs_oper.write_buf = sfs_write_buf; + sfs_oper.statfs = sfs_statfs; + sfs_oper.fallocate = sfs_fallocate; + sfs_oper.flock = sfs_flock; +#ifdef HAVE_SETXATTR + sfs_oper.setxattr = sfs_setxattr; + sfs_oper.getxattr = sfs_getxattr; + sfs_oper.listxattr = sfs_listxattr; + sfs_oper.removexattr = sfs_removexattr; +#endif +} + +static void print_usage(char *prog_name) +{ + cout << "Usage: " << prog_name << " --help\n" + << " " << prog_name << " [options] \n"; +} + +static cxxopts::ParseResult parse_wrapper(cxxopts::Options &parser, int &argc, + char **&argv) +{ + try { + return parser.parse(argc, argv); + } catch (cxxopts::option_not_exists_exception &exc) { + std::cout << argv[0] << ": " << exc.what() << std::endl; + print_usage(argv[0]); + exit(2); + } +} + +static void string_split(std::string s, std::vector &out, + std::string delimiter) +{ + size_t pos_start = 0, pos_end, delim_len = delimiter.length(); + std::string token; + + while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) { + token = s.substr(pos_start, pos_end - pos_start); + pos_start = pos_end + delim_len; + out.push_back(token); + } + + out.push_back(s.substr(pos_start)); +} + +static std::string string_join(const std::vector &elems, + char delim) +{ + std::ostringstream out; + for (auto ii = elems.begin(); ii != elems.end(); ++ii) { + out << (*ii); + if (ii + 1 != elems.end()) { + out << delim; + } + } + return out.str(); +} + +static cxxopts::ParseResult parse_options(int argc, char **argv) +{ + cxxopts::Options opt_parser(argv[0]); + std::vector mount_options; + opt_parser.add_options()("debug", "Enable filesystem debug messages")( + "debug-fuse", "Enable libfuse debug messages")( + "foreground", "Run in foreground")("help", "Print help")( + "nocache", "Disable attribute all caching")( + "nosplice", "Do not use splice(2) to transfer data")( + "nopassthrough", "Do not use pass-through mode for read/write")( + "single", "Run single-threaded")( + "o", + "Mount options (see mount.fuse(5) - only use if you know what " + "you are doing)", + cxxopts::value(mount_options))( + "num-threads", "Number of libfuse worker threads", + cxxopts::value()->default_value(SFS_DEFAULT_THREADS))( + "clone-fd", "use separate fuse device fd for each thread")( + "direct-io", "enable fuse kernel internal direct-io"); + + // FIXME: Find a better way to limit the try clause to just + // opt_parser.parse() (cf. https://github.com/jarro2783/cxxopts/issues/146) + auto options = parse_wrapper(opt_parser, argc, argv); + + if (options.count("help")) { + print_usage(argv[0]); + // Strip everything before the option list from the + // default help string. + auto help = opt_parser.help(); + std::cout << std::endl + << "options:" + << help.substr(help.find("\n\n") + 1, string::npos); + std::cout << "\nFuse lowlevel options:\n"; + fuse_lowlevel_help(); + exit(0); + + } else if (argc != 3) { + std::cout << argv[0] << ": invalid number of arguments\n"; + print_usage(argv[0]); + exit(2); + } + + fs.debug = options.count("debug") != 0; + fs.debug_fuse = options.count("debug-fuse") != 0; + + fs.foreground = options.count("foreground") != 0; + if (fs.debug || fs.debug_fuse) + fs.foreground = true; + + fs.nosplice = options.count("nosplice") != 0; + fs.passthrough = options.count("nopassthrough") == 0; + fs.num_threads = options["num-threads"].as(); + fs.clone_fd = options.count("clone-fd"); + fs.direct_io = options.count("direct-io"); + + char *resolved_path = realpath(argv[1], NULL); + if (resolved_path == NULL) + warn("WARNING: realpath() failed with"); + fs.source = std::string{ resolved_path }; + free(resolved_path); + + std::vector flattened_mount_opts; + for (auto opt : mount_options) { + string_split(opt, flattened_mount_opts, ","); + } + + bool found_fsname = false; + for (auto opt : flattened_mount_opts) { + if (opt.find("fsname=") == 0) { + found_fsname = true; + continue; + } + + /* Filter out some obviously incorrect options. */ + if (opt == "fd") { + std::cout << argv[0] + << ": Unsupported mount option: " << opt + << "\n"; + print_usage(argv[0]); + exit(2); + } + } + if (!found_fsname) { + flattened_mount_opts.push_back("fsname=" + fs.source); + } + flattened_mount_opts.push_back("default_permissions"); + fs.fuse_mount_options = string_join(flattened_mount_opts, ','); + return options; +} + +static void maximize_fd_limit() +{ + struct rlimit lim {}; + auto res = getrlimit(RLIMIT_NOFILE, &lim); + if (res != 0) { + warn("WARNING: getrlimit() failed with"); + return; + } + lim.rlim_cur = lim.rlim_max; + res = setrlimit(RLIMIT_NOFILE, &lim); + if (res != 0) + warn("WARNING: setrlimit() failed with"); +} + +int main(int argc, char *argv[]) +{ + struct fuse_loop_config *loop_config = NULL; + + // Parse command line options + auto options{ parse_options(argc, argv) }; + + // We need an fd for every dentry in our the filesystem that the + // kernel knows about. This is way more than most processes need, + // so try to get rid of any resource softlimit. + maximize_fd_limit(); + + // Initialize filesystem root + fs.root.fd = -1; + fs.root.nlookup = 9999; + fs.timeout = options.count("nocache") ? 0 : 86400.0; + + struct stat stat; + auto ret = lstat(fs.source.c_str(), &stat); + if (ret == -1) + err(1, "ERROR: failed to stat source (\"%s\")", + fs.source.c_str()); + if (!S_ISDIR(stat.st_mode)) + errx(1, "ERROR: source is not a directory"); + fs.src_dev = stat.st_dev; + + fs.root.fd = open(fs.source.c_str(), O_PATH); + if (fs.root.fd == -1) + err(1, "ERROR: open(\"%s\", O_PATH)", fs.source.c_str()); + + // Initialize fuse + fuse_args args = FUSE_ARGS_INIT(0, nullptr); + if (fuse_opt_add_arg(&args, argv[0]) || fuse_opt_add_arg(&args, "-o") || + fuse_opt_add_arg(&args, fs.fuse_mount_options.c_str()) || + (fs.debug_fuse && fuse_opt_add_arg(&args, "-odebug"))) + errx(3, "ERROR: Out of memory adding arguments"); + + ret = -1; + fuse_lowlevel_ops sfs_oper{}; + assign_operations(sfs_oper); + auto se = fuse_session_new(&args, &sfs_oper, sizeof(sfs_oper), &fs); + if (se == nullptr) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_set_fail_signal_handlers(se) != 0) + goto err_out2; + + // Don't apply umask, use modes exactly as specified + umask(0); + + // Mount and run main loop + loop_config = fuse_loop_cfg_create(); + + if (fs.num_threads != -1) + fuse_loop_cfg_set_max_threads(loop_config, fs.num_threads); + + fuse_loop_cfg_set_clone_fd(loop_config, fs.clone_fd); + + if (fuse_session_mount(se, argv[2]) != 0) + goto err_out3; + + fuse_daemonize(fs.foreground); + + if (!fs.foreground) + fuse_log_enable_syslog("passthrough-hp", LOG_PID | LOG_CONS, + LOG_DAEMON); + + if (options.count("single")) + ret = fuse_session_loop(se); + else + ret = fuse_session_loop_mt(se, loop_config); + + fuse_session_unmount(se); + +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + + fuse_loop_cfg_destroy(loop_config); + fuse_opt_free_args(&args); + + if (!fs.foreground) + fuse_log_close_syslog(); + + return ret ? 1 : 0; +} diff --git a/example/passthrough_ll.c b/example/passthrough_ll.c new file mode 100644 index 0000000..7dfaee3 --- /dev/null +++ b/example/passthrough_ll.c @@ -0,0 +1,1416 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This file system mirrors the existing file system hierarchy of the + * system, starting at the root file system. This is implemented by + * just "passing through" all requests to the corresponding user-space + * libc functions. In contrast to passthrough.c and passthrough_fh.c, + * this implementation uses the low-level API. Its performance should + * be the least bad among the three. + * + * When writeback caching is enabled (-o writeback mount option), it + * is only possible to write to files for which the mounting user has + * read permissions. This is because the writeback cache requires the + * kernel to be able to issue read requests for all files (which the + * passthrough filesystem cannot satisfy if it can't read the file in + * the underlying filesystem). + * + * Compile with: + * + * gcc -Wall passthrough_ll.c `pkg-config fuse3 --cflags --libs` -o passthrough_ll + * + * ## Source code ## + * \include passthrough_ll.c + */ + +#define _GNU_SOURCE +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "passthrough_helpers.h" + +/* We are re-using pointers to our `struct lo_inode` and `struct + lo_dirp` elements as inodes. This means that we must be able to + store uintptr_t values in a fuse_ino_t variable. The following + incantation checks this condition at compile time. */ +#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus +_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t), + "fuse_ino_t too small to hold uintptr_t values!"); +#else +struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \ + { unsigned _uintptr_to_must_hold_fuse_ino_t: + ((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); }; +#endif + +struct lo_inode { + struct lo_inode *next; /* protected by lo->mutex */ + struct lo_inode *prev; /* protected by lo->mutex */ + int fd; + ino_t ino; + dev_t dev; + uint64_t refcount; /* protected by lo->mutex */ +}; + +enum { + CACHE_NEVER, + CACHE_NORMAL, + CACHE_ALWAYS, +}; + +struct lo_data { + pthread_mutex_t mutex; + int debug; + int writeback; + int flock; + int xattr; + char *source; + double timeout; + int cache; + int timeout_set; + struct lo_inode root; /* protected by lo->mutex */ +}; + +static const struct fuse_opt lo_opts[] = { + { "writeback", + offsetof(struct lo_data, writeback), 1 }, + { "no_writeback", + offsetof(struct lo_data, writeback), 0 }, + { "source=%s", + offsetof(struct lo_data, source), 0 }, + { "flock", + offsetof(struct lo_data, flock), 1 }, + { "no_flock", + offsetof(struct lo_data, flock), 0 }, + { "xattr", + offsetof(struct lo_data, xattr), 1 }, + { "no_xattr", + offsetof(struct lo_data, xattr), 0 }, + { "timeout=%lf", + offsetof(struct lo_data, timeout), 0 }, + { "timeout=", + offsetof(struct lo_data, timeout_set), 1 }, + { "cache=never", + offsetof(struct lo_data, cache), CACHE_NEVER }, + { "cache=auto", + offsetof(struct lo_data, cache), CACHE_NORMAL }, + { "cache=always", + offsetof(struct lo_data, cache), CACHE_ALWAYS }, + + FUSE_OPT_END +}; + +static void passthrough_ll_help(void) +{ + printf( +" -o writeback Enable writeback\n" +" -o no_writeback Disable write back\n" +" -o source=/home/dir Source directory to be mounted\n" +" -o flock Enable flock\n" +" -o no_flock Disable flock\n" +" -o xattr Enable xattr\n" +" -o no_xattr Disable xattr\n" +" -o timeout=1.0 Caching timeout\n" +" -o timeout=0/1 Timeout is set\n" +" -o cache=never Disable cache\n" +" -o cache=auto Auto enable cache\n" +" -o cache=always Cache always\n"); +} + +static struct lo_data *lo_data(fuse_req_t req) +{ + return (struct lo_data *) fuse_req_userdata(req); +} + +static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino) +{ + if (ino == FUSE_ROOT_ID) + return &lo_data(req)->root; + else + return (struct lo_inode *) (uintptr_t) ino; +} + +static int lo_fd(fuse_req_t req, fuse_ino_t ino) +{ + return lo_inode(req, ino)->fd; +} + +static bool lo_debug(fuse_req_t req) +{ + return lo_data(req)->debug != 0; +} + +static void lo_init(void *userdata, + struct fuse_conn_info *conn) +{ + struct lo_data *lo = (struct lo_data *)userdata; + bool has_flag; + + if (lo->writeback) { + has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); + if (lo->debug && has_flag) + fuse_log(FUSE_LOG_DEBUG, + "lo_init: activating writeback\n"); + } + if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) { + has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); + if (lo->debug && has_flag) + fuse_log(FUSE_LOG_DEBUG, + "lo_init: activating flock locks\n"); + } + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; +} + +static void lo_destroy(void *userdata) +{ + struct lo_data *lo = (struct lo_data*) userdata; + + while (lo->root.next != &lo->root) { + struct lo_inode* next = lo->root.next; + lo->root.next = next->next; + close(next->fd); + free(next); + } +} + +static void lo_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + int res; + struct stat buf; + struct lo_data *lo = lo_data(req); + int fd = fi ? fi->fh : lo_fd(req, ino); + + (void) fi; + + res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + return (void) fuse_reply_err(req, errno); + + fuse_reply_attr(req, &buf, lo->timeout); +} + +static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int valid, struct fuse_file_info *fi) +{ + int saverr; + char procname[64]; + struct lo_inode *inode = lo_inode(req, ino); + int ifd = inode->fd; + int res; + + if (valid & FUSE_SET_ATTR_MODE) { + if (fi) { + res = fchmod(fi->fh, attr->st_mode); + } else { + sprintf(procname, "/proc/self/fd/%i", ifd); + res = chmod(procname, attr->st_mode); + } + if (res == -1) + goto out_err; + } + if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) { + uid_t uid = (valid & FUSE_SET_ATTR_UID) ? + attr->st_uid : (uid_t) -1; + gid_t gid = (valid & FUSE_SET_ATTR_GID) ? + attr->st_gid : (gid_t) -1; + + res = fchownat(ifd, "", uid, gid, + AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + goto out_err; + } + if (valid & FUSE_SET_ATTR_SIZE) { + if (fi) { + res = ftruncate(fi->fh, attr->st_size); + } else { + sprintf(procname, "/proc/self/fd/%i", ifd); + res = truncate(procname, attr->st_size); + } + if (res == -1) + goto out_err; + } + if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) { + struct timespec tv[2]; + + tv[0].tv_sec = 0; + tv[1].tv_sec = 0; + tv[0].tv_nsec = UTIME_OMIT; + tv[1].tv_nsec = UTIME_OMIT; + + if (valid & FUSE_SET_ATTR_ATIME_NOW) + tv[0].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_ATIME) + tv[0] = attr->st_atim; + + if (valid & FUSE_SET_ATTR_MTIME_NOW) + tv[1].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_MTIME) + tv[1] = attr->st_mtim; + + if (fi) + res = futimens(fi->fh, tv); + else { + sprintf(procname, "/proc/self/fd/%i", ifd); + res = utimensat(AT_FDCWD, procname, tv, 0); + } + if (res == -1) + goto out_err; + } + + return lo_getattr(req, ino, fi); + +out_err: + saverr = errno; + fuse_reply_err(req, saverr); +} + +static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st) +{ + struct lo_inode *p; + struct lo_inode *ret = NULL; + + pthread_mutex_lock(&lo->mutex); + for (p = lo->root.next; p != &lo->root; p = p->next) { + if (p->ino == st->st_ino && p->dev == st->st_dev) { + assert(p->refcount > 0); + ret = p; + ret->refcount++; + break; + } + } + pthread_mutex_unlock(&lo->mutex); + return ret; +} + + +static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo) +{ + struct lo_inode *inode = NULL; + struct lo_inode *prev, *next; + + inode = calloc(1, sizeof(struct lo_inode)); + if (!inode) + return NULL; + + inode->refcount = 1; + inode->fd = fd; + inode->ino = e->attr.st_ino; + inode->dev = e->attr.st_dev; + + pthread_mutex_lock(&lo->mutex); + prev = &lo->root; + next = prev->next; + next->prev = inode; + inode->next = next; + inode->prev = prev; + prev->next = inode; + pthread_mutex_unlock(&lo->mutex); + return inode; +} + +static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e) +{ + int res; + struct lo_data *lo = lo_data(req); + + memset(e, 0, sizeof(*e)); + e->attr_timeout = lo->timeout; + e->entry_timeout = lo->timeout; + + res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + return errno; + + e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo); + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, " %lli/%d -> %lli\n", + (unsigned long long) parent, fd, (unsigned long long) e->ino); + + return 0; + +} + +static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name, + struct fuse_entry_param *e) +{ + int newfd; + int res; + int saverr; + struct lo_data *lo = lo_data(req); + struct lo_inode *inode; + + memset(e, 0, sizeof(*e)); + e->attr_timeout = lo->timeout; + e->entry_timeout = lo->timeout; + + newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW); + if (newfd == -1) + goto out_err; + + res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + goto out_err; + + inode = lo_find(lo_data(req), &e->attr); + if (inode) { + close(newfd); + newfd = -1; + } else { + inode = create_new_inode(newfd, e, lo); + if (!inode) + goto out_err; + } + e->ino = (uintptr_t) inode; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n", + (unsigned long long) parent, name, (unsigned long long) e->ino); + + return 0; + +out_err: + saverr = errno; + if (newfd != -1) + close(newfd); + return saverr; +} + +static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e; + int err; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n", + parent, name); + + err = lo_do_lookup(req, parent, name, &e); + if (err) + fuse_reply_err(req, err); + else + fuse_reply_entry(req, &e); +} + +static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent, + const char *name, mode_t mode, dev_t rdev, + const char *link) +{ + int res; + int saverr; + struct lo_inode *dir = lo_inode(req, parent); + struct fuse_entry_param e; + + res = mknod_wrapper(dir->fd, name, link, mode, rdev); + + saverr = errno; + if (res == -1) + goto out; + + saverr = lo_do_lookup(req, parent, name, &e); + if (saverr) + goto out; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n", + (unsigned long long) parent, name, (unsigned long long) e.ino); + + fuse_reply_entry(req, &e); + return; + +out: + fuse_reply_err(req, saverr); +} + +static void lo_mknod(fuse_req_t req, fuse_ino_t parent, + const char *name, mode_t mode, dev_t rdev) +{ + lo_mknod_symlink(req, parent, name, mode, rdev, NULL); +} + +static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode) +{ + lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL); +} + +static void lo_symlink(fuse_req_t req, const char *link, + fuse_ino_t parent, const char *name) +{ + lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link); +} + +static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent, + const char *name) +{ + int res; + struct lo_data *lo = lo_data(req); + struct lo_inode *inode = lo_inode(req, ino); + struct fuse_entry_param e; + char procname[64]; + int saverr; + + memset(&e, 0, sizeof(struct fuse_entry_param)); + e.attr_timeout = lo->timeout; + e.entry_timeout = lo->timeout; + + sprintf(procname, "/proc/self/fd/%i", inode->fd); + res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name, + AT_SYMLINK_FOLLOW); + if (res == -1) + goto out_err; + + res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + goto out_err; + + pthread_mutex_lock(&lo->mutex); + inode->refcount++; + pthread_mutex_unlock(&lo->mutex); + e.ino = (uintptr_t) inode; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n", + (unsigned long long) parent, name, + (unsigned long long) e.ino); + + fuse_reply_entry(req, &e); + return; + +out_err: + saverr = errno; + fuse_reply_err(req, saverr); +} + +static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + int res; + + res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR); + + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name, + fuse_ino_t newparent, const char *newname, + unsigned int flags) +{ + int res; + + if (flags) { + fuse_reply_err(req, EINVAL); + return; + } + + res = renameat(lo_fd(req, parent), name, + lo_fd(req, newparent), newname); + + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + int res; + + res = unlinkat(lo_fd(req, parent), name, 0); + + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n) +{ + if (!inode) + return; + + pthread_mutex_lock(&lo->mutex); + assert(inode->refcount >= n); + inode->refcount -= n; + if (!inode->refcount) { + struct lo_inode *prev, *next; + + prev = inode->prev; + next = inode->next; + next->prev = prev; + prev->next = next; + + pthread_mutex_unlock(&lo->mutex); + close(inode->fd); + free(inode); + + } else { + pthread_mutex_unlock(&lo->mutex); + } +} + +static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + struct lo_data *lo = lo_data(req); + struct lo_inode *inode = lo_inode(req, ino); + + if (lo_debug(req)) { + fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n", + (unsigned long long) ino, + (unsigned long long) inode->refcount, + (unsigned long long) nlookup); + } + + unref_inode(lo, inode, nlookup); +} + +static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + lo_forget_one(req, ino, nlookup); + fuse_reply_none(req); +} + +static void lo_forget_multi(fuse_req_t req, size_t count, + struct fuse_forget_data *forgets) +{ + int i; + + for (i = 0; i < count; i++) + lo_forget_one(req, forgets[i].ino, forgets[i].nlookup); + fuse_reply_none(req); +} + +static void lo_readlink(fuse_req_t req, fuse_ino_t ino) +{ + char buf[PATH_MAX + 1]; + int res; + + res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf)); + if (res == -1) + return (void) fuse_reply_err(req, errno); + + if (res == sizeof(buf)) + return (void) fuse_reply_err(req, ENAMETOOLONG); + + buf[res] = '\0'; + + fuse_reply_readlink(req, buf); +} + +struct lo_dirp { + DIR *dp; + struct dirent *entry; + off_t offset; +}; + +static struct lo_dirp *lo_dirp(struct fuse_file_info *fi) +{ + return (struct lo_dirp *) (uintptr_t) fi->fh; +} + +static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + int error = ENOMEM; + struct lo_data *lo = lo_data(req); + struct lo_dirp *d; + int fd = -1; + + d = calloc(1, sizeof(struct lo_dirp)); + if (d == NULL) + goto out_err; + + fd = openat(lo_fd(req, ino), ".", O_RDONLY); + if (fd == -1) + goto out_errno; + + d->dp = fdopendir(fd); + if (d->dp == NULL) + goto out_errno; + + d->offset = 0; + d->entry = NULL; + + fi->fh = (uintptr_t) d; + if (lo->cache != CACHE_NEVER) + fi->cache_readdir = 1; + if (lo->cache == CACHE_ALWAYS) + fi->keep_cache = 1; + fuse_reply_open(req, fi); + return; + +out_errno: + error = errno; +out_err: + if (d) { + if (fd != -1) + close(fd); + free(d); + } + fuse_reply_err(req, error); +} + +static int is_dot_or_dotdot(const char *name) +{ + return name[0] == '.' && (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0')); +} + +static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, struct fuse_file_info *fi, int plus) +{ + struct lo_dirp *d = lo_dirp(fi); + char *buf; + char *p; + size_t rem = size; + int err; + + (void) ino; + + buf = calloc(1, size); + if (!buf) { + err = ENOMEM; + goto error; + } + p = buf; + + if (offset != d->offset) { + seekdir(d->dp, offset); + d->entry = NULL; + d->offset = offset; + } + while (1) { + size_t entsize; + off_t nextoff; + const char *name; + + if (!d->entry) { + errno = 0; + d->entry = readdir(d->dp); + if (!d->entry) { + if (errno) { // Error + err = errno; + goto error; + } else { // End of stream + break; + } + } + } + nextoff = d->entry->d_off; + name = d->entry->d_name; + fuse_ino_t entry_ino = 0; + if (plus) { + struct fuse_entry_param e; + if (is_dot_or_dotdot(name)) { + e = (struct fuse_entry_param) { + .attr.st_ino = d->entry->d_ino, + .attr.st_mode = d->entry->d_type << 12, + }; + } else { + err = lo_do_lookup(req, ino, name, &e); + if (err) + goto error; + entry_ino = e.ino; + } + + entsize = fuse_add_direntry_plus(req, p, rem, name, + &e, nextoff); + } else { + struct stat st = { + .st_ino = d->entry->d_ino, + .st_mode = d->entry->d_type << 12, + }; + entsize = fuse_add_direntry(req, p, rem, name, + &st, nextoff); + } + if (entsize > rem) { + if (entry_ino != 0) + lo_forget_one(req, entry_ino, 1); + break; + } + + p += entsize; + rem -= entsize; + + d->entry = NULL; + d->offset = nextoff; + } + + err = 0; +error: + // If there's an error, we can only signal it if we haven't stored + // any entries yet - otherwise we'd end up with wrong lookup + // counts for the entries that are already in the buffer. So we + // return what we've collected until that point. + if (err && rem == size) + fuse_reply_err(req, err); + else + fuse_reply_buf(req, buf, size - rem); + free(buf); +} + +static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + lo_do_readdir(req, ino, size, offset, fi, 0); +} + +static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + lo_do_readdir(req, ino, size, offset, fi, 1); +} + +static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + struct lo_dirp *d = lo_dirp(fi); + (void) ino; + closedir(d->dp); + free(d); + fuse_reply_err(req, 0); +} + +static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent, + mode_t mode, struct fuse_file_info *fi) +{ + int fd; + struct lo_data *lo = lo_data(req); + struct fuse_entry_param e; + int err; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n", + parent); + + fd = openat(lo_fd(req, parent), ".", + (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode); + if (fd == -1) + return (void) fuse_reply_err(req, errno); + + fi->fh = fd; + if (lo->cache == CACHE_NEVER) + fi->direct_io = 1; + else if (lo->cache == CACHE_ALWAYS) + fi->keep_cache = 1; + + /* parallel_direct_writes feature depends on direct_io features. + To make parallel_direct_writes valid, need set fi->direct_io + in current function. */ + fi->parallel_direct_writes = 1; + + err = fill_entry_param_new_inode(req, parent, fd, &e); + if (err) + fuse_reply_err(req, err); + else + fuse_reply_create(req, &e, fi); +} + +static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, struct fuse_file_info *fi) +{ + int fd; + struct lo_data *lo = lo_data(req); + struct fuse_entry_param e; + int err; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n", + parent, name); + + fd = openat(lo_fd(req, parent), name, + (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode); + if (fd == -1) + return (void) fuse_reply_err(req, errno); + + fi->fh = fd; + if (lo->cache == CACHE_NEVER) + fi->direct_io = 1; + else if (lo->cache == CACHE_ALWAYS) + fi->keep_cache = 1; + + /* parallel_direct_writes feature depends on direct_io features. + To make parallel_direct_writes valid, need set fi->direct_io + in current function. */ + fi->parallel_direct_writes = 1; + + err = lo_do_lookup(req, parent, name, &e); + if (err) + fuse_reply_err(req, err); + else + fuse_reply_create(req, &e, fi); +} + +static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi) +{ + int res; + int fd = dirfd(lo_dirp(fi)->dp); + (void) ino; + if (datasync) + res = fdatasync(fd); + else + res = fsync(fd); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + int fd; + char buf[64]; + struct lo_data *lo = lo_data(req); + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n", + ino, fi->flags); + + /* With writeback cache, kernel may send read requests even + when userspace opened write-only */ + if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) { + fi->flags &= ~O_ACCMODE; + fi->flags |= O_RDWR; + } + + /* With writeback cache, O_APPEND is handled by the kernel. + This breaks atomicity (since the file may change in the + underlying filesystem, so that the kernel's idea of the + end of the file isn't accurate anymore). In this example, + we just accept that. A more rigorous filesystem may want + to return an error here */ + if (lo->writeback && (fi->flags & O_APPEND)) + fi->flags &= ~O_APPEND; + + sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino)); + fd = open(buf, fi->flags & ~O_NOFOLLOW); + if (fd == -1) + return (void) fuse_reply_err(req, errno); + + fi->fh = fd; + if (lo->cache == CACHE_NEVER) + fi->direct_io = 1; + else if (lo->cache == CACHE_ALWAYS) + fi->keep_cache = 1; + + /* Enable direct_io when open has flags O_DIRECT to enjoy the feature + parallel_direct_writes (i.e., to get a shared lock, not exclusive lock, + for writes to the same file in the kernel). */ + if (fi->flags & O_DIRECT) + fi->direct_io = 1; + + /* parallel_direct_writes feature depends on direct_io features. + To make parallel_direct_writes valid, need set fi->direct_io + in current function. */ + fi->parallel_direct_writes = 1; + + fuse_reply_open(req, fi); +} + +static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + (void) ino; + + close(fi->fh); + fuse_reply_err(req, 0); +} + +static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + int res; + (void) ino; + res = close(dup(fi->fh)); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi) +{ + int res; + (void) ino; + if (datasync) + res = fdatasync(fi->fh); + else + res = fsync(fi->fh); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size); + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, " + "off=%lu)\n", ino, size, (unsigned long) offset); + + buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; + buf.buf[0].fd = fi->fh; + buf.buf[0].pos = offset; + + fuse_reply_data(req, &buf, FUSE_BUF_SPLICE_MOVE); +} + +static void lo_write_buf(fuse_req_t req, fuse_ino_t ino, + struct fuse_bufvec *in_buf, off_t off, + struct fuse_file_info *fi) +{ + (void) ino; + ssize_t res; + struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf)); + + out_buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; + out_buf.buf[0].fd = fi->fh; + out_buf.buf[0].pos = off; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%jd)\n", + ino, out_buf.buf[0].size, (intmax_t) off); + + res = fuse_buf_copy(&out_buf, in_buf, 0); + if(res < 0) + fuse_reply_err(req, -res); + else + fuse_reply_write(req, (size_t) res); +} + +static void lo_statfs(fuse_req_t req, fuse_ino_t ino) +{ + int res; + struct statvfs stbuf; + + res = fstatvfs(lo_fd(req, ino), &stbuf); + if (res == -1) + fuse_reply_err(req, errno); + else + fuse_reply_statfs(req, &stbuf); +} + +static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode, + off_t offset, off_t length, struct fuse_file_info *fi) +{ + int err; + (void) ino; + + err = -do_fallocate(fi->fh, mode, offset, length); + + fuse_reply_err(req, err); +} + +static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, + int op) +{ + int res; + (void) ino; + + res = flock(fi->fh, op); + + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size) +{ + char *value = NULL; + char procname[64]; + struct lo_inode *inode = lo_inode(req, ino); + ssize_t ret; + int saverr; + + saverr = ENOSYS; + if (!lo_data(req)->xattr) + goto out; + + if (lo_debug(req)) { + fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n", + ino, name, size); + } + + sprintf(procname, "/proc/self/fd/%i", inode->fd); + + if (size) { + value = malloc(size); + if (!value) + goto out_err; + + ret = getxattr(procname, name, value, size); + if (ret == -1) + goto out_err; + saverr = 0; + if (ret == 0) + goto out; + + fuse_reply_buf(req, value, ret); + } else { + ret = getxattr(procname, name, NULL, 0); + if (ret == -1) + goto out_err; + + fuse_reply_xattr(req, ret); + } +out_free: + free(value); + return; + +out_err: + saverr = errno; +out: + fuse_reply_err(req, saverr); + goto out_free; +} + +static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) +{ + char *value = NULL; + char procname[64]; + struct lo_inode *inode = lo_inode(req, ino); + ssize_t ret; + int saverr; + + saverr = ENOSYS; + if (!lo_data(req)->xattr) + goto out; + + if (lo_debug(req)) { + fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n", + ino, size); + } + + sprintf(procname, "/proc/self/fd/%i", inode->fd); + + if (size) { + value = malloc(size); + if (!value) + goto out_err; + + ret = listxattr(procname, value, size); + if (ret == -1) + goto out_err; + saverr = 0; + if (ret == 0) + goto out; + + fuse_reply_buf(req, value, ret); + } else { + ret = listxattr(procname, NULL, 0); + if (ret == -1) + goto out_err; + + fuse_reply_xattr(req, ret); + } +out_free: + free(value); + return; + +out_err: + saverr = errno; +out: + fuse_reply_err(req, saverr); + goto out_free; +} + +static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags) +{ + char procname[64]; + struct lo_inode *inode = lo_inode(req, ino); + ssize_t ret; + int saverr; + + saverr = ENOSYS; + if (!lo_data(req)->xattr) + goto out; + + if (lo_debug(req)) { + fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n", + ino, name, value, size); + } + + sprintf(procname, "/proc/self/fd/%i", inode->fd); + + ret = setxattr(procname, name, value, size, flags); + saverr = ret == -1 ? errno : 0; + +out: + fuse_reply_err(req, saverr); +} + +static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name) +{ + char procname[64]; + struct lo_inode *inode = lo_inode(req, ino); + ssize_t ret; + int saverr; + + saverr = ENOSYS; + if (!lo_data(req)->xattr) + goto out; + + if (lo_debug(req)) { + fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n", + ino, name); + } + + sprintf(procname, "/proc/self/fd/%i", inode->fd); + + ret = removexattr(procname, name); + saverr = ret == -1 ? errno : 0; + +out: + fuse_reply_err(req, saverr); +} + +#ifdef HAVE_COPY_FILE_RANGE +static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, + struct fuse_file_info *fi_in, + fuse_ino_t ino_out, off_t off_out, + struct fuse_file_info *fi_out, size_t len, + int flags) +{ + ssize_t res; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, + "%s(ino=%lld fd=%lld off=%jd ino=%lld fd=%lld off=%jd, size=%zd, flags=0x%x)\n", + __func__, (unsigned long long)ino_in, + (unsigned long long)fi_in->fh, + (intmax_t) off_in, (unsigned long long)ino_out, + (unsigned long long)fi_out->fh, (intmax_t) off_out, + len, flags); + + res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len, + flags); + if (res < 0) + fuse_reply_err(req, errno); + else + fuse_reply_write(req, res); +} +#endif + +static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, + struct fuse_file_info *fi) +{ + off_t res; + + (void)ino; + res = lseek(fi->fh, off, whence); + if (res != -1) + fuse_reply_lseek(req, res); + else + fuse_reply_err(req, errno); +} + +#ifdef HAVE_STATX +static void lo_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask, + struct fuse_file_info *fi) +{ + struct lo_data *lo = lo_data(req); + struct statx buf; + int res; + int fd; + + if (fi) + fd = fi->fh; + else + fd = lo_fd(req, ino); + + res = statx(fd, "", flags | AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, mask, &buf); + if (res == -1) + fuse_reply_err(req, errno); + else + fuse_reply_statx(req, 0, &buf, lo->timeout); +} +#endif + +static const struct fuse_lowlevel_ops lo_oper = { + .init = lo_init, + .destroy = lo_destroy, + .lookup = lo_lookup, + .mkdir = lo_mkdir, + .mknod = lo_mknod, + .symlink = lo_symlink, + .link = lo_link, + .unlink = lo_unlink, + .rmdir = lo_rmdir, + .rename = lo_rename, + .forget = lo_forget, + .forget_multi = lo_forget_multi, + .getattr = lo_getattr, + .setattr = lo_setattr, + .readlink = lo_readlink, + .opendir = lo_opendir, + .readdir = lo_readdir, + .readdirplus = lo_readdirplus, + .releasedir = lo_releasedir, + .fsyncdir = lo_fsyncdir, + .create = lo_create, + .tmpfile = lo_tmpfile, + .open = lo_open, + .release = lo_release, + .flush = lo_flush, + .fsync = lo_fsync, + .read = lo_read, + .write_buf = lo_write_buf, + .statfs = lo_statfs, + .fallocate = lo_fallocate, + .flock = lo_flock, + .getxattr = lo_getxattr, + .listxattr = lo_listxattr, + .setxattr = lo_setxattr, + .removexattr = lo_removexattr, +#ifdef HAVE_COPY_FILE_RANGE + .copy_file_range = lo_copy_file_range, +#endif + .lseek = lo_lseek, +#ifdef HAVE_STATX + .statx = lo_statx, +#endif +}; + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + struct fuse_loop_config *config; + struct lo_data lo = { .debug = 0, + .writeback = 0 }; + int ret = -1; + + /* Don't mask creation mode, kernel already did that */ + umask(0); + + pthread_mutex_init(&lo.mutex, NULL); + lo.root.next = lo.root.prev = &lo.root; + lo.root.fd = -1; + lo.cache = CACHE_NORMAL; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + if (opts.show_help) { + printf("usage: %s [options] \n\n", argv[0]); + fuse_cmdline_help(); + fuse_lowlevel_help(); + passthrough_ll_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + if(opts.mountpoint == NULL) { + printf("usage: %s [options] \n", argv[0]); + printf(" %s --help\n", argv[0]); + ret = 1; + goto err_out1; + } + + if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1) + return 1; + + lo.debug = opts.debug; + lo.root.refcount = 2; + if (lo.source) { + struct stat stat; + int res; + + res = lstat(lo.source, &stat); + if (res == -1) { + fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n", + lo.source); + exit(1); + } + if (!S_ISDIR(stat.st_mode)) { + fuse_log(FUSE_LOG_ERR, "source is not a directory\n"); + exit(1); + } + + } else { + lo.source = strdup("/"); + if(!lo.source) { + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + exit(1); + } + } + if (!lo.timeout_set) { + switch (lo.cache) { + case CACHE_NEVER: + lo.timeout = 0.0; + break; + + case CACHE_NORMAL: + lo.timeout = 1.0; + break; + + case CACHE_ALWAYS: + lo.timeout = 86400.0; + break; + } + } else if (lo.timeout < 0) { + fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n", + lo.timeout); + exit(1); + } + + lo.root.fd = open(lo.source, O_PATH); + if (lo.root.fd == -1) { + fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n", + lo.source); + exit(1); + } + + se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + /* Block until ctrl+c or fusermount -u */ + if (opts.singlethread) + ret = fuse_session_loop(se); + else { + config = fuse_loop_cfg_create(); + fuse_loop_cfg_set_clone_fd(config, opts.clone_fd); + fuse_loop_cfg_set_max_threads(config, opts.max_threads); + ret = fuse_session_loop_mt(se, config); + fuse_loop_cfg_destroy(config); + config = NULL; + } + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + if (lo.root.fd >= 0) + close(lo.root.fd); + + free(lo.source); + return ret ? 1 : 0; +} diff --git a/example/poll.c b/example/poll.c new file mode 100644 index 0000000..cdcdd87 --- /dev/null +++ b/example/poll.c @@ -0,0 +1,304 @@ +/* + FUSE fsel: FUSE select example + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This example illustrates how to write a FUSE file system that + * supports polling for changes that don't come through the kernel. It + * can be tested with the poll_client.c program. + * + * Compile with: + * + * gcc -Wall poll.c `pkg-config fuse3 --cflags --libs` -o poll + * + * ## Source code ## + * \include poll.c + */ + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * fsel_open_mask is used to limit the number of opens to 1 per file. + * This is to use file index (0-F) as fh as poll support requires + * unique fh per open file. Lifting this would require proper open + * file management. + */ +static unsigned fsel_open_mask; +static const char fsel_hex_map[] = "0123456789ABCDEF"; +static struct fuse *fsel_fuse; /* needed for poll notification */ + +#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */ +#define FSEL_FILES 16 + +static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */ +static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */ +static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */ +static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */ +static _Atomic bool fsel_stop = false; +static pthread_t fsel_producer_thread; + + +static int fsel_path_index(const char *path) +{ + char ch = path[1]; + + if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch)) + return -1; + return ch <= '9' ? ch - '0' : ch - 'A' + 10; +} + +static void fsel_destroy(void *private_data) +{ + (void)private_data; + + fsel_stop = true; + + pthread_join(fsel_producer_thread, NULL); +} + +static int fsel_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void) fi; + int idx; + + memset(stbuf, 0, sizeof(struct stat)); + + if (strcmp(path, "/") == 0) { + stbuf->st_mode = S_IFDIR | 0555; + stbuf->st_nlink = 2; + return 0; + } + + idx = fsel_path_index(path); + if (idx < 0) + return -ENOENT; + + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = fsel_cnt[idx]; + return 0; +} + +static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + char name[2] = { }; + int i; + + (void) offset; + (void) fi; + (void) flags; + + if (strcmp(path, "/") != 0) + return -ENOENT; + + for (i = 0; i < FSEL_FILES; i++) { + name[0] = fsel_hex_map[i]; + filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS); + } + + return 0; +} + +static int fsel_open(const char *path, struct fuse_file_info *fi) +{ + int idx = fsel_path_index(path); + + if (idx < 0) + return -ENOENT; + if ((fi->flags & O_ACCMODE) != O_RDONLY) + return -EACCES; + if (fsel_open_mask & (1 << idx)) + return -EBUSY; + fsel_open_mask |= (1 << idx); + + /* + * fsel files are nonseekable somewhat pipe-like files which + * gets filled up periodically by producer thread and consumed + * on read. Tell FUSE as such. + */ + fi->fh = idx; + fi->direct_io = 1; + fi->nonseekable = 1; + + return 0; +} + +static int fsel_release(const char *path, struct fuse_file_info *fi) +{ + int idx = fi->fh; + + (void) path; + + fsel_open_mask &= ~(1 << idx); + return 0; +} + +static int fsel_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + int idx = fi->fh; + + (void) path; + (void) offset; + + pthread_mutex_lock(&fsel_mutex); + if (fsel_cnt[idx] < size) + size = fsel_cnt[idx]; + printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]); + fsel_cnt[idx] -= size; + pthread_mutex_unlock(&fsel_mutex); + + memset(buf, fsel_hex_map[idx], size); + return size; +} + +static int fsel_poll(const char *path, struct fuse_file_info *fi, + struct fuse_pollhandle *ph, unsigned *reventsp) +{ + static unsigned polled_zero; + int idx = fi->fh; + + (void) path; + + /* + * Poll notification requires pointer to struct fuse which + * can't be obtained when using fuse_main(). As notification + * happens only after poll is called, fill it here from + * fuse_context. + */ + if (!fsel_fuse) { + struct fuse_context *cxt = fuse_get_context(); + if (cxt) + fsel_fuse = cxt->fuse; + } + + pthread_mutex_lock(&fsel_mutex); + + if (ph != NULL) { + struct fuse_pollhandle *oldph = fsel_poll_handle[idx]; + + if (oldph) + fuse_pollhandle_destroy(oldph); + + fsel_poll_notify_mask |= (1 << idx); + fsel_poll_handle[idx] = ph; + } + + if (fsel_cnt[idx]) { + *reventsp |= POLLIN; + printf("POLL %X cnt=%u polled_zero=%u\n", + idx, fsel_cnt[idx], polled_zero); + polled_zero = 0; + } else + polled_zero++; + + pthread_mutex_unlock(&fsel_mutex); + return 0; +} + +static const struct fuse_operations fsel_oper = { + .destroy = fsel_destroy, + .getattr = fsel_getattr, + .readdir = fsel_readdir, + .open = fsel_open, + .release = fsel_release, + .read = fsel_read, + .poll = fsel_poll, +}; + +static void *fsel_producer(void *data) +{ + const struct timespec interval = { 0, 250000000 }; + unsigned idx = 0, nr = 1; + + (void) data; + + while (!fsel_stop) { + int i, t; + + pthread_mutex_lock(&fsel_mutex); + + /* + * This is the main producer loop which is executed + * ever 500ms. On each iteration, it fills one byte + * to 1, 2 or 4 files and sends poll notification if + * requested. + */ + for (i = 0, t = idx; i < nr; + i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) { + if (fsel_cnt[t] == FSEL_CNT_MAX) + continue; + + fsel_cnt[t]++; + if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) { + struct fuse_pollhandle *ph; + + printf("NOTIFY %X\n", t); + ph = fsel_poll_handle[t]; + fuse_notify_poll(ph); + fuse_pollhandle_destroy(ph); + fsel_poll_notify_mask &= ~(1 << t); + fsel_poll_handle[t] = NULL; + } + } + + idx = (idx + 1) % FSEL_FILES; + if (idx == 0) + nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */ + + pthread_mutex_unlock(&fsel_mutex); + + nanosleep(&interval, NULL); + } + + return NULL; +} + +int main(int argc, char *argv[]) +{ + pthread_attr_t attr; + int ret; + + errno = pthread_mutex_init(&fsel_mutex, NULL); + if (errno) { + perror("pthread_mutex_init"); + return 1; + } + + errno = pthread_attr_init(&attr); + if (errno) { + perror("pthread_attr_init"); + return 1; + } + + errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL); + if (errno) { + perror("pthread_create"); + return 1; + } + + ret = fuse_main(argc, argv, &fsel_oper, NULL); + + return ret; +} diff --git a/example/poll_client.c b/example/poll_client.c new file mode 100644 index 0000000..96f1419 --- /dev/null +++ b/example/poll_client.c @@ -0,0 +1,84 @@ +/* + FUSE fselclient: FUSE select example client + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This program tests the poll.c example file systsem. + * + * Compile with: + * + * gcc -Wall poll_client.c -o poll_client + * + * ## Source code ## + * \include poll_client.c + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FSEL_FILES 16 + +int main(void) +{ + static const char hex_map[FSEL_FILES] = "0123456789ABCDEF"; + int fds[FSEL_FILES]; + int i, nfds, tries; + + for (i = 0; i < FSEL_FILES; i++) { + char name[] = { hex_map[i], '\0' }; + fds[i] = open(name, O_RDONLY); + if (fds[i] < 0) { + perror("open"); + return 1; + } + } + nfds = fds[FSEL_FILES - 1] + 1; + + for(tries=0; tries < 16; tries++) { + static char buf[4096]; + fd_set rfds; + int rc; + + FD_ZERO(&rfds); + for (i = 0; i < FSEL_FILES; i++) + FD_SET(fds[i], &rfds); + + rc = select(nfds, &rfds, NULL, NULL, NULL); + + if (rc < 0) { + perror("select"); + return 1; + } + + for (i = 0; i < FSEL_FILES; i++) { + if (!FD_ISSET(fds[i], &rfds)) { + printf("_: "); + continue; + } + printf("%X:", i); + rc = read(fds[i], buf, sizeof(buf)); + if (rc < 0) { + perror("read"); + return 1; + } + printf("%02d ", rc); + } + printf("\n"); + } + return 0; +} diff --git a/example/printcap.c b/example/printcap.c new file mode 100644 index 0000000..4c59582 --- /dev/null +++ b/example/printcap.c @@ -0,0 +1,136 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2017 Nikolaus Rath + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * minimal example filesystem that prints out all capabilities + * supported by the kernel and then exits. + * + * Compile with: + * + * gcc -Wall printcap.c `pkg-config fuse3 --cflags --libs` -o printcap + * + * ## Source code ## + * \include printcap.c + */ + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include + +struct fuse_session *se; + +// Define a structure to hold capability information +struct cap_info { + uint64_t flag; + const char *name; +}; + +// Define an array of all capabilities +static const struct cap_info capabilities[] = { + {FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"}, + {FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"}, + {FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"}, + {FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"}, + {FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"}, + {FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"}, + {FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"}, + {FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"}, + {FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"}, + {FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"}, + {FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"}, + {FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"}, + {FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"}, + {FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"}, + {FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"}, + {FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"}, + {FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"}, + {FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"}, + {FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"}, + {FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"}, + {FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"}, + {FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"}, + {FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"}, + {FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"}, + {FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"}, + {FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"}, + {FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"}, + {FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"}, + // Add any new capabilities here + {0, NULL} // Sentinel to mark the end of the array +}; + +static void print_capabilities(struct fuse_conn_info *conn) +{ + printf("Capabilities:\n"); + for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) { + if (fuse_get_feature_flag(conn, cap->flag)) { + printf("\t%s\n", cap->name); + } + } +} + +static void pc_init(void *userdata, struct fuse_conn_info *conn) +{ + (void) userdata; + + printf("Protocol version: %d.%d\n", conn->proto_major, + conn->proto_minor); + print_capabilities(conn); + fuse_session_exit(se); +} + + +static const struct fuse_lowlevel_ops pc_oper = { + .init = pc_init, +}; + +int main(int argc, char **argv) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + char *mountpoint; + int ret = -1; + + mountpoint = strdup("/tmp/fuse_printcap_XXXXXX"); + if(mkdtemp(mountpoint) == NULL) { + perror("mkdtemp"); + return 1; + } + + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + + se = fuse_session_new(&args, &pc_oper, + sizeof(pc_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, mountpoint) != 0) + goto err_out3; + + ret = fuse_session_loop(se); + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + rmdir(mountpoint); + free(mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} diff --git a/example/usdt.bt b/example/usdt.bt new file mode 100644 index 0000000..41cfcdc --- /dev/null +++ b/example/usdt.bt @@ -0,0 +1,19 @@ +#!/usr/bin/env bpftrace + +// To run, do `sudo bpftrace usdt.bt` + +usdt:../build/lib/libfuse3.so:libfuse:request_receive +{ + printf("libfuse:request_receive hit, err=%d\n", arg0); +} + +usdt:../build/lib/libfuse3.so:libfuse:request_process +{ + printf("libfuse:request_process hit, opcode=%u, unique=%u\n", arg0, arg1); +} + +usdt:../build/lib/libfuse3.so:libfuse:request_reply +{ + printf("libfuse:request_reply hit, unique=%lu, len=%u, err=%u, reply_err=%d\n", + arg0, arg1, arg2, arg3); +} diff --git a/include/cuse_lowlevel.h b/include/cuse_lowlevel.h new file mode 100644 index 0000000..fe0b6f2 --- /dev/null +++ b/include/cuse_lowlevel.h @@ -0,0 +1,87 @@ +/* + CUSE: Character device in Userspace + Copyright (C) 2008-2009 SUSE Linux Products GmbH + Copyright (C) 2008-2009 Tejun Heo + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. + + Read example/cusexmp.c for usages. +*/ + +#ifndef CUSE_LOWLEVEL_H_ +#define CUSE_LOWLEVEL_H_ + +#ifndef FUSE_USE_VERSION +#define FUSE_USE_VERSION 29 +#endif + +#include "fuse_lowlevel.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define CUSE_UNRESTRICTED_IOCTL (1 << 0) /* use unrestricted ioctl */ + +struct fuse_session; + +struct cuse_info { + unsigned dev_major; + unsigned dev_minor; + unsigned dev_info_argc; + const char **dev_info_argv; + unsigned flags; +}; + +/* + * Most ops behave almost identically to the matching fuse_lowlevel + * ops except that they don't take @ino. + * + * init_done : called after initialization is complete + * read/write : always direct IO, simultaneous operations allowed + * ioctl : might be in unrestricted mode depending on ci->flags + */ +struct cuse_lowlevel_ops { + void (*init) (void *userdata, struct fuse_conn_info *conn); + void (*init_done) (void *userdata); + void (*destroy) (void *userdata); + void (*open) (fuse_req_t req, struct fuse_file_info *fi); + void (*read) (fuse_req_t req, size_t size, off_t off, + struct fuse_file_info *fi); + void (*write) (fuse_req_t req, const char *buf, size_t size, off_t off, + struct fuse_file_info *fi); + void (*flush) (fuse_req_t req, struct fuse_file_info *fi); + void (*release) (fuse_req_t req, struct fuse_file_info *fi); + void (*fsync) (fuse_req_t req, int datasync, struct fuse_file_info *fi); + void (*ioctl) (fuse_req_t req, int cmd, void *arg, + struct fuse_file_info *fi, unsigned int flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz); + void (*poll) (fuse_req_t req, struct fuse_file_info *fi, + struct fuse_pollhandle *ph); +}; + +struct fuse_session *cuse_lowlevel_new(struct fuse_args *args, + const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, + void *userdata); + +struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[], + const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, + int *multithreaded, void *userdata); + +void cuse_lowlevel_teardown(struct fuse_session *se); + +int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, void *userdata); + +#ifdef __cplusplus +} +#endif + +#endif /* CUSE_LOWLEVEL_H_ */ diff --git a/include/fuse.h b/include/fuse.h new file mode 100644 index 0000000..32d921b --- /dev/null +++ b/include/fuse.h @@ -0,0 +1,1434 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#ifndef FUSE_H_ +#define FUSE_H_ + +/** @file + * + * This file defines the library interface of FUSE + * + * IMPORTANT: you should define FUSE_USE_VERSION before including this header. + */ + +#include "fuse_common.h" + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----------------------------------------------------------- * + * Basic FUSE API * + * ----------------------------------------------------------- */ + +/* Forward declaration */ +struct statx; + +/** Handle for a FUSE filesystem */ +struct fuse; + +/** + * Readdir flags, passed to ->readdir() + */ +enum fuse_readdir_flags { + /** + * "Plus" mode. + * + * The kernel wants to prefill the inode cache during readdir. The + * filesystem may honour this by filling in the attributes and setting + * FUSE_FILL_DIR_FLAGS for the filler function. The filesystem may also + * just ignore this flag completely. + */ + FUSE_READDIR_DEFAULTS = 0, + FUSE_READDIR_PLUS = (1 << 0) +}; + +/** + * Readdir flags, passed to fuse_fill_dir_t callback. + */ +enum fuse_fill_dir_flags { + /** + * "Plus" mode: file attributes are valid + * + * The attributes are used by the kernel to prefill the inode cache + * during a readdir. + * + * It is okay to set FUSE_FILL_DIR_PLUS if FUSE_READDIR_PLUS is not set + * and vice versa. + * + * This does not make libfuse honor the 'st_ino' field. That is + * controlled by the 'use_ino' option instead. + */ + FUSE_FILL_DIR_DEFAULTS = 0, + FUSE_FILL_DIR_PLUS = (1 << 1) +}; + +/** Function to add an entry in a readdir() operation + * + * The *off* parameter can be any non-zero value that enables the + * filesystem to identify the current point in the directory + * stream. It does not need to be the actual physical position. A + * value of zero is reserved to indicate that seeking in directories + * is not supported. + * + * @param buf the buffer passed to the readdir() operation + * @param name the file name of the directory entry + * @param stbuf file attributes, can be NULL + * @param off offset of the next entry or zero + * @param flags fill flags + * @return 1 if buffer is full, zero otherwise + */ +typedef int (*fuse_fill_dir_t) (void *buf, const char *name, + const struct stat *stbuf, off_t off, + enum fuse_fill_dir_flags flags); +/** + * Configuration of the high-level API + * + * This structure is initialized from the arguments passed to + * fuse_new(), and then passed to the file system's init() handler + * which should ensure that the configuration is compatible with the + * file system implementation. + * + * Note: this data structure is ABI sensitive, new options have to be + * appended at the end of the structure + */ +struct fuse_config { + /** + * If `set_gid` is non-zero, the st_gid attribute of each file + * is overwritten with the value of `gid`. + */ + int32_t set_gid; + uint32_t gid; + + /** + * If `set_uid` is non-zero, the st_uid attribute of each file + * is overwritten with the value of `uid`. + */ + int32_t set_uid; + uint32_t uid; + + /** + * If `set_mode` is non-zero, the any permissions bits set in + * `umask` are unset in the st_mode attribute of each file. + */ + int32_t set_mode; + uint32_t umask; + + /** + * The timeout in seconds for which name lookups will be + * cached. + */ + double entry_timeout; + + /** + * The timeout in seconds for which a negative lookup will be + * cached. This means, that if file did not exist (lookup + * returned ENOENT), the lookup will only be redone after the + * timeout, and the file/directory will be assumed to not + * exist until then. A value of zero means that negative + * lookups are not cached. + */ + double negative_timeout; + + /** + * The timeout in seconds for which file/directory attributes + * (as returned by e.g. the `getattr` handler) are cached. + */ + double attr_timeout; + + /** + * Allow requests to be interrupted + */ + int32_t intr; + + /** + * Specify which signal number to send to the filesystem when + * a request is interrupted. The default is hardcoded to + * USR1. + */ + int32_t intr_signal; + + /** + * Normally, FUSE assigns inodes to paths only for as long as + * the kernel is aware of them. With this option inodes are + * instead remembered for at least this many seconds. This + * will require more memory, but may be necessary when using + * applications that make use of inode numbers. + * + * A number of -1 means that inodes will be remembered for the + * entire life-time of the file-system process. + */ + int32_t remember; + + /** + * The default behavior is that if an open file is deleted, + * the file is renamed to a hidden file (.fuse_hiddenXXX), and + * only removed when the file is finally released. This + * relieves the filesystem implementation of having to deal + * with this problem. This option disables the hiding + * behavior, and files are removed immediately in an unlink + * operation (or in a rename operation which overwrites an + * existing file). + * + * It is recommended that you not use the hard_remove + * option. When hard_remove is set, the following libc + * functions fail on unlinked files (returning errno of + * ENOENT): read(2), write(2), fsync(2), close(2), f*xattr(2), + * ftruncate(2), fstat(2), fchmod(2), fchown(2) + */ + int32_t hard_remove; + + /** + * Honor the st_ino field in the functions getattr() and + * fill_dir(). This value is used to fill in the st_ino field + * in the stat(2), lstat(2), fstat(2) functions and the d_ino + * field in the readdir(2) function. The filesystem does not + * have to guarantee uniqueness, however some applications + * rely on this value being unique for the whole filesystem. + * + * Note that this does *not* affect the inode that libfuse + * and the kernel use internally (also called the "nodeid"). + */ + int32_t use_ino; + + /** + * If use_ino option is not given, still try to fill in the + * d_ino field in readdir(2). If the name was previously + * looked up, and is still in the cache, the inode number + * found there will be used. Otherwise it will be set to -1. + * If use_ino option is given, this option is ignored. + */ + int32_t readdir_ino; + + /** + * This option disables the use of page cache (file content cache) + * in the kernel for this filesystem. This has several affects: + * + * 1. Each read(2) or write(2) system call will initiate one + * or more read or write operations, data will not be + * cached in the kernel. + * + * 2. The return value of the read() and write() system calls + * will correspond to the return values of the read and + * write operations. This is useful for example if the + * file size is not known in advance (before reading it). + * + * Internally, enabling this option causes fuse to set the + * `direct_io` field of `struct fuse_file_info` - overwriting + * any value that was put there by the file system. + */ + int32_t direct_io; + + /** + * This option disables flushing the cache of the file + * contents on every open(2). This should only be enabled on + * filesystems where the file data is never changed + * externally (not through the mounted FUSE filesystem). Thus + * it is not suitable for network filesystems and other + * intermediate filesystems. + * + * NOTE: if this option is not specified (and neither + * direct_io) data is still cached after the open(2), so a + * read(2) system call will not always initiate a read + * operation. + * + * Internally, enabling this option causes fuse to set the + * `keep_cache` field of `struct fuse_file_info` - overwriting + * any value that was put there by the file system. + */ + int32_t kernel_cache; + + /** + * This option is an alternative to `kernel_cache`. Instead of + * unconditionally keeping cached data, the cached data is + * invalidated on open(2) if if the modification time or the + * size of the file has changed since it was last opened. + */ + int32_t auto_cache; + + /* + * The timeout in seconds for which file attributes are cached + * for the purpose of checking if auto_cache should flush the + * file data on open. + */ + int32_t ac_attr_timeout_set; + double ac_attr_timeout; + + /** + * If this option is given the file-system handlers for the + * following operations will not receive path information: + * read, write, flush, release, fallocate, fsync, readdir, + * releasedir, fsyncdir, lock, ioctl and poll. + * + * For the truncate, getattr, chmod, chown and utimens + * operations the path will be provided only if the struct + * fuse_file_info argument is NULL. + */ + int32_t nullpath_ok; + + /** + * These 3 options are used by libfuse internally and + * should not be touched. + */ + int32_t show_help; + char *modules; + int32_t debug; + + /** + * `fmask` and `dmask` function the same way as `umask`, but apply + * to files and directories separately. If non-zero, `fmask` and + * `dmask` take precedence over the `umask` setting. + */ + uint32_t fmask; + uint32_t dmask; + + /** + * By default, fuse waits for all pending writes to complete + * and calls the FLUSH operation on close(2) of every fuse fd. + * With this option, wait and FLUSH are not done for read-only + * fuse fd, similar to the behavior of NFS/SMB clients. + */ + int32_t no_rofd_flush; + + /** + * Allow parallel direct-io writes to operate on the same file. + * + * FUSE implementations which do not handle parallel writes on + * same file/region should NOT enable this option at all as it + * might lead to data inconsistencies. + * + * For the FUSE implementations which have their own mechanism + * of cache/data integrity are beneficiaries of this setting as + * it now open doors to parallel writes on the same file (without + * enabling this setting, all direct writes on the same file are + * serialized, resulting in huge data bandwidth loss). + */ + int32_t parallel_direct_writes; + + + /** + * Reserved for future use. + */ + uint32_t flags; + + /** + * Reserved for future use. + */ + uint64_t reserved[48]; +}; + + +/** + * The file system operations: + * + * Most of these should work very similarly to the well known UNIX + * file system operations. A major exception is that instead of + * returning an error in 'errno', the operation should return the + * negated error value (-errno) directly. + * + * All methods are optional, but some are essential for a useful + * filesystem (e.g. getattr). Open, flush, release, fsync, opendir, + * releasedir, fsyncdir, access, create, truncate, lock, init and + * destroy are special purpose methods, without which a full featured + * filesystem can still be implemented. + * + * In general, all methods are expected to perform any necessary + * permission checking. However, a filesystem may delegate this task + * to the kernel by passing the `default_permissions` mount option to + * `fuse_new()`. In this case, methods will only be called if + * the kernel's permission check has succeeded. + * + * Almost all operations take a path which can be of any length. + */ +struct fuse_operations { + /** Get file attributes. + * + * Similar to stat(). The 'st_dev' and 'st_blksize' fields are + * ignored. The 'st_ino' field is ignored except if the 'use_ino' + * mount option is given. In that case it is passed to userspace, + * but libfuse and the kernel will still assign a different + * inode for internal use (called the "nodeid"). + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + */ + int (*getattr) (const char *, struct stat *, struct fuse_file_info *fi); + + /** Read the target of a symbolic link + * + * The buffer should be filled with a null terminated string. The + * buffer size argument includes the space for the terminating + * null character. If the linkname is too long to fit in the + * buffer, it should be truncated. The return value should be 0 + * for success. + */ + int (*readlink) (const char *, char *, size_t); + + /** Create a file node + * + * This is called for creation of all non-directory, non-symlink + * nodes. If the filesystem defines a create() method, then for + * regular files that will be called instead. + */ + int (*mknod) (const char *, mode_t, dev_t); + + /** Create a directory + * + * Note that the mode argument may not have the type specification + * bits set, i.e. S_ISDIR(mode) can be false. To obtain the + * correct directory type bits use mode|S_IFDIR + * */ + int (*mkdir) (const char *, mode_t); + + /** Remove a file */ + int (*unlink) (const char *); + + /** Remove a directory */ + int (*rmdir) (const char *); + + /** Create a symbolic link */ + int (*symlink) (const char *, const char *); + + /** Rename a file + * + * *flags* may be `RENAME_EXCHANGE` or `RENAME_NOREPLACE`. If + * RENAME_NOREPLACE is specified, the filesystem must not + * overwrite *newname* if it exists and return an error + * instead. If `RENAME_EXCHANGE` is specified, the filesystem + * must atomically exchange the two files, i.e. both must + * exist and neither may be deleted. + */ + int (*rename) (const char *, const char *, unsigned int flags); + + /** Create a hard link to a file */ + int (*link) (const char *, const char *); + + /** Change the permission bits of a file + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + */ + int (*chmod) (const char *, mode_t, struct fuse_file_info *fi); + + /** Change the owner and group of a file + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + */ + int (*chown) (const char *, uid_t, gid_t, struct fuse_file_info *fi); + + /** Change the size of a file + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + */ + int (*truncate) (const char *, off_t, struct fuse_file_info *fi); + + /** Open a file + * + * Open flags are available in fi->flags. The following rules + * apply. + * + * - Creation (O_CREAT, O_EXCL, O_NOCTTY) flags will be + * filtered out / handled by the kernel. + * + * - Access modes (O_RDONLY, O_WRONLY, O_RDWR, O_EXEC, O_SEARCH) + * should be used by the filesystem to check if the operation is + * permitted. If the ``-o default_permissions`` mount option is + * given, this check is already done by the kernel before calling + * open() and may thus be omitted by the filesystem. + * + * - When writeback caching is enabled, the kernel may send + * read requests even for files opened with O_WRONLY. The + * filesystem should be prepared to handle this. + * + * - When writeback caching is disabled, the filesystem is + * expected to properly handle the O_APPEND flag and ensure + * that each write is appending to the end of the file. + * + * - When writeback caching is enabled, the kernel will + * handle O_APPEND. However, unless all changes to the file + * come through the kernel this will not work reliably. The + * filesystem should thus either ignore the O_APPEND flag + * (and let the kernel handle it), or return an error + * (indicating that reliably O_APPEND is not available). + * + * Filesystem may store an arbitrary file handle (pointer, + * index, etc) in fi->fh, and use this in other all other file + * operations (read, write, flush, release, fsync). + * + * Filesystem may also implement stateless file I/O and not store + * anything in fi->fh. + * + * There are also some flags (direct_io, keep_cache) which the + * filesystem may set in fi, to change the way the file is opened. + * See fuse_file_info structure in for more details. + * + * If this request is answered with an error code of ENOSYS + * and FUSE_CAP_NO_OPEN_SUPPORT is set in + * `fuse_conn_info.capable`, this is treated as success and + * future calls to open will also succeed without being sent + * to the filesystem process. + * + */ + int (*open) (const char *, struct fuse_file_info *); + + /** Read data from an open file + * + * Read should return exactly the number of bytes requested except + * on EOF or error, otherwise the rest of the data will be + * substituted with zeroes. An exception to this is when the + * 'direct_io' mount option is specified, in which case the return + * value of the read system call will reflect the return value of + * this operation. + */ + int (*read) (const char *, char *, size_t, off_t, + struct fuse_file_info *); + + /** Write data to an open file + * + * Write should return exactly the number of bytes requested + * except on error. An exception to this is when the 'direct_io' + * mount option is specified (see read operation). + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + */ + int (*write) (const char *, const char *, size_t, off_t, + struct fuse_file_info *); + + /** Get file system statistics + * + * The 'f_favail', 'f_fsid' and 'f_flag' fields are ignored + */ + int (*statfs) (const char *, struct statvfs *); + + /** Possibly flush cached data + * + * BIG NOTE: This is not equivalent to fsync(). It's not a + * request to sync dirty data. + * + * Flush is called on each close() of a file descriptor, as opposed to + * release which is called on the close of the last file descriptor for + * a file. Under Linux, errors returned by flush() will be passed to + * userspace as errors from close(), so flush() is a good place to write + * back any cached dirty data. However, many applications ignore errors + * on close(), and on non-Linux systems, close() may succeed even if flush() + * returns an error. For these reasons, filesystems should not assume + * that errors returned by flush will ever be noticed or even + * delivered. + * + * NOTE: The flush() method may be called more than once for each + * open(). This happens if more than one file descriptor refers to an + * open file handle, e.g. due to dup(), dup2() or fork() calls. It is + * not possible to determine if a flush is final, so each flush should + * be treated equally. Multiple write-flush sequences are relatively + * rare, so this shouldn't be a problem. + * + * Filesystems shouldn't assume that flush will be called at any + * particular point. It may be called more times than expected, or not + * at all. + * + * [close]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html + */ + int (*flush) (const char *, struct fuse_file_info *); + + /** Release an open file + * + * Release is called when there are no more references to an open + * file: all file descriptors are closed and all memory mappings + * are unmapped. + * + * For every open() call there will be exactly one release() call + * with the same flags and file handle. It is possible to + * have a file opened more than once, in which case only the last + * release will mean, that no more reads/writes will happen on the + * file. The return value of release is ignored. + */ + int (*release) (const char *, struct fuse_file_info *); + + /** Synchronize file contents + * + * If the datasync parameter is non-zero, then only the user data + * should be flushed, not the meta data. + */ + int (*fsync) (const char *, int, struct fuse_file_info *); + + /** Set extended attributes */ + int (*setxattr) (const char *, const char *, const char *, size_t, int); + + /** Get extended attributes */ + int (*getxattr) (const char *, const char *, char *, size_t); + + /** List extended attributes */ + int (*listxattr) (const char *, char *, size_t); + + /** Remove extended attributes */ + int (*removexattr) (const char *, const char *); + + /** Open directory + * + * Unless the 'default_permissions' mount option is given, + * this method should check if opendir is permitted for this + * directory. Optionally opendir may also return an arbitrary + * filehandle in the fuse_file_info structure, which will be + * passed to readdir, releasedir and fsyncdir. + */ + int (*opendir) (const char *, struct fuse_file_info *); + + /** Read directory + * + * The filesystem may choose between two modes of operation: + * + * 1) The readdir implementation ignores the offset parameter, and + * passes zero to the filler function's offset. The filler + * function will not return '1' (unless an error happens), so the + * whole directory is read in a single readdir operation. + * + * 2) The readdir implementation keeps track of the offsets of the + * directory entries. It uses the offset parameter and always + * passes non-zero offset to the filler function. When the buffer + * is full (or an error happens) the filler function will return + * '1'. + * + * When FUSE_READDIR_PLUS is not set, only some parameters of the + * fill function (the fuse_fill_dir_t parameter) are actually used: + * The file type (which is part of stat::st_mode) is used. And if + * fuse_config::use_ino is set, the inode (stat::st_ino) is also + * used. The other fields are ignored when FUSE_READDIR_PLUS is not + * set. + */ + int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t, + struct fuse_file_info *, enum fuse_readdir_flags); + + /** Release directory + * + * If the directory has been removed after the call to opendir, the + * path parameter will be NULL. + */ + int (*releasedir) (const char *, struct fuse_file_info *); + + /** Synchronize directory contents + * + * If the directory has been removed after the call to opendir, the + * path parameter will be NULL. + * + * If the datasync parameter is non-zero, then only the user data + * should be flushed, not the meta data + */ + int (*fsyncdir) (const char *, int, struct fuse_file_info *); + + /** + * Initialize filesystem + * + * The return value will passed in the `private_data` field of + * `struct fuse_context` to all file operations, and as a + * parameter to the destroy() method. It overrides the initial + * value provided to fuse_main() / fuse_new(). + */ + void *(*init) (struct fuse_conn_info *conn, + struct fuse_config *cfg); + + /** + * Clean up filesystem + * + * Called on filesystem exit. + */ + void (*destroy) (void *private_data); + + /** + * Check file access permissions + * + * This will be called for the access() system call. If the + * 'default_permissions' mount option is given, this method is not + * called. + * + * This method is not called under Linux kernel versions 2.4.x + */ + int (*access) (const char *, int); + + /** + * Create and open a file + * + * If the file does not exist, first create it with the specified + * mode, and then open it. + * + * If this method is not implemented or under Linux kernel + * versions earlier than 2.6.15, the mknod() and open() methods + * will be called instead. + */ + int (*create) (const char *, mode_t, struct fuse_file_info *); + + /** + * Perform POSIX file locking operation + * + * The cmd argument will be either F_GETLK, F_SETLK or F_SETLKW. + * + * For the meaning of fields in 'struct flock' see the man page + * for fcntl(2). The l_whence field will always be set to + * SEEK_SET. + * + * For checking lock ownership, the 'fuse_file_info->owner' + * argument must be used. + * + * For F_GETLK operation, the library will first check currently + * held locks, and if a conflicting lock is found it will return + * information without calling this method. This ensures, that + * for local locks the l_pid field is correctly filled in. The + * results may not be accurate in case of race conditions and in + * the presence of hard links, but it's unlikely that an + * application would rely on accurate GETLK results in these + * cases. If a conflicting lock is not found, this method will be + * called, and the filesystem may fill out l_pid by a meaningful + * value, or it may leave this field zero. + * + * For F_SETLK and F_SETLKW the l_pid field will be set to the pid + * of the process performing the locking operation. + * + * Note: if this method is not implemented, the kernel will still + * allow file locking to work locally. Hence it is only + * interesting for network filesystems and similar. + */ + int (*lock) (const char *, struct fuse_file_info *, int cmd, + struct flock *); + + /** + * Change the access and modification times of a file with + * nanosecond resolution + * + * This supersedes the old utime() interface. New applications + * should use this. + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + * + * See the utimensat(2) man page for details. + */ + int (*utimens) (const char *, const struct timespec tv[2], + struct fuse_file_info *fi); + + /** + * Map block index within file to block index within device + * + * Note: This makes sense only for block device backed filesystems + * mounted with the 'blkdev' option + */ + int (*bmap) (const char *, size_t blocksize, uint64_t *idx); + +#if FUSE_USE_VERSION < 35 + int (*ioctl) (const char *, int cmd, void *arg, + struct fuse_file_info *, unsigned int flags, void *data); +#else + /** + * Ioctl + * + * flags will have FUSE_IOCTL_COMPAT set for 32bit ioctls in + * 64bit environment. The size and direction of data is + * determined by _IOC_*() decoding of cmd. For _IOC_NONE, + * data will be NULL, for _IOC_WRITE data is out area, for + * _IOC_READ in area and if both are set in/out area. In all + * non-NULL cases, the area is of _IOC_SIZE(cmd) bytes. + * + * If flags has FUSE_IOCTL_DIR then the fuse_file_info refers to a + * directory file handle. + * + * Note : the unsigned long request submitted by the application + * is truncated to 32 bits. + */ + int (*ioctl) (const char *, unsigned int cmd, void *arg, + struct fuse_file_info *, unsigned int flags, void *data); +#endif + + /** + * Poll for IO readiness events + * + * Note: If ph is non-NULL, the client should notify + * when IO readiness events occur by calling + * fuse_notify_poll() with the specified ph. + * + * Regardless of the number of times poll with a non-NULL ph + * is received, single notification is enough to clear all. + * Notifying more times incurs overhead but doesn't harm + * correctness. + * + * The callee is responsible for destroying ph with + * fuse_pollhandle_destroy() when no longer in use. + */ + int (*poll) (const char *, struct fuse_file_info *, + struct fuse_pollhandle *ph, unsigned *reventsp); + + /** Write contents of buffer to an open file + * + * Similar to the write() method, but data is supplied in a + * generic buffer. Use fuse_buf_copy() to transfer data to + * the destination. + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + */ + int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off, + struct fuse_file_info *); + + /** Store data from an open file in a buffer + * + * Similar to the read() method, but data is stored and + * returned in a generic buffer. + * + * No actual copying of data has to take place, the source + * file descriptor may simply be stored in the buffer for + * later data transfer. + * + * The buffer must be allocated dynamically and stored at the + * location pointed to by bufp. If the buffer contains memory + * regions, they too must be allocated using malloc(). The + * allocated memory will be freed by the caller. + */ + int (*read_buf) (const char *, struct fuse_bufvec **bufp, + size_t size, off_t off, struct fuse_file_info *); + /** + * Perform BSD file locking operation + * + * The op argument will be either LOCK_SH, LOCK_EX or LOCK_UN + * + * Nonblocking requests will be indicated by ORing LOCK_NB to + * the above operations + * + * For more information see the flock(2) manual page. + * + * Additionally fi->owner will be set to a value unique to + * this open file. This same value will be supplied to + * ->release() when the file is released. + * + * Note: if this method is not implemented, the kernel will still + * allow file locking to work locally. Hence it is only + * interesting for network filesystems and similar. + */ + int (*flock) (const char *, struct fuse_file_info *, int op); + + /** + * Allocates space for an open file + * + * This function ensures that required space is allocated for specified + * file. If this function returns success then any subsequent write + * request to specified range is guaranteed not to fail because of lack + * of space on the file system media. + */ + int (*fallocate) (const char *, int, off_t, off_t, + struct fuse_file_info *); + + /** + * Copy a range of data from one file to another + * + * Performs an optimized copy between two file descriptors without the + * additional cost of transferring data through the FUSE kernel module + * to user space (glibc) and then back into the FUSE filesystem again. + * + * In case this method is not implemented, applications are expected to + * fall back to a regular file copy. (Some glibc versions did this + * emulation automatically, but the emulation has been removed from all + * glibc release branches.) + */ + ssize_t (*copy_file_range) (const char *path_in, + struct fuse_file_info *fi_in, + off_t offset_in, const char *path_out, + struct fuse_file_info *fi_out, + off_t offset_out, size_t size, int flags); + + /** + * Find next data or hole after the specified offset + */ + off_t (*lseek) (const char *, off_t off, int whence, struct fuse_file_info *); + + /** + * Get extended file attributes. + * + * fi may be NULL. + * + * If path is NULL, then the AT_EMPTY_PATH bit in flags will be + * already set. + */ + int (*statx)(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi); +}; + +/** Extra context that may be needed by some filesystems + * + * The uid, gid and pid fields are not filled in case of a writepage + * operation. + */ +struct fuse_context { + /** Pointer to the fuse object */ + struct fuse *fuse; + + /** User ID of the calling process */ + uid_t uid; + + /** Group ID of the calling process */ + gid_t gid; + + /** Process ID of the calling thread */ + pid_t pid; + + /** Private filesystem data */ + void *private_data; + + /** Umask of the calling process */ + mode_t umask; +}; + +/** + * The real main function + * + * Do not call this directly, use fuse_main() + */ +int fuse_main_real_versioned(int argc, char *argv[], + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data); +static inline int fuse_main_real(int argc, char *argv[], + const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + struct libfuse_version version = { .major = FUSE_MAJOR_VERSION, + .minor = FUSE_MINOR_VERSION, + .hotfix = FUSE_HOTFIX_VERSION, + .padding = 0 }; + + fuse_log(FUSE_LOG_ERR, + "%s is a libfuse internal function, please use fuse_main()\n", + __func__); + + return fuse_main_real_versioned(argc, argv, op, op_size, &version, + user_data); +} + +/** + * Main function of FUSE. + * + * This is for the lazy. This is all that has to be called from the + * main() function. + * + * This function does the following: + * - parses command line options, and handles --help and + * --version + * - installs signal handlers for INT, HUP, TERM and PIPE + * - registers an exit handler to unmount the filesystem on program exit + * - creates a fuse handle + * - registers the operations + * - calls either the single-threaded or the multi-threaded event loop + * + * Most file systems will have to parse some file-system specific + * arguments before calling this function. It is recommended to do + * this with fuse_opt_parse() and a processing function that passes + * through any unknown options (this can also be achieved by just + * passing NULL as the processing function). That way, the remaining + * options can be passed directly to fuse_main(). + * + * fuse_main() accepts all options that can be passed to + * fuse_parse_cmdline(), fuse_new(), or fuse_session_new(). + * + * Option parsing skips argv[0], which is assumed to contain the + * program name. This element must always be present and is used to + * construct a basic ``usage: `` message for the --help + * output. argv[0] may also be set to the empty string. In this case + * the usage message is suppressed. This can be used by file systems + * to print their own usage line first. See hello.c for an example of + * how to do this. + * + * Note: this is currently implemented as a macro. + * + * The following error codes may be returned from fuse_main(): + * 1: Invalid option arguments + * 2: No mount point specified + * 3: FUSE setup failed + * 4: Mounting failed + * 5: Failed to daemonize (detach from session) + * 6: Failed to set up signal handlers + * 7: An error occurred during the life of the file system + * + * @param argc the argument counter passed to the main() function + * @param argv the argument vector passed to the main() function + * @param op the file system operation + * @param private_data Initial value for the `private_data` + * field of `struct fuse_context`. May be overridden by the + * `struct fuse_operations.init` handler. + * @return 0 on success, nonzero on failure + * + * Example usage, see hello.c + */ +static inline int fuse_main_fn(int argc, char *argv[], + const struct fuse_operations *op, + void *user_data) +{ + struct libfuse_version version = { + .major = FUSE_MAJOR_VERSION, + .minor = FUSE_MINOR_VERSION, + .hotfix = FUSE_HOTFIX_VERSION, + .padding = 0 + }; + + return fuse_main_real_versioned(argc, argv, op, sizeof(*(op)), &version, + user_data); +} +#define fuse_main(argc, argv, op, user_data) \ + fuse_main_fn(argc, argv, op, user_data) + +/* ----------------------------------------------------------- * + * More detailed API * + * ----------------------------------------------------------- */ + +/** + * Print available options (high- and low-level) to stdout. This is + * not an exhaustive list, but includes only those options that may be + * of interest to an end-user of a file system. + * + * The function looks at the argument vector only to determine if + * there are additional modules to be loaded (module=foo option), + * and attempts to call their help functions as well. + * + * @param args the argument vector. + */ +void fuse_lib_help(struct fuse_args *args); + +/* Do not call this directly, use fuse_new() instead */ +struct fuse *_fuse_new_30(struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data); +struct fuse *_fuse_new_31(struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data); + +/** + * Create a new FUSE filesystem. + * + * This function accepts most file-system independent mount options + * (like context, nodev, ro - see mount(8)), as well as the + * FUSE-specific mount options from mount.fuse(8). + * + * If the --help option is specified, the function writes a help text + * to stdout and returns NULL. + * + * Option parsing skips argv[0], which is assumed to contain the + * program name. This element must always be present and is used to + * construct a basic ``usage: `` message for the --help output. If + * argv[0] is set to the empty string, no usage message is included in + * the --help output. + * + * If an unknown option is passed in, an error message is written to + * stderr and the function returns NULL. + * + * @param args argument vector + * @param op the filesystem operations + * @param op_size the size of the fuse_operations structure + * @param private_data Initial value for the `private_data` + * field of `struct fuse_context`. May be overridden by the + * `struct fuse_operations.init` handler. + * @return the created FUSE handle + */ +#if FUSE_USE_VERSION == 30 +static inline struct fuse *fuse_new_fn(struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + struct libfuse_version version = { + .major = FUSE_MAJOR_VERSION, + .minor = FUSE_MINOR_VERSION, + .hotfix = FUSE_HOTFIX_VERSION, + .padding = 0 + }; + + return _fuse_new_30(args, op, op_size, &version, user_data); +} +#else /* FUSE_USE_VERSION */ +static inline struct fuse *fuse_new_fn(struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + struct libfuse_version version = { + .major = FUSE_MAJOR_VERSION, + .minor = FUSE_MINOR_VERSION, + .hotfix = FUSE_HOTFIX_VERSION, + .padding = 0 + }; + + return _fuse_new_31(args, op, op_size, &version, user_data); +} +#endif +#define fuse_new(args, op, size, data) fuse_new_fn(args, op, size, data) + +/** + * Mount a FUSE file system. + * + * @param mountpoint the mount point path + * @param f the FUSE handle + * + * @return 0 on success, -1 on failure. + **/ +int fuse_mount(struct fuse *f, const char *mountpoint); + +/** + * Unmount a FUSE file system. + * + * See fuse_session_unmount() for additional information. + * + * @param f the FUSE handle + **/ +void fuse_unmount(struct fuse *f); + +/** + * Destroy the FUSE handle. + * + * NOTE: This function does not unmount the filesystem. If this is + * needed, call fuse_unmount() before calling this function. + * + * @param f the FUSE handle + */ +void fuse_destroy(struct fuse *f); + +/** + * FUSE event loop. + * + * Requests from the kernel are processed, and the appropriate + * operations are called. + * + * For a description of the return value and the conditions when the + * event loop exits, refer to the documentation of + * fuse_session_loop(). + * + * @param f the FUSE handle + * @return see fuse_session_loop() + * + * See also: fuse_loop_mt() + */ +int fuse_loop(struct fuse *f); + +/** + * Flag session as terminated + * + * This function will cause any running event loops to exit on + * the next opportunity. + * + * @param f the FUSE handle + */ +void fuse_exit(struct fuse *f); + +#if FUSE_USE_VERSION < 32 +int fuse_loop_mt_31(struct fuse *f, int clone_fd); +#define fuse_loop_mt(f, clone_fd) fuse_loop_mt_31(f, clone_fd) +#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12) +int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config *config); +#define fuse_loop_mt(f, config) fuse_loop_mt_32(f, config) +#else +/** + * FUSE event loop with multiple threads + * + * Requests from the kernel are processed, and the appropriate + * operations are called. Request are processed in parallel by + * distributing them between multiple threads. + * + * For a description of the return value and the conditions when the + * event loop exits, refer to the documentation of + * fuse_session_loop(). + * + * Note: using fuse_loop() instead of fuse_loop_mt() means you are running in + * single-threaded mode, and that you will not have to worry about reentrancy, + * though you will have to worry about recursive lookups. In single-threaded + * mode, FUSE will wait for one callback to return before calling another. + * + * Enabling multiple threads, by using fuse_loop_mt(), will cause FUSE to make + * multiple simultaneous calls into the various callback functions given by your + * fuse_operations record. + * + * If you are using multiple threads, you can enjoy all the parallel execution + * and interactive response benefits of threads, and you get to enjoy all the + * benefits of race conditions and locking bugs, too. Ensure that any code used + * in the callback function of fuse_operations is also thread-safe. + * + * @param f the FUSE handle + * @param config loop configuration, may be NULL and defaults will be used then + * @return see fuse_session_loop() + * + * See also: fuse_loop() + */ +#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS)) +int fuse_loop_mt(struct fuse *f, struct fuse_loop_config *config); +#else +#define fuse_loop_mt(f, config) fuse_loop_mt_312(f, config) +#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */ +#endif + + +/** + * Get the current context + * + * The context is only valid for the duration of a filesystem + * operation, and thus must not be stored and used later. + * + * @return the context + */ +struct fuse_context *fuse_get_context(void); + +/** + * Get the current supplementary group IDs for the current request + * + * Similar to the getgroups(2) system call, except the return value is + * always the total number of group IDs, even if it is larger than the + * specified size. + * + * The current fuse kernel module in linux (as of 2.6.30) doesn't pass + * the group list to userspace, hence this function needs to parse + * "/proc/$TID/task/$TID/status" to get the group IDs. + * + * This feature may not be supported on all operating systems. In + * such a case this function will return -ENOSYS. + * + * @param size size of given array + * @param list array of group IDs to be filled in + * @return the total number of supplementary group IDs or -errno on failure + */ +int fuse_getgroups(int size, gid_t list[]); + +/** + * Check if the current request has already been interrupted + * + * @return 1 if the request has been interrupted, 0 otherwise + */ +int fuse_interrupted(void); + +/** + * Invalidates cache for the given path. + * + * This calls fuse_lowlevel_notify_inval_inode internally. + * + * @return 0 on successful invalidation, negative error value otherwise. + * This routine may return -ENOENT to indicate that there was + * no entry to be invalidated, e.g., because the path has not + * been seen before or has been forgotten; this should not be + * considered to be an error. + */ +int fuse_invalidate_path(struct fuse *f, const char *path); + +/** + * Start the cleanup thread when using option "remember". + * + * This is done automatically by fuse_loop_mt() + * @param fuse struct fuse pointer for fuse instance + * @return 0 on success and -1 on error + */ +int fuse_start_cleanup_thread(struct fuse *fuse); + +/** + * Stop the cleanup thread when using option "remember". + * + * This is done automatically by fuse_loop_mt() + * @param fuse struct fuse pointer for fuse instance + */ +void fuse_stop_cleanup_thread(struct fuse *fuse); + +/** + * Iterate over cache removing stale entries + * use in conjunction with "-oremember" + * + * NOTE: This is already done for the standard sessions + * + * @param fuse struct fuse pointer for fuse instance + * @return the number of seconds until the next cleanup + */ +int fuse_clean_cache(struct fuse *fuse); + +/* + * Stacking API + */ + +/** + * Fuse filesystem object + * + * This is opaque object represents a filesystem layer + */ +struct fuse_fs; + +/* + * These functions call the relevant filesystem operation, and return + * the result. + * + * If the operation is not defined, they return -ENOSYS, with the + * exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir, + * fuse_fs_releasedir and fuse_fs_statfs, which return 0. + */ + +int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf, + struct fuse_file_info *fi); +int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath, + const char *newpath, unsigned int flags); +int fuse_fs_unlink(struct fuse_fs *fs, const char *path); +int fuse_fs_rmdir(struct fuse_fs *fs, const char *path); +int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, + const char *path); +int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath); +int fuse_fs_release(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi); +int fuse_fs_open(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi); +int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size, + off_t off, struct fuse_file_info *fi); +int fuse_fs_read_buf(struct fuse_fs *fs, const char *path, + struct fuse_bufvec **bufp, size_t size, off_t off, + struct fuse_file_info *fi); +int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf, + size_t size, off_t off, struct fuse_file_info *fi); +int fuse_fs_write_buf(struct fuse_fs *fs, const char *path, + struct fuse_bufvec *buf, off_t off, + struct fuse_file_info *fi); +int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync, + struct fuse_file_info *fi); +int fuse_fs_flush(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi); +int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf); +int fuse_fs_opendir(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi); +int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf, + fuse_fill_dir_t filler, off_t off, + struct fuse_file_info *fi, enum fuse_readdir_flags flags); +int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync, + struct fuse_file_info *fi); +int fuse_fs_releasedir(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi); +int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode, + struct fuse_file_info *fi); +int fuse_fs_lock(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, int cmd, struct flock *lock); +int fuse_fs_flock(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, int op); +int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode, + struct fuse_file_info *fi); +int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid, + struct fuse_file_info *fi); +int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size, + struct fuse_file_info *fi); +int fuse_fs_utimens(struct fuse_fs *fs, const char *path, + const struct timespec tv[2], struct fuse_file_info *fi); +int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask); +int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf, + size_t len); +int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode, + dev_t rdev); +int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode); +int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name, + const char *value, size_t size, int flags); +int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name, + char *value, size_t size); +int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list, + size_t size); +int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, + const char *name); +int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize, + uint64_t *idx); +#if FUSE_USE_VERSION < 35 +int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd, + void *arg, struct fuse_file_info *fi, unsigned int flags, + void *data); +#else +int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd, + void *arg, struct fuse_file_info *fi, unsigned int flags, + void *data); +#endif +int fuse_fs_poll(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, struct fuse_pollhandle *ph, + unsigned *reventsp); +int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode, + off_t offset, off_t length, struct fuse_file_info *fi); +ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in, + struct fuse_file_info *fi_in, off_t off_in, + const char *path_out, + struct fuse_file_info *fi_out, off_t off_out, + size_t len, int flags); +off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence, + struct fuse_file_info *fi); +int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask, + struct statx *stxbuf, struct fuse_file_info *fi); +void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn, + struct fuse_config *cfg); +void fuse_fs_destroy(struct fuse_fs *fs); + +int fuse_notify_poll(struct fuse_pollhandle *ph); + +/** + * Create a new fuse filesystem object + * + * This is usually called from the factory of a fuse module to create + * a new instance of a filesystem. + * + * @param op the filesystem operations + * @param op_size the size of the fuse_operations structure + * @param private_data Initial value for the `private_data` + * field of `struct fuse_context`. May be overridden by the + * `struct fuse_operations.init` handler. + * @return a new filesystem object + */ +struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size, + void *private_data); + +/** + * Factory for creating filesystem objects + * + * The function may use and remove options from 'args' that belong + * to this module. + * + * For now the 'fs' vector always contains exactly one filesystem. + * This is the filesystem which will be below the newly created + * filesystem in the stack. + * + * @param args the command line arguments + * @param fs NULL terminated filesystem object vector + * @return the new filesystem object + */ +typedef struct fuse_fs *(*fuse_module_factory_t)(struct fuse_args *args, + struct fuse_fs *fs[]); +/** + * Register filesystem module + * + * If the "-omodules=*name*_:..." option is present, filesystem + * objects are created and pushed onto the stack with the *factory_* + * function. + * + * @param name_ the name of this filesystem module + * @param factory_ the factory function for this filesystem module + */ +#define FUSE_REGISTER_MODULE(name_, factory_) \ + fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_ + +/** Get session from fuse object */ +struct fuse_session *fuse_get_session(struct fuse *f); + +/** + * Open a FUSE file descriptor and set up the mount for the given + * mountpoint and flags. + * + * @param mountpoint reference to the mount in the file system + * @param options mount options + * @return the FUSE file descriptor or -1 upon error + */ +int fuse_open_channel(const char *mountpoint, const char *options); + +#ifdef __cplusplus +} +#endif + +#endif /* FUSE_H_ */ diff --git a/include/fuse_common.h b/include/fuse_common.h new file mode 100644 index 0000000..041188e --- /dev/null +++ b/include/fuse_common.h @@ -0,0 +1,1161 @@ +/* FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +/** @file */ + +#if !defined(FUSE_H_) && !defined(FUSE_LOWLEVEL_H_) +#error "Never include directly; use or instead." +#endif + +#ifndef FUSE_COMMON_H_ +#define FUSE_COMMON_H_ + +#ifdef HAVE_LIBFUSE_PRIVATE_CONFIG_H +#include "fuse_config.h" +#endif + +#include "libfuse_config.h" + +#include "fuse_opt.h" +#include "fuse_log.h" +#include +#include +#include +#include + +#define FUSE_MAKE_VERSION(maj, min) ((maj) * 100 + (min)) +#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION) + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Information about an open file. + * + * File Handles are created by the open, opendir, and create methods and closed + * by the release and releasedir methods. Multiple file handles may be + * concurrently open for the same file. Generally, a client will create one + * file handle per file descriptor, though in some cases multiple file + * descriptors can share a single file handle. + * + * Note: This data structure is ABI sensitive, new parameters have to be + * added within padding/padding2 bits and always below existing + * parameters. + */ +struct fuse_file_info { + /** Open flags. Available in open(), release() and create() */ + int32_t flags; + + /** In case of a write operation indicates if this was caused + by a delayed write from the page cache. If so, then the + context's pid, uid, and gid fields will not be valid, and + the *fh* value may not match the *fh* value that would + have been sent with the corresponding individual write + requests if write caching had been disabled. */ + uint32_t writepage : 1; + + /** Can be filled in by open/create, to use direct I/O on this file. */ + uint32_t direct_io : 1; + + /** Can be filled in by open and opendir. It signals the kernel that any + currently cached data (ie., data that the filesystem provided the + last time the file/directory was open) need not be invalidated when + the file/directory is closed. */ + uint32_t keep_cache : 1; + + /** Indicates a flush operation. Set in flush operation, also + maybe set in highlevel lock operation and lowlevel release + operation. */ + uint32_t flush : 1; + + /** Can be filled in by open, to indicate that the file is not + seekable. */ + uint32_t nonseekable : 1; + + /* Indicates that flock locks for this file should be + released. If set, lock_owner shall contain a valid value. + May only be set in ->release(). */ + uint32_t flock_release : 1; + + /** Can be filled in by opendir. It signals the kernel to + enable caching of entries returned by readdir(). Has no + effect when set in other contexts (in particular it does + nothing when set by open()). */ + uint32_t cache_readdir : 1; + + /** Can be filled in by open, to indicate that flush is not needed + on close. */ + uint32_t noflush : 1; + + /** Can be filled by open/create, to allow parallel direct writes on this + file */ + uint32_t parallel_direct_writes : 1; + + /** Padding. Reserved for future use*/ + uint32_t padding : 23; + uint32_t padding2 : 32; + uint32_t padding3 : 32; + + /** File handle id. May be filled in by filesystem in create, + * open, and opendir(). Available in most other file operations on the + * same file handle. */ + uint64_t fh; + + /** Lock owner id. Available in locking operations and flush */ + uint64_t lock_owner; + + /** Requested poll events. Available in ->poll. Only set on kernels + which support it. If unsupported, this field is set to zero. */ + uint32_t poll_events; + + /** Passthrough backing file id. May be filled in by filesystem in + * create and open. It is used to create a passthrough connection + * between FUSE file and backing file. */ + int32_t backing_id; + + /** struct fuse_file_info api and abi flags */ + uint64_t compat_flags; + + uint64_t reserved[2]; +}; + +/** + * Configuration parameters passed to fuse_session_loop_mt() and + * fuse_loop_mt(). + * For FUSE API versions less than 312, use a public struct + * fuse_loop_config in applications and struct fuse_loop_config_v1 + * is used in library (i.e., libfuse.so). These two structs are binary + * compatible in earlier API versions and can be linked. + * Deprecated and replaced by a newer private struct in FUSE API + * version 312 (FUSE_MAKE_VERSION(3, 12)). + */ +#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12) +struct fuse_loop_config_v1; /* forward declaration */ +struct fuse_loop_config { +#else +struct fuse_loop_config_v1 { +#endif + /** + * whether to use separate device fds for each thread + * (may increase performance) + */ + int clone_fd; + + /** + * The maximum number of available worker threads before they + * start to get deleted when they become idle. If not + * specified, the default is 10. + * + * Adjusting this has performance implications; a very small number + * of threads in the pool will cause a lot of thread creation and + * deletion overhead and performance may suffer. When set to 0, a new + * thread will be created to service every operation. + */ + unsigned int max_idle_threads; +}; + + +/************************************************************************** + * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want' * + **************************************************************************/ + +/** + * Indicates that the filesystem supports asynchronous read requests. + * + * If this capability is not requested/available, the kernel will + * ensure that there is at most one pending read request per + * file-handle at any time, and will attempt to order read requests by + * increasing offset. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_ASYNC_READ (1UL << 0) + +/** + * Indicates that the filesystem supports "remote" locking. + * + * This feature is enabled by default when supported by the kernel, + * and if getlk() and setlk() handlers are implemented. + */ +#define FUSE_CAP_POSIX_LOCKS (1UL << 1) + +/** + * Indicates that the filesystem supports the O_TRUNC open flag. If + * disabled, and an application specifies O_TRUNC, fuse first calls + * truncate() and then open() with O_TRUNC filtered out. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_ATOMIC_O_TRUNC (1UL << 3) + +/** + * Indicates that the filesystem supports lookups of "." and "..". + * + * When this flag is set, the filesystem must be prepared to receive requests + * for invalid inodes (i.e., for which a FORGET request was received or + * which have been used in a previous instance of the filesystem daemon) and + * must not reuse node-ids (even when setting generation numbers). + * + * This feature is disabled by default. + */ +#define FUSE_CAP_EXPORT_SUPPORT (1UL << 4) + +/** + * Indicates that the kernel should not apply the umask to the + * file mode on create operations. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_DONT_MASK (1UL << 6) + +/** + * Indicates that libfuse should try to use splice() when writing to + * the fuse device. This may improve performance. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_SPLICE_WRITE (1UL << 7) + +/** + * Indicates that libfuse should try to move pages instead of copying when + * writing to / reading from the fuse device. This may improve performance. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_SPLICE_MOVE (1UL << 8) + +/** + * Indicates that libfuse should try to use splice() when reading from + * the fuse device. This may improve performance. + * + * This feature is enabled by default when supported by the kernel and + * if the filesystem implements a write_buf() handler. + */ +#define FUSE_CAP_SPLICE_READ (1UL << 9) + +/** + * If set, the calls to flock(2) will be emulated using POSIX locks and must + * then be handled by the filesystem's setlock() handler. + * + * If not set, flock(2) calls will be handled by the FUSE kernel module + * internally (so any access that does not go through the kernel cannot be taken + * into account). + * + * This feature is enabled by default when supported by the kernel and + * if the filesystem implements a flock() handler. + */ +#define FUSE_CAP_FLOCK_LOCKS (1UL << 10) + +/** + * Indicates that the filesystem supports ioctl's on directories. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_IOCTL_DIR (1UL << 11) + +/** + * Traditionally, while a file is open the FUSE kernel module only + * asks the filesystem for an update of the file's attributes when a + * client attempts to read beyond EOF. This is unsuitable for + * e.g. network filesystems, where the file contents may change + * without the kernel knowing about it. + * + * If this flag is set, FUSE will check the validity of the attributes + * on every read. If the attributes are no longer valid (i.e., if the + * *attr_timeout* passed to fuse_reply_attr() or set in `struct + * fuse_entry_param` has passed), it will first issue a `getattr` + * request. If the new mtime differs from the previous value, any + * cached file *contents* will be invalidated as well. + * + * This flag should always be set when available. If all file changes + * go through the kernel, *attr_timeout* should be set to a very large + * number to avoid unnecessary getattr() calls. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_AUTO_INVAL_DATA (1UL << 12) + +/** + * Indicates that the filesystem supports readdirplus. + * + * This feature is enabled by default when supported by the kernel and if the + * filesystem implements a readdirplus() handler. + */ +#define FUSE_CAP_READDIRPLUS (1UL << 13) + +/** + * Indicates that the filesystem supports adaptive readdirplus. + * + * If FUSE_CAP_READDIRPLUS is not set, this flag has no effect. + * + * If FUSE_CAP_READDIRPLUS is set and this flag is not set, the kernel + * will always issue readdirplus() requests to retrieve directory + * contents. + * + * If FUSE_CAP_READDIRPLUS is set and this flag is set, the kernel + * will issue both readdir() and readdirplus() requests, depending on + * how much information is expected to be required. + * + * As of Linux 4.20, the algorithm is as follows: when userspace + * starts to read directory entries, issue a READDIRPLUS request to + * the filesystem. If any entry attributes have been looked up by the + * time userspace requests the next batch of entries continue with + * READDIRPLUS, otherwise switch to plain READDIR. This will reasult + * in eg plain "ls" triggering READDIRPLUS first then READDIR after + * that because it doesn't do lookups. "ls -l" should result in all + * READDIRPLUS, except if dentries are already cached. + * + * This feature is enabled by default when supported by the kernel and + * if the filesystem implements both a readdirplus() and a readdir() + * handler. + */ +#define FUSE_CAP_READDIRPLUS_AUTO (1UL << 14) + +/** + * Indicates that the filesystem supports asynchronous direct I/O submission. + * + * If this capability is not requested/available, the kernel will ensure that + * there is at most one pending read and one pending write request per direct + * I/O file-handle at any time. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_ASYNC_DIO (1UL << 15) + +/** + * Indicates that writeback caching should be enabled. This means that + * individual write request may be buffered and merged in the kernel + * before they are send to the filesystem. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_WRITEBACK_CACHE (1UL << 16) + +/** + * Indicates support for zero-message opens. If this flag is set in + * the `capable` field of the `fuse_conn_info` structure, then the + * filesystem may return `ENOSYS` from the open() handler to indicate + * success. Further attempts to open files will be handled in the + * kernel. (If this flag is not set, returning ENOSYS will be treated + * as an error and signaled to the caller). + * + * Setting this flag in the `want` field enables this behavior automatically + * within libfuse for low level API users. If non-low level users wish to have + * this behavior you must return `ENOSYS` from the open() handler on supporting + * kernels. + */ +#define FUSE_CAP_NO_OPEN_SUPPORT (1UL << 17) + +/** + * Indicates support for parallel directory operations. If this flag + * is unset, the FUSE kernel module will ensure that lookup() and + * readdir() requests are never issued concurrently for the same + * directory. + */ +#define FUSE_CAP_PARALLEL_DIROPS (1UL << 18) + +/** + * Indicates support for POSIX ACLs. + * + * If this feature is enabled, the kernel will cache and have + * responsibility for enforcing ACLs. ACL will be stored as xattrs and + * passed to userspace, which is responsible for updating the ACLs in + * the filesystem, keeping the file mode in sync with the ACL, and + * ensuring inheritance of default ACLs when new filesystem nodes are + * created. Note that this requires that the file system is able to + * parse and interpret the xattr representation of ACLs. + * + * Enabling this feature implicitly turns on the + * ``default_permissions`` mount option (even if it was not passed to + * mount(2)). + * + * This feature is disabled by default. + */ +#define FUSE_CAP_POSIX_ACL (1UL << 19) + +/** + * Indicates that the filesystem is responsible for unsetting + * setuid and setgid bits when a file is written, truncated, or + * its owner is changed. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_HANDLE_KILLPRIV (1UL << 20) + +/** + * Indicates that the filesystem is responsible for unsetting + * setuid and setgid bit and additionally cap (stored as xattr) when a + * file is written, truncated, or its owner is changed. + * Upon write/truncate suid/sgid is only killed if caller + * does not have CAP_FSETID. Additionally upon + * write/truncate sgid is killed only if file has group + * execute permission. (Same as Linux VFS behavior). + * KILLPRIV_V2 requires handling of + * - FUSE_OPEN_KILL_SUIDGID (set in struct fuse_create_in::open_flags) + * - FATTR_KILL_SUIDGID (set in struct fuse_setattr_in::valid) + * - FUSE_WRITE_KILL_SUIDGID (set in struct fuse_write_in::write_flags) + * + * This feature is disabled by default. + */ +#define FUSE_CAP_HANDLE_KILLPRIV_V2 (1UL << 21) + +/** + * Indicates that the kernel supports caching symlinks in its page cache. + * + * When this feature is enabled, symlink targets are saved in the page cache. + * You can invalidate a cached link by calling: + * `fuse_lowlevel_notify_inval_inode(se, ino, 0, 0);` + * + * This feature is disabled by default. + * If the kernel supports it (>= 4.20), you can enable this feature by + * setting this flag in the `want` field of the `fuse_conn_info` structure. + */ +#define FUSE_CAP_CACHE_SYMLINKS (1UL << 23) + +/** + * Indicates support for zero-message opendirs. If this flag is set in + * the `capable` field of the `fuse_conn_info` structure, then the filesystem + * may return `ENOSYS` from the opendir() handler to indicate success. Further + * opendir and releasedir messages will be handled in the kernel. (If this + * flag is not set, returning ENOSYS will be treated as an error and signalled + * to the caller.) + * + * Setting this flag in the `want` field enables this behavior automatically + * within libfuse for low level API users. If non-low level users with to have + * this behavior you must return `ENOSYS` from the opendir() handler on + * supporting kernels. + */ +#define FUSE_CAP_NO_OPENDIR_SUPPORT (1UL << 24) + +/** + * Indicates support for invalidating cached pages only on explicit request. + * + * If this flag is set in the `capable` field of the `fuse_conn_info` structure, + * then the FUSE kernel module supports invalidating cached pages only on + * explicit request by the filesystem through fuse_lowlevel_notify_inval_inode() + * or fuse_invalidate_path(). + * + * By setting this flag in the `want` field of the `fuse_conn_info` structure, + * the filesystem is responsible for invalidating cached pages through explicit + * requests to the kernel. + * + * Note that setting this flag does not prevent the cached pages from being + * flushed by OS itself and/or through user actions. + * + * Note that if both FUSE_CAP_EXPLICIT_INVAL_DATA and FUSE_CAP_AUTO_INVAL_DATA + * are set in the `capable` field of the `fuse_conn_info` structure then + * FUSE_CAP_AUTO_INVAL_DATA takes precedence. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_EXPLICIT_INVAL_DATA (1UL << 25) + +/** + * Indicates support that dentries can be expired. + * + * Expiring dentries, instead of invalidating them, makes a difference for + * overmounted dentries, where plain invalidation would detach all submounts + * before dropping the dentry from the cache. If only expiry is set on the + * dentry, then any overmounts are left alone and until ->d_revalidate() + * is called. + * + * Note: ->d_revalidate() is not called for the case of following a submount, + * so invalidation will only be triggered for the non-overmounted case. + * The dentry could also be mounted in a different mount instance, in which case + * any submounts will still be detached. + */ +#define FUSE_CAP_EXPIRE_ONLY (1UL << 26) + +/** + * Indicates that an extended 'struct fuse_setxattr' is used by the kernel + * side - extra_flags are passed, which are used (as of now by acl) processing. + * For example FUSE_SETXATTR_ACL_KILL_SGID might be set. + */ +#define FUSE_CAP_SETXATTR_EXT (1UL << 27) + +/** + * Files opened with FUSE_DIRECT_IO do not support MAP_SHARED mmap. This restriction + * is relaxed through FUSE_CAP_DIRECT_IO_RELAX (kernel flag: FUSE_DIRECT_IO_RELAX). + * MAP_SHARED is disabled by default for FUSE_DIRECT_IO, as this flag can be used to + * ensure coherency between mount points (or network clients) and with kernel page + * cache as enforced by mmap that cannot be guaranteed anymore. + */ +#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP (1UL << 28) + +/** + * Indicates support for passthrough mode access for read/write operations. + * + * If this flag is set in the `capable` field of the `fuse_conn_info` + * structure, then the FUSE kernel module supports redirecting read/write + * operations to the backing file instead of letting them to be handled + * by the FUSE daemon. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_PASSTHROUGH (1UL << 29) + +/** + * Indicates that the file system cannot handle NFS export + * + * If this flag is set NFS export and name_to_handle_at + * is not going to work at all and will fail with EOPNOTSUPP. + */ +#define FUSE_CAP_NO_EXPORT_SUPPORT (1UL << 30) + +/** + * Indicates support for io-uring between fuse-server and fuse-client + */ +#define FUSE_CAP_OVER_IO_URING (1UL << 31) + +/** + * Ioctl flags + * + * FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine + * FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed + * FUSE_IOCTL_RETRY: retry with new iovecs + * FUSE_IOCTL_DIR: is a directory + * + * FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs + */ +#define FUSE_IOCTL_COMPAT (1 << 0) +#define FUSE_IOCTL_UNRESTRICTED (1 << 1) +#define FUSE_IOCTL_RETRY (1 << 2) +#define FUSE_IOCTL_DIR (1 << 4) + +#define FUSE_IOCTL_MAX_IOV 256 + +/** + * Connection information, passed to the ->init() method + * + * Some of the elements are read-write, these can be changed to + * indicate the value requested by the filesystem. The requested + * value must usually be smaller than the indicated value. + * + * Note: The `capable` and `want` fields are limited to 32 bits for + * ABI compatibility. For full 64-bit capability support, use the + * `capable_ext` and `want_ext` fields instead. + */ +struct fuse_conn_info { + /** + * Major version of the protocol (read-only) + */ + uint32_t proto_major; + + /** + * Minor version of the protocol (read-only) + */ + uint32_t proto_minor; + + /** + * Maximum size of the write buffer + */ + uint32_t max_write; + + /** + * Maximum size of read requests. A value of zero indicates no + * limit. However, even if the filesystem does not specify a + * limit, the maximum size of read requests will still be + * limited by the kernel. + * + * NOTE: For the time being, the maximum size of read requests + * must be set both here *and* passed to fuse_session_new() + * using the ``-o max_read=`` mount option. At some point + * in the future, specifying the mount option will no longer + * be necessary. + */ + uint32_t max_read; + + /** + * Maximum readahead + */ + uint32_t max_readahead; + + /** + * Capability flags that the kernel supports (read-only) + * + * Deprecated left over for ABI compatibility, use capable_ext + */ + uint32_t capable; + + /** + * Capability flags that the filesystem wants to enable. + * + * libfuse attempts to initialize this field with + * reasonable default values before calling the init() handler. + * + * Deprecated left over for ABI compatibility. + * Use want_ext with the helper functions + * fuse_set_feature_flag() / fuse_unset_feature_flag() + */ + uint32_t want; + + /** + * Maximum number of pending "background" requests. A + * background request is any type of request for which the + * total number is not limited by other means. As of kernel + * 4.8, only two types of requests fall into this category: + * + * 1. Read-ahead requests + * 2. Asynchronous direct I/O requests + * + * Read-ahead requests are generated (if max_readahead is + * non-zero) by the kernel to preemptively fill its caches + * when it anticipates that userspace will soon read more + * data. + * + * Asynchronous direct I/O requests are generated if + * FUSE_CAP_ASYNC_DIO is enabled and userspace submits a large + * direct I/O request. In this case the kernel will internally + * split it up into multiple smaller requests and submit them + * to the filesystem concurrently. + * + * Note that the following requests are *not* background + * requests: writeback requests (limited by the kernel's + * flusher algorithm), regular (i.e., synchronous and + * buffered) userspace read/write requests (limited to one per + * thread), asynchronous read requests (Linux's io_submit(2) + * call actually blocks, so these are also limited to one per + * thread). + */ + uint32_t max_background; + + /** + * Kernel congestion threshold parameter. If the number of pending + * background requests exceeds this number, the FUSE kernel module will + * mark the filesystem as "congested". This instructs the kernel to + * expect that queued requests will take some time to complete, and to + * adjust its algorithms accordingly (e.g. by putting a waiting thread + * to sleep instead of using a busy-loop). + */ + uint32_t congestion_threshold; + + /** + * When FUSE_CAP_WRITEBACK_CACHE is enabled, the kernel is responsible + * for updating mtime and ctime when write requests are received. The + * updated values are passed to the filesystem with setattr() requests. + * However, if the filesystem does not support the full resolution of + * the kernel timestamps (nanoseconds), the mtime and ctime values used + * by kernel and filesystem will differ (and result in an apparent + * change of times after a cache flush). + * + * To prevent this problem, this variable can be used to inform the + * kernel about the timestamp granularity supported by the file-system. + * The value should be power of 10. The default is 1, i.e. full + * nano-second resolution. Filesystems supporting only second resolution + * should set this to 1000000000. + */ + uint32_t time_gran; + + /** + * When FUSE_CAP_PASSTHROUGH is enabled, this is the maximum allowed + * stacking depth of the backing files. In current kernel, the maximum + * allowed stack depth if FILESYSTEM_MAX_STACK_DEPTH (2), which includes + * the FUSE passthrough layer, so the maximum stacking depth for backing + * files is 1. + * + * The default is FUSE_BACKING_STACKED_UNDER (0), meaning that the + * backing files cannot be on a stacked filesystem, but another stacked + * filesystem can be stacked over this FUSE passthrough filesystem. + * + * Set this to FUSE_BACKING_STACKED_OVER (1) if backing files may be on + * a stacked filesystem, such as overlayfs or another FUSE passthrough. + * In this configuration, another stacked filesystem cannot be stacked + * over this FUSE passthrough filesystem. + */ +#define FUSE_BACKING_STACKED_UNDER (0) +#define FUSE_BACKING_STACKED_OVER (1) + uint32_t max_backing_stack_depth; + + /** + * Disable FUSE_INTERRUPT requests. + * + * Enable `no_interrupt` option to: + * 1) Avoid unnecessary locking operations and list operations, + * 2) Return ENOSYS for the reply of FUSE_INTERRUPT request to + * inform the kernel not to send the FUSE_INTERRUPT request. + */ + uint32_t no_interrupt : 1; + + /* reserved bits for future use */ + uint32_t padding : 31; + + /** + * Extended capability flags that the kernel supports (read-only) + * This field provides full 64-bit capability support. + */ + uint64_t capable_ext; + + /** + * Extended capability flags that the filesystem wants to enable. + * This field provides full 64-bit capability support. + * + * Don't set this field directly, but use the helper functions + * fuse_set_feature_flag() / fuse_unset_feature_flag() + * + */ + uint64_t want_ext; + + /** + * Request timeout (in seconds). If the request is not answered by + * this timeout, the connection will be aborted by the kernel. + */ + uint16_t request_timeout; + + /** + * For future use. + */ + uint16_t reserved[31]; +}; + +struct fuse_session; +struct fuse_pollhandle; +struct fuse_conn_info_opts; + +/** + * This function parses several command-line options that can be used + * to override elements of struct fuse_conn_info. The pointer returned + * by this function should be passed to the + * fuse_apply_conn_info_opts() method by the file system's init() + * handler. + * + * Before using this function, think twice if you really want these + * parameters to be adjustable from the command line. In most cases, + * they should be determined by the file system internally. + * + * The following options are recognized: + * + * -o max_write=N sets conn->max_write + * -o max_readahead=N sets conn->max_readahead + * -o max_background=N sets conn->max_background + * -o congestion_threshold=N sets conn->congestion_threshold + * -o async_read sets FUSE_CAP_ASYNC_READ in conn->want + * -o sync_read unsets FUSE_CAP_ASYNC_READ in conn->want + * -o atomic_o_trunc sets FUSE_CAP_ATOMIC_O_TRUNC in conn->want + * -o no_remote_lock Equivalent to -o no_remote_flock,no_remote_posix_lock + * -o no_remote_flock Unsets FUSE_CAP_FLOCK_LOCKS in conn->want + * -o no_remote_posix_lock Unsets FUSE_CAP_POSIX_LOCKS in conn->want + * -o [no_]splice_write (un-)sets FUSE_CAP_SPLICE_WRITE in conn->want + * -o [no_]splice_move (un-)sets FUSE_CAP_SPLICE_MOVE in conn->want + * -o [no_]splice_read (un-)sets FUSE_CAP_SPLICE_READ in conn->want + * -o [no_]auto_inval_data (un-)sets FUSE_CAP_AUTO_INVAL_DATA in conn->want + * -o readdirplus=no unsets FUSE_CAP_READDIRPLUS in conn->want + * -o readdirplus=yes sets FUSE_CAP_READDIRPLUS and unsets + * FUSE_CAP_READDIRPLUS_AUTO in conn->want + * -o readdirplus=auto sets FUSE_CAP_READDIRPLUS and + * FUSE_CAP_READDIRPLUS_AUTO in conn->want + * -o [no_]async_dio (un-)sets FUSE_CAP_ASYNC_DIO in conn->want + * -o [no_]writeback_cache (un-)sets FUSE_CAP_WRITEBACK_CACHE in conn->want + * -o time_gran=N sets conn->time_gran + * + * Known options will be removed from *args*, unknown options will be + * passed through unchanged. + * + * @param args argument vector (input+output) + * @return parsed options + **/ +struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args); + +/** + * This function applies the (parsed) parameters in *opts* to the + * *conn* pointer. It may modify the following fields: wants, + * max_write, max_readahead, congestion_threshold, max_background, + * time_gran. A field is only set (or unset) if the corresponding + * option has been explicitly set. + */ +void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, + struct fuse_conn_info *conn); + +/** + * Go into the background + * + * @param foreground if true, stay in the foreground + * @return 0 on success, -1 on failure + */ +int fuse_daemonize(int foreground); + +/** + * Get the version of the library + * + * @return the version + */ +int fuse_version(void); + +/** + * Get the full package version string of the library + * + * @return the package version + */ +const char *fuse_pkgversion(void); + +/** + * Destroy poll handle + * + * @param ph the poll handle + */ +void fuse_pollhandle_destroy(struct fuse_pollhandle *ph); + +/* ----------------------------------------------------------- * + * Data buffer * + * ----------------------------------------------------------- */ + +/** + * Buffer flags + */ +enum fuse_buf_flags { + /** + * Buffer contains a file descriptor + * + * If this flag is set, the .fd field is valid, otherwise the + * .mem fields is valid. + */ + FUSE_BUF_IS_FD = (1 << 1), + + /** + * Seek on the file descriptor + * + * If this flag is set then the .pos field is valid and is + * used to seek to the given offset before performing + * operation on file descriptor. + */ + FUSE_BUF_FD_SEEK = (1 << 2), + + /** + * Retry operation on file descriptor + * + * If this flag is set then retry operation on file descriptor + * until .size bytes have been copied or an error or EOF is + * detected. + */ + FUSE_BUF_FD_RETRY = (1 << 3) +}; + +/** + * Buffer copy flags + */ +enum fuse_buf_copy_flags { + /** + * Don't use splice(2) + * + * Always fall back to using read and write instead of + * splice(2) to copy data from one file descriptor to another. + * + * If this flag is not set, then only fall back if splice is + * unavailable. + */ + FUSE_BUF_NO_SPLICE = (1 << 1), + + /** + * Force splice + * + * Always use splice(2) to copy data from one file descriptor + * to another. If splice is not available, return -EINVAL. + */ + FUSE_BUF_FORCE_SPLICE = (1 << 2), + + /** + * Try to move data with splice. + * + * If splice is used, try to move pages from the source to the + * destination instead of copying. See documentation of + * SPLICE_F_MOVE in splice(2) man page. + */ + FUSE_BUF_SPLICE_MOVE = (1 << 3), + + /** + * Don't block on the pipe when copying data with splice + * + * Makes the operations on the pipe non-blocking (if the pipe + * is full or empty). See SPLICE_F_NONBLOCK in the splice(2) + * man page. + */ + FUSE_BUF_SPLICE_NONBLOCK= (1 << 4) +}; + +/** + * Single data buffer + * + * Generic data buffer for I/O, extended attributes, etc... Data may + * be supplied as a memory pointer or as a file descriptor + */ +struct fuse_buf { + /** + * Size of data in bytes + */ + size_t size; + + /** + * Buffer flags + */ + enum fuse_buf_flags flags; + + /** + * Memory pointer + * + * Used unless FUSE_BUF_IS_FD flag is set. + */ + void *mem; + + /** + * File descriptor + * + * Used if FUSE_BUF_IS_FD flag is set. + */ + int fd; + + /** + * File position + * + * Used if FUSE_BUF_FD_SEEK flag is set. + */ + off_t pos; + + /** + * Size of memory pointer + * + * Used only if mem was internally allocated. + * Not used if mem was user-provided. + */ + size_t mem_size; +}; + +/** + * Data buffer vector + * + * An array of data buffers, each containing a memory pointer or a + * file descriptor. + * + * Allocate dynamically to add more than one buffer. + */ +struct fuse_bufvec { + /** + * Number of buffers in the array + */ + size_t count; + + /** + * Index of current buffer within the array + */ + size_t idx; + + /** + * Current offset within the current buffer + */ + size_t off; + + /** + * Array of buffers + */ + struct fuse_buf buf[1]; +}; + +/** + * libfuse version a file system was compiled with. Should be filled in from + * defines in 'libfuse_config.h' + */ +struct libfuse_version +{ + uint32_t major; + uint32_t minor; + uint32_t hotfix; + uint32_t padding; +}; + +/* Initialize bufvec with a single buffer of given size */ +#define FUSE_BUFVEC_INIT(size__) \ + ((struct fuse_bufvec) { \ + /* .count= */ 1, \ + /* .idx = */ 0, \ + /* .off = */ 0, \ + /* .buf = */ { /* [0] = */ { \ + /* .size = */ (size__), \ + /* .flags = */ (enum fuse_buf_flags) 0, \ + /* .mem = */ NULL, \ + /* .fd = */ -1, \ + /* .pos = */ 0, \ + /* .mem_size = */ 0, \ + } } \ + } ) + +/** + * Get total size of data in a fuse buffer vector + * + * @param bufv buffer vector + * @return size of data + */ +size_t fuse_buf_size(const struct fuse_bufvec *bufv); + +/** + * Copy data from one buffer vector to another + * + * @param dst destination buffer vector + * @param src source buffer vector + * @param flags flags controlling the copy + * @return actual number of bytes copied or -errno on error + */ +ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, + enum fuse_buf_copy_flags flags); + +/* ----------------------------------------------------------- * + * Signal handling * + * ----------------------------------------------------------- */ + +/** + * Exit session on HUP, TERM and INT signals and ignore PIPE signal + * + * Stores session in a global variable. May only be called once per + * process until fuse_remove_signal_handlers() is called. + * + * Once either of the POSIX signals arrives, the signal handler calls + * fuse_session_exit(). + * + * @param se the session to exit + * @return 0 on success, -1 on failure + * + * See also: + * fuse_remove_signal_handlers() + */ +int fuse_set_signal_handlers(struct fuse_session *se); + +/** + * Print a stack backtrace diagnostic on critical signals () + * + * Stores session in a global variable. May only be called once per + * process until fuse_remove_signal_handlers() is called. + * + * Once either of the POSIX signals arrives, the signal handler calls + * fuse_session_exit(). + * + * @param se the session to exit + * @return 0 on success, -1 on failure + * + * See also: + * fuse_remove_signal_handlers() + */ +int fuse_set_fail_signal_handlers(struct fuse_session *se); + +/** + * Restore default signal handlers + * + * Resets global session. After this fuse_set_signal_handlers() may + * be called again. + * + * @param se the same session as given in fuse_set_signal_handlers() + * + * See also: + * fuse_set_signal_handlers() + */ +void fuse_remove_signal_handlers(struct fuse_session *se); + +/** + * Config operations. + * These APIs are introduced in version 312 (FUSE_MAKE_VERSION(3, 12)). + * Using them in earlier versions will result in errors. + */ +#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12) +/** + * Create and set default config for fuse_session_loop_mt and fuse_loop_mt. + * + * @return anonymous config struct + */ +struct fuse_loop_config *fuse_loop_cfg_create(void); + +/** + * Free the config data structure + */ +void fuse_loop_cfg_destroy(struct fuse_loop_config *config); + +/** + * fuse_loop_config setter to set the number of max idle threads. + */ +void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config, + unsigned int value); + +/** + * fuse_loop_config setter to set the number of max threads. + */ +void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config, + unsigned int value); + +/** + * fuse_loop_config setter to enable the clone_fd feature + */ +void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config, + unsigned int value); + +/** + * Convert old config to more recernt fuse_loop_config2 + * + * @param config current config2 type + * @param v1_conf older config1 type (below FUSE API 312) + */ +void fuse_loop_cfg_convert(struct fuse_loop_config *config, + struct fuse_loop_config_v1 *v1_conf); +#endif + +/** + * Set a feature flag in the want_ext field of fuse_conn_info. + * + * @param conn connection information + * @param flag feature flag to be set + * @return true if the flag was set, false if the flag is not supported + */ +bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag); + +/** + * Unset a feature flag in the want_ext field of fuse_conn_info. + * + * @param conn connection information + * @param flag feature flag to be unset + */ +void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag); + +/** + * Get the value of a feature flag in the want_ext field of fuse_conn_info. + * + * @param conn connection information + * @param flag feature flag to be checked + * @return true if the flag is set, false otherwise + */ +bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag); + +/* + * DO NOT USE: Not part of public API, for internal test use only. + * The function signature or any use of it is not guaranteeed to + * remain stable. And neither are results of what this function does. + */ +int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn); + + + +/* ----------------------------------------------------------- * + * Compatibility stuff * + * ----------------------------------------------------------- */ + +#if !defined(FUSE_USE_VERSION) || FUSE_USE_VERSION < 30 +# error only API version 30 or greater is supported +#endif + +#ifdef __cplusplus +} +#endif + + +/* + * This interface uses 64 bit off_t. + * + * On 32bit systems please add -D_FILE_OFFSET_BITS=64 to your compile flags! + */ + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +_Static_assert(sizeof(off_t) == 8, "fuse: off_t must be 64bit"); +#else +struct _fuse_off_t_must_be_64bit_dummy_struct \ + { unsigned _fuse_off_t_must_be_64bit:((sizeof(off_t) == 8) ? 1 : -1); }; +#endif + +#endif /* FUSE_COMMON_H_ */ diff --git a/include/fuse_kernel.h b/include/fuse_kernel.h new file mode 100644 index 0000000..94621f6 --- /dev/null +++ b/include/fuse_kernel.h @@ -0,0 +1,1303 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */ +/* + This file defines the kernel interface of FUSE + Copyright (C) 2001-2008 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. + + This -- and only this -- header file may also be distributed under + the terms of the BSD Licence as follows: + + Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. +*/ + +/* + * This file defines the kernel interface of FUSE + * + * Protocol changelog: + * + * 7.1: + * - add the following messages: + * FUSE_SETATTR, FUSE_SYMLINK, FUSE_MKNOD, FUSE_MKDIR, FUSE_UNLINK, + * FUSE_RMDIR, FUSE_RENAME, FUSE_LINK, FUSE_OPEN, FUSE_READ, FUSE_WRITE, + * FUSE_RELEASE, FUSE_FSYNC, FUSE_FLUSH, FUSE_SETXATTR, FUSE_GETXATTR, + * FUSE_LISTXATTR, FUSE_REMOVEXATTR, FUSE_OPENDIR, FUSE_READDIR, + * FUSE_RELEASEDIR + * - add padding to messages to accommodate 32-bit servers on 64-bit kernels + * + * 7.2: + * - add FOPEN_DIRECT_IO and FOPEN_KEEP_CACHE flags + * - add FUSE_FSYNCDIR message + * + * 7.3: + * - add FUSE_ACCESS message + * - add FUSE_CREATE message + * - add filehandle to fuse_setattr_in + * + * 7.4: + * - add frsize to fuse_kstatfs + * - clean up request size limit checking + * + * 7.5: + * - add flags and max_write to fuse_init_out + * + * 7.6: + * - add max_readahead to fuse_init_in and fuse_init_out + * + * 7.7: + * - add FUSE_INTERRUPT message + * - add POSIX file lock support + * + * 7.8: + * - add lock_owner and flags fields to fuse_release_in + * - add FUSE_BMAP message + * - add FUSE_DESTROY message + * + * 7.9: + * - new fuse_getattr_in input argument of GETATTR + * - add lk_flags in fuse_lk_in + * - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in + * - add blksize field to fuse_attr + * - add file flags field to fuse_read_in and fuse_write_in + * - Add ATIME_NOW and MTIME_NOW flags to fuse_setattr_in + * + * 7.10 + * - add nonseekable open flag + * + * 7.11 + * - add IOCTL message + * - add unsolicited notification support + * - add POLL message and NOTIFY_POLL notification + * + * 7.12 + * - add umask flag to input argument of create, mknod and mkdir + * - add notification messages for invalidation of inodes and + * directory entries + * + * 7.13 + * - make max number of background requests and congestion threshold + * tunables + * + * 7.14 + * - add splice support to fuse device + * + * 7.15 + * - add store notify + * - add retrieve notify + * + * 7.16 + * - add BATCH_FORGET request + * - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct + * fuse_ioctl_iovec' instead of ambiguous 'struct iovec' + * - add FUSE_IOCTL_32BIT flag + * + * 7.17 + * - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK + * + * 7.18 + * - add FUSE_IOCTL_DIR flag + * - add FUSE_NOTIFY_DELETE + * + * 7.19 + * - add FUSE_FALLOCATE + * + * 7.20 + * - add FUSE_AUTO_INVAL_DATA + * + * 7.21 + * - add FUSE_READDIRPLUS + * - send the requested events in POLL request + * + * 7.22 + * - add FUSE_ASYNC_DIO + * + * 7.23 + * - add FUSE_WRITEBACK_CACHE + * - add time_gran to fuse_init_out + * - add reserved space to fuse_init_out + * - add FATTR_CTIME + * - add ctime and ctimensec to fuse_setattr_in + * - add FUSE_RENAME2 request + * - add FUSE_NO_OPEN_SUPPORT flag + * + * 7.24 + * - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support + * + * 7.25 + * - add FUSE_PARALLEL_DIROPS + * + * 7.26 + * - add FUSE_HANDLE_KILLPRIV + * - add FUSE_POSIX_ACL + * + * 7.27 + * - add FUSE_ABORT_ERROR + * + * 7.28 + * - add FUSE_COPY_FILE_RANGE + * - add FOPEN_CACHE_DIR + * - add FUSE_MAX_PAGES, add max_pages to init_out + * - add FUSE_CACHE_SYMLINKS + * + * 7.29 + * - add FUSE_NO_OPENDIR_SUPPORT flag + * + * 7.30 + * - add FUSE_EXPLICIT_INVAL_DATA + * - add FUSE_IOCTL_COMPAT_X32 + * + * 7.31 + * - add FUSE_WRITE_KILL_PRIV flag + * - add FUSE_SETUPMAPPING and FUSE_REMOVEMAPPING + * - add map_alignment to fuse_init_out, add FUSE_MAP_ALIGNMENT flag + * + * 7.32 + * - add flags to fuse_attr, add FUSE_ATTR_SUBMOUNT, add FUSE_SUBMOUNTS + * + * 7.33 + * - add FUSE_HANDLE_KILLPRIV_V2, FUSE_WRITE_KILL_SUIDGID, FATTR_KILL_SUIDGID + * - add FUSE_OPEN_KILL_SUIDGID + * - extend fuse_setxattr_in, add FUSE_SETXATTR_EXT + * - add FUSE_SETXATTR_ACL_KILL_SGID + * + * 7.34 + * - add FUSE_SYNCFS + * + * 7.35 + * - add FOPEN_NOFLUSH + * + * 7.36 + * - extend fuse_init_in with reserved fields, add FUSE_INIT_EXT init flag + * - add flags2 to fuse_init_in and fuse_init_out + * - add FUSE_SECURITY_CTX init flag + * - add security context to create, mkdir, symlink, and mknod requests + * - add FUSE_HAS_INODE_DAX, FUSE_ATTR_DAX + * + * 7.37 + * - add FUSE_TMPFILE + * + * 7.38 + * - add FUSE_EXPIRE_ONLY flag to fuse_notify_inval_entry + * - add FOPEN_PARALLEL_DIRECT_WRITES + * - add total_extlen to fuse_in_header + * - add FUSE_MAX_NR_SECCTX + * - add extension header + * - add FUSE_EXT_GROUPS + * - add FUSE_CREATE_SUPP_GROUP + * - add FUSE_HAS_EXPIRE_ONLY + * + * 7.39 + * - add FUSE_DIRECT_IO_ALLOW_MMAP + * - add FUSE_STATX and related structures + * + * 7.40 + * - add max_stack_depth to fuse_init_out, add FUSE_PASSTHROUGH init flag + * - add backing_id to fuse_open_out, add FOPEN_PASSTHROUGH open flag + * - add FUSE_NO_EXPORT_SUPPORT init flag + * - add FUSE_NOTIFY_RESEND, add FUSE_HAS_RESEND init flag + * + * 7.41 + * - add FUSE_ALLOW_IDMAP + * 7.42 + * - Add FUSE_OVER_IO_URING and all other io-uring related flags and data + * structures: + * - struct fuse_uring_ent_in_out + * - struct fuse_uring_req_header + * - struct fuse_uring_cmd_req + * - FUSE_URING_IN_OUT_HEADER_SZ + * - FUSE_URING_OP_IN_OUT_SZ + * - enum fuse_uring_cmd + * + * 7.43 + * - add FUSE_REQUEST_TIMEOUT + * + * 7.44 + * - add FUSE_NOTIFY_INC_EPOCH + * + * 7.45 + * - add FUSE_COPY_FILE_RANGE_64 + * - add struct fuse_copy_file_range_out + */ + +#ifndef _LINUX_FUSE_H +#define _LINUX_FUSE_H + +#ifdef __KERNEL__ +#include +#else +#include +#endif + +/* + * Version negotiation: + * + * Both the kernel and userspace send the version they support in the + * INIT request and reply respectively. + * + * If the major versions match then both shall use the smallest + * of the two minor versions for communication. + * + * If the kernel supports a larger major version, then userspace shall + * reply with the major version it supports, ignore the rest of the + * INIT message and expect a new INIT message from the kernel with a + * matching major version. + * + * If the library supports a larger major version, then it shall fall + * back to the major protocol version sent by the kernel for + * communication and reply with that major version (and an arbitrary + * supported minor version). + */ + +/** Version number of this interface */ +#define FUSE_KERNEL_VERSION 7 + +/** Minor version number of this interface */ +#define FUSE_KERNEL_MINOR_VERSION 45 + +/** The node ID of the root inode */ +#define FUSE_ROOT_ID 1 + +/* Make sure all structures are padded to 64bit boundary, so 32bit + userspace works under 64bit kernels */ + +struct fuse_attr { + uint64_t ino; + uint64_t size; + uint64_t blocks; + uint64_t atime; + uint64_t mtime; + uint64_t ctime; + uint32_t atimensec; + uint32_t mtimensec; + uint32_t ctimensec; + uint32_t mode; + uint32_t nlink; + uint32_t uid; + uint32_t gid; + uint32_t rdev; + uint32_t blksize; + uint32_t flags; +}; + +/* + * The following structures are bit-for-bit compatible with the statx(2) ABI in + * Linux. + */ +struct fuse_sx_time { + int64_t tv_sec; + uint32_t tv_nsec; + int32_t __reserved; +}; + +struct fuse_statx { + uint32_t mask; + uint32_t blksize; + uint64_t attributes; + uint32_t nlink; + uint32_t uid; + uint32_t gid; + uint16_t mode; + uint16_t __spare0[1]; + uint64_t ino; + uint64_t size; + uint64_t blocks; + uint64_t attributes_mask; + struct fuse_sx_time atime; + struct fuse_sx_time btime; + struct fuse_sx_time ctime; + struct fuse_sx_time mtime; + uint32_t rdev_major; + uint32_t rdev_minor; + uint32_t dev_major; + uint32_t dev_minor; + uint64_t __spare2[14]; +}; + +struct fuse_kstatfs { + uint64_t blocks; + uint64_t bfree; + uint64_t bavail; + uint64_t files; + uint64_t ffree; + uint32_t bsize; + uint32_t namelen; + uint32_t frsize; + uint32_t padding; + uint32_t spare[6]; +}; + +struct fuse_file_lock { + uint64_t start; + uint64_t end; + uint32_t type; + uint32_t pid; /* tgid */ +}; + +/** + * Bitmasks for fuse_setattr_in.valid + */ +#define FATTR_MODE (1 << 0) +#define FATTR_UID (1 << 1) +#define FATTR_GID (1 << 2) +#define FATTR_SIZE (1 << 3) +#define FATTR_ATIME (1 << 4) +#define FATTR_MTIME (1 << 5) +#define FATTR_FH (1 << 6) +#define FATTR_ATIME_NOW (1 << 7) +#define FATTR_MTIME_NOW (1 << 8) +#define FATTR_LOCKOWNER (1 << 9) +#define FATTR_CTIME (1 << 10) +#define FATTR_KILL_SUIDGID (1 << 11) + +/** + * Flags returned by the OPEN request + * + * FOPEN_DIRECT_IO: bypass page cache for this open file + * FOPEN_KEEP_CACHE: don't invalidate the data cache on open + * FOPEN_NONSEEKABLE: the file is not seekable + * FOPEN_CACHE_DIR: allow caching this directory + * FOPEN_STREAM: the file is stream-like (no file position at all) + * FOPEN_NOFLUSH: don't flush data cache on close (unless FUSE_WRITEBACK_CACHE) + * FOPEN_PARALLEL_DIRECT_WRITES: Allow concurrent direct writes on the same inode + * FOPEN_PASSTHROUGH: passthrough read/write io for this open file + */ +#define FOPEN_DIRECT_IO (1 << 0) +#define FOPEN_KEEP_CACHE (1 << 1) +#define FOPEN_NONSEEKABLE (1 << 2) +#define FOPEN_CACHE_DIR (1 << 3) +#define FOPEN_STREAM (1 << 4) +#define FOPEN_NOFLUSH (1 << 5) +#define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6) +#define FOPEN_PASSTHROUGH (1 << 7) + +/** + * INIT request/reply flags + * + * FUSE_ASYNC_READ: asynchronous read requests + * FUSE_POSIX_LOCKS: remote locking for POSIX file locks + * FUSE_FILE_OPS: kernel sends file handle for fstat, etc... (not yet supported) + * FUSE_ATOMIC_O_TRUNC: handles the O_TRUNC open flag in the filesystem + * FUSE_EXPORT_SUPPORT: filesystem handles lookups of "." and ".." + * FUSE_BIG_WRITES: filesystem can handle write size larger than 4kB + * FUSE_DONT_MASK: don't apply umask to file mode on create operations + * FUSE_SPLICE_WRITE: kernel supports splice write on the device + * FUSE_SPLICE_MOVE: kernel supports splice move on the device + * FUSE_SPLICE_READ: kernel supports splice read on the device + * FUSE_FLOCK_LOCKS: remote locking for BSD style file locks + * FUSE_HAS_IOCTL_DIR: kernel supports ioctl on directories + * FUSE_AUTO_INVAL_DATA: automatically invalidate cached pages + * FUSE_DO_READDIRPLUS: do READDIRPLUS (READDIR+LOOKUP in one) + * FUSE_READDIRPLUS_AUTO: adaptive readdirplus + * FUSE_ASYNC_DIO: asynchronous direct I/O submission + * FUSE_WRITEBACK_CACHE: use writeback cache for buffered writes + * FUSE_NO_OPEN_SUPPORT: kernel supports zero-message opens + * FUSE_PARALLEL_DIROPS: allow parallel lookups and readdir + * FUSE_HANDLE_KILLPRIV: fs handles killing suid/sgid/cap on write/chown/trunc + * FUSE_POSIX_ACL: filesystem supports posix acls + * FUSE_ABORT_ERROR: reading the device after abort returns ECONNABORTED + * FUSE_MAX_PAGES: init_out.max_pages contains the max number of req pages + * FUSE_CACHE_SYMLINKS: cache READLINK responses + * FUSE_NO_OPENDIR_SUPPORT: kernel supports zero-message opendir + * FUSE_EXPLICIT_INVAL_DATA: only invalidate cached pages on explicit request + * FUSE_MAP_ALIGNMENT: init_out.map_alignment contains log2(byte alignment) for + * foffset and moffset fields in struct + * fuse_setupmapping_out and fuse_removemapping_one. + * FUSE_SUBMOUNTS: kernel supports auto-mounting directory submounts + * FUSE_HANDLE_KILLPRIV_V2: fs kills suid/sgid/cap on write/chown/trunc. + * Upon write/truncate suid/sgid is only killed if caller + * does not have CAP_FSETID. Additionally upon + * write/truncate sgid is killed only if file has group + * execute permission. (Same as Linux VFS behavior). + * FUSE_SETXATTR_EXT: Server supports extended struct fuse_setxattr_in + * FUSE_INIT_EXT: extended fuse_init_in request + * FUSE_INIT_RESERVED: reserved, do not use + * FUSE_SECURITY_CTX: add security context to create, mkdir, symlink, and + * mknod + * FUSE_HAS_INODE_DAX: use per inode DAX + * FUSE_CREATE_SUPP_GROUP: add supplementary group info to create, mkdir, + * symlink and mknod (single group that matches parent) + * FUSE_HAS_EXPIRE_ONLY: kernel supports expiry-only entry invalidation + * FUSE_DIRECT_IO_ALLOW_MMAP: allow shared mmap in FOPEN_DIRECT_IO mode. + * FUSE_NO_EXPORT_SUPPORT: explicitly disable export support + * FUSE_HAS_RESEND: kernel supports resending pending requests, and the high bit + * of the request ID indicates resend requests + * FUSE_ALLOW_IDMAP: allow creation of idmapped mounts + * FUSE_OVER_IO_URING: Indicate that client supports io-uring + * FUSE_REQUEST_TIMEOUT: kernel supports timing out requests. + * init_out.request_timeout contains the timeout (in secs) + */ +#define FUSE_ASYNC_READ (1 << 0) +#define FUSE_POSIX_LOCKS (1 << 1) +#define FUSE_FILE_OPS (1 << 2) +#define FUSE_ATOMIC_O_TRUNC (1 << 3) +#define FUSE_EXPORT_SUPPORT (1 << 4) +#define FUSE_BIG_WRITES (1 << 5) +#define FUSE_DONT_MASK (1 << 6) +#define FUSE_SPLICE_WRITE (1 << 7) +#define FUSE_SPLICE_MOVE (1 << 8) +#define FUSE_SPLICE_READ (1 << 9) +#define FUSE_FLOCK_LOCKS (1 << 10) +#define FUSE_HAS_IOCTL_DIR (1 << 11) +#define FUSE_AUTO_INVAL_DATA (1 << 12) +#define FUSE_DO_READDIRPLUS (1 << 13) +#define FUSE_READDIRPLUS_AUTO (1 << 14) +#define FUSE_ASYNC_DIO (1 << 15) +#define FUSE_WRITEBACK_CACHE (1 << 16) +#define FUSE_NO_OPEN_SUPPORT (1 << 17) +#define FUSE_PARALLEL_DIROPS (1 << 18) +#define FUSE_HANDLE_KILLPRIV (1 << 19) +#define FUSE_POSIX_ACL (1 << 20) +#define FUSE_ABORT_ERROR (1 << 21) +#define FUSE_MAX_PAGES (1 << 22) +#define FUSE_CACHE_SYMLINKS (1 << 23) +#define FUSE_NO_OPENDIR_SUPPORT (1 << 24) +#define FUSE_EXPLICIT_INVAL_DATA (1 << 25) +#define FUSE_MAP_ALIGNMENT (1 << 26) +#define FUSE_SUBMOUNTS (1 << 27) +#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28) +#define FUSE_SETXATTR_EXT (1 << 29) +#define FUSE_INIT_EXT (1 << 30) +#define FUSE_INIT_RESERVED (1 << 31) +/* bits 32..63 get shifted down 32 bits into the flags2 field */ +#define FUSE_SECURITY_CTX (1ULL << 32) +#define FUSE_HAS_INODE_DAX (1ULL << 33) +#define FUSE_CREATE_SUPP_GROUP (1ULL << 34) +#define FUSE_HAS_EXPIRE_ONLY (1ULL << 35) +#define FUSE_DIRECT_IO_ALLOW_MMAP (1ULL << 36) +#define FUSE_PASSTHROUGH (1ULL << 37) +#define FUSE_NO_EXPORT_SUPPORT (1ULL << 38) +#define FUSE_HAS_RESEND (1ULL << 39) +/* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */ +#define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP +#define FUSE_ALLOW_IDMAP (1ULL << 40) +#define FUSE_OVER_IO_URING (1ULL << 41) +#define FUSE_REQUEST_TIMEOUT (1ULL << 42) + +/** + * CUSE INIT request/reply flags + * + * CUSE_UNRESTRICTED_IOCTL: use unrestricted ioctl + */ +#define CUSE_UNRESTRICTED_IOCTL (1 << 0) + +/** + * Release flags + */ +#define FUSE_RELEASE_FLUSH (1 << 0) +#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1) + +/** + * Getattr flags + */ +#define FUSE_GETATTR_FH (1 << 0) + +/** + * Lock flags + */ +#define FUSE_LK_FLOCK (1 << 0) + +/** + * WRITE flags + * + * FUSE_WRITE_CACHE: delayed write from page cache, file handle is guessed + * FUSE_WRITE_LOCKOWNER: lock_owner field is valid + * FUSE_WRITE_KILL_SUIDGID: kill suid and sgid bits + */ +#define FUSE_WRITE_CACHE (1 << 0) +#define FUSE_WRITE_LOCKOWNER (1 << 1) +#define FUSE_WRITE_KILL_SUIDGID (1 << 2) + +/* Obsolete alias; this flag implies killing suid/sgid only. */ +#define FUSE_WRITE_KILL_PRIV FUSE_WRITE_KILL_SUIDGID + +/** + * Read flags + */ +#define FUSE_READ_LOCKOWNER (1 << 1) + +/** + * Ioctl flags + * + * FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine + * FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed + * FUSE_IOCTL_RETRY: retry with new iovecs + * FUSE_IOCTL_32BIT: 32bit ioctl + * FUSE_IOCTL_DIR: is a directory + * FUSE_IOCTL_COMPAT_X32: x32 compat ioctl on 64bit machine (64bit time_t) + * + * FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs + */ +#define FUSE_IOCTL_COMPAT (1 << 0) +#define FUSE_IOCTL_UNRESTRICTED (1 << 1) +#define FUSE_IOCTL_RETRY (1 << 2) +#define FUSE_IOCTL_32BIT (1 << 3) +#define FUSE_IOCTL_DIR (1 << 4) +#define FUSE_IOCTL_COMPAT_X32 (1 << 5) + +#define FUSE_IOCTL_MAX_IOV 256 + +/** + * Poll flags + * + * FUSE_POLL_SCHEDULE_NOTIFY: request poll notify + */ +#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0) + +/** + * Fsync flags + * + * FUSE_FSYNC_FDATASYNC: Sync data only, not metadata + */ +#define FUSE_FSYNC_FDATASYNC (1 << 0) + +/** + * fuse_attr flags + * + * FUSE_ATTR_SUBMOUNT: Object is a submount root + * FUSE_ATTR_DAX: Enable DAX for this file in per inode DAX mode + */ +#define FUSE_ATTR_SUBMOUNT (1 << 0) +#define FUSE_ATTR_DAX (1 << 1) + +/** + * Open flags + * FUSE_OPEN_KILL_SUIDGID: Kill suid and sgid if executable + */ +#define FUSE_OPEN_KILL_SUIDGID (1 << 0) + +/** + * setxattr flags + * FUSE_SETXATTR_ACL_KILL_SGID: Clear SGID when system.posix_acl_access is set + */ +#define FUSE_SETXATTR_ACL_KILL_SGID (1 << 0) + +/** + * notify_inval_entry flags + * FUSE_EXPIRE_ONLY + */ +#define FUSE_EXPIRE_ONLY (1 << 0) + +/** + * extension type + * FUSE_MAX_NR_SECCTX: maximum value of &fuse_secctx_header.nr_secctx + * FUSE_EXT_GROUPS: &fuse_supp_groups extension + */ +enum fuse_ext_type { + /* Types 0..31 are reserved for fuse_secctx_header */ + FUSE_MAX_NR_SECCTX = 31, + FUSE_EXT_GROUPS = 32, +}; + +enum fuse_opcode { + FUSE_LOOKUP = 1, + FUSE_FORGET = 2, /* no reply */ + FUSE_GETATTR = 3, + FUSE_SETATTR = 4, + FUSE_READLINK = 5, + FUSE_SYMLINK = 6, + FUSE_MKNOD = 8, + FUSE_MKDIR = 9, + FUSE_UNLINK = 10, + FUSE_RMDIR = 11, + FUSE_RENAME = 12, + FUSE_LINK = 13, + FUSE_OPEN = 14, + FUSE_READ = 15, + FUSE_WRITE = 16, + FUSE_STATFS = 17, + FUSE_RELEASE = 18, + FUSE_FSYNC = 20, + FUSE_SETXATTR = 21, + FUSE_GETXATTR = 22, + FUSE_LISTXATTR = 23, + FUSE_REMOVEXATTR = 24, + FUSE_FLUSH = 25, + FUSE_INIT = 26, + FUSE_OPENDIR = 27, + FUSE_READDIR = 28, + FUSE_RELEASEDIR = 29, + FUSE_FSYNCDIR = 30, + FUSE_GETLK = 31, + FUSE_SETLK = 32, + FUSE_SETLKW = 33, + FUSE_ACCESS = 34, + FUSE_CREATE = 35, + FUSE_INTERRUPT = 36, + FUSE_BMAP = 37, + FUSE_DESTROY = 38, + FUSE_IOCTL = 39, + FUSE_POLL = 40, + FUSE_NOTIFY_REPLY = 41, + FUSE_BATCH_FORGET = 42, + FUSE_FALLOCATE = 43, + FUSE_READDIRPLUS = 44, + FUSE_RENAME2 = 45, + FUSE_LSEEK = 46, + FUSE_COPY_FILE_RANGE = 47, + FUSE_SETUPMAPPING = 48, + FUSE_REMOVEMAPPING = 49, + FUSE_SYNCFS = 50, + FUSE_TMPFILE = 51, + FUSE_STATX = 52, + FUSE_COPY_FILE_RANGE_64 = 53, + + /* CUSE specific operations */ + CUSE_INIT = 4096, + + /* Reserved opcodes: helpful to detect structure endian-ness */ + CUSE_INIT_BSWAP_RESERVED = 1048576, /* CUSE_INIT << 8 */ + FUSE_INIT_BSWAP_RESERVED = 436207616, /* FUSE_INIT << 24 */ +}; + +enum fuse_notify_code { + FUSE_NOTIFY_POLL = 1, + FUSE_NOTIFY_INVAL_INODE = 2, + FUSE_NOTIFY_INVAL_ENTRY = 3, + FUSE_NOTIFY_STORE = 4, + FUSE_NOTIFY_RETRIEVE = 5, + FUSE_NOTIFY_DELETE = 6, + FUSE_NOTIFY_RESEND = 7, + FUSE_NOTIFY_INC_EPOCH = 8, + FUSE_NOTIFY_CODE_MAX, +}; + +/* The read buffer is required to be at least 8k, but may be much larger */ +#define FUSE_MIN_READ_BUFFER 8192 + +#define FUSE_COMPAT_ENTRY_OUT_SIZE 120 + +struct fuse_entry_out { + uint64_t nodeid; /* Inode ID */ + uint64_t generation; /* Inode generation: nodeid:gen must + be unique for the fs's lifetime */ + uint64_t entry_valid; /* Cache timeout for the name */ + uint64_t attr_valid; /* Cache timeout for the attributes */ + uint32_t entry_valid_nsec; + uint32_t attr_valid_nsec; + struct fuse_attr attr; +}; + +struct fuse_forget_in { + uint64_t nlookup; +}; + +struct fuse_forget_one { + uint64_t nodeid; + uint64_t nlookup; +}; + +struct fuse_batch_forget_in { + uint32_t count; + uint32_t dummy; +}; + +struct fuse_getattr_in { + uint32_t getattr_flags; + uint32_t dummy; + uint64_t fh; +}; + +#define FUSE_COMPAT_ATTR_OUT_SIZE 96 + +struct fuse_attr_out { + uint64_t attr_valid; /* Cache timeout for the attributes */ + uint32_t attr_valid_nsec; + uint32_t dummy; + struct fuse_attr attr; +}; + +struct fuse_statx_in { + uint32_t getattr_flags; + uint32_t reserved; + uint64_t fh; + uint32_t sx_flags; + uint32_t sx_mask; +}; + +struct fuse_statx_out { + uint64_t attr_valid; /* Cache timeout for the attributes */ + uint32_t attr_valid_nsec; + uint32_t flags; + uint64_t spare[2]; + struct fuse_statx stat; +}; + +#define FUSE_COMPAT_MKNOD_IN_SIZE 8 + +struct fuse_mknod_in { + uint32_t mode; + uint32_t rdev; + uint32_t umask; + uint32_t padding; +}; + +struct fuse_mkdir_in { + uint32_t mode; + uint32_t umask; +}; + +struct fuse_rename_in { + uint64_t newdir; +}; + +struct fuse_rename2_in { + uint64_t newdir; + uint32_t flags; + uint32_t padding; +}; + +struct fuse_link_in { + uint64_t oldnodeid; +}; + +struct fuse_setattr_in { + uint32_t valid; + uint32_t padding; + uint64_t fh; + uint64_t size; + uint64_t lock_owner; + uint64_t atime; + uint64_t mtime; + uint64_t ctime; + uint32_t atimensec; + uint32_t mtimensec; + uint32_t ctimensec; + uint32_t mode; + uint32_t unused4; + uint32_t uid; + uint32_t gid; + uint32_t unused5; +}; + +struct fuse_open_in { + uint32_t flags; + uint32_t open_flags; /* FUSE_OPEN_... */ +}; + +struct fuse_create_in { + uint32_t flags; + uint32_t mode; + uint32_t umask; + uint32_t open_flags; /* FUSE_OPEN_... */ +}; + +struct fuse_open_out { + uint64_t fh; + uint32_t open_flags; + int32_t backing_id; +}; + +struct fuse_release_in { + uint64_t fh; + uint32_t flags; + uint32_t release_flags; + uint64_t lock_owner; +}; + +struct fuse_flush_in { + uint64_t fh; + uint32_t unused; + uint32_t padding; + uint64_t lock_owner; +}; + +struct fuse_read_in { + uint64_t fh; + uint64_t offset; + uint32_t size; + uint32_t read_flags; + uint64_t lock_owner; + uint32_t flags; + uint32_t padding; +}; + +#define FUSE_COMPAT_WRITE_IN_SIZE 24 + +struct fuse_write_in { + uint64_t fh; + uint64_t offset; + uint32_t size; + uint32_t write_flags; + uint64_t lock_owner; + uint32_t flags; + uint32_t padding; +}; + +struct fuse_write_out { + uint32_t size; + uint32_t padding; +}; + +#define FUSE_COMPAT_STATFS_SIZE 48 + +struct fuse_statfs_out { + struct fuse_kstatfs st; +}; + +struct fuse_fsync_in { + uint64_t fh; + uint32_t fsync_flags; + uint32_t padding; +}; + +#define FUSE_COMPAT_SETXATTR_IN_SIZE 8 + +struct fuse_setxattr_in { + uint32_t size; + uint32_t flags; + uint32_t setxattr_flags; + uint32_t padding; +}; + +struct fuse_getxattr_in { + uint32_t size; + uint32_t padding; +}; + +struct fuse_getxattr_out { + uint32_t size; + uint32_t padding; +}; + +struct fuse_lk_in { + uint64_t fh; + uint64_t owner; + struct fuse_file_lock lk; + uint32_t lk_flags; + uint32_t padding; +}; + +struct fuse_lk_out { + struct fuse_file_lock lk; +}; + +struct fuse_access_in { + uint32_t mask; + uint32_t padding; +}; + +struct fuse_init_in { + uint32_t major; + uint32_t minor; + uint32_t max_readahead; + uint32_t flags; + uint32_t flags2; + uint32_t unused[11]; +}; + +#define FUSE_COMPAT_INIT_OUT_SIZE 8 +#define FUSE_COMPAT_22_INIT_OUT_SIZE 24 + +struct fuse_init_out { + uint32_t major; + uint32_t minor; + uint32_t max_readahead; + uint32_t flags; + uint16_t max_background; + uint16_t congestion_threshold; + uint32_t max_write; + uint32_t time_gran; + uint16_t max_pages; + uint16_t map_alignment; + uint32_t flags2; + uint32_t max_stack_depth; + uint16_t request_timeout; + uint16_t unused[11]; +}; + +#define CUSE_INIT_INFO_MAX 4096 + +struct cuse_init_in { + uint32_t major; + uint32_t minor; + uint32_t unused; + uint32_t flags; +}; + +struct cuse_init_out { + uint32_t major; + uint32_t minor; + uint32_t unused; + uint32_t flags; + uint32_t max_read; + uint32_t max_write; + uint32_t dev_major; /* chardev major */ + uint32_t dev_minor; /* chardev minor */ + uint32_t spare[10]; +}; + +struct fuse_interrupt_in { + uint64_t unique; +}; + +struct fuse_bmap_in { + uint64_t block; + uint32_t blocksize; + uint32_t padding; +}; + +struct fuse_bmap_out { + uint64_t block; +}; + +struct fuse_ioctl_in { + uint64_t fh; + uint32_t flags; + uint32_t cmd; + uint64_t arg; + uint32_t in_size; + uint32_t out_size; +}; + +struct fuse_ioctl_iovec { + uint64_t base; + uint64_t len; +}; + +struct fuse_ioctl_out { + int32_t result; + uint32_t flags; + uint32_t in_iovs; + uint32_t out_iovs; +}; + +struct fuse_poll_in { + uint64_t fh; + uint64_t kh; + uint32_t flags; + uint32_t events; +}; + +struct fuse_poll_out { + uint32_t revents; + uint32_t padding; +}; + +struct fuse_notify_poll_wakeup_out { + uint64_t kh; +}; + +struct fuse_fallocate_in { + uint64_t fh; + uint64_t offset; + uint64_t length; + uint32_t mode; + uint32_t padding; +}; + +/** + * FUSE request unique ID flag + * + * Indicates whether this is a resend request. The receiver should handle this + * request accordingly. + */ +#define FUSE_UNIQUE_RESEND (1ULL << 63) + +/** + * This value will be set by the kernel to + * (struct fuse_in_header).{uid,gid} fields in + * case when: + * - fuse daemon enabled FUSE_ALLOW_IDMAP + * - idmapping information is not available and uid/gid + * can not be mapped in accordance with an idmapping. + * + * Note: an idmapping information always available + * for inode creation operations like: + * FUSE_MKNOD, FUSE_SYMLINK, FUSE_MKDIR, FUSE_TMPFILE, + * FUSE_CREATE and FUSE_RENAME2 (with RENAME_WHITEOUT). + */ +#define FUSE_INVALID_UIDGID ((uint32_t)(-1)) + +struct fuse_in_header { + uint32_t len; + uint32_t opcode; + uint64_t unique; + uint64_t nodeid; + uint32_t uid; + uint32_t gid; + uint32_t pid; + uint16_t total_extlen; /* length of extensions in 8byte units */ + uint16_t padding; +}; + +struct fuse_out_header { + uint32_t len; + int32_t error; + uint64_t unique; +}; + +struct fuse_dirent { + uint64_t ino; + uint64_t off; + uint32_t namelen; + uint32_t type; + char name[]; +}; + +/* Align variable length records to 64bit boundary */ +#define FUSE_REC_ALIGN(x) \ + (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1)) + +#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name) +#define FUSE_DIRENT_ALIGN(x) FUSE_REC_ALIGN(x) +#define FUSE_DIRENT_SIZE(d) \ + FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen) + +struct fuse_direntplus { + struct fuse_entry_out entry_out; + struct fuse_dirent dirent; +}; + +#define FUSE_NAME_OFFSET_DIRENTPLUS \ + offsetof(struct fuse_direntplus, dirent.name) +#define FUSE_DIRENTPLUS_SIZE(d) \ + FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen) + +struct fuse_notify_inval_inode_out { + uint64_t ino; + int64_t off; + int64_t len; +}; + +struct fuse_notify_inval_entry_out { + uint64_t parent; + uint32_t namelen; + uint32_t flags; +}; + +struct fuse_notify_delete_out { + uint64_t parent; + uint64_t child; + uint32_t namelen; + uint32_t padding; +}; + +struct fuse_notify_store_out { + uint64_t nodeid; + uint64_t offset; + uint32_t size; + uint32_t padding; +}; + +struct fuse_notify_retrieve_out { + uint64_t notify_unique; + uint64_t nodeid; + uint64_t offset; + uint32_t size; + uint32_t padding; +}; + +/* Matches the size of fuse_write_in */ +struct fuse_notify_retrieve_in { + uint64_t dummy1; + uint64_t offset; + uint32_t size; + uint32_t dummy2; + uint64_t dummy3; + uint64_t dummy4; +}; + +struct fuse_backing_map { + int32_t fd; + uint32_t flags; + uint64_t padding; +}; + +/* Device ioctls: */ +#define FUSE_DEV_IOC_MAGIC 229 +#define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t) +#define FUSE_DEV_IOC_BACKING_OPEN _IOW(FUSE_DEV_IOC_MAGIC, 1, \ + struct fuse_backing_map) +#define FUSE_DEV_IOC_BACKING_CLOSE _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t) + +struct fuse_lseek_in { + uint64_t fh; + uint64_t offset; + uint32_t whence; + uint32_t padding; +}; + +struct fuse_lseek_out { + uint64_t offset; +}; + +struct fuse_copy_file_range_in { + uint64_t fh_in; + uint64_t off_in; + uint64_t nodeid_out; + uint64_t fh_out; + uint64_t off_out; + uint64_t len; + uint64_t flags; +}; + +/* For FUSE_COPY_FILE_RANGE_64 */ +struct fuse_copy_file_range_out { + uint64_t bytes_copied; +}; + +#define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0) +#define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1) +struct fuse_setupmapping_in { + /* An already open handle */ + uint64_t fh; + /* Offset into the file to start the mapping */ + uint64_t foffset; + /* Length of mapping required */ + uint64_t len; + /* Flags, FUSE_SETUPMAPPING_FLAG_* */ + uint64_t flags; + /* Offset in Memory Window */ + uint64_t moffset; +}; + +struct fuse_removemapping_in { + /* number of fuse_removemapping_one follows */ + uint32_t count; +}; + +struct fuse_removemapping_one { + /* Offset into the dax window start the unmapping */ + uint64_t moffset; + /* Length of mapping required */ + uint64_t len; +}; + +#define FUSE_REMOVEMAPPING_MAX_ENTRY \ + (PAGE_SIZE / sizeof(struct fuse_removemapping_one)) + +struct fuse_syncfs_in { + uint64_t padding; +}; + +/* + * For each security context, send fuse_secctx with size of security context + * fuse_secctx will be followed by security context name and this in turn + * will be followed by actual context label. + * fuse_secctx, name, context + */ +struct fuse_secctx { + uint32_t size; + uint32_t padding; +}; + +/* + * Contains the information about how many fuse_secctx structures are being + * sent and what's the total size of all security contexts (including + * size of fuse_secctx_header). + * + */ +struct fuse_secctx_header { + uint32_t size; + uint32_t nr_secctx; +}; + +/** + * struct fuse_ext_header - extension header + * @size: total size of this extension including this header + * @type: type of extension + * + * This is made compatible with fuse_secctx_header by using type values > + * FUSE_MAX_NR_SECCTX + */ +struct fuse_ext_header { + uint32_t size; + uint32_t type; +}; + +/** + * struct fuse_supp_groups - Supplementary group extension + * @nr_groups: number of supplementary groups + * @groups: flexible array of group IDs + */ +struct fuse_supp_groups { + uint32_t nr_groups; + uint32_t groups[]; +}; + +/** + * Size of the ring buffer header + */ +#define FUSE_URING_IN_OUT_HEADER_SZ 128 +#define FUSE_URING_OP_IN_OUT_SZ 128 + +/* Used as part of the fuse_uring_req_header */ +struct fuse_uring_ent_in_out { + uint64_t flags; + + /* + * commit ID to be used in a reply to a ring request (see also + * struct fuse_uring_cmd_req) + */ + uint64_t commit_id; + + /* size of user payload buffer */ + uint32_t payload_sz; + uint32_t padding; + + uint64_t reserved; +}; + +/** + * Header for all fuse-io-uring requests + */ +struct fuse_uring_req_header { + /* struct fuse_in_header / struct fuse_out_header */ + char in_out[FUSE_URING_IN_OUT_HEADER_SZ]; + + /* per op code header */ + char op_in[FUSE_URING_OP_IN_OUT_SZ]; + + struct fuse_uring_ent_in_out ring_ent_in_out; +}; + +/** + * sqe commands to the kernel + */ +enum fuse_uring_cmd { + FUSE_IO_URING_CMD_INVALID = 0, + + /* register the request buffer and fetch a fuse request */ + FUSE_IO_URING_CMD_REGISTER = 1, + + /* commit fuse request result and fetch next request */ + FUSE_IO_URING_CMD_COMMIT_AND_FETCH = 2, +}; + +/** + * In the 80B command area of the SQE. + */ +struct fuse_uring_cmd_req { + uint64_t flags; + + /* entry identifier for commits */ + uint64_t commit_id; + + /* queue the command is for (queue index) */ + uint16_t qid; + uint8_t padding[6]; +}; + +#endif /* _LINUX_FUSE_H */ diff --git a/include/fuse_log.h b/include/fuse_log.h new file mode 100644 index 0000000..7948bab --- /dev/null +++ b/include/fuse_log.h @@ -0,0 +1,95 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2019 Red Hat, Inc. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#ifndef FUSE_LOG_H_ +#define FUSE_LOG_H_ + +/** @file + * + * This file defines the logging interface of FUSE + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Log severity level + * + * These levels correspond to syslog(2) log levels since they are widely used. + */ +enum fuse_log_level { + FUSE_LOG_EMERG, + FUSE_LOG_ALERT, + FUSE_LOG_CRIT, + FUSE_LOG_ERR, + FUSE_LOG_WARNING, + FUSE_LOG_NOTICE, + FUSE_LOG_INFO, + FUSE_LOG_DEBUG +}; + +/** + * Log message handler function. + * + * This function must be thread-safe. It may be called from any libfuse + * function, including fuse_parse_cmdline() and other functions invoked before + * a FUSE filesystem is created. + * + * Install a custom log message handler function using fuse_set_log_func(). + * + * @param level log severity level + * @param fmt sprintf-style format string including newline + * @param ap format string arguments + */ +typedef void (*fuse_log_func_t)(enum fuse_log_level level, + const char *fmt, va_list ap); + +/** + * Install a custom log handler function. + * + * Log messages are emitted by libfuse functions to report errors and debug + * information. Messages are printed to stderr by default but this can be + * overridden by installing a custom log message handler function. + * + * The log message handler function is global and affects all FUSE filesystems + * created within this process. + * + * @param func a custom log message handler function or NULL to revert to + * the default + */ +void fuse_set_log_func(fuse_log_func_t func); + +/** + * Emit a log message + * + * @param level severity level (FUSE_LOG_ERR, FUSE_LOG_DEBUG, etc) + * @param fmt sprintf-style format string including newline + */ +void fuse_log(enum fuse_log_level level, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); + +/** + * Switch default log handler from stderr to syslog + * + * Passed options are according to 'man 3 openlog' + */ +void fuse_log_enable_syslog(const char *ident, int option, int facility); + +/** + * To be called at teardown to close syslog. +*/ +void fuse_log_close_syslog(void); + +#ifdef __cplusplus +} +#endif + +#endif /* FUSE_LOG_H_ */ diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h new file mode 100644 index 0000000..6be0295 --- /dev/null +++ b/include/fuse_lowlevel.h @@ -0,0 +1,2391 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#ifndef FUSE_LOWLEVEL_H_ +#define FUSE_LOWLEVEL_H_ + +/** @file + * + * Low level API + * + * IMPORTANT: you should define FUSE_USE_VERSION before including this + * header. To use the newest API define it to 35 (recommended for any + * new application). + */ + +#ifndef FUSE_USE_VERSION +#error FUSE_USE_VERSION not defined +#endif + +#include "fuse_common.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----------------------------------------------------------- * + * Miscellaneous definitions * + * ----------------------------------------------------------- */ + +/** The node ID of the root inode */ +#define FUSE_ROOT_ID 1 + +/** Inode number type */ +typedef uint64_t fuse_ino_t; + +/** Request pointer type */ +typedef struct fuse_req *fuse_req_t; + +/* Forward declaration */ +struct statx; + +/** + * Session + * + * This provides hooks for processing requests, and exiting + */ +struct fuse_session; + +/** Directory entry parameters supplied to fuse_reply_entry() */ +struct fuse_entry_param { + /** Unique inode number + * + * In lookup, zero means negative entry (from version 2.5) + * Returning ENOENT also means negative entry, but by setting zero + * ino the kernel may cache negative entries for entry_timeout + * seconds. + */ + fuse_ino_t ino; + + /** Generation number for this entry. + * + * If the file system will be exported over NFS, the + * ino/generation pairs need to be unique over the file + * system's lifetime (rather than just the mount time). So if + * the file system reuses an inode after it has been deleted, + * it must assign a new, previously unused generation number + * to the inode at the same time. + * + */ + uint64_t generation; + + /** Inode attributes. + * + * Even if attr_timeout == 0, attr must be correct. For example, + * for open(), FUSE uses attr.st_size from lookup() to determine + * how many bytes to request. If this value is not correct, + * incorrect data will be returned. + */ + struct stat attr; + + /** Validity timeout (in seconds) for inode attributes. If + attributes only change as a result of requests that come + through the kernel, this should be set to a very large + value. */ + double attr_timeout; + + /** Validity timeout (in seconds) for the name. If directory + entries are changed/deleted only as a result of requests + that come through the kernel, this should be set to a very + large value. */ + double entry_timeout; +}; + +/** + * Additional context associated with requests. + * + * Note that the reported client uid, gid and pid may be zero in some + * situations. For example, if the FUSE file system is running in a + * PID or user namespace but then accessed from outside the namespace, + * there is no valid uid/pid/gid that could be reported. + */ +struct fuse_ctx { + /** User ID of the calling process */ + uid_t uid; + + /** Group ID of the calling process */ + gid_t gid; + + /** Thread ID of the calling process */ + pid_t pid; + + /** Umask of the calling process */ + mode_t umask; +}; + +struct fuse_forget_data { + fuse_ino_t ino; + uint64_t nlookup; +}; + +struct fuse_custom_io { + ssize_t (*writev)(int fd, struct iovec *iov, int count, void *userdata); + ssize_t (*read)(int fd, void *buf, size_t buf_len, void *userdata); + ssize_t (*splice_receive)(int fdin, off_t *offin, int fdout, + off_t *offout, size_t len, + unsigned int flags, void *userdata); + ssize_t (*splice_send)(int fdin, off_t *offin, int fdout, + off_t *offout, size_t len, + unsigned int flags, void *userdata); + int (*clone_fd)(int master_fd); +}; + +/** + * Flags for fuse_lowlevel_notify_entry() + * 0 = invalidate entry + * FUSE_LL_EXPIRE_ONLY = expire entry +*/ +enum fuse_notify_entry_flags { + FUSE_LL_INVALIDATE = 0, + FUSE_LL_EXPIRE_ONLY = (1 << 0), +}; + +/* 'to_set' flags in setattr */ +#define FUSE_SET_ATTR_MODE (1 << 0) +#define FUSE_SET_ATTR_UID (1 << 1) +#define FUSE_SET_ATTR_GID (1 << 2) +#define FUSE_SET_ATTR_SIZE (1 << 3) +#define FUSE_SET_ATTR_ATIME (1 << 4) +#define FUSE_SET_ATTR_MTIME (1 << 5) +#define FUSE_SET_ATTR_ATIME_NOW (1 << 7) +#define FUSE_SET_ATTR_MTIME_NOW (1 << 8) +#define FUSE_SET_ATTR_FORCE (1 << 9) +#define FUSE_SET_ATTR_CTIME (1 << 10) +#define FUSE_SET_ATTR_KILL_SUID (1 << 11) +#define FUSE_SET_ATTR_KILL_SGID (1 << 12) +#define FUSE_SET_ATTR_FILE (1 << 13) +#define FUSE_SET_ATTR_KILL_PRIV (1 << 14) +#define FUSE_SET_ATTR_OPEN (1 << 15) +#define FUSE_SET_ATTR_TIMES_SET (1 << 16) +#define FUSE_SET_ATTR_TOUCH (1 << 17) + +/* ----------------------------------------------------------- * + * Request methods and replies * + * ----------------------------------------------------------- */ + +/** + * Low level filesystem operations + * + * Most of the methods (with the exception of init and destroy) + * receive a request handle (fuse_req_t) as their first argument. + * This handle must be passed to one of the specified reply functions. + * + * This may be done inside the method invocation, or after the call + * has returned. The request handle is valid until one of the reply + * functions is called. + * + * Other pointer arguments (name, fuse_file_info, etc) are not valid + * after the call has returned, so if they are needed later, their + * contents have to be copied. + * + * In general, all methods are expected to perform any necessary + * permission checking. However, a filesystem may delegate this task + * to the kernel by passing the `default_permissions` mount option to + * `fuse_session_new()`. In this case, methods will only be called if + * the kernel's permission check has succeeded. + * + * It is generally not really necessary to check the fuse_reply_* return + * values for errors, as any error in sending a reply indicates an + * unrecoverable problem with the kernel fuse connection, which will also + * terminate the session loop anyway. + * + * This data structure is ABI sensitive, on adding new functions these need to + * be appended at the end of the struct + */ +struct fuse_lowlevel_ops { + /** + * Initialize filesystem + * + * This function is called when libfuse establishes + * communication with the FUSE kernel module. The file system + * should use this module to inspect and/or modify the + * connection parameters provided in the `conn` structure. + * + * Note that some parameters may be overwritten by options + * passed to fuse_session_new() which take precedence over the + * values set in this handler. + * + * There's no reply to this function + * + * @param userdata the user data passed to fuse_session_new() + */ + void (*init) (void *userdata, struct fuse_conn_info *conn); + + /** + * Clean up filesystem. + * + * Called on filesystem exit. When this method is called, the + * connection to the kernel may be gone already, so that eg. calls + * to fuse_lowlevel_notify_* will fail. + * + * There's no reply to this function + * + * @param userdata the user data passed to fuse_session_new() + */ + void (*destroy) (void *userdata); + + /** + * Look up a directory entry by name and get its attributes. + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name the name to look up + */ + void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name); + + /** + * Forget about an inode + * + * This function is called when the kernel removes an inode + * from its internal caches. + * + * The inode's lookup count increases by one for every call to + * fuse_reply_entry and fuse_reply_create. The nlookup parameter + * indicates by how much the lookup count should be decreased. + * + * Inodes with a non-zero lookup count may receive request from + * the kernel even after calls to unlink, rmdir or (when + * overwriting an existing file) rename. Filesystems must handle + * such requests properly and it is recommended to defer removal + * of the inode until the lookup count reaches zero. Calls to + * unlink, rmdir or rename will be followed closely by forget + * unless the file or directory is open, in which case the + * kernel issues forget only after the release or releasedir + * calls. + * + * Note that if a file system will be exported over NFS the + * inodes lifetime must extend even beyond forget. See the + * generation field in struct fuse_entry_param above. + * + * On unmount the lookup count for all inodes implicitly drops + * to zero. It is not guaranteed that the file system will + * receive corresponding forget messages for the affected + * inodes. + * + * Valid replies: + * fuse_reply_none + * + * @param req request handle + * @param ino the inode number + * @param nlookup the number of lookups to forget + */ + void (*forget) (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup); + + /** + * Get file attributes. + * + * If writeback caching is enabled, the kernel may have a + * better idea of a file's length than the FUSE file system + * (eg if there has been a write that extended the file size, + * but that has not yet been passed to the filesystem. + * + * In this case, the st_size value provided by the file system + * will be ignored. + * + * Valid replies: + * fuse_reply_attr + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information, or NULL + */ + void (*getattr) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Set file attributes + * + * In the 'attr' argument only members indicated by the 'to_set' + * bitmask contain valid values. Other members contain undefined + * values. + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits if the file + * size or owner is being changed. + * + * This method will not be called to update st_atime or st_ctime implicitly + * (eg. after a read() request), and only be called to implicitly update st_mtime + * if writeback caching is active. It is the filesystem's responsibility to update + * these timestamps when needed, and (if desired) to implement mount options like + * `noatime` or `relatime`. + * + * If the setattr was invoked from the ftruncate() system call + * under Linux kernel versions 2.6.15 or later, the fi->fh will + * contain the value set by the open method or will be undefined + * if the open method didn't set any value. Otherwise (not + * ftruncate call, or kernel version earlier than 2.6.15) the fi + * parameter will be NULL. + * + * Valid replies: + * fuse_reply_attr + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param attr the attributes + * @param to_set bit mask of attributes which should be set + * @param fi file information, or NULL + */ + void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int to_set, struct fuse_file_info *fi); + + /** + * Read symbolic link + * + * Valid replies: + * fuse_reply_readlink + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + */ + void (*readlink) (fuse_req_t req, fuse_ino_t ino); + + /** + * Create file node + * + * Create a regular file, character device, block device, fifo or + * socket node. + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to create + * @param mode file type and mode with which to create the new file + * @param rdev the device number (only valid if created file is a device) + */ + void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, dev_t rdev); + + /** + * Create a directory + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to create + * @param mode with which to create the new file + */ + void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode); + + /** + * Remove a file + * + * If the file's inode's lookup count is non-zero, the file + * system is expected to postpone any removal of the inode + * until the lookup count reaches zero (see description of the + * forget function). + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to remove + */ + void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name); + + /** + * Remove a directory + * + * If the directory's inode's lookup count is non-zero, the + * file system is expected to postpone any removal of the + * inode until the lookup count reaches zero (see description + * of the forget function). + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to remove + */ + void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name); + + /** + * Create a symbolic link + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param link the contents of the symbolic link + * @param parent inode number of the parent directory + * @param name to create + */ + void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent, + const char *name); + + /** Rename a file + * + * If the target exists it should be atomically replaced. If + * the target's inode's lookup count is non-zero, the file + * system is expected to postpone any removal of the inode + * until the lookup count reaches zero (see description of the + * forget function). + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EINVAL, i.e. all + * future rename requests will fail with EINVAL without being + * send to the filesystem process. + * + * *flags* may be `RENAME_EXCHANGE` or `RENAME_NOREPLACE`. If + * RENAME_NOREPLACE is specified, the filesystem must not + * overwrite *newname* if it exists and return an error + * instead. If `RENAME_EXCHANGE` is specified, the filesystem + * must atomically exchange the two files, i.e. both must + * exist and neither may be deleted. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the old parent directory + * @param name old name + * @param newparent inode number of the new parent directory + * @param newname new name + */ + void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name, + fuse_ino_t newparent, const char *newname, + unsigned int flags); + + /** + * Create a hard link + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param ino the old inode number + * @param newparent inode number of the new parent directory + * @param newname new name to create + */ + void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, + const char *newname); + + /** + * Open a file + * + * Open flags are available in fi->flags. The following rules + * apply. + * + * - Creation (O_CREAT, O_EXCL, O_NOCTTY) flags will be + * filtered out / handled by the kernel. + * + * - Access modes (O_RDONLY, O_WRONLY, O_RDWR) should be used + * by the filesystem to check if the operation is + * permitted. If the ``-o default_permissions`` mount + * option is given, this check is already done by the + * kernel before calling open() and may thus be omitted by + * the filesystem. + * + * - When writeback caching is enabled, the kernel may send + * read requests even for files opened with O_WRONLY. The + * filesystem should be prepared to handle this. + * + * - When writeback caching is disabled, the filesystem is + * expected to properly handle the O_APPEND flag and ensure + * that each write is appending to the end of the file. + * + * - When writeback caching is enabled, the kernel will + * handle O_APPEND. However, unless all changes to the file + * come through the kernel this will not work reliably. The + * filesystem should thus either ignore the O_APPEND flag + * (and let the kernel handle it), or return an error + * (indicating that reliably O_APPEND is not available). + * + * Filesystem may store an arbitrary file handle (pointer, + * index, etc) in fi->fh, and use this in other all other file + * operations (read, write, flush, release, fsync). + * + * Filesystem may also implement stateless file I/O and not store + * anything in fi->fh. + * + * There are also some flags (direct_io, keep_cache) which the + * filesystem may set in fi, to change the way the file is opened. + * See fuse_file_info structure in for more details. + * + * If this request is answered with an error code of ENOSYS + * and FUSE_CAP_NO_OPEN_SUPPORT is set in + * `fuse_conn_info.capable`, this is treated as success and + * future calls to open and release will also succeed without being + * sent to the filesystem process. + * + * To get this behavior without providing an opendir handler, you may + * set FUSE_CAP_NO_OPEN_SUPPORT in `fuse_conn_info.want` on supported + * kernels to automatically get the zero message open(). + * + * If this callback is not provided and FUSE_CAP_NO_OPEN_SUPPORT is not + * set in `fuse_conn_info.want` then an empty reply will be sent. + * + * Valid replies: + * fuse_reply_open + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + */ + void (*open) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Read data + * + * Read should send exactly the number of bytes requested except + * on EOF or error, otherwise the rest of the data will be + * substituted with zeroes. An exception to this is when the file + * has been opened in 'direct_io' mode, in which case the return + * value of the read system call will reflect the return value of + * this operation. + * + * fi->fh will contain the value set by the open method, or will + * be undefined if the open method didn't set any value. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_iov + * fuse_reply_data + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param size number of bytes to read + * @param off offset to read from + * @param fi file information + */ + void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *fi); + + /** + * Write data + * + * Write should return exactly the number of bytes requested + * except on error. An exception to this is when the file has + * been opened in 'direct_io' mode, in which case the return value + * of the write system call will reflect the return value of this + * operation. + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + * + * fi->fh will contain the value set by the open method, or will + * be undefined if the open method didn't set any value. + * + * Valid replies: + * fuse_reply_write + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param buf data to write + * @param size number of bytes to write + * @param off offset to write to + * @param fi file information + */ + void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf, + size_t size, off_t off, struct fuse_file_info *fi); + + /** + * Flush method + * + * This is called on each close() of the opened file. + * + * Since file descriptors can be duplicated (dup, dup2, fork), for + * one open call there may be many flush calls. + * + * Filesystems shouldn't assume that flush will always be called + * after some writes, or that if will be called at all. + * + * fi->fh will contain the value set by the open method, or will + * be undefined if the open method didn't set any value. + * + * NOTE: the name of the method is misleading, since (unlike + * fsync) the filesystem is not forced to flush pending writes. + * One reason to flush data is if the filesystem wants to return + * write errors during close. However, such use is non-portable + * because POSIX does not require [close] to wait for delayed I/O to + * complete. + * + * If the filesystem supports file locking operations (setlk, + * getlk) it should remove all locks belonging to 'fi->owner'. + * + * If this request is answered with an error code of ENOSYS, + * this is treated as success and future calls to flush() will + * succeed automatically without being send to the filesystem + * process. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + * + * [close]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html + */ + void (*flush) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Release an open file + * + * Release is called when there are no more references to an open + * file: all file descriptors are closed and all memory mappings + * are unmapped. + * + * For every open call there will be exactly one release call (unless + * the filesystem is force-unmounted). + * + * The filesystem may reply with an error, but error values are + * not returned to close() or munmap() which triggered the + * release. + * + * fi->fh will contain the value set by the open method, or will + * be undefined if the open method didn't set any value. + * fi->flags will contain the same flags as for open. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + */ + void (*release) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Synchronize file contents + * + * If the datasync parameter is non-zero, then only the user data + * should be flushed, not the meta data. + * + * If this request is answered with an error code of ENOSYS, + * this is treated as success and future calls to fsync() will + * succeed automatically without being send to the filesystem + * process. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param datasync flag indicating if only data should be flushed + * @param fi file information + */ + void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi); + + /** + * Open a directory + * + * Filesystem may store an arbitrary file handle (pointer, index, + * etc) in fi->fh, and use this in other all other directory + * stream operations (readdir, releasedir, fsyncdir). + * + * If this request is answered with an error code of ENOSYS and + * FUSE_CAP_NO_OPENDIR_SUPPORT is set in `fuse_conn_info.capable`, + * this is treated as success and future calls to opendir and + * releasedir will also succeed without being sent to the filesystem + * process. In addition, the kernel will cache readdir results + * as if opendir returned FOPEN_KEEP_CACHE | FOPEN_CACHE_DIR. + * + * To get this behavior without providing an opendir handler, you may + * set FUSE_CAP_NO_OPENDIR_SUPPORT in `fuse_conn_info.want` on supported + * kernels to automatically get the zero message opendir(). + * + * If this callback is not provided and FUSE_CAP_NO_OPENDIR_SUPPORT is + * not set in `fuse_conn_info.want` then an empty reply will be sent. + * + * Valid replies: + * fuse_reply_open + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + */ + void (*opendir) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Read directory + * + * Send a buffer filled using fuse_add_direntry(), with size not + * exceeding the requested size. Send an empty buffer on end of + * stream. + * + * fi->fh will contain the value set by the opendir method, or + * will be undefined if the opendir method didn't set any value. + * + * Returning a directory entry from readdir() does not affect + * its lookup count. + * + * If off_t is non-zero, then it will correspond to one of the off_t + * values that was previously returned by readdir() for the same + * directory handle. In this case, readdir() should skip over entries + * coming before the position defined by the off_t value. If entries + * are added or removed while the directory handle is open, the filesystem + * may still include the entries that have been removed, and may not + * report the entries that have been created. However, addition or + * removal of entries must never cause readdir() to skip over unrelated + * entries or to report them more than once. This means + * that off_t can not be a simple index that enumerates the entries + * that have been returned but must contain sufficient information to + * uniquely determine the next directory entry to return even when the + * set of entries is changing. + * + * The function does not have to report the '.' and '..' + * entries, but is allowed to do so. Note that, if readdir does + * not return '.' or '..', they will not be implicitly returned, + * and this behavior is observable by the caller. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_data + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param size maximum number of bytes to send + * @param off offset to continue reading the directory stream + * @param fi file information + */ + void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *fi); + + /** + * Release an open directory + * + * For every opendir call there will be exactly one releasedir + * call (unless the filesystem is force-unmounted). + * + * fi->fh will contain the value set by the opendir method, or + * will be undefined if the opendir method didn't set any value. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + */ + void (*releasedir) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Synchronize directory contents + * + * If the datasync parameter is non-zero, then only the directory + * contents should be flushed, not the meta data. + * + * fi->fh will contain the value set by the opendir method, or + * will be undefined if the opendir method didn't set any value. + * + * If this request is answered with an error code of ENOSYS, + * this is treated as success and future calls to fsyncdir() will + * succeed automatically without being send to the filesystem + * process. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param datasync flag indicating if only data should be flushed + * @param fi file information + */ + void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi); + + /** + * Get file system statistics + * + * Valid replies: + * fuse_reply_statfs + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number, zero means "undefined" + */ + void (*statfs) (fuse_req_t req, fuse_ino_t ino); + + /** + * Set an extended attribute + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EOPNOTSUPP, i.e. all + * future setxattr() requests will fail with EOPNOTSUPP without being + * send to the filesystem process. + * + * Valid replies: + * fuse_reply_err + */ + void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags); + + /** + * Get an extended attribute + * + * If size is zero, the size of the value should be sent with + * fuse_reply_xattr. + * + * If the size is non-zero, and the value fits in the buffer, the + * value should be sent with fuse_reply_buf. + * + * If the size is too small for the value, the ERANGE error should + * be sent. + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EOPNOTSUPP, i.e. all + * future getxattr() requests will fail with EOPNOTSUPP without being + * send to the filesystem process. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_data + * fuse_reply_xattr + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param name of the extended attribute + * @param size maximum size of the value to send + */ + void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size); + + /** + * List extended attribute names + * + * If size is zero, the total size of the attribute list should be + * sent with fuse_reply_xattr. + * + * If the size is non-zero, and the null character separated + * attribute list fits in the buffer, the list should be sent with + * fuse_reply_buf. + * + * If the size is too small for the list, the ERANGE error should + * be sent. + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EOPNOTSUPP, i.e. all + * future listxattr() requests will fail with EOPNOTSUPP without being + * send to the filesystem process. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_data + * fuse_reply_xattr + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param size maximum size of the list to send + */ + void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size); + + /** + * Remove an extended attribute + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EOPNOTSUPP, i.e. all + * future removexattr() requests will fail with EOPNOTSUPP without being + * send to the filesystem process. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param name of the extended attribute + */ + void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name); + + /** + * Check file access permissions + * + * This will be called for the access() and chdir() system + * calls. If the 'default_permissions' mount option is given, + * this method is not called. + * + * This method is not called under Linux kernel versions 2.4.x + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent success, i.e. this and all future access() + * requests will succeed without being send to the filesystem process. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param mask requested access mode + */ + void (*access) (fuse_req_t req, fuse_ino_t ino, int mask); + + /** + * Create and open a file + * + * If the file does not exist, first create it with the specified + * mode, and then open it. + * + * See the description of the open handler for more + * information. + * + * If this method is not implemented or under Linux kernel + * versions earlier than 2.6.15, the mknod() and open() methods + * will be called instead. + * + * If this request is answered with an error code of ENOSYS, the handler + * is treated as not implemented (i.e., for this and future requests the + * mknod() and open() handlers will be called instead). + * + * Valid replies: + * fuse_reply_create + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to create + * @param mode file type and mode with which to create the new file + * @param fi file information + */ + void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, struct fuse_file_info *fi); + + /** + * Test for a POSIX file lock + * + * Valid replies: + * fuse_reply_lock + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + * @param lock the region/type to test + */ + void (*getlk) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct flock *lock); + + /** + * Acquire, modify or release a POSIX file lock + * + * For POSIX threads (NPTL) there's a 1-1 relation between pid and + * owner, but otherwise this is not always the case. For checking + * lock ownership, 'fi->owner' must be used. The l_pid field in + * 'struct flock' should only be used to fill in this field in + * getlk(). + * + * Note: if the locking methods are not implemented, the kernel + * will still allow file locking to work locally. Hence these are + * only interesting for network filesystems and similar. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + * @param lock the region/type to set + * @param sleep locking operation may sleep + */ + void (*setlk) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, + struct flock *lock, int sleep); + + /** + * Map block index within file to block index within device + * + * Note: This makes sense only for block device backed filesystems + * mounted with the 'blkdev' option + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure, i.e. all future bmap() requests will + * fail with the same error code without being send to the filesystem + * process. + * + * Valid replies: + * fuse_reply_bmap + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param blocksize unit of block index + * @param idx block index within file + */ + void (*bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize, + uint64_t idx); + +#if FUSE_USE_VERSION < 35 + void (*ioctl) (fuse_req_t req, fuse_ino_t ino, int cmd, + void *arg, struct fuse_file_info *fi, unsigned flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz); +#else + /** + * Ioctl + * + * Note: For unrestricted ioctls (not allowed for FUSE + * servers), data in and out areas can be discovered by giving + * iovs and setting FUSE_IOCTL_RETRY in *flags*. For + * restricted ioctls, kernel prepares in/out data area + * according to the information encoded in cmd. + * + * Valid replies: + * fuse_reply_ioctl_retry + * fuse_reply_ioctl + * fuse_reply_ioctl_iov + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param cmd ioctl command + * @param arg ioctl argument + * @param fi file information + * @param flags for FUSE_IOCTL_* flags + * @param in_buf data fetched from the caller + * @param in_bufsz number of fetched bytes + * @param out_bufsz maximum size of output data + * + * Note : the unsigned long request submitted by the application + * is truncated to 32 bits. + */ + void (*ioctl) (fuse_req_t req, fuse_ino_t ino, unsigned int cmd, + void *arg, struct fuse_file_info *fi, unsigned flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz); +#endif + + /** + * Poll for IO readiness + * + * The client should immediately respond with fuse_reply_poll(), + * setting revents appropriately according to which events are ready. + * + * Additionally, if ph is non-NULL, the client must retain it and + * notify when all future IO readiness events occur by calling + * fuse_lowlevel_notify_poll() with the specified ph. + * + * Regardless of the number of times poll with a non-NULL ph is + * received, a single notify_poll is enough to service all. (Notifying + * more times incurs overhead but doesn't harm correctness.) Any + * additional received handles can be immediately destroyed. + * + * The callee is responsible for destroying ph with + * fuse_pollhandle_destroy() when no longer in use. + * + * If this request is answered with an error code of ENOSYS, this is + * treated as success (with a kernel-defined default poll-mask) and + * future calls to poll() will succeed the same way without being send + * to the filesystem process. + * + * Valid replies: + * fuse_reply_poll + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + * @param ph poll handle to be used for notification + */ + void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, + struct fuse_pollhandle *ph); + + /** + * Write data made available in a buffer + * + * This is a more generic version of the ->write() method. If + * FUSE_CAP_SPLICE_READ is set in fuse_conn_info.want and the + * kernel supports splicing from the fuse device, then the + * data will be made available in pipe for supporting zero + * copy data transfer. + * + * buf->count is guaranteed to be one (and thus buf->idx is + * always zero). The write_buf handler must ensure that + * bufv->off is correctly updated (reflecting the number of + * bytes read from bufv->buf[0]). + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + * + * Valid replies: + * fuse_reply_write + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param bufv buffer containing the data + * @param off offset to write to + * @param fi file information + */ + void (*write_buf) (fuse_req_t req, fuse_ino_t ino, + struct fuse_bufvec *bufv, off_t off, + struct fuse_file_info *fi); + + /** + * Callback function for the retrieve request + * + * Valid replies: + * fuse_reply_none + * + * @param req request handle + * @param cookie user data supplied to fuse_lowlevel_notify_retrieve() + * @param ino the inode number supplied to fuse_lowlevel_notify_retrieve() + * @param offset the offset supplied to fuse_lowlevel_notify_retrieve() + * @param bufv the buffer containing the returned data + */ + void (*retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino, + off_t offset, struct fuse_bufvec *bufv); + + /** + * Forget about multiple inodes + * + * See description of the forget function for more + * information. + * + * Valid replies: + * fuse_reply_none + * + * @param req request handle + */ + void (*forget_multi) (fuse_req_t req, size_t count, + struct fuse_forget_data *forgets); + + /** + * Acquire, modify or release a BSD file lock + * + * Note: if the locking methods are not implemented, the kernel + * will still allow file locking to work locally. Hence these are + * only interesting for network filesystems and similar. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + * @param op the locking operation, see flock(2) + */ + void (*flock) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, int op); + + /** + * Allocate requested space. If this function returns success then + * subsequent writes to the specified range shall not fail due to the lack + * of free space on the file system storage media. + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EOPNOTSUPP, i.e. all + * future fallocate() requests will fail with EOPNOTSUPP without being + * send to the filesystem process. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param offset starting point for allocated region + * @param length size of allocated region + * @param mode determines the operation to be performed on the given range, + * see fallocate(2) + */ + void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode, + off_t offset, off_t length, struct fuse_file_info *fi); + + /** + * Read directory with attributes + * + * Send a buffer filled using fuse_add_direntry_plus(), with size not + * exceeding the requested size. Send an empty buffer on end of + * stream. + * + * fi->fh will contain the value set by the opendir method, or + * will be undefined if the opendir method didn't set any value. + * + * In contrast to readdir() (which does not affect the lookup counts), + * the lookup count of every entry returned by readdirplus(), except "." + * and "..", is incremented by one. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_data + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param size maximum number of bytes to send + * @param off offset to continue reading the directory stream + * @param fi file information + */ + void (*readdirplus) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *fi); + + /** + * Copy a range of data from one file to another + * + * Performs an optimized copy between two file descriptors without the + * additional cost of transferring data through the FUSE kernel module + * to user space (glibc) and then back into the FUSE filesystem again. + * + * In case this method is not implemented, glibc falls back to reading + * data from the source and writing to the destination. Effectively + * doing an inefficient copy of the data. + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EOPNOTSUPP, i.e. all + * future copy_file_range() requests will fail with EOPNOTSUPP without + * being send to the filesystem process. + * + * Valid replies: + * fuse_reply_write + * fuse_reply_err + * + * @param req request handle + * @param ino_in the inode number or the source file + * @param off_in starting point from were the data should be read + * @param fi_in file information of the source file + * @param ino_out the inode number or the destination file + * @param off_out starting point where the data should be written + * @param fi_out file information of the destination file + * @param len maximum size of the data to copy + * @param flags passed along with the copy_file_range() syscall + */ + void (*copy_file_range) (fuse_req_t req, fuse_ino_t ino_in, + off_t off_in, struct fuse_file_info *fi_in, + fuse_ino_t ino_out, off_t off_out, + struct fuse_file_info *fi_out, size_t len, + int flags); + + /** + * Find next data or hole after the specified offset + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure, i.e. all future lseek() requests will + * fail with the same error code without being send to the filesystem + * process. + * + * Valid replies: + * fuse_reply_lseek + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param off offset to start search from + * @param whence either SEEK_DATA or SEEK_HOLE + * @param fi file information + */ + void (*lseek) (fuse_req_t req, fuse_ino_t ino, off_t off, int whence, + struct fuse_file_info *fi); + + /** + * Create a tempfile + * + * Tempfile means an anonymous file. It can be made into a normal file later + * by using linkat or such. + * + * If this is answered with an error ENOSYS this is treated by the kernel as + * a permanent failure and it will disable the feature and not ask again. + * + * Valid replies: + * fuse_reply_create + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param mode file type and mode with which to create the new file + * @param fi file information + */ + void (*tmpfile) (fuse_req_t req, fuse_ino_t parent, + mode_t mode, struct fuse_file_info *fi); + + /** + * Get extended file attributes. + * + * Valid replies: + * fuse_reply_statx + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param flags bitmask of requested flags + * @param mask bitmask of requested fields + * @param fi file information (may be NULL) + */ + void (*statx)(fuse_req_t req, fuse_ino_t ino, int flags, int mask, + struct fuse_file_info *fi); +}; + +/** + * Reply with an error code or success. + * + * Possible requests: + * all except forget, forget_multi, retrieve_reply + * + * Wherever possible, error codes should be chosen from the list of + * documented error conditions in the corresponding system calls + * manpage. + * + * An error code of ENOSYS is sometimes treated specially. This is + * indicated in the documentation of the affected handler functions. + * + * The following requests may be answered with a zero error code: + * unlink, rmdir, rename, flush, release, fsync, fsyncdir, setxattr, + * removexattr, setlk. + * + * @param req request handle + * @param err the positive error value, or zero for success + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_err(fuse_req_t req, int err); + +/** + * Don't send reply + * + * Possible requests: + * forget + * forget_multi + * retrieve_reply + * + * @param req request handle + */ +void fuse_reply_none(fuse_req_t req); + +/** + * Reply with a directory entry + * + * Possible requests: + * lookup, mknod, mkdir, symlink, link + * + * Side effects: + * increments the lookup count on success + * + * @param req request handle + * @param e the entry parameters + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e); + +/** + * Reply with a directory entry and open parameters + * + * currently the following members of 'fi' are used: + * fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, + * parallel_direct_writes + * + * Possible requests: + * create + * + * Side effects: + * increments the lookup count on success + * + * @param req request handle + * @param e the entry parameters + * @param fi file information + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, + const struct fuse_file_info *fi); + +/** + * Reply with attributes + * + * Possible requests: + * getattr, setattr + * + * @param req request handle + * @param attr the attributes + * @param attr_timeout validity timeout (in seconds) for the attributes + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_attr(fuse_req_t req, const struct stat *attr, + double attr_timeout); + +/** + * Reply with the contents of a symbolic link + * + * Possible requests: + * readlink + * + * @param req request handle + * @param link symbolic link contents + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_readlink(fuse_req_t req, const char *link); + +/** + * Setup passthrough backing file for open reply + * + * Currently there should be only one backing id per node / backing file. + * + * Possible requests: + * open, opendir, create + * + * @param req request handle + * @param fd backing file descriptor + * @return positive backing id for success, 0 for failure + */ +int fuse_passthrough_open(fuse_req_t req, int fd); +int fuse_passthrough_close(fuse_req_t req, int backing_id); + +/** + * Reply with open parameters + * + * currently the following members of 'fi' are used: + * fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, + * parallel_direct_writes, + * + * Possible requests: + * open, opendir + * + * @param req request handle + * @param fi file information + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi); + +/** + * Reply with number of bytes written + * + * Possible requests: + * write + * + * @param req request handle + * @param count the number of bytes written + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_write(fuse_req_t req, size_t count); + +/** + * Reply with data + * + * Possible requests: + * read, readdir, getxattr, listxattr + * + * @param req request handle + * @param buf buffer containing data + * @param size the size of data in bytes + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size); + +/** + * Reply with data copied/moved from buffer(s) + * + * Zero copy data transfer ("splicing") will be used under + * the following circumstances: + * + * 1. FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.want, and + * 2. the kernel supports splicing from the fuse device + * (FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.capable), and + * 3. *flags* does not contain FUSE_BUF_NO_SPLICE + * 4. The amount of data that is provided in file-descriptor backed + * buffers (i.e., buffers for which bufv[n].flags == FUSE_BUF_FD) + * is at least twice the page size. + * + * In order for SPLICE_F_MOVE to be used, the following additional + * conditions have to be fulfilled: + * + * 1. FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.want, and + * 2. the kernel supports it (i.e, FUSE_CAP_SPLICE_MOVE is set in + fuse_conn_info.capable), and + * 3. *flags* contains FUSE_BUF_SPLICE_MOVE + * + * Note that, if splice is used, the data is actually spliced twice: + * once into a temporary pipe (to prepend header data), and then again + * into the kernel. If some of the provided buffers are memory-backed, + * the data in them is copied in step one and spliced in step two. + * + * The FUSE_BUF_SPLICE_FORCE_SPLICE and FUSE_BUF_SPLICE_NONBLOCK flags + * are silently ignored. + * + * Possible requests: + * read, readdir, getxattr, listxattr + * + * Side effects: + * when used to return data from a readdirplus() (but not readdir()) + * call, increments the lookup count of each returned entry by one + * on success. + * + * @param req request handle + * @param bufv buffer vector + * @param flags flags controlling the copy + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags); + +/** + * Reply with data vector + * + * Possible requests: + * read, readdir, getxattr, listxattr + * + * @param req request handle + * @param iov the vector containing the data + * @param count the size of vector + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count); + +/** + * Reply with filesystem statistics + * + * Possible requests: + * statfs + * + * @param req request handle + * @param stbuf filesystem statistics + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf); + +/** + * Reply with needed buffer size + * + * Possible requests: + * getxattr, listxattr + * + * @param req request handle + * @param count the buffer size needed in bytes + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_xattr(fuse_req_t req, size_t count); + +/** + * Reply with file lock information + * + * Possible requests: + * getlk + * + * @param req request handle + * @param lock the lock information + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_lock(fuse_req_t req, const struct flock *lock); + +/** + * Reply with block index + * + * Possible requests: + * bmap + * + * @param req request handle + * @param idx block index within device + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_bmap(fuse_req_t req, uint64_t idx); + +/* ----------------------------------------------------------- * + * Filling a buffer in readdir * + * ----------------------------------------------------------- */ + +/** + * Add a directory entry to the buffer + * + * Buffer needs to be large enough to hold the entry. If it's not, + * then the entry is not filled in but the size of the entry is still + * returned. The caller can check this by comparing the bufsize + * parameter with the returned entry size. If the entry size is + * larger than the buffer size, the operation failed. + * + * From the 'stbuf' argument the st_ino field and bits 12-15 of the + * st_mode field are used. The other fields are ignored. + * + * *off* should be any non-zero value that the filesystem can use to + * identify the current point in the directory stream. It does not + * need to be the actual physical position. A value of zero is + * reserved to mean "from the beginning", and should therefore never + * be used (the first call to fuse_add_direntry should be passed the + * offset of the second directory entry). + * + * @param req request handle + * @param buf the point where the new entry will be added to the buffer + * @param bufsize remaining size of the buffer + * @param name the name of the entry + * @param stbuf the file attributes + * @param off the offset of the next entry + * @return the space needed for the entry + */ +size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, + const char *name, const struct stat *stbuf, + off_t off); + +/** + * Add a directory entry to the buffer with the attributes + * + * See documentation of `fuse_add_direntry()` for more details. + * + * @param req request handle + * @param buf the point where the new entry will be added to the buffer + * @param bufsize remaining size of the buffer + * @param name the name of the entry + * @param e the directory entry + * @param off the offset of the next entry + * @return the space needed for the entry + */ +size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, + const char *name, + const struct fuse_entry_param *e, off_t off); + +/** + * Reply to ask for data fetch and output buffer preparation. ioctl + * will be retried with the specified input data fetched and output + * buffer prepared. + * + * Possible requests: + * ioctl + * + * @param req request handle + * @param in_iov iovec specifying data to fetch from the caller + * @param in_count number of entries in in_iov + * @param out_iov iovec specifying addresses to write output to + * @param out_count number of entries in out_iov + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_ioctl_retry(fuse_req_t req, + const struct iovec *in_iov, size_t in_count, + const struct iovec *out_iov, size_t out_count); + +/** + * Reply to finish ioctl + * + * Possible requests: + * ioctl + * + * @param req request handle + * @param result result to be passed to the caller + * @param buf buffer containing output data + * @param size length of output data + */ +int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size); + +/** + * Reply to finish ioctl with iov buffer + * + * Possible requests: + * ioctl + * + * @param req request handle + * @param result result to be passed to the caller + * @param iov the vector containing the data + * @param count the size of vector + */ +int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, + int count); + +/** + * Reply with poll result event mask + * + * @param req request handle + * @param revents poll result event mask + */ +int fuse_reply_poll(fuse_req_t req, unsigned revents); + +/** + * Reply with offset + * + * Possible requests: + * lseek + * + * @param req request handle + * @param off offset of next data or hole + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_lseek(fuse_req_t req, off_t off); + +/** + * Reply with extended file attributes. + * + * Possible requests: + * statx + * + * @param req request handle + * @param flags statx flags + * @param statx the attributes + * @param attr_timeout validity timeout (in seconds) for the attributes + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout); + +/* ----------------------------------------------------------- * + * Notification * + * ----------------------------------------------------------- */ + +/** + * Notify IO readiness event + * + * For more information, please read comment for poll operation. + * + * @param ph poll handle to notify IO readiness event for + */ +int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph); + +/** + * Notify to invalidate cache for an inode. + * + * Added in FUSE protocol version 7.12. If the kernel does not support + * this (or a newer) version, the function will return -ENOSYS and do + * nothing. + * + * If the filesystem has writeback caching enabled, invalidating an + * inode will first trigger a writeback of all dirty pages. The call + * will block until all writeback requests have completed and the + * inode has been invalidated. It will, however, not wait for + * completion of pending writeback requests that have been issued + * before. + * + * If there are no dirty pages, this function will never block. + * + * @param se the session object + * @param ino the inode number + * @param off the offset in the inode where to start invalidating + * or negative to invalidate attributes only + * @param len the amount of cache to invalidate or 0 for all + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, + off_t off, off_t len); + +/** + * Notify to increment the epoch for the current + * + * Each fuse connection has an 'epoch', which is initialized during INIT. + * Caching will then be validated against the epoch value: if the current epoch + * is higher than an object being revalidated, the object is invalid. + * + * This function simply increment the current epoch value. + * + * @param se the session object + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se); + +/** + * Notify to invalidate parent attributes and the dentry matching parent/name + * + * To avoid a deadlock this function must not be called in the + * execution path of a related filesystem operation or within any code + * that could hold a lock that could be needed to execute such an + * operation. As of kernel 4.18, a "related operation" is a lookup(), + * symlink(), mknod(), mkdir(), unlink(), rename(), link() or create() + * request for the parent, and a setattr(), unlink(), rmdir(), + * rename(), setxattr(), removexattr(), readdir() or readdirplus() + * request for the inode itself. + * + * When called correctly, this function will never block. + * + * Added in FUSE protocol version 7.12. If the kernel does not support + * this (or a newer) version, the function will return -ENOSYS and do + * nothing. + * + * @param se the session object + * @param parent inode number + * @param name file name + * @param namelen strlen() of file name + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, + const char *name, size_t namelen); + +/** + * Notify to expire parent attributes and the dentry matching parent/name + * + * Same restrictions apply as for fuse_lowlevel_notify_inval_entry() + * + * Compared to invalidating an entry, expiring the entry results not in a + * forceful removal of that entry from kernel cache but instead the next access + * to it forces a lookup from the filesystem. + * + * This makes a difference for overmounted dentries, where plain invalidation + * would detach all submounts before dropping the dentry from the cache. + * If only expiry is set on the dentry, then any overmounts are left alone and + * until ->d_revalidate() is called. + * + * Note: ->d_revalidate() is not called for the case of following a submount, + * so invalidation will only be triggered for the non-overmounted case. + * The dentry could also be mounted in a different mount instance, in which case + * any submounts will still be detached. + * + * Added in FUSE protocol version 7.38. If the kernel does not support + * this (or a newer) version, the function will return -ENOSYS and do nothing. + * + * @param se the session object + * @param parent inode number + * @param name file name + * @param namelen strlen() of file name + * @return zero for success, -errno for failure, -enosys if no kernel support +*/ +int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, + const char *name, size_t namelen); + +/** + * This function behaves like fuse_lowlevel_notify_inval_entry() with + * the following additional effect (at least as of Linux kernel 4.8): + * + * If the provided *child* inode matches the inode that is currently + * associated with the cached dentry, and if there are any inotify + * watches registered for the dentry, then the watchers are informed + * that the dentry has been deleted. + * + * To avoid a deadlock this function must not be called while + * executing a related filesystem operation or while holding a lock + * that could be needed to execute such an operation (see the + * description of fuse_lowlevel_notify_inval_entry() for more + * details). + * + * When called correctly, this function will never block. + * + * Added in FUSE protocol version 7.18. If the kernel does not support + * this (or a newer) version, the function will return -ENOSYS and do + * nothing. + * + * @param se the session object + * @param parent inode number + * @param child inode number + * @param name file name + * @param namelen strlen() of file name + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_delete(struct fuse_session *se, + fuse_ino_t parent, fuse_ino_t child, + const char *name, size_t namelen); + +/** + * Store data to the kernel buffers + * + * Synchronously store data in the kernel buffers belonging to the + * given inode. The stored data is marked up-to-date (no read will be + * performed against it, unless it's invalidated or evicted from the + * cache). + * + * If the stored data overflows the current file size, then the size + * is extended, similarly to a write(2) on the filesystem. + * + * If this function returns an error, then the store wasn't fully + * completed, but it may have been partially completed. + * + * Added in FUSE protocol version 7.15. If the kernel does not support + * this (or a newer) version, the function will return -ENOSYS and do + * nothing. + * + * @param se the session object + * @param ino the inode number + * @param offset the starting offset into the file to store to + * @param bufv buffer vector + * @param flags flags controlling the copy + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, + off_t offset, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags); +/** + * Retrieve data from the kernel buffers + * + * Retrieve data in the kernel buffers belonging to the given inode. + * If successful then the retrieve_reply() method will be called with + * the returned data. + * + * Only present pages are returned in the retrieve reply. Retrieving + * stops when it finds a non-present page and only data prior to that + * is returned. + * + * If this function returns an error, then the retrieve will not be + * completed and no reply will be sent. + * + * This function doesn't change the dirty state of pages in the kernel + * buffer. For dirty pages the write() method will be called + * regardless of having been retrieved previously. + * + * Added in FUSE protocol version 7.15. If the kernel does not support + * this (or a newer) version, the function will return -ENOSYS and do + * nothing. + * + * @param se the session object + * @param ino the inode number + * @param size the number of bytes to retrieve + * @param offset the starting offset into the file to retrieve from + * @param cookie user data to supply to the reply callback + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, + size_t size, off_t offset, void *cookie); + + +/* ----------------------------------------------------------- * + * Utility functions * + * ----------------------------------------------------------- */ + +/** + * Get the userdata from the request + * + * @param req request handle + * @return the user data passed to fuse_session_new() + */ +void *fuse_req_userdata(fuse_req_t req); + +/** + * Get the context from the request + * + * The pointer returned by this function will only be valid for the + * request's lifetime + * + * @param req request handle + * @return the context structure + */ +const struct fuse_ctx *fuse_req_ctx(fuse_req_t req); + +/** + * Get the current supplementary group IDs for the specified request + * + * Similar to the getgroups(2) system call, except the return value is + * always the total number of group IDs, even if it is larger than the + * specified size. + * + * The current fuse kernel module in linux (as of 2.6.30) doesn't pass + * the group list to userspace, hence this function needs to parse + * "/proc/$TID/task/$TID/status" to get the group IDs. + * + * This feature may not be supported on all operating systems. In + * such a case this function will return -ENOSYS. + * + * @param req request handle + * @param size size of given array + * @param list array of group IDs to be filled in + * @return the total number of supplementary group IDs or -errno on failure + */ +int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]); + +/** + * Callback function for an interrupt + * + * @param req interrupted request + * @param data user data + */ +typedef void (*fuse_interrupt_func_t)(fuse_req_t req, void *data); + +/** + * Register/unregister callback for an interrupt + * + * If an interrupt has already happened, then the callback function is + * called from within this function, hence it's not possible for + * interrupts to be lost. + * + * @param req request handle + * @param func the callback function or NULL for unregister + * @param data user data passed to the callback function + */ +void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, + void *data); + +/** + * Check if a request has already been interrupted + * + * @param req request handle + * @return 1 if the request has been interrupted, 0 otherwise + */ +int fuse_req_interrupted(fuse_req_t req); + + +/* ----------------------------------------------------------- * + * Inquiry functions * + * ----------------------------------------------------------- */ + +/** + * Print low-level version information to stdout. + */ +void fuse_lowlevel_version(void); + +/** + * Print available low-level options to stdout. This is not an + * exhaustive list, but includes only those options that may be of + * interest to an end-user of a file system. + */ +void fuse_lowlevel_help(void); + +/** + * Print available options for `fuse_parse_cmdline()`. + */ +void fuse_cmdline_help(void); + +/* ----------------------------------------------------------- * + * Filesystem setup & teardown * + * ----------------------------------------------------------- */ + +/** + * Note: Any addition to this struct needs to create a compatibility symbol + * for fuse_parse_cmdline(). For ABI compatibility reasons it is also + * not possible to remove struct members. + */ +struct fuse_cmdline_opts { + int singlethread; + int foreground; + int debug; + int nodefault_subtype; + char *mountpoint; + int show_version; + int show_help; + int clone_fd; + unsigned int max_idle_threads; /* discouraged, due to thread + * destruct overhead */ + + /* Added in libfuse-3.12 */ + unsigned int max_threads; +}; + +/** + * Utility function to parse common options for simple file systems + * using the low-level API. A help text that describes the available + * options can be printed with `fuse_cmdline_help`. A single + * non-option argument is treated as the mountpoint. Multiple + * non-option arguments will result in an error. + * + * If neither -o subtype= or -o fsname= options are given, a new + * subtype option will be added and set to the basename of the program + * (the fsname will remain unset, and then defaults to "fuse"). + * + * Known options will be removed from *args*, unknown options will + * remain. + * + * @param args argument vector (input+output) + * @param opts output argument for parsed options + * @return 0 on success, -1 on failure + */ +#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS)) +int fuse_parse_cmdline(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +#else +#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12) +int fuse_parse_cmdline_30(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_30(args, opts) +#else +int fuse_parse_cmdline_312(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_312(args, opts) +#endif +#endif + +/* Do not call this directly, use fuse_session_new() instead */ +struct fuse_session * +fuse_session_new_versioned(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, size_t op_size, + struct libfuse_version *version, void *userdata); + +/** + * Create a low level session. + * + * Returns a session structure suitable for passing to + * fuse_session_mount() and fuse_session_loop(). + * + * This function accepts most file-system independent mount options + * (like context, nodev, ro - see mount(8)), as well as the general + * fuse mount options listed in mount.fuse(8) (e.g. -o allow_root and + * -o default_permissions, but not ``-o use_ino``). Instead of `-o + * debug`, debugging may also enabled with `-d` or `--debug`. + * + * If not all options are known, an error message is written to stderr + * and the function returns NULL. + * + * To create a no-op session just for mounting pass op as NULL. + * + * Option parsing skips argv[0], which is assumed to contain the + * program name. To prevent accidentally passing an option in + * argv[0], this element must always be present (even if no options + * are specified). It may be set to the empty string ('\0') if no + * reasonable value can be provided. + * + * @param args argument vector + * @param op the (low-level) filesystem operations + * @param op_size sizeof(struct fuse_lowlevel_ops) + * @param version the libfuse version a file system server was compiled against + * @param userdata user data + * @return the fuse session on success, NULL on failure + **/ +static inline struct fuse_session * +fuse_session_new_fn(struct fuse_args *args, const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata) +{ + struct libfuse_version version = { + .major = FUSE_MAJOR_VERSION, + .minor = FUSE_MINOR_VERSION, + .hotfix = FUSE_HOTFIX_VERSION, + .padding = 0 + }; + + return fuse_session_new_versioned(args, op, op_size, &version, + userdata); +} +#define fuse_session_new(args, op, op_size, userdata) \ + fuse_session_new_fn(args, op, op_size, userdata) + +/* + * This should mostly not be called directly, but instead the + * fuse_session_custom_io() should be used. + */ +int fuse_session_custom_io_317(struct fuse_session *se, + const struct fuse_custom_io *io, size_t op_size, int fd); + +/** + * Set a file descriptor for the session. + * + * This function can be used if you want to have a custom communication + * interface instead of using a mountpoint. In practice, this means that instead + * of calling fuse_session_mount() and fuse_session_unmount(), one could call + * fuse_session_custom_io() where fuse_session_mount() would have otherwise been + * called. + * + * In `io`, implementations for read and writev MUST be provided. Otherwise -1 + * will be returned and `fd` will not be used. Implementations for `splice_send` + * and `splice_receive` are optional. If they are not provided splice will not + * be used for send or receive respectively. + * + * The provided file descriptor `fd` will be closed when fuse_session_destroy() + * is called. + * + * @param se session object + * @param io Custom io to use when retrieving/sending requests/responses + * @param fd file descriptor for the session + * + * @return 0 on success + * @return -EINVAL if `io`, `io->read` or `ìo->writev` are NULL + * @return -EBADF if `fd` was smaller than 0 + * @return -errno if failed to allocate memory to store `io` + * + **/ +#if FUSE_MAKE_VERSION(3, 17) <= FUSE_USE_VERSION +static inline int fuse_session_custom_io(struct fuse_session *se, + const struct fuse_custom_io *io, size_t op_size, int fd) +{ + return fuse_session_custom_io_317(se, io, op_size, fd); +} +#else +static inline int fuse_session_custom_io(struct fuse_session *se, + const struct fuse_custom_io *io, int fd) +{ + return fuse_session_custom_io_317(se, io, + offsetof(struct fuse_custom_io, clone_fd), fd); +} +#endif + +/** + * Mount a FUSE file system. + * + * @param mountpoint the mount point path + * @param se session object + * + * @return 0 on success, -1 on failure. + **/ +int fuse_session_mount(struct fuse_session *se, const char *mountpoint); + +/** + * Enter a single threaded, blocking event loop. + * + * When the event loop terminates because the connection to the FUSE + * kernel module has been closed, this function returns zero. This + * happens when the filesystem is unmounted regularly (by the + * filesystem owner or root running the umount(8) or fusermount(1) + * command), or if connection is explicitly severed by writing ``1`` + * to the``abort`` file in ``/sys/fs/fuse/connections/NNN``. The only + * way to distinguish between these two conditions is to check if the + * filesystem is still mounted after the session loop returns. + * + * When some error occurs during request processing, the function + * returns a negated errno(3) value. + * + * If the loop has been terminated because of a signal handler + * installed by fuse_set_signal_handlers(), this function returns the + * (positive) signal value that triggered the exit. + * + * @param se the session + * @return 0, -errno, or a signal value + */ +int fuse_session_loop(struct fuse_session *se); + +#if FUSE_USE_VERSION < 32 + int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd); + #define fuse_session_loop_mt(se, clone_fd) fuse_session_loop_mt_31(se, clone_fd) +#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12) + int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config *config); + #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_32(se, config) +#else + #if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS)) + /** + * Enter a multi-threaded event loop. + * + * For a description of the return value and the conditions when the + * event loop exits, refer to the documentation of + * fuse_session_loop(). + * + * @param se the session + * @param config session loop configuration + * @return see fuse_session_loop() + */ + int fuse_session_loop_mt(struct fuse_session *se, struct fuse_loop_config *config); + #else + int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config); + #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_312(se, config) + #endif +#endif + +/** + * Flag a session as terminated. + * + * This will cause any running event loops to terminate on the next opportunity. If this function is + * called by a thread that is not a FUSE worker thread, the next + * opportunity will be when FUSE a request is received (which may be far in the future if the + * filesystem is not currently being used by any clients). One way to avoid this delay is to + * afterwards sent a signal to the main thread (if fuse_set_signal_handlers() is used, SIGPIPE + * will cause the main thread to wake-up but otherwise be ignored). + * + * @param se the session + */ +void fuse_session_exit(struct fuse_session *se); + +/** + * Reset the terminated flag of a session + * + * @param se the session + */ +void fuse_session_reset(struct fuse_session *se); + +/** + * Query the terminated flag of a session + * + * @param se the session + * @return 1 if exited, 0 if not exited + */ +int fuse_session_exited(struct fuse_session *se); + +/** + * Ensure that file system is unmounted. + * + * In regular operation, the file system is typically unmounted by the + * user calling umount(8) or fusermount(1), which then terminates the + * FUSE session loop. However, the session loop may also terminate as + * a result of an explicit call to fuse_session_exit() (e.g. by a + * signal handler installed by fuse_set_signal_handler()). In this + * case the filesystem remains mounted, but any attempt to access it + * will block (while the filesystem process is still running) or give + * an ESHUTDOWN error (after the filesystem process has terminated). + * + * If the communication channel with the FUSE kernel module is still + * open (i.e., if the session loop was terminated by an explicit call + * to fuse_session_exit()), this function will close it and unmount + * the filesystem. If the communication channel has been closed by the + * kernel, this method will do (almost) nothing. + * + * NOTE: The above semantics mean that if the connection to the kernel + * is terminated via the ``/sys/fs/fuse/connections/NNN/abort`` file, + * this method will *not* unmount the filesystem. + * + * @param se the session + */ +void fuse_session_unmount(struct fuse_session *se); + +/** + * Destroy a session + * + * @param se the session + */ +void fuse_session_destroy(struct fuse_session *se); + +/* ----------------------------------------------------------- * + * Custom event loop support * + * ----------------------------------------------------------- */ + +/** + * Return file descriptor for communication with kernel. + * + * The file selector can be used to integrate FUSE with a custom event + * loop. Whenever data is available for reading on the provided fd, + * the event loop should call `fuse_session_receive_buf` followed by + * `fuse_session_process_buf` to process the request. + * + * The returned file descriptor is valid until `fuse_session_unmount` + * is called. + * + * @param se the session + * @return a file descriptor + */ +int fuse_session_fd(struct fuse_session *se); + +/** + * Process a raw request supplied in a generic buffer + * + * The fuse_buf may contain a memory buffer or a pipe file descriptor. + * + * @param se the session + * @param buf the fuse_buf containing the request + */ +void fuse_session_process_buf(struct fuse_session *se, + const struct fuse_buf *buf); + +/** + * Read a raw request from the kernel into the supplied buffer. + * + * Depending on file system options, system capabilities, and request + * size the request is either read into a memory buffer or spliced + * into a temporary pipe. + * + * @param se the session + * @param buf the fuse_buf to store the request in + * @return the actual size of the raw request, or -errno on error + */ +int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf); + +/** + * Check if the request is submitted through fuse-io-uring + */ +bool fuse_req_is_uring(fuse_req_t req); + +/** + * Get the payload of a request + * (for requests submitted through fuse-io-uring only) + * + * This is useful for a file system that wants to write data directly + * to the request buffer. With io-uring the req is the buffer owner + * and the file system can write directly to the buffer and avoid + * extra copying. For example useful for network file systems. + * + * @param req the request + * @param payload pointer to the payload + * @param payload_sz size of the payload + * @param mr memory registration handle, currently unused + * @return 0 on success, -errno on failure + */ +int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, + void **mr); + +#ifdef __cplusplus +} +#endif + +#endif /* FUSE_LOWLEVEL_H_ */ diff --git a/include/fuse_mount_compat.h b/include/fuse_mount_compat.h new file mode 100644 index 0000000..be8d576 --- /dev/null +++ b/include/fuse_mount_compat.h @@ -0,0 +1,49 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2023 Giulio Benetti + + Logging API. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LICENSE +*/ + +#ifndef FUSE_MOUNT_COMPAT_H_ +#define FUSE_MOUNT_COMPAT_H_ + +#include + +/* Some libc don't define MS_*, so define them manually + * (values taken from https://elixir.bootlin.com/linux/v6.10/source/include/uapi/linux/mount.h#L13 on) + */ +#ifndef MS_DIRSYNC +#define MS_DIRSYNC 128 +#endif + +#ifndef MS_NOSYMFOLLOW +#define MS_NOSYMFOLLOW 256 +#endif + +#ifndef MS_REC +#define MS_REC 16384 +#endif + +#ifndef MS_PRIVATE +#define MS_PRIVATE (1<<18) +#endif + +#ifndef MS_LAZYTIME +#define MS_LAZYTIME (1<<25) +#endif + +#ifndef UMOUNT_DETACH +#define UMOUNT_DETACH 0x00000002 /* Just detach from the tree */ +#endif +#ifndef UMOUNT_NOFOLLOW +#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */ +#endif +#ifndef UMOUNT_UNUSED +#define UMOUNT_UNUSED 0x80000000 /* Flag guaranteed to be unused */ +#endif + +#endif /* FUSE_MOUNT_COMPAT_H_ */ diff --git a/include/fuse_opt.h b/include/fuse_opt.h new file mode 100644 index 0000000..a9a41fe --- /dev/null +++ b/include/fuse_opt.h @@ -0,0 +1,271 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#ifndef FUSE_OPT_H_ +#define FUSE_OPT_H_ + +/** @file + * + * This file defines the option parsing interface of FUSE + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Option description + * + * This structure describes a single option, and action associated + * with it, in case it matches. + * + * More than one such match may occur, in which case the action for + * each match is executed. + * + * There are three possible actions in case of a match: + * + * i) An integer (int or unsigned) variable determined by 'offset' is + * set to 'value' + * + * ii) The processing function is called, with 'value' as the key + * + * iii) An integer (any) or string (char *) variable determined by + * 'offset' is set to the value of an option parameter + * + * 'offset' should normally be either set to + * + * - 'offsetof(struct foo, member)' actions i) and iii) + * + * - -1 action ii) + * + * The 'offsetof()' macro is defined in the header. + * + * The template determines which options match, and also have an + * effect on the action. Normally the action is either i) or ii), but + * if a format is present in the template, then action iii) is + * performed. + * + * The types of templates are: + * + * 1) "-x", "-foo", "--foo", "--foo-bar", etc. These match only + * themselves. Invalid values are "--" and anything beginning + * with "-o" + * + * 2) "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or + * the relevant option in a comma separated option list + * + * 3) "bar=", "--foo=", etc. These are variations of 1) and 2) + * which have a parameter + * + * 4) "bar=%s", "--foo=%lu", etc. Same matching as above but perform + * action iii). + * + * 5) "-x ", etc. Matches either "-xparam" or "-x param" as + * two separate arguments + * + * 6) "-x %s", etc. Combination of 4) and 5) + * + * If the format is "%s", memory is allocated for the string unlike with + * scanf(). The previous value (if non-NULL) stored at the this location is + * freed. + */ +struct fuse_opt { + /** Matching template and optional parameter formatting */ + const char *templ; + + /** + * Offset of variable within 'data' parameter of fuse_opt_parse() + * or -1 + */ + unsigned long offset; + + /** + * Value to set the variable to, or to be passed as 'key' to the + * processing function. Ignored if template has a format + */ + int value; +}; + +/** + * Key option. In case of a match, the processing function will be + * called with the specified key. + */ +#define FUSE_OPT_KEY(templ, key) { templ, -1U, key } + +/** + * Last option. An array of 'struct fuse_opt' must end with a NULL + * template value + */ +#define FUSE_OPT_END { NULL, 0, 0 } + +/** + * Argument list + */ +struct fuse_args { + /** Argument count */ + int argc; + + /** Argument vector. NULL terminated */ + char **argv; + + /** Is 'argv' allocated? */ + int allocated; +}; + +/** + * Initializer for 'struct fuse_args' + */ +#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 } + +/** + * Key value passed to the processing function if an option did not + * match any template + */ +#define FUSE_OPT_KEY_OPT -1 + +/** + * Key value passed to the processing function for all non-options + * + * Non-options are the arguments beginning with a character other than + * '-' or all arguments after the special '--' option + */ +#define FUSE_OPT_KEY_NONOPT -2 + +/** + * Special key value for options to keep + * + * Argument is not passed to processing function, but behave as if the + * processing function returned 1 + */ +#define FUSE_OPT_KEY_KEEP -3 + +/** + * Special key value for options to discard + * + * Argument is not passed to processing function, but behave as if the + * processing function returned zero + */ +#define FUSE_OPT_KEY_DISCARD -4 + +/** + * Processing function + * + * This function is called if + * - option did not match any 'struct fuse_opt' + * - argument is a non-option + * - option did match and offset was set to -1 + * + * The 'arg' parameter will always contain the whole argument or + * option including the parameter if exists. A two-argument option + * ("-x foo") is always converted to single argument option of the + * form "-xfoo" before this function is called. + * + * Options of the form '-ofoo' are passed to this function without the + * '-o' prefix. + * + * The return value of this function determines whether this argument + * is to be inserted into the output argument vector, or discarded. + * + * @param data is the user data passed to the fuse_opt_parse() function + * @param arg is the whole argument or option + * @param key determines why the processing function was called + * @param outargs the current output argument list + * @return -1 on error, 0 if arg is to be discarded, 1 if arg should be kept + */ +typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key, + struct fuse_args *outargs); + +/** + * Option parsing function + * + * If 'args' was returned from a previous call to fuse_opt_parse() or + * it was constructed from + * + * A NULL 'args' is equivalent to an empty argument vector + * + * A NULL 'opts' is equivalent to an 'opts' array containing a single + * end marker + * + * A NULL 'proc' is equivalent to a processing function always + * returning '1' + * + * @param args is the input and output argument list + * @param data is the user data + * @param opts is the option description array + * @param proc is the processing function + * @return -1 on error, 0 on success + */ +int fuse_opt_parse(struct fuse_args *args, void *data, + const struct fuse_opt opts[], fuse_opt_proc_t proc); + +/** + * Add an option to a comma separated option list + * + * @param opts is a pointer to an option list, may point to a NULL value + * @param opt is the option to add + * @return -1 on allocation error, 0 on success + */ +int fuse_opt_add_opt(char **opts, const char *opt); + +/** + * Add an option, escaping commas, to a comma separated option list + * + * @param opts is a pointer to an option list, may point to a NULL value + * @param opt is the option to add + * @return -1 on allocation error, 0 on success + */ +int fuse_opt_add_opt_escaped(char **opts, const char *opt); + +/** + * Add an argument to a NULL terminated argument vector + * + * @param args is the structure containing the current argument list + * @param arg is the new argument to add + * @return -1 on allocation error, 0 on success + */ +int fuse_opt_add_arg(struct fuse_args *args, const char *arg); + +/** + * Add an argument at the specified position in a NULL terminated + * argument vector + * + * Adds the argument to the N-th position. This is useful for adding + * options at the beginning of the array which must not come after the + * special '--' option. + * + * @param args is the structure containing the current argument list + * @param pos is the position at which to add the argument + * @param arg is the new argument to add + * @return -1 on allocation error, 0 on success + */ +int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg); + +/** + * Free the contents of argument list + * + * The structure itself is not freed + * + * @param args is the structure containing the argument list + */ +void fuse_opt_free_args(struct fuse_args *args); + + +/** + * Check if an option matches + * + * @param opts is the option description array + * @param opt is the option to match + * @return 1 if a match is found, 0 if not + */ +int fuse_opt_match(const struct fuse_opt opts[], const char *opt); + +#ifdef __cplusplus +} +#endif + +#endif /* FUSE_OPT_H_ */ diff --git a/include/meson.build b/include/meson.build new file mode 100644 index 0000000..bf67197 --- /dev/null +++ b/include/meson.build @@ -0,0 +1,4 @@ +libfuse_headers = [ 'fuse.h', 'fuse_common.h', 'fuse_lowlevel.h', + 'fuse_opt.h', 'cuse_lowlevel.h', 'fuse_log.h' ] + +install_headers(libfuse_headers, subdir: 'fuse3') diff --git a/lib/buffer.c b/lib/buffer.c new file mode 100644 index 0000000..2b72d95 --- /dev/null +++ b/lib/buffer.c @@ -0,0 +1,324 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2010 Miklos Szeredi + + Functions for dealing with `struct fuse_buf` and `struct + fuse_bufvec`. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#define _GNU_SOURCE + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_lowlevel.h" +#include +#include +#include +#include + +size_t fuse_buf_size(const struct fuse_bufvec *bufv) +{ + size_t i; + size_t size = 0; + + for (i = 0; i < bufv->count; i++) { + if (bufv->buf[i].size >= SIZE_MAX - size) + return SIZE_MAX; + + size += bufv->buf[i].size; + } + + return size; +} + +static size_t min_size(size_t s1, size_t s2) +{ + return s1 < s2 ? s1 : s2; +} + +static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len) +{ + ssize_t res = 0; + size_t copied = 0; + + while (len) { + if (dst->flags & FUSE_BUF_FD_SEEK) { + res = pwrite(dst->fd, (char *)src->mem + src_off, len, + dst->pos + dst_off); + } else { + res = write(dst->fd, (char *)src->mem + src_off, len); + } + if (res == -1) { + if (!copied) + return -errno; + break; + } + if (res == 0) + break; + + copied += res; + if (!(dst->flags & FUSE_BUF_FD_RETRY)) + break; + + src_off += res; + dst_off += res; + len -= res; + } + + return copied; +} + +static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len) +{ + ssize_t res = 0; + size_t copied = 0; + + while (len) { + if (src->flags & FUSE_BUF_FD_SEEK) { + res = pread(src->fd, (char *)dst->mem + dst_off, len, + src->pos + src_off); + } else { + res = read(src->fd, (char *)dst->mem + dst_off, len); + } + if (res == -1) { + if (!copied) + return -errno; + break; + } + if (res == 0) + break; + + copied += res; + if (!(src->flags & FUSE_BUF_FD_RETRY)) + break; + + dst_off += res; + src_off += res; + len -= res; + } + + return copied; +} + +static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len) +{ + char buf[4096]; + struct fuse_buf tmp = { + .size = sizeof(buf), + .flags = 0, + }; + ssize_t res; + size_t copied = 0; + + tmp.mem = buf; + + while (len) { + size_t this_len = min_size(tmp.size, len); + size_t read_len; + + res = fuse_buf_read(&tmp, 0, src, src_off, this_len); + if (res < 0) { + if (!copied) + return res; + break; + } + if (res == 0) + break; + + read_len = res; + res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len); + if (res < 0) { + if (!copied) + return res; + break; + } + if (res == 0) + break; + + copied += res; + + if (res < this_len) + break; + + dst_off += res; + src_off += res; + len -= res; + } + + return copied; +} + +#ifdef HAVE_SPLICE +static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len, enum fuse_buf_copy_flags flags) +{ + int splice_flags = 0; + off_t *srcpos = NULL; + off_t *dstpos = NULL; + off_t srcpos_val; + off_t dstpos_val; + ssize_t res; + size_t copied = 0; + + if (flags & FUSE_BUF_SPLICE_MOVE) + splice_flags |= SPLICE_F_MOVE; + if (flags & FUSE_BUF_SPLICE_NONBLOCK) + splice_flags |= SPLICE_F_NONBLOCK; + + if (src->flags & FUSE_BUF_FD_SEEK) { + srcpos_val = src->pos + src_off; + srcpos = &srcpos_val; + } + if (dst->flags & FUSE_BUF_FD_SEEK) { + dstpos_val = dst->pos + dst_off; + dstpos = &dstpos_val; + } + + while (len) { + res = splice(src->fd, srcpos, dst->fd, dstpos, len, + splice_flags); + if (res == -1) { + if (copied) + break; + + if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE)) + return -errno; + + /* Maybe splice is not supported for this combination */ + return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, + len); + } + if (res == 0) + break; + + copied += res; + if (!(src->flags & FUSE_BUF_FD_RETRY) && + !(dst->flags & FUSE_BUF_FD_RETRY)) { + break; + } + + len -= res; + } + + return copied; +} +#else +static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len, enum fuse_buf_copy_flags flags) +{ + (void) flags; + + return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len); +} +#endif + + +static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len, enum fuse_buf_copy_flags flags) +{ + int src_is_fd = src->flags & FUSE_BUF_IS_FD; + int dst_is_fd = dst->flags & FUSE_BUF_IS_FD; + + if (!src_is_fd && !dst_is_fd) { + char *dstmem = (char *)dst->mem + dst_off; + char *srcmem = (char *)src->mem + src_off; + + if (dstmem != srcmem) { + if (dstmem + len <= srcmem || srcmem + len <= dstmem) + memcpy(dstmem, srcmem, len); + else + memmove(dstmem, srcmem, len); + } + + return len; + } else if (!src_is_fd) { + return fuse_buf_write(dst, dst_off, src, src_off, len); + } else if (!dst_is_fd) { + return fuse_buf_read(dst, dst_off, src, src_off, len); + } else if (flags & FUSE_BUF_NO_SPLICE) { + return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len); + } else { + return fuse_buf_splice(dst, dst_off, src, src_off, len, flags); + } +} + +static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv) +{ + if (bufv->idx < bufv->count) + return &bufv->buf[bufv->idx]; + else + return NULL; +} + +static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len) +{ + const struct fuse_buf *buf = fuse_bufvec_current(bufv); + + if (!buf) + return 0; + + bufv->off += len; + assert(bufv->off <= buf->size); + if (bufv->off == buf->size) { + assert(bufv->idx < bufv->count); + bufv->idx++; + if (bufv->idx == bufv->count) + return 0; + bufv->off = 0; + } + return 1; +} + +ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv, + enum fuse_buf_copy_flags flags) +{ + size_t copied = 0; + + if (dstv == srcv) + return fuse_buf_size(dstv); + + for (;;) { + const struct fuse_buf *src = fuse_bufvec_current(srcv); + const struct fuse_buf *dst = fuse_bufvec_current(dstv); + size_t src_len; + size_t dst_len; + size_t len; + ssize_t res; + + if (src == NULL || dst == NULL) + break; + + src_len = src->size - srcv->off; + dst_len = dst->size - dstv->off; + len = min_size(src_len, dst_len); + + res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags); + if (res < 0) { + if (!copied) + return res; + break; + } + copied += res; + + if (!fuse_bufvec_advance(srcv, res) || + !fuse_bufvec_advance(dstv, res)) + break; + + if (res < len) + break; + } + + return copied; +} diff --git a/lib/compat.c b/lib/compat.c new file mode 100644 index 0000000..5fac415 --- /dev/null +++ b/lib/compat.c @@ -0,0 +1,86 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Helper functions to create (simple) standalone programs. With the + aid of these functions it should be possible to create full FUSE + file system by implementing nothing but the request handlers. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +/* Description: + This file has compatibility symbols for platforms that do not + support version symboling +*/ + +#include "libfuse_config.h" + +#include +#include + +struct fuse_args; +struct fuse_cmdline_opts; +struct fuse_cmdline_opts; +struct fuse_session; +struct fuse_custom_io; +struct fuse_operations; +struct fuse_lowlevel_ops; + +/** + * Compatibility ABI symbol for systems that do not support version symboling + */ +#if (!defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS)) +/* With current libfuse fuse_parse_cmdline is a macro pointing to the + * versioned function. Here in this file we need to provide the ABI symbol + * and the redirecting macro is conflicting. + */ +#ifdef fuse_parse_cmdline +#undef fuse_parse_cmdline +#endif +int fuse_parse_cmdline_30(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +int fuse_parse_cmdline(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +int fuse_parse_cmdline(struct fuse_args *args, + struct fuse_cmdline_opts *opts) +{ + return fuse_parse_cmdline_30(args, opts); +} + +int fuse_session_custom_io_30(struct fuse_session *se, + const struct fuse_custom_io *io, int fd); +int fuse_session_custom_io(struct fuse_session *se, + const struct fuse_custom_io *io, int fd); +int fuse_session_custom_io(struct fuse_session *se, + const struct fuse_custom_io *io, int fd) + +{ + return fuse_session_custom_io_30(se, io, fd); +} + +#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */ + +int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data); +int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data); +int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + return fuse_main_real_30(argc, argv, op, op_size, user_data); +} + +struct fuse_session *fuse_session_new_30(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata); +struct fuse_session *fuse_session_new(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata); +struct fuse_session *fuse_session_new(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata) +{ + return fuse_session_new_30(args, op, op_size, userdata); +} diff --git a/lib/cuse_lowlevel.c b/lib/cuse_lowlevel.c new file mode 100644 index 0000000..0bc2e66 --- /dev/null +++ b/lib/cuse_lowlevel.c @@ -0,0 +1,375 @@ +/* + CUSE: Character device in Userspace + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#include "fuse_config.h" +#include "cuse_lowlevel.h" +#include "fuse_kernel.h" +#include "fuse_i.h" +#include "fuse_opt.h" + +#include +#include +#include +#include +#include +#include + +struct cuse_data { + struct cuse_lowlevel_ops clop; + unsigned max_read; + unsigned dev_major; + unsigned dev_minor; + unsigned flags; + unsigned dev_info_len; + char dev_info[]; +}; + +static struct cuse_lowlevel_ops *req_clop(fuse_req_t req) +{ + return &req->se->cuse_data->clop; +} + +static void cuse_fll_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->open(req, fi); +} + +static void cuse_fll_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->read(req, size, off, fi); +} + +static void cuse_fll_write(fuse_req_t req, fuse_ino_t ino, const char *buf, + size_t size, off_t off, struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->write(req, buf, size, off, fi); +} + +static void cuse_fll_flush(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->flush(req, fi); +} + +static void cuse_fll_release(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->release(req, fi); +} + +static void cuse_fll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->fsync(req, datasync, fi); +} + +static void cuse_fll_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, + struct fuse_file_info *fi, unsigned int flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz) +{ + (void)ino; + req_clop(req)->ioctl(req, cmd, arg, fi, flags, in_buf, in_bufsz, + out_bufsz); +} + +static void cuse_fll_poll(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct fuse_pollhandle *ph) +{ + (void)ino; + req_clop(req)->poll(req, fi, ph); +} + +static size_t cuse_pack_info(int argc, const char **argv, char *buf) +{ + size_t size = 0; + int i; + + for (i = 0; i < argc; i++) { + size_t len; + + len = strlen(argv[i]) + 1; + size += len; + if (buf) { + memcpy(buf, argv[i], len); + buf += len; + } + } + + return size; +} + +static struct cuse_data *cuse_prep_data(const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop) +{ + struct cuse_data *cd; + size_t dev_info_len; + + dev_info_len = cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, + NULL); + + if (dev_info_len > CUSE_INIT_INFO_MAX) { + fuse_log(FUSE_LOG_ERR, "cuse: dev_info (%zu) too large, limit=%u\n", + dev_info_len, CUSE_INIT_INFO_MAX); + return NULL; + } + + cd = calloc(1, sizeof(*cd) + dev_info_len); + if (!cd) { + fuse_log(FUSE_LOG_ERR, "cuse: failed to allocate cuse_data\n"); + return NULL; + } + + memcpy(&cd->clop, clop, sizeof(cd->clop)); + cd->max_read = 131072; + cd->dev_major = ci->dev_major; + cd->dev_minor = ci->dev_minor; + cd->dev_info_len = dev_info_len; + cd->flags = ci->flags; + cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info); + + return cd; +} + +struct fuse_session *cuse_lowlevel_new(struct fuse_args *args, + const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, + void *userdata) +{ + struct fuse_lowlevel_ops lop; + struct cuse_data *cd; + struct fuse_session *se; + + cd = cuse_prep_data(ci, clop); + if (!cd) + return NULL; + + memset(&lop, 0, sizeof(lop)); + lop.init = clop->init; + lop.destroy = clop->destroy; + lop.open = clop->open ? cuse_fll_open : NULL; + lop.read = clop->read ? cuse_fll_read : NULL; + lop.write = clop->write ? cuse_fll_write : NULL; + lop.flush = clop->flush ? cuse_fll_flush : NULL; + lop.release = clop->release ? cuse_fll_release : NULL; + lop.fsync = clop->fsync ? cuse_fll_fsync : NULL; + lop.ioctl = clop->ioctl ? cuse_fll_ioctl : NULL; + lop.poll = clop->poll ? cuse_fll_poll : NULL; + + se = fuse_session_new(args, &lop, sizeof(lop), userdata); + if (!se) { + free(cd); + return NULL; + } + se->cuse_data = cd; + + return se; +} + +static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg, + char *dev_info, unsigned dev_info_len) +{ + struct iovec iov[3]; + + iov[1].iov_base = arg; + iov[1].iov_len = sizeof(struct cuse_init_out); + iov[2].iov_base = dev_info; + iov[2].iov_len = dev_info_len; + + return fuse_send_reply_iov_nofree(req, 0, iov, 3); +} + +void _cuse_lowlevel_init(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *req_payload) +{ + const struct fuse_init_in *arg = op_in; + (void)req_payload; + struct cuse_init_out outarg; + struct fuse_session *se = req->se; + struct cuse_data *cd = se->cuse_data; + size_t bufsize = se->bufsize; + struct cuse_lowlevel_ops *clop = req_clop(req); + + (void) nodeid; + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, "CUSE_INIT: %u.%u\n", arg->major, arg->minor); + fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags); + } + se->conn.proto_major = arg->major; + se->conn.proto_minor = arg->minor; + + /* XXX This is not right.*/ + se->conn.capable_ext = 0; + se->conn.want_ext = 0; + + if (arg->major < 7) { + fuse_log(FUSE_LOG_ERR, "cuse: unsupported protocol version: %u.%u\n", + arg->major, arg->minor); + fuse_reply_err(req, EPROTO); + return; + } + + if (bufsize < FUSE_MIN_READ_BUFFER) { + fuse_log(FUSE_LOG_ERR, "cuse: warning: buffer size too small: %zu\n", + bufsize); + bufsize = FUSE_MIN_READ_BUFFER; + } + + bufsize -= 4096; + if (bufsize < se->conn.max_write) + se->conn.max_write = bufsize; + + se->got_init = 1; + if (se->op.init) + se->op.init(se->userdata, &se->conn); + + memset(&outarg, 0, sizeof(outarg)); + outarg.major = FUSE_KERNEL_VERSION; + outarg.minor = FUSE_KERNEL_MINOR_VERSION; + outarg.flags = cd->flags; + outarg.max_read = cd->max_read; + outarg.max_write = se->conn.max_write; + outarg.dev_major = cd->dev_major; + outarg.dev_minor = cd->dev_minor; + + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, " CUSE_INIT: %u.%u\n", + outarg.major, outarg.minor); + fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags); + fuse_log(FUSE_LOG_DEBUG, " max_read=0x%08x\n", outarg.max_read); + fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write); + fuse_log(FUSE_LOG_DEBUG, " dev_major=%u\n", outarg.dev_major); + fuse_log(FUSE_LOG_DEBUG, " dev_minor=%u\n", outarg.dev_minor); + fuse_log(FUSE_LOG_DEBUG, " dev_info: %.*s\n", cd->dev_info_len, + cd->dev_info); + } + + cuse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len); + + if (clop->init_done) + clop->init_done(se->userdata); + + fuse_free_req(req); +} + +void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _cuse_lowlevel_init(req, nodeid, inarg, NULL); +} + +struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[], + const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, + int *multithreaded, void *userdata) +{ + const char *devname = "/dev/cuse"; + static const struct fuse_opt kill_subtype_opts[] = { + FUSE_OPT_KEY("subtype=", FUSE_OPT_KEY_DISCARD), + FUSE_OPT_END + }; + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + int fd; + int res; + + if (fuse_parse_cmdline(&args, &opts) == -1) + return NULL; + *multithreaded = !opts.singlethread; + + /* Remove subtype= option */ + res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL); + if (res == -1) + goto out1; + + /* + * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos + * would ensue. + */ + do { + fd = open("/dev/null", O_RDWR); + if (fd > 2) + close(fd); + } while (fd >= 0 && fd <= 2); + + se = cuse_lowlevel_new(&args, ci, clop, userdata); + if (se == NULL) + goto out1; + + fd = open(devname, O_RDWR); + if (fd == -1) { + if (errno == ENODEV || errno == ENOENT) + fuse_log(FUSE_LOG_ERR, "cuse: device not found, try 'modprobe cuse' first\n"); + else + fuse_log(FUSE_LOG_ERR, "cuse: failed to open %s: %s\n", + devname, strerror(errno)); + goto err_se; + } + se->fd = fd; + + res = fuse_set_signal_handlers(se); + if (res == -1) + goto err_se; + + res = fuse_daemonize(opts.foreground); + if (res == -1) + goto err_sig; + + fuse_opt_free_args(&args); + return se; + +err_sig: + fuse_remove_signal_handlers(se); +err_se: + fuse_session_destroy(se); +out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + return NULL; +} + +void cuse_lowlevel_teardown(struct fuse_session *se) +{ + fuse_remove_signal_handlers(se); + fuse_session_destroy(se); +} + +int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, void *userdata) +{ + struct fuse_session *se; + int multithreaded; + int res; + + se = cuse_lowlevel_setup(argc, argv, ci, clop, &multithreaded, + userdata); + if (se == NULL) + return 1; + + if (multithreaded) { + struct fuse_loop_config *config = fuse_loop_cfg_create(); + res = fuse_session_loop_mt(se, config); + fuse_loop_cfg_destroy(config); + } + else + res = fuse_session_loop(se); + + cuse_lowlevel_teardown(se); + if (res == -1) + return 1; + + return 0; +} diff --git a/lib/fuse.c b/lib/fuse.c new file mode 100644 index 0000000..9607bb0 --- /dev/null +++ b/lib/fuse.c @@ -0,0 +1,5264 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Implementation of the high-level FUSE API on top of the low-level + API. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#define _GNU_SOURCE +#include "fuse.h" +#include + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_lowlevel.h" +#include "fuse_opt.h" +#include "fuse_misc.h" +#include "fuse_kernel.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FUSE_NODE_SLAB 1 + +#ifndef MAP_ANONYMOUS +#undef FUSE_NODE_SLAB +#endif + +#ifndef RENAME_EXCHANGE +#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */ +#endif + +#define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1 + +#define FUSE_UNKNOWN_INO 0xffffffff +#define OFFSET_MAX 0x7fffffffffffffffLL + +#define NODE_TABLE_MIN_SIZE 8192 + +struct fuse_fs { + struct fuse_operations op; + void *user_data; + int debug; +}; + +struct fusemod_so { + void *handle; + int ctr; +}; + +struct lock_queue_element { + struct lock_queue_element *next; + pthread_cond_t cond; + fuse_ino_t nodeid1; + const char *name1; + char **path1; + struct node **wnode1; + fuse_ino_t nodeid2; + const char *name2; + char **path2; + struct node **wnode2; + int err; + bool done : 1; +}; + +struct node_table { + struct node **array; + size_t use; + size_t size; + size_t split; +}; + +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +struct list_head { + struct list_head *next; + struct list_head *prev; +}; + +struct node_slab { + struct list_head list; /* must be the first member */ + struct list_head freelist; + int used; +}; + +struct fuse { + struct fuse_session *se; + struct node_table name_table; + struct node_table id_table; + struct list_head lru_table; + fuse_ino_t ctr; + unsigned int generation; + unsigned int hidectr; + pthread_mutex_t lock; + struct fuse_config conf; + int intr_installed; + struct fuse_fs *fs; + struct lock_queue_element *lockq; + int pagesize; + struct list_head partial_slabs; + struct list_head full_slabs; + pthread_t prune_thread; +}; + +struct lock { + int type; + off_t start; + off_t end; + pid_t pid; + uint64_t owner; + struct lock *next; +}; + +struct node { + struct node *name_next; + struct node *id_next; + fuse_ino_t nodeid; + unsigned int generation; + int refctr; + struct node *parent; + char *name; + uint64_t nlookup; + int open_count; + struct timespec stat_updated; + struct timespec mtime; + off_t size; + struct lock *locks; + unsigned int is_hidden : 1; + unsigned int cache_valid : 1; + int treelock; + char inline_name[32]; +}; + +#define TREELOCK_WRITE -1 +#define TREELOCK_WAIT_OFFSET INT_MIN + +struct node_lru { + struct node node; + struct list_head lru; + struct timespec forget_time; +}; + +struct fuse_direntry { + struct stat stat; + enum fuse_fill_dir_flags flags; + char *name; + struct fuse_direntry *next; +}; + +struct fuse_dh { + pthread_mutex_t lock; + struct fuse *fuse; + fuse_req_t req; + char *contents; + struct fuse_direntry *first; + struct fuse_direntry **last; + unsigned len; + unsigned size; + unsigned needlen; + int filled; + uint64_t fh; + int error; + fuse_ino_t nodeid; +}; + +struct fuse_context_i { + struct fuse_context ctx; + fuse_req_t req; +}; + +/* Defined by FUSE_REGISTER_MODULE() in lib/modules/subdir.c and iconv.c. */ +extern fuse_module_factory_t fuse_module_subdir_factory; +#ifdef HAVE_ICONV +extern fuse_module_factory_t fuse_module_iconv_factory; +#endif + +static pthread_key_t fuse_context_key; +static pthread_mutex_t fuse_context_lock = PTHREAD_MUTEX_INITIALIZER; +static int fuse_context_ref; +static struct fuse_module *fuse_modules = NULL; + +static int fuse_register_module(const char *name, + fuse_module_factory_t factory, + struct fusemod_so *so) +{ + struct fuse_module *mod; + + mod = calloc(1, sizeof(struct fuse_module)); + if (!mod) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module\n"); + return -1; + } + mod->name = strdup(name); + if (!mod->name) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module name\n"); + free(mod); + return -1; + } + mod->factory = factory; + mod->ctr = 0; + mod->so = so; + if (mod->so) + mod->so->ctr++; + mod->next = fuse_modules; + fuse_modules = mod; + + return 0; +} + +static void fuse_unregister_module(struct fuse_module *m) +{ + struct fuse_module **mp; + for (mp = &fuse_modules; *mp; mp = &(*mp)->next) { + if (*mp == m) { + *mp = (*mp)->next; + break; + } + } + free(m->name); + free(m); +} + +static int fuse_load_so_module(const char *module) +{ + int ret = -1; + char *tmp; + struct fusemod_so *so; + fuse_module_factory_t *factory; + + tmp = malloc(strlen(module) + 64); + if (!tmp) { + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + return -1; + } + sprintf(tmp, "libfusemod_%s.so", module); + so = calloc(1, sizeof(struct fusemod_so)); + if (!so) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module so\n"); + goto out; + } + + so->handle = dlopen(tmp, RTLD_NOW); + if (so->handle == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: dlopen(%s) failed: %s\n", + tmp, dlerror()); + goto out_free_so; + } + + sprintf(tmp, "fuse_module_%s_factory", module); + factory = (fuse_module_factory_t*)dlsym(so->handle, tmp); + if (factory == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: symbol <%s> not found in module: %s\n", + tmp, dlerror()); + goto out_dlclose; + } + ret = fuse_register_module(module, *factory, so); + if (ret) + goto out_dlclose; + +out: + free(tmp); + return ret; + +out_dlclose: + dlclose(so->handle); +out_free_so: + free(so); + goto out; +} + +static struct fuse_module *fuse_find_module(const char *module) +{ + struct fuse_module *m; + for (m = fuse_modules; m; m = m->next) { + if (strcmp(module, m->name) == 0) { + m->ctr++; + break; + } + } + return m; +} + +static struct fuse_module *fuse_get_module(const char *module) +{ + struct fuse_module *m; + + pthread_mutex_lock(&fuse_context_lock); + m = fuse_find_module(module); + if (!m) { + int err = fuse_load_so_module(module); + if (!err) + m = fuse_find_module(module); + } + pthread_mutex_unlock(&fuse_context_lock); + return m; +} + +static void fuse_put_module(struct fuse_module *m) +{ + pthread_mutex_lock(&fuse_context_lock); + if (m->so) + assert(m->ctr > 0); + /* Builtin modules may already have m->ctr == 0 */ + if (m->ctr > 0) + m->ctr--; + if (!m->ctr && m->so) { + struct fusemod_so *so = m->so; + assert(so->ctr > 0); + so->ctr--; + if (!so->ctr) { + struct fuse_module **mp; + for (mp = &fuse_modules; *mp;) { + if ((*mp)->so == so) + fuse_unregister_module(*mp); + else + mp = &(*mp)->next; + } + dlclose(so->handle); + free(so); + } + } else if (!m->ctr) { + fuse_unregister_module(m); + } + pthread_mutex_unlock(&fuse_context_lock); +} + +static void init_list_head(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +static int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +static void list_add(struct list_head *new, struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +static inline void list_add_head(struct list_head *new, struct list_head *head) +{ + list_add(new, head, head->next); +} + +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + list_add(new, head->prev, head); +} + +static inline void list_del(struct list_head *entry) +{ + struct list_head *prev = entry->prev; + struct list_head *next = entry->next; + + next->prev = prev; + prev->next = next; +} + +static inline int lru_enabled(struct fuse *f) +{ + return f->conf.remember > 0; +} + +static struct node_lru *node_lru(struct node *node) +{ + return (struct node_lru *) node; +} + +static size_t get_node_size(struct fuse *f) +{ + if (lru_enabled(f)) + return sizeof(struct node_lru); + else + return sizeof(struct node); +} + +#ifdef FUSE_NODE_SLAB +static struct node_slab *list_to_slab(struct list_head *head) +{ + return (struct node_slab *) head; +} + +static struct node_slab *node_to_slab(struct fuse *f, struct node *node) +{ + return (struct node_slab *) (((uintptr_t) node) & ~((uintptr_t) f->pagesize - 1)); +} + +static int alloc_slab(struct fuse *f) +{ + void *mem; + struct node_slab *slab; + char *start; + size_t num; + size_t i; + size_t node_size = get_node_size(f); + + mem = mmap(NULL, f->pagesize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (mem == MAP_FAILED) + return -1; + + slab = mem; + init_list_head(&slab->freelist); + slab->used = 0; + num = (f->pagesize - sizeof(struct node_slab)) / node_size; + + start = (char *) mem + f->pagesize - num * node_size; + for (i = 0; i < num; i++) { + struct list_head *n; + + n = (struct list_head *) (start + i * node_size); + list_add_tail(n, &slab->freelist); + } + list_add_tail(&slab->list, &f->partial_slabs); + + return 0; +} + +static struct node *alloc_node(struct fuse *f) +{ + struct node_slab *slab; + struct list_head *node; + + if (list_empty(&f->partial_slabs)) { + int res = alloc_slab(f); + if (res != 0) + return NULL; + } + slab = list_to_slab(f->partial_slabs.next); + slab->used++; + node = slab->freelist.next; + list_del(node); + if (list_empty(&slab->freelist)) { + list_del(&slab->list); + list_add_tail(&slab->list, &f->full_slabs); + } + memset(node, 0, sizeof(struct node)); + + return (struct node *) node; +} + +static void free_slab(struct fuse *f, struct node_slab *slab) +{ + int res; + + list_del(&slab->list); + res = munmap(slab, f->pagesize); + if (res == -1) + fuse_log(FUSE_LOG_WARNING, "fuse warning: munmap(%p) failed\n", + slab); +} + +static void free_node_mem(struct fuse *f, struct node *node) +{ + struct node_slab *slab = node_to_slab(f, node); + struct list_head *n = (struct list_head *) node; + + slab->used--; + if (slab->used) { + if (list_empty(&slab->freelist)) { + list_del(&slab->list); + list_add_tail(&slab->list, &f->partial_slabs); + } + list_add_head(n, &slab->freelist); + } else { + free_slab(f, slab); + } +} +#else +static struct node *alloc_node(struct fuse *f) +{ + return (struct node *) calloc(1, get_node_size(f)); +} + +static void free_node_mem(struct fuse *f, struct node *node) +{ + (void) f; + free(node); +} +#endif + +static size_t id_hash(struct fuse *f, fuse_ino_t ino) +{ + uint64_t hash = ((uint32_t) ino * 2654435761U) % f->id_table.size; + uint64_t oldhash = hash % (f->id_table.size / 2); + + if (oldhash >= f->id_table.split) + return oldhash; + else + return hash; +} + +static struct node *get_node_nocheck(struct fuse *f, fuse_ino_t nodeid) +{ + size_t hash = id_hash(f, nodeid); + struct node *node; + + for (node = f->id_table.array[hash]; node != NULL; node = node->id_next) + if (node->nodeid == nodeid) + return node; + + return NULL; +} + +static struct node *get_node(struct fuse *f, fuse_ino_t nodeid) +{ + struct node *node = get_node_nocheck(f, nodeid); + if (!node) { + fuse_log(FUSE_LOG_ERR, "fuse internal error: node %llu not found\n", + (unsigned long long) nodeid); + abort(); + } + return node; +} + +static void curr_time(struct timespec *now); +static double diff_timespec(const struct timespec *t1, + const struct timespec *t2); + +static void remove_node_lru(struct node *node) +{ + struct node_lru *lnode = node_lru(node); + list_del(&lnode->lru); + init_list_head(&lnode->lru); +} + +static void set_forget_time(struct fuse *f, struct node *node) +{ + struct node_lru *lnode = node_lru(node); + + list_del(&lnode->lru); + list_add_tail(&lnode->lru, &f->lru_table); + curr_time(&lnode->forget_time); +} + +static void free_node(struct fuse *f, struct node *node) +{ + if (node->name != node->inline_name) + free(node->name); + free_node_mem(f, node); +} + +static void node_table_reduce(struct node_table *t) +{ + size_t newsize = t->size / 2; + void *newarray; + + if (newsize < NODE_TABLE_MIN_SIZE) + return; + + newarray = realloc(t->array, sizeof(struct node *) * newsize); + if (newarray != NULL) + t->array = newarray; + + t->size = newsize; + t->split = t->size / 2; +} + +static void remerge_id(struct fuse *f) +{ + struct node_table *t = &f->id_table; + int iter; + + if (t->split == 0) + node_table_reduce(t); + + for (iter = 8; t->split > 0 && iter; iter--) { + struct node **upper; + + t->split--; + upper = &t->array[t->split + t->size / 2]; + if (*upper) { + struct node **nodep; + + for (nodep = &t->array[t->split]; *nodep; + nodep = &(*nodep)->id_next); + + *nodep = *upper; + *upper = NULL; + break; + } + } +} + +static void unhash_id(struct fuse *f, struct node *node) +{ + struct node **nodep = &f->id_table.array[id_hash(f, node->nodeid)]; + + for (; *nodep != NULL; nodep = &(*nodep)->id_next) + if (*nodep == node) { + *nodep = node->id_next; + f->id_table.use--; + + if(f->id_table.use < f->id_table.size / 4) + remerge_id(f); + return; + } +} + +static int node_table_resize(struct node_table *t) +{ + size_t newsize = t->size * 2; + void *newarray; + + newarray = realloc(t->array, sizeof(struct node *) * newsize); + if (newarray == NULL) + return -1; + + t->array = newarray; + memset(t->array + t->size, 0, t->size * sizeof(struct node *)); + t->size = newsize; + t->split = 0; + + return 0; +} + +static void rehash_id(struct fuse *f) +{ + struct node_table *t = &f->id_table; + struct node **nodep; + struct node **next; + size_t hash; + + if (t->split == t->size / 2) + return; + + hash = t->split; + t->split++; + for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) { + struct node *node = *nodep; + size_t newhash = id_hash(f, node->nodeid); + + if (newhash != hash) { + next = nodep; + *nodep = node->id_next; + node->id_next = t->array[newhash]; + t->array[newhash] = node; + } else { + next = &node->id_next; + } + } + if (t->split == t->size / 2) + node_table_resize(t); +} + +static void hash_id(struct fuse *f, struct node *node) +{ + size_t hash = id_hash(f, node->nodeid); + node->id_next = f->id_table.array[hash]; + f->id_table.array[hash] = node; + f->id_table.use++; + + if (f->id_table.use >= f->id_table.size / 2) + rehash_id(f); +} + +static size_t name_hash(struct fuse *f, fuse_ino_t parent, + const char *name) +{ + uint64_t hash = parent; + uint64_t oldhash; + + for (; *name; name++) + hash = hash * 31 + (unsigned char) *name; + + hash %= f->name_table.size; + oldhash = hash % (f->name_table.size / 2); + if (oldhash >= f->name_table.split) + return oldhash; + else + return hash; +} + +static void unref_node(struct fuse *f, struct node *node); + +static void remerge_name(struct fuse *f) +{ + struct node_table *t = &f->name_table; + int iter; + + if (t->split == 0) + node_table_reduce(t); + + for (iter = 8; t->split > 0 && iter; iter--) { + struct node **upper; + + t->split--; + upper = &t->array[t->split + t->size / 2]; + if (*upper) { + struct node **nodep; + + for (nodep = &t->array[t->split]; *nodep; + nodep = &(*nodep)->name_next); + + *nodep = *upper; + *upper = NULL; + break; + } + } +} + +static void unhash_name(struct fuse *f, struct node *node) +{ + if (node->name) { + size_t hash = name_hash(f, node->parent->nodeid, node->name); + struct node **nodep = &f->name_table.array[hash]; + + for (; *nodep != NULL; nodep = &(*nodep)->name_next) + if (*nodep == node) { + *nodep = node->name_next; + node->name_next = NULL; + unref_node(f, node->parent); + if (node->name != node->inline_name) + free(node->name); + node->name = NULL; + node->parent = NULL; + f->name_table.use--; + + if (f->name_table.use < f->name_table.size / 4) + remerge_name(f); + return; + } + fuse_log(FUSE_LOG_ERR, + "fuse internal error: unable to unhash node: %llu\n", + (unsigned long long) node->nodeid); + abort(); + } +} + +static void rehash_name(struct fuse *f) +{ + struct node_table *t = &f->name_table; + struct node **nodep; + struct node **next; + size_t hash; + + if (t->split == t->size / 2) + return; + + hash = t->split; + t->split++; + for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) { + struct node *node = *nodep; + size_t newhash = name_hash(f, node->parent->nodeid, node->name); + + if (newhash != hash) { + next = nodep; + *nodep = node->name_next; + node->name_next = t->array[newhash]; + t->array[newhash] = node; + } else { + next = &node->name_next; + } + } + if (t->split == t->size / 2) + node_table_resize(t); +} + +static int hash_name(struct fuse *f, struct node *node, fuse_ino_t parentid, + const char *name) +{ + size_t hash = name_hash(f, parentid, name); + struct node *parent = get_node(f, parentid); + if (strlen(name) < sizeof(node->inline_name)) { + strcpy(node->inline_name, name); + node->name = node->inline_name; + } else { + node->name = strdup(name); + if (node->name == NULL) + return -1; + } + + parent->refctr ++; + node->parent = parent; + node->name_next = f->name_table.array[hash]; + f->name_table.array[hash] = node; + f->name_table.use++; + + if (f->name_table.use >= f->name_table.size / 2) + rehash_name(f); + + return 0; +} + +static void delete_node(struct fuse *f, struct node *node) +{ + if (f->conf.debug) + fuse_log(FUSE_LOG_DEBUG, "DELETE: %llu\n", + (unsigned long long) node->nodeid); + + assert(node->treelock == 0); + unhash_name(f, node); + if (lru_enabled(f)) + remove_node_lru(node); + unhash_id(f, node); + free_node(f, node); +} + +static void unref_node(struct fuse *f, struct node *node) +{ + assert(node->refctr > 0); + node->refctr --; + if (!node->refctr) + delete_node(f, node); +} + +static fuse_ino_t next_id(struct fuse *f) +{ + do { + f->ctr = (f->ctr + 1) & 0xffffffff; + if (!f->ctr) + f->generation ++; + } while (f->ctr == 0 || f->ctr == FUSE_UNKNOWN_INO || + get_node_nocheck(f, f->ctr) != NULL); + return f->ctr; +} + +static struct node *lookup_node(struct fuse *f, fuse_ino_t parent, + const char *name) +{ + size_t hash = name_hash(f, parent, name); + struct node *node; + + for (node = f->name_table.array[hash]; node != NULL; node = node->name_next) + if (node->parent->nodeid == parent && + strcmp(node->name, name) == 0) + return node; + + return NULL; +} + +static void inc_nlookup(struct node *node) +{ + if (!node->nlookup) + node->refctr++; + node->nlookup++; +} + +static struct node *find_node(struct fuse *f, fuse_ino_t parent, + const char *name) +{ + struct node *node; + + pthread_mutex_lock(&f->lock); + if (!name) + node = get_node(f, parent); + else + node = lookup_node(f, parent, name); + if (node == NULL) { + node = alloc_node(f); + if (node == NULL) + goto out_err; + + node->nodeid = next_id(f); + node->generation = f->generation; + if (f->conf.remember) + inc_nlookup(node); + + if (hash_name(f, node, parent, name) == -1) { + free_node(f, node); + node = NULL; + goto out_err; + } + hash_id(f, node); + if (lru_enabled(f)) { + struct node_lru *lnode = node_lru(node); + init_list_head(&lnode->lru); + } + } else if (lru_enabled(f) && node->nlookup == 1) { + remove_node_lru(node); + } + inc_nlookup(node); +out_err: + pthread_mutex_unlock(&f->lock); + return node; +} + +static int lookup_path_in_cache(struct fuse *f, + const char *path, fuse_ino_t *inop) +{ + char *tmp = strdup(path); + if (!tmp) + return -ENOMEM; + + pthread_mutex_lock(&f->lock); + fuse_ino_t ino = FUSE_ROOT_ID; + + int err = 0; + char *save_ptr; + char *path_element = strtok_r(tmp, "/", &save_ptr); + while (path_element != NULL) { + struct node *node = lookup_node(f, ino, path_element); + if (node == NULL) { + err = -ENOENT; + break; + } + ino = node->nodeid; + path_element = strtok_r(NULL, "/", &save_ptr); + } + pthread_mutex_unlock(&f->lock); + free(tmp); + + if (!err) + *inop = ino; + return err; +} + +static char *add_name(char **buf, unsigned *bufsize, char *s, const char *name) +{ + size_t len = strlen(name); + + if (s - len <= *buf) { + unsigned pathlen = *bufsize - (s - *buf); + unsigned newbufsize = *bufsize; + char *newbuf; + + while (newbufsize < pathlen + len + 1) { + if (newbufsize >= 0x80000000) + newbufsize = 0xffffffff; + else + newbufsize *= 2; + } + + newbuf = realloc(*buf, newbufsize); + if (newbuf == NULL) + return NULL; + + *buf = newbuf; + s = newbuf + newbufsize - pathlen; + memmove(s, newbuf + *bufsize - pathlen, pathlen); + *bufsize = newbufsize; + } + s -= len; + memcpy(s, name, len); + s--; + *s = '/'; + + return s; +} + +static void unlock_path(struct fuse *f, fuse_ino_t nodeid, struct node *wnode, + struct node *end) +{ + struct node *node; + + if (wnode) { + assert(wnode->treelock == TREELOCK_WRITE); + wnode->treelock = 0; + } + + for (node = get_node(f, nodeid); + node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) { + assert(node->treelock != 0); + assert(node->treelock != TREELOCK_WAIT_OFFSET); + assert(node->treelock != TREELOCK_WRITE); + node->treelock--; + if (node->treelock == TREELOCK_WAIT_OFFSET) + node->treelock = 0; + } +} + +static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name, + char **path, struct node **wnodep, bool need_lock) +{ + unsigned bufsize = 256; + char *buf; + char *s; + struct node *node; + struct node *wnode = NULL; + int err; + + *path = NULL; + + err = -ENOMEM; + buf = malloc(bufsize); + if (buf == NULL) + goto out_err; + + s = buf + bufsize - 1; + *s = '\0'; + + if (name != NULL) { + s = add_name(&buf, &bufsize, s, name); + err = -ENOMEM; + if (s == NULL) + goto out_free; + } + + if (wnodep) { + assert(need_lock); + wnode = lookup_node(f, nodeid, name); + if (wnode) { + if (wnode->treelock != 0) { + if (wnode->treelock > 0) + wnode->treelock += TREELOCK_WAIT_OFFSET; + err = -EAGAIN; + goto out_free; + } + wnode->treelock = TREELOCK_WRITE; + } + } + + for (node = get_node(f, nodeid); node->nodeid != FUSE_ROOT_ID; + node = node->parent) { + err = -ESTALE; + if (node->name == NULL || node->parent == NULL) + goto out_unlock; + + err = -ENOMEM; + s = add_name(&buf, &bufsize, s, node->name); + if (s == NULL) + goto out_unlock; + + if (need_lock) { + err = -EAGAIN; + if (node->treelock < 0) + goto out_unlock; + + node->treelock++; + } + } + + if (s[0]) + memmove(buf, s, bufsize - (s - buf)); + else + strcpy(buf, "/"); + + *path = buf; + if (wnodep) + *wnodep = wnode; + + return 0; + + out_unlock: + if (need_lock) + unlock_path(f, nodeid, wnode, node); + out_free: + free(buf); + + out_err: + return err; +} + +static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1, + fuse_ino_t nodeid2, const char *name2, + char **path1, char **path2, + struct node **wnode1, struct node **wnode2) +{ + int err; + + /* FIXME: locking two paths needs deadlock checking */ + err = try_get_path(f, nodeid1, name1, path1, wnode1, true); + if (!err) { + err = try_get_path(f, nodeid2, name2, path2, wnode2, true); + if (err) { + struct node *wn1 = wnode1 ? *wnode1 : NULL; + + unlock_path(f, nodeid1, wn1, NULL); + free(*path1); + } + } + return err; +} + +static void queue_element_wakeup(struct fuse *f, struct lock_queue_element *qe) +{ + int err; + + if (!qe->path1) { + /* Just waiting for it to be unlocked */ + if (get_node(f, qe->nodeid1)->treelock == 0) + pthread_cond_signal(&qe->cond); + + return; + } + + if (qe->done) + return; // Don't try to double-lock the element + + if (!qe->path2) { + err = try_get_path(f, qe->nodeid1, qe->name1, qe->path1, + qe->wnode1, true); + } else { + err = try_get_path2(f, qe->nodeid1, qe->name1, qe->nodeid2, + qe->name2, qe->path1, qe->path2, qe->wnode1, + qe->wnode2); + } + + if (err == -EAGAIN) + return; /* keep trying */ + + qe->err = err; + qe->done = true; + pthread_cond_signal(&qe->cond); +} + +static void wake_up_queued(struct fuse *f) +{ + struct lock_queue_element *qe; + + for (qe = f->lockq; qe != NULL; qe = qe->next) + queue_element_wakeup(f, qe); +} + +static void debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid, + const char *name, bool wr) +{ + if (f->conf.debug) { + struct node *wnode = NULL; + + if (wr) + wnode = lookup_node(f, nodeid, name); + + if (wnode) { + fuse_log(FUSE_LOG_DEBUG, "%s %llu (w)\n", + msg, (unsigned long long) wnode->nodeid); + } else { + fuse_log(FUSE_LOG_DEBUG, "%s %llu\n", + msg, (unsigned long long) nodeid); + } + } +} + +static void queue_path(struct fuse *f, struct lock_queue_element *qe) +{ + struct lock_queue_element **qp; + + qe->done = false; + pthread_cond_init(&qe->cond, NULL); + qe->next = NULL; + for (qp = &f->lockq; *qp != NULL; qp = &(*qp)->next); + *qp = qe; +} + +static void dequeue_path(struct fuse *f, struct lock_queue_element *qe) +{ + struct lock_queue_element **qp; + + pthread_cond_destroy(&qe->cond); + for (qp = &f->lockq; *qp != qe; qp = &(*qp)->next); + *qp = qe->next; +} + +static int wait_path(struct fuse *f, struct lock_queue_element *qe) +{ + queue_path(f, qe); + + do { + pthread_cond_wait(&qe->cond, &f->lock); + } while (!qe->done); + + dequeue_path(f, qe); + + return qe->err; +} + +static int get_path_common(struct fuse *f, fuse_ino_t nodeid, const char *name, + char **path, struct node **wnode) +{ + int err; + + pthread_mutex_lock(&f->lock); + err = try_get_path(f, nodeid, name, path, wnode, true); + if (err == -EAGAIN) { + struct lock_queue_element qe = { + .nodeid1 = nodeid, + .name1 = name, + .path1 = path, + .wnode1 = wnode, + }; + debug_path(f, "QUEUE PATH", nodeid, name, !!wnode); + err = wait_path(f, &qe); + debug_path(f, "DEQUEUE PATH", nodeid, name, !!wnode); + } + pthread_mutex_unlock(&f->lock); + + return err; +} + +static int get_path(struct fuse *f, fuse_ino_t nodeid, char **path) +{ + return get_path_common(f, nodeid, NULL, path, NULL); +} + +static int get_path_nullok(struct fuse *f, fuse_ino_t nodeid, char **path) +{ + int err = 0; + + if (f->conf.nullpath_ok) { + *path = NULL; + } else { + err = get_path_common(f, nodeid, NULL, path, NULL); + if (err == -ESTALE) + err = 0; + } + + return err; +} + +static int get_path_name(struct fuse *f, fuse_ino_t nodeid, const char *name, + char **path) +{ + return get_path_common(f, nodeid, name, path, NULL); +} + +static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name, + char **path, struct node **wnode) +{ + return get_path_common(f, nodeid, name, path, wnode); +} + +#if defined(__FreeBSD__) +#define CHECK_DIR_LOOP +#endif + +#if defined(CHECK_DIR_LOOP) +static int check_dir_loop(struct fuse *f, + fuse_ino_t nodeid1, const char *name1, + fuse_ino_t nodeid2, const char *name2) +{ + struct node *node, *node1, *node2; + fuse_ino_t id1, id2; + + node1 = lookup_node(f, nodeid1, name1); + id1 = node1 ? node1->nodeid : nodeid1; + + node2 = lookup_node(f, nodeid2, name2); + id2 = node2 ? node2->nodeid : nodeid2; + + for (node = get_node(f, id2); node->nodeid != FUSE_ROOT_ID; + node = node->parent) { + if (node->name == NULL || node->parent == NULL) + break; + + if (node->nodeid != id2 && node->nodeid == id1) + return -EINVAL; + } + + if (node2) + { + for (node = get_node(f, id1); node->nodeid != FUSE_ROOT_ID; + node = node->parent) { + if (node->name == NULL || node->parent == NULL) + break; + + if (node->nodeid != id1 && node->nodeid == id2) + return -ENOTEMPTY; + } + } + + return 0; +} +#endif + +static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1, + fuse_ino_t nodeid2, const char *name2, + char **path1, char **path2, + struct node **wnode1, struct node **wnode2) +{ + int err; + + pthread_mutex_lock(&f->lock); + +#if defined(CHECK_DIR_LOOP) + if (name1) + { + // called during rename; perform dir loop check + err = check_dir_loop(f, nodeid1, name1, nodeid2, name2); + if (err) + goto out_unlock; + } +#endif + + err = try_get_path2(f, nodeid1, name1, nodeid2, name2, + path1, path2, wnode1, wnode2); + if (err == -EAGAIN) { + struct lock_queue_element qe = { + .nodeid1 = nodeid1, + .name1 = name1, + .path1 = path1, + .wnode1 = wnode1, + .nodeid2 = nodeid2, + .name2 = name2, + .path2 = path2, + .wnode2 = wnode2, + }; + + debug_path(f, "QUEUE PATH1", nodeid1, name1, !!wnode1); + debug_path(f, " PATH2", nodeid2, name2, !!wnode2); + err = wait_path(f, &qe); + debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1); + debug_path(f, " PATH2", nodeid2, name2, !!wnode2); + } + +#if defined(CHECK_DIR_LOOP) +out_unlock: +#endif + pthread_mutex_unlock(&f->lock); + + return err; +} + +static void free_path_wrlock(struct fuse *f, fuse_ino_t nodeid, + struct node *wnode, char *path) +{ + pthread_mutex_lock(&f->lock); + unlock_path(f, nodeid, wnode, NULL); + if (f->lockq) + wake_up_queued(f); + pthread_mutex_unlock(&f->lock); + free(path); +} + +static void free_path(struct fuse *f, fuse_ino_t nodeid, char *path) +{ + if (path) + free_path_wrlock(f, nodeid, NULL, path); +} + +static void free_path2(struct fuse *f, fuse_ino_t nodeid1, fuse_ino_t nodeid2, + struct node *wnode1, struct node *wnode2, + char *path1, char *path2) +{ + pthread_mutex_lock(&f->lock); + unlock_path(f, nodeid1, wnode1, NULL); + unlock_path(f, nodeid2, wnode2, NULL); + wake_up_queued(f); + pthread_mutex_unlock(&f->lock); + free(path1); + free(path2); +} + +static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup) +{ + struct node *node; + if (nodeid == FUSE_ROOT_ID) + return; + pthread_mutex_lock(&f->lock); + node = get_node(f, nodeid); + + /* + * Node may still be locked due to interrupt idiocy in open, + * create and opendir + */ + while (node->nlookup == nlookup && node->treelock) { + struct lock_queue_element qe = { + .nodeid1 = nodeid, + }; + + debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false); + queue_path(f, &qe); + + do { + pthread_cond_wait(&qe.cond, &f->lock); + } while (node->nlookup == nlookup && node->treelock); + + dequeue_path(f, &qe); + debug_path(f, "DEQUEUE_PATH (forget)", nodeid, NULL, false); + } + + assert(node->nlookup >= nlookup); + node->nlookup -= nlookup; + if (!node->nlookup) { + unref_node(f, node); + } else if (lru_enabled(f) && node->nlookup == 1) { + set_forget_time(f, node); + } + pthread_mutex_unlock(&f->lock); +} + +static void unlink_node(struct fuse *f, struct node *node) +{ + if (f->conf.remember) { + assert(node->nlookup > 1); + node->nlookup--; + } + unhash_name(f, node); +} + +static void remove_node(struct fuse *f, fuse_ino_t dir, const char *name) +{ + struct node *node; + + pthread_mutex_lock(&f->lock); + node = lookup_node(f, dir, name); + if (node != NULL) + unlink_node(f, node); + pthread_mutex_unlock(&f->lock); +} + +static int rename_node(struct fuse *f, fuse_ino_t olddir, const char *oldname, + fuse_ino_t newdir, const char *newname, int hide) +{ + struct node *node; + struct node *newnode; + int err = 0; + + pthread_mutex_lock(&f->lock); + node = lookup_node(f, olddir, oldname); + newnode = lookup_node(f, newdir, newname); + if (node == NULL) + goto out; + + if (newnode != NULL) { + if (hide) { + fuse_log(FUSE_LOG_ERR, "fuse: hidden file got created during hiding\n"); + err = -EBUSY; + goto out; + } + unlink_node(f, newnode); + } + + unhash_name(f, node); + if (hash_name(f, node, newdir, newname) == -1) { + err = -ENOMEM; + goto out; + } + + if (hide) + node->is_hidden = 1; + +out: + pthread_mutex_unlock(&f->lock); + return err; +} + +static int exchange_node(struct fuse *f, fuse_ino_t olddir, const char *oldname, + fuse_ino_t newdir, const char *newname) +{ + struct node *oldnode; + struct node *newnode; + int err; + + pthread_mutex_lock(&f->lock); + oldnode = lookup_node(f, olddir, oldname); + newnode = lookup_node(f, newdir, newname); + + if (oldnode) + unhash_name(f, oldnode); + if (newnode) + unhash_name(f, newnode); + + err = -ENOMEM; + if (oldnode) { + if (hash_name(f, oldnode, newdir, newname) == -1) + goto out; + } + if (newnode) { + if (hash_name(f, newnode, olddir, oldname) == -1) + goto out; + } + err = 0; +out: + pthread_mutex_unlock(&f->lock); + return err; +} + +static void set_stat(struct fuse *f, fuse_ino_t nodeid, struct stat *stbuf) +{ + if (!f->conf.use_ino) + stbuf->st_ino = nodeid; + if (f->conf.set_mode) { + if (f->conf.dmask && S_ISDIR(stbuf->st_mode)) + stbuf->st_mode = (stbuf->st_mode & S_IFMT) | + (0777 & ~f->conf.dmask); + else if (f->conf.fmask) + stbuf->st_mode = (stbuf->st_mode & S_IFMT) | + (0777 & ~f->conf.fmask); + else + stbuf->st_mode = (stbuf->st_mode & S_IFMT) | + (0777 & ~f->conf.umask); + } + if (f->conf.set_uid) + stbuf->st_uid = f->conf.uid; + if (f->conf.set_gid) + stbuf->st_gid = f->conf.gid; +} + +#ifdef HAVE_STATX +static void set_statx(struct fuse *f, fuse_ino_t nodeid, struct statx *stxbuf) +{ + if (!f->conf.use_ino) + stxbuf->stx_ino = nodeid; + if (f->conf.set_mode) { + if (f->conf.dmask && S_ISDIR(stxbuf->stx_mode)) + stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) | + (0777 & ~f->conf.dmask); + else if (f->conf.fmask) + stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) | + (0777 & ~f->conf.fmask); + else + stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) | + (0777 & ~f->conf.umask); + } + if (f->conf.set_uid) + stxbuf->stx_uid = f->conf.uid; + if (f->conf.set_gid) + stxbuf->stx_gid = f->conf.gid; +} +#endif + +static struct fuse *req_fuse(fuse_req_t req) +{ + return (struct fuse *) fuse_req_userdata(req); +} + +static void fuse_intr_sighandler(int sig) +{ + (void) sig; + /* Nothing to do */ +} + +struct fuse_intr_data { + pthread_t id; + pthread_cond_t cond; + int finished; +}; + +static void fuse_interrupt(fuse_req_t req, void *d_) +{ + struct fuse_intr_data *d = d_; + struct fuse *f = req_fuse(req); + + if (d->id == pthread_self()) + return; + + pthread_mutex_lock(&f->lock); + while (!d->finished) { + struct timeval now; + struct timespec timeout; + + pthread_kill(d->id, f->conf.intr_signal); + gettimeofday(&now, NULL); + timeout.tv_sec = now.tv_sec + 1; + timeout.tv_nsec = now.tv_usec * 1000; + pthread_cond_timedwait(&d->cond, &f->lock, &timeout); + } + pthread_mutex_unlock(&f->lock); +} + +static void fuse_do_finish_interrupt(struct fuse *f, fuse_req_t req, + struct fuse_intr_data *d) +{ + pthread_mutex_lock(&f->lock); + d->finished = 1; + pthread_cond_broadcast(&d->cond); + pthread_mutex_unlock(&f->lock); + fuse_req_interrupt_func(req, NULL, NULL); + pthread_cond_destroy(&d->cond); +} + +static void fuse_do_prepare_interrupt(fuse_req_t req, struct fuse_intr_data *d) +{ + d->id = pthread_self(); + pthread_cond_init(&d->cond, NULL); + d->finished = 0; + fuse_req_interrupt_func(req, fuse_interrupt, d); +} + +static inline void fuse_finish_interrupt(struct fuse *f, fuse_req_t req, + struct fuse_intr_data *d) +{ + if (f->conf.intr) + fuse_do_finish_interrupt(f, req, d); +} + +static inline void fuse_prepare_interrupt(struct fuse *f, fuse_req_t req, + struct fuse_intr_data *d) +{ + if (f->conf.intr) + fuse_do_prepare_interrupt(req, d); +} + +static const char* file_info_string(struct fuse_file_info *fi, + char* buf, size_t len) +{ + if(fi == NULL) + return "NULL"; + snprintf(buf, len, "%llu", (unsigned long long) fi->fh); + return buf; +} + +int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.getattr) + return -ENOSYS; + + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "getattr[%s] %s\n", + file_info_string(fi, buf, sizeof(buf)), + path); + } + return fs->op.getattr(path, buf, fi); +} + +int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath, + const char *newpath, unsigned int flags) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.rename) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "rename %s %s 0x%x\n", oldpath, newpath, + flags); + + return fs->op.rename(oldpath, newpath, flags); +} + +int fuse_fs_unlink(struct fuse_fs *fs, const char *path) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.unlink) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "unlink %s\n", path); + + return fs->op.unlink(path); +} + +int fuse_fs_rmdir(struct fuse_fs *fs, const char *path) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.rmdir) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "rmdir %s\n", path); + + return fs->op.rmdir(path); +} + +int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.symlink) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "symlink %s %s\n", linkname, path); + + return fs->op.symlink(linkname, path); +} + +int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.link) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "link %s %s\n", oldpath, newpath); + + return fs->op.link(oldpath, newpath); +} + +int fuse_fs_release(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.release) + return 0; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "release%s[%llu] flags: 0x%x\n", + fi->flush ? "+flush" : "", + (unsigned long long) fi->fh, fi->flags); + + return fs->op.release(path, fi); +} + +int fuse_fs_opendir(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + int err; + + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.opendir) + return 0; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "opendir flags: 0x%x %s\n", fi->flags, + path); + + err = fs->op.opendir(path, fi); + + if (fs->debug && !err) + fuse_log(FUSE_LOG_DEBUG, " opendir[%llu] flags: 0x%x %s\n", + (unsigned long long) fi->fh, fi->flags, path); + + return err; +} + +int fuse_fs_open(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + int err; + + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.open) + return 0; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "open flags: 0x%x %s\n", fi->flags, + path); + + err = fs->op.open(path, fi); + + if (fs->debug && !err) + fuse_log(FUSE_LOG_DEBUG, " open[%llu] flags: 0x%x %s\n", + (unsigned long long) fi->fh, fi->flags, path); + + return err; +} + +static void fuse_free_buf(struct fuse_bufvec *buf) +{ + if (buf != NULL) { + size_t i; + + for (i = 0; i < buf->count; i++) + if (!(buf->buf[i].flags & FUSE_BUF_IS_FD)) + free(buf->buf[i].mem); + free(buf); + } +} + +int fuse_fs_read_buf(struct fuse_fs *fs, const char *path, + struct fuse_bufvec **bufp, size_t size, off_t off, + struct fuse_file_info *fi) +{ + int res; + + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.read && !fs->op.read_buf) + return -ENOSYS; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, + "read[%llu] %zu bytes from %llu flags: 0x%x\n", + (unsigned long long) fi->fh, + size, (unsigned long long) off, fi->flags); + + if (fs->op.read_buf) { + res = fs->op.read_buf(path, bufp, size, off, fi); + } else { + struct fuse_bufvec *buf; + void *mem; + + buf = malloc(sizeof(struct fuse_bufvec)); + if (buf == NULL) + return -ENOMEM; + + mem = malloc(size); + if (mem == NULL) { + free(buf); + return -ENOMEM; + } + *buf = FUSE_BUFVEC_INIT(size); + buf->buf[0].mem = mem; + *bufp = buf; + + res = fs->op.read(path, mem, size, off, fi); + if (res >= 0) + buf->buf[0].size = res; + } + + if (fs->debug && res >= 0) + fuse_log(FUSE_LOG_DEBUG, " read[%llu] %zu bytes from %llu\n", + (unsigned long long) fi->fh, + fuse_buf_size(*bufp), + (unsigned long long) off); + if (res >= 0 && fuse_buf_size(*bufp) > size) + fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n"); + + if (res < 0) + return res; + + return 0; +} + +int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size, + off_t off, struct fuse_file_info *fi) +{ + int res; + + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.read && !fs->op.read_buf) + return -ENOSYS; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, + "read[%llu] %zu bytes from %llu flags: 0x%x\n", + (unsigned long long) fi->fh, + size, (unsigned long long) off, fi->flags); + + if (fs->op.read_buf) { + struct fuse_bufvec *buf = NULL; + + res = fs->op.read_buf(path, &buf, size, off, fi); + if (res == 0) { + struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size); + + dst.buf[0].mem = mem; + res = fuse_buf_copy(&dst, buf, 0); + } + fuse_free_buf(buf); + } else { + res = fs->op.read(path, mem, size, off, fi); + } + + if (fs->debug && res >= 0) + fuse_log(FUSE_LOG_DEBUG, " read[%llu] %u bytes from %llu\n", + (unsigned long long) fi->fh, + res, + (unsigned long long) off); + if (res >= 0 && res > (int) size) + fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n"); + + return res; +} + +int fuse_fs_write_buf(struct fuse_fs *fs, const char *path, + struct fuse_bufvec *buf, off_t off, + struct fuse_file_info *fi) +{ + int res; + size_t size; + + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.write_buf && !fs->op.write) + return -ENOSYS; + + size = fuse_buf_size(buf); + assert(buf->idx == 0 && buf->off == 0); + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, + "write%s[%llu] %zu bytes to %llu flags: 0x%x\n", + fi->writepage ? "page" : "", + (unsigned long long) fi->fh, + size, + (unsigned long long) off, + fi->flags); + + if (fs->op.write_buf) { + res = fs->op.write_buf(path, buf, off, fi); + } else { + void *mem = NULL; + struct fuse_buf *flatbuf; + struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size); + + if (buf->count == 1 && + !(buf->buf[0].flags & FUSE_BUF_IS_FD)) { + flatbuf = &buf->buf[0]; + } else { + res = -ENOMEM; + mem = malloc(size); + if (mem == NULL) + goto out; + + tmp.buf[0].mem = mem; + res = fuse_buf_copy(&tmp, buf, 0); + if (res <= 0) + goto out_free; + + tmp.buf[0].size = res; + flatbuf = &tmp.buf[0]; + } + + res = fs->op.write(path, flatbuf->mem, flatbuf->size, + off, fi); +out_free: + free(mem); + } +out: + if (fs->debug && res >= 0) + fuse_log(FUSE_LOG_DEBUG, " write%s[%llu] %u bytes to %llu\n", + fi->writepage ? "page" : "", + (unsigned long long) fi->fh, res, + (unsigned long long) off); + if (res > (int) size) + fuse_log(FUSE_LOG_ERR, "fuse: wrote too many bytes\n"); + + return res; +} + +int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem, + size_t size, off_t off, struct fuse_file_info *fi) +{ + struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(size); + + bufv.buf[0].mem = (void *) mem; + + return fuse_fs_write_buf(fs, path, &bufv, off, fi); +} + +int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.fsync) + return -ENOSYS; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "fsync[%llu] datasync: %i\n", + (unsigned long long) fi->fh, datasync); + + return fs->op.fsync(path, datasync, fi); +} + +int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.fsyncdir) + return -ENOSYS; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "fsyncdir[%llu] datasync: %i\n", + (unsigned long long) fi->fh, datasync); + + return fs->op.fsyncdir(path, datasync, fi); +} + +int fuse_fs_flush(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.flush) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "flush[%llu]\n", + (unsigned long long) fi->fh); + + return fs->op.flush(path, fi); +} + +int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.statfs) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "statfs %s\n", path); + + return fs->op.statfs(path, buf); + } else { + buf->f_namemax = 255; + buf->f_bsize = 512; + return 0; + } +} + +int fuse_fs_releasedir(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.releasedir) + return 0; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "releasedir[%llu] flags: 0x%x\n", + (unsigned long long) fi->fh, fi->flags); + + return fs->op.releasedir(path, fi); +} + +int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf, + fuse_fill_dir_t filler, off_t off, + struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.readdir) + return -ENOSYS; + if (fs->debug) { + fuse_log(FUSE_LOG_DEBUG, "readdir%s[%llu] from %llu\n", + (flags & FUSE_READDIR_PLUS) ? "plus" : "", + (unsigned long long) fi->fh, + (unsigned long long) off); + } + + return fs->op.readdir(path, buf, filler, off, fi, flags); +} + +int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + int err; + + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.create) + return -ENOSYS; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, + "create flags: 0x%x %s 0%o umask=0%03o\n", + fi->flags, path, mode, + fuse_get_context()->umask); + + err = fs->op.create(path, mode, fi); + + if (fs->debug && !err) + fuse_log(FUSE_LOG_DEBUG, " create[%llu] flags: 0x%x %s\n", + (unsigned long long) fi->fh, fi->flags, path); + + return err; +} + +int fuse_fs_lock(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, int cmd, struct flock *lock) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.lock) + return -ENOSYS; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n", + (unsigned long long) fi->fh, + (cmd == F_GETLK ? "F_GETLK" : + (cmd == F_SETLK ? "F_SETLK" : + (cmd == F_SETLKW ? "F_SETLKW" : "???"))), + (lock->l_type == F_RDLCK ? "F_RDLCK" : + (lock->l_type == F_WRLCK ? "F_WRLCK" : + (lock->l_type == F_UNLCK ? "F_UNLCK" : + "???"))), + (unsigned long long) lock->l_start, + (unsigned long long) lock->l_len, + (unsigned long long) lock->l_pid); + + return fs->op.lock(path, fi, cmd, lock); +} + +int fuse_fs_flock(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, int op) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.flock) + return -ENOSYS; + + if (fs->debug) { + int xop = op & ~LOCK_NB; + + fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s%s\n", + (unsigned long long) fi->fh, + xop == LOCK_SH ? "LOCK_SH" : + (xop == LOCK_EX ? "LOCK_EX" : + (xop == LOCK_UN ? "LOCK_UN" : "???")), + (op & LOCK_NB) ? "|LOCK_NB" : ""); + } + return fs->op.flock(path, fi, op); +} + +int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, + gid_t gid, struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.chown) + return -ENOSYS; + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "chown[%s] %s %lu %lu\n", + file_info_string(fi, buf, sizeof(buf)), + path, (unsigned long) uid, (unsigned long) gid); + } + return fs->op.chown(path, uid, gid, fi); +} + +int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.truncate) + return -ENOSYS; + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "truncate[%s] %llu\n", + file_info_string(fi, buf, sizeof(buf)), + (unsigned long long) size); + } + return fs->op.truncate(path, size, fi); +} + +int fuse_fs_utimens(struct fuse_fs *fs, const char *path, + const struct timespec tv[2], struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.utimens) + return -ENOSYS; + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "utimens[%s] %s %jd.%09ld %jd.%09ld\n", + file_info_string(fi, buf, sizeof(buf)), + path, (intmax_t)tv[0].tv_sec, tv[0].tv_nsec, + (intmax_t)tv[1].tv_sec, tv[1].tv_nsec); + } + return fs->op.utimens(path, tv, fi); +} + +int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.access) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "access %s 0%o\n", path, mask); + + return fs->op.access(path, mask); +} + +int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf, + size_t len) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.readlink) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "readlink %s %lu\n", path, + (unsigned long) len); + + return fs->op.readlink(path, buf, len); +} + +int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode, + dev_t rdev) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.mknod) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "mknod %s 0%o 0x%llx umask=0%03o\n", + path, mode, (unsigned long long) rdev, + fuse_get_context()->umask); + + return fs->op.mknod(path, mode, rdev); +} + +int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.mkdir) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "mkdir %s 0%o umask=0%03o\n", + path, mode, fuse_get_context()->umask); + + return fs->op.mkdir(path, mode); +} + +int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name, + const char *value, size_t size, int flags) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.setxattr) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "setxattr %s %s %lu 0x%x\n", + path, name, (unsigned long) size, flags); + + return fs->op.setxattr(path, name, value, size, flags); +} + +int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name, + char *value, size_t size) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.getxattr) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "getxattr %s %s %lu\n", + path, name, (unsigned long) size); + + return fs->op.getxattr(path, name, value, size); +} + +int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list, + size_t size) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.listxattr) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "listxattr %s %lu\n", + path, (unsigned long) size); + + return fs->op.listxattr(path, list, size); +} + +int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize, + uint64_t *idx) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.bmap) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "bmap %s blocksize: %lu index: %llu\n", + path, (unsigned long) blocksize, + (unsigned long long) *idx); + + return fs->op.bmap(path, blocksize, idx); +} + +int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.removexattr) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "removexattr %s %s\n", path, name); + + return fs->op.removexattr(path, name); +} + +int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd, + void *arg, struct fuse_file_info *fi, unsigned int flags, + void *data) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.ioctl) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "ioctl[%llu] 0x%x flags: 0x%x\n", + (unsigned long long) fi->fh, cmd, flags); + + return fs->op.ioctl(path, cmd, arg, fi, flags, data); +} + +int fuse_fs_poll(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, struct fuse_pollhandle *ph, + unsigned *reventsp) +{ + int res; + + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.poll) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "poll[%llu] ph: %p, events 0x%x\n", + (unsigned long long) fi->fh, ph, + fi->poll_events); + + res = fs->op.poll(path, fi, ph, reventsp); + + if (fs->debug && !res) + fuse_log(FUSE_LOG_DEBUG, " poll[%llu] revents: 0x%x\n", + (unsigned long long) fi->fh, *reventsp); + + return res; +} + +int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode, + off_t offset, off_t length, struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.fallocate) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "fallocate %s mode %x, offset: %llu, length: %llu\n", + path, + mode, + (unsigned long long) offset, + (unsigned long long) length); + + return fs->op.fallocate(path, mode, offset, length, fi); +} + +ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in, + struct fuse_file_info *fi_in, off_t off_in, + const char *path_out, + struct fuse_file_info *fi_out, off_t off_out, + size_t len, int flags) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.copy_file_range) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "copy_file_range from %s:%llu to " + "%s:%llu, length: %llu\n", + path_in, + (unsigned long long) off_in, + path_out, + (unsigned long long) off_out, + (unsigned long long) len); + + return fs->op.copy_file_range(path_in, fi_in, off_in, path_out, + fi_out, off_out, len, flags); +} + +off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.lseek) + return -ENOSYS; + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "lseek[%s] %llu %d\n", + file_info_string(fi, buf, sizeof(buf)), + (unsigned long long) off, whence); + } + return fs->op.lseek(path, off, whence, fi); +} + +#ifdef HAVE_STATX +int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask, + struct statx *stxbuf, struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.statx) { + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "statx[%s] %s %d %d\n", + file_info_string(fi, buf, sizeof(buf)), path, + flags, mask); + } + return fs->op.statx(path, flags, mask, stxbuf, fi); + } + + return -ENOSYS; +} +#else +int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask, + struct statx *stxbuf, struct fuse_file_info *fi) +{ + (void)fs; + (void)path; + (void)flags; + (void)mask; + (void)stxbuf; + (void)fi; + + return -ENOSYS; +} +#endif + +static int is_open(struct fuse *f, fuse_ino_t dir, const char *name) +{ + struct node *node; + int isopen = 0; + pthread_mutex_lock(&f->lock); + node = lookup_node(f, dir, name); + if (node && node->open_count > 0) + isopen = 1; + pthread_mutex_unlock(&f->lock); + return isopen; +} + +static char *hidden_name(struct fuse *f, fuse_ino_t dir, const char *oldname, + char *newname, size_t bufsize) +{ + struct stat buf; + struct node *node; + struct node *newnode; + char *newpath; + int res; + int failctr = 10; + + do { + pthread_mutex_lock(&f->lock); + node = lookup_node(f, dir, oldname); + if (node == NULL) { + pthread_mutex_unlock(&f->lock); + return NULL; + } + do { + f->hidectr ++; + snprintf(newname, bufsize, ".fuse_hidden%08x%08x", + (unsigned int) node->nodeid, f->hidectr); + newnode = lookup_node(f, dir, newname); + } while(newnode); + + res = try_get_path(f, dir, newname, &newpath, NULL, false); + pthread_mutex_unlock(&f->lock); + if (res) + break; + + memset(&buf, 0, sizeof(buf)); + res = fuse_fs_getattr(f->fs, newpath, &buf, NULL); + if (res == -ENOENT) + break; + free(newpath); + newpath = NULL; + } while(res == 0 && --failctr); + + return newpath; +} + +static int hide_node(struct fuse *f, const char *oldpath, + fuse_ino_t dir, const char *oldname) +{ + char newname[64]; + char *newpath; + int err = -EBUSY; + + newpath = hidden_name(f, dir, oldname, newname, sizeof(newname)); + if (newpath) { + err = fuse_fs_rename(f->fs, oldpath, newpath, 0); + if (!err) + err = rename_node(f, dir, oldname, dir, newname, 1); + free(newpath); + } + return err; +} + +static int mtime_eq(const struct stat *stbuf, const struct timespec *ts) +{ + return stbuf->st_mtime == ts->tv_sec && + ST_MTIM_NSEC(stbuf) == ts->tv_nsec; +} + +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC CLOCK_REALTIME +#endif + +static void curr_time(struct timespec *now) +{ + static clockid_t clockid = CLOCK_MONOTONIC; + int res = clock_gettime(clockid, now); + if (res == -1 && errno == EINVAL) { + clockid = CLOCK_REALTIME; + res = clock_gettime(clockid, now); + } + if (res == -1) { + perror("fuse: clock_gettime"); + abort(); + } +} + +static void update_stat(struct node *node, const struct stat *stbuf) +{ + if (node->cache_valid && (!mtime_eq(stbuf, &node->mtime) || + stbuf->st_size != node->size)) + node->cache_valid = 0; + node->mtime.tv_sec = stbuf->st_mtime; + node->mtime.tv_nsec = ST_MTIM_NSEC(stbuf); + node->size = stbuf->st_size; + curr_time(&node->stat_updated); +} + +static int do_lookup(struct fuse *f, fuse_ino_t nodeid, const char *name, + struct fuse_entry_param *e) +{ + struct node *node; + + node = find_node(f, nodeid, name); + if (node == NULL) + return -ENOMEM; + + e->ino = node->nodeid; + e->generation = node->generation; + e->entry_timeout = f->conf.entry_timeout; + e->attr_timeout = f->conf.attr_timeout; + if (f->conf.auto_cache) { + pthread_mutex_lock(&f->lock); + update_stat(node, &e->attr); + pthread_mutex_unlock(&f->lock); + } + set_stat(f, e->ino, &e->attr); + return 0; +} + +static int lookup_path(struct fuse *f, fuse_ino_t nodeid, + const char *name, const char *path, + struct fuse_entry_param *e, struct fuse_file_info *fi) +{ + int res; + + memset(e, 0, sizeof(struct fuse_entry_param)); + res = fuse_fs_getattr(f->fs, path, &e->attr, fi); + if (res == 0) { + res = do_lookup(f, nodeid, name, e); + if (res == 0 && f->conf.debug) { + fuse_log(FUSE_LOG_DEBUG, " NODEID: %llu\n", + (unsigned long long) e->ino); + } + } + return res; +} + +static struct fuse_context_i *fuse_get_context_internal(void) +{ + return (struct fuse_context_i *) pthread_getspecific(fuse_context_key); +} + +static struct fuse_context_i *fuse_create_context(struct fuse *f) +{ + struct fuse_context_i *c = fuse_get_context_internal(); + if (c == NULL) { + c = (struct fuse_context_i *) + calloc(1, sizeof(struct fuse_context_i)); + if (c == NULL) { + /* This is hard to deal with properly, so just + abort. If memory is so low that the + context cannot be allocated, there's not + much hope for the filesystem anyway */ + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate thread specific data\n"); + abort(); + } + pthread_setspecific(fuse_context_key, c); + } else { + memset(c, 0, sizeof(*c)); + } + c->ctx.fuse = f; + + return c; +} + +static void fuse_freecontext(void *data) +{ + free(data); +} + +static int fuse_create_context_key(void) +{ + int err = 0; + pthread_mutex_lock(&fuse_context_lock); + if (!fuse_context_ref) { + err = pthread_key_create(&fuse_context_key, fuse_freecontext); + if (err) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n", + strerror(err)); + pthread_mutex_unlock(&fuse_context_lock); + return -1; + } + } + fuse_context_ref++; + pthread_mutex_unlock(&fuse_context_lock); + return 0; +} + +static void fuse_delete_context_key(void) +{ + pthread_mutex_lock(&fuse_context_lock); + fuse_context_ref--; + if (!fuse_context_ref) { + free(pthread_getspecific(fuse_context_key)); + pthread_key_delete(fuse_context_key); + } + pthread_mutex_unlock(&fuse_context_lock); +} + +static struct fuse *req_fuse_prepare(fuse_req_t req) +{ + struct fuse_context_i *c = fuse_create_context(req_fuse(req)); + const struct fuse_ctx *ctx = fuse_req_ctx(req); + c->req = req; + c->ctx.uid = ctx->uid; + c->ctx.gid = ctx->gid; + c->ctx.pid = ctx->pid; + c->ctx.umask = ctx->umask; + return c->ctx.fuse; +} + +static inline void reply_err(fuse_req_t req, int err) +{ + /* fuse_reply_err() uses non-negated errno values */ + fuse_reply_err(req, -err); +} + +static void reply_entry(fuse_req_t req, const struct fuse_entry_param *e, + int err) +{ + if (!err) { + struct fuse *f = req_fuse(req); + if (fuse_reply_entry(req, e) == -ENOENT) { + /* Skip forget for negative result */ + if (e->ino != 0) + forget_node(f, e->ino, 1); + } + } else + reply_err(req, err); +} + +void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.write_buf) + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ); + if (!fs->op.lock) + fuse_unset_feature_flag(conn, FUSE_CAP_POSIX_LOCKS); + if (!fs->op.flock) + fuse_unset_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); + if (fs->op.init) + fs->user_data = fs->op.init(conn, cfg); +} + +static int fuse_init_intr_signal(int signum, int *installed); + +static void fuse_lib_init(void *data, struct fuse_conn_info *conn) +{ + struct fuse *f = (struct fuse *) data; + + fuse_create_context(f); + fuse_set_feature_flag(conn, FUSE_CAP_EXPORT_SUPPORT); + fuse_fs_init(f->fs, conn, &f->conf); + + if (f->conf.intr) { + if (fuse_init_intr_signal(f->conf.intr_signal, + &f->intr_installed) == -1) + fuse_log(FUSE_LOG_ERR, "fuse: failed to init interrupt signal\n"); + } else { + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; + } +} + +void fuse_fs_destroy(struct fuse_fs *fs) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.destroy) + fs->op.destroy(fs->user_data); +} + +static void fuse_lib_destroy(void *data) +{ + struct fuse *f = (struct fuse *) data; + + fuse_create_context(f); + fuse_fs_destroy(f->fs); +} + +static void fuse_lib_lookup(fuse_req_t req, fuse_ino_t parent, + const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e = { .ino = 0 }; /* invalid ino */ + char *path; + int err; + struct node *dot = NULL; + + if (name[0] == '.') { + int len = strlen(name); + + if (len == 1 || (name[1] == '.' && len == 2)) { + pthread_mutex_lock(&f->lock); + if (len == 1) { + if (f->conf.debug) + fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOT\n"); + dot = get_node_nocheck(f, parent); + if (dot == NULL) { + pthread_mutex_unlock(&f->lock); + reply_entry(req, &e, -ESTALE); + return; + } + dot->refctr++; + } else { + if (f->conf.debug) + fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOTDOT\n"); + parent = get_node(f, parent)->parent->nodeid; + } + pthread_mutex_unlock(&f->lock); + name = NULL; + } + } + + err = get_path_name(f, parent, name, &path); + if (!err) { + struct fuse_intr_data d; + if (f->conf.debug) + fuse_log(FUSE_LOG_DEBUG, "LOOKUP %s\n", path); + fuse_prepare_interrupt(f, req, &d); + err = lookup_path(f, parent, name, path, &e, NULL); + if (err == -ENOENT && f->conf.negative_timeout != 0.0) { + e.ino = 0; + e.entry_timeout = f->conf.negative_timeout; + err = 0; + } + fuse_finish_interrupt(f, req, &d); + free_path(f, parent, path); + } + if (dot) { + pthread_mutex_lock(&f->lock); + unref_node(f, dot); + pthread_mutex_unlock(&f->lock); + } + reply_entry(req, &e, err); +} + +static void do_forget(struct fuse *f, fuse_ino_t ino, uint64_t nlookup) +{ + if (f->conf.debug) + fuse_log(FUSE_LOG_DEBUG, "FORGET %llu/%llu\n", (unsigned long long)ino, + (unsigned long long) nlookup); + forget_node(f, ino, nlookup); +} + +static void fuse_lib_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + do_forget(req_fuse(req), ino, nlookup); + fuse_reply_none(req); +} + +static void fuse_lib_forget_multi(fuse_req_t req, size_t count, + struct fuse_forget_data *forgets) +{ + struct fuse *f = req_fuse(req); + size_t i; + + for (i = 0; i < count; i++) + do_forget(f, forgets[i].ino, forgets[i].nlookup); + + fuse_reply_none(req); +} + + +static void fuse_lib_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct stat buf; + char *path; + int err; + + memset(&buf, 0, sizeof(buf)); + + if (fi != NULL) + err = get_path_nullok(f, ino, &path); + else + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_getattr(f->fs, path, &buf, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) { + struct node *node; + + pthread_mutex_lock(&f->lock); + node = get_node(f, ino); + if (node->is_hidden && buf.st_nlink > 0) + buf.st_nlink--; + if (f->conf.auto_cache) + update_stat(node, &buf); + pthread_mutex_unlock(&f->lock); + set_stat(f, ino, &buf); + fuse_reply_attr(req, &buf, f->conf.attr_timeout); + } else + reply_err(req, err); +} + +int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.chmod) + return -ENOSYS; + + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "chmod[%s] %s %llo\n", + file_info_string(fi, buf, sizeof(buf)), + path, (unsigned long long) mode); + } + return fs->op.chmod(path, mode, fi); +} + +static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int valid, struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct stat buf; + char *path; + int err; + + memset(&buf, 0, sizeof(buf)); + if (fi != NULL) + err = get_path_nullok(f, ino, &path); + else + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = 0; + if (!err && (valid & FUSE_SET_ATTR_MODE)) + err = fuse_fs_chmod(f->fs, path, attr->st_mode, fi); + if (!err && (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))) { + uid_t uid = (valid & FUSE_SET_ATTR_UID) ? + attr->st_uid : (uid_t) -1; + gid_t gid = (valid & FUSE_SET_ATTR_GID) ? + attr->st_gid : (gid_t) -1; + err = fuse_fs_chown(f->fs, path, uid, gid, fi); + } + if (!err && (valid & FUSE_SET_ATTR_SIZE)) { + err = fuse_fs_truncate(f->fs, path, + attr->st_size, fi); + } +#ifdef HAVE_UTIMENSAT + if (!err && + (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) { + struct timespec tv[2]; + + tv[0].tv_sec = 0; + tv[1].tv_sec = 0; + tv[0].tv_nsec = UTIME_OMIT; + tv[1].tv_nsec = UTIME_OMIT; + + if (valid & FUSE_SET_ATTR_ATIME_NOW) + tv[0].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_ATIME) + tv[0] = attr->st_atim; + + if (valid & FUSE_SET_ATTR_MTIME_NOW) + tv[1].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_MTIME) + tv[1] = attr->st_mtim; + + err = fuse_fs_utimens(f->fs, path, tv, fi); + } else +#endif + if (!err && + (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) == + (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) { + struct timespec tv[2]; + tv[0].tv_sec = attr->st_atime; + tv[0].tv_nsec = ST_ATIM_NSEC(attr); + tv[1].tv_sec = attr->st_mtime; + tv[1].tv_nsec = ST_MTIM_NSEC(attr); + err = fuse_fs_utimens(f->fs, path, tv, fi); + } + if (!err) { + err = fuse_fs_getattr(f->fs, path, &buf, fi); + } + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) { + if (f->conf.auto_cache) { + pthread_mutex_lock(&f->lock); + update_stat(get_node(f, ino), &buf); + pthread_mutex_unlock(&f->lock); + } + set_stat(f, ino, &buf); + fuse_reply_attr(req, &buf, f->conf.attr_timeout); + } else + reply_err(req, err); +} + +static void fuse_lib_access(fuse_req_t req, fuse_ino_t ino, int mask) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_access(f->fs, path, mask); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static void fuse_lib_readlink(fuse_req_t req, fuse_ino_t ino) +{ + struct fuse *f = req_fuse_prepare(req); + char linkname[PATH_MAX + 1]; + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_readlink(f->fs, path, linkname, sizeof(linkname)); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) { + linkname[PATH_MAX] = '\0'; + fuse_reply_readlink(req, linkname); + } else + reply_err(req, err); +} + +static void fuse_lib_mknod(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, dev_t rdev) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e; + char *path; + int err; + + err = get_path_name(f, parent, name, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = -ENOSYS; + if (S_ISREG(mode)) { + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = O_CREAT | O_EXCL | O_WRONLY; + err = fuse_fs_create(f->fs, path, mode, &fi); + if (!err) { + err = lookup_path(f, parent, name, path, &e, + &fi); + fuse_fs_release(f->fs, path, &fi); + } + } + if (err == -ENOSYS) { + err = fuse_fs_mknod(f->fs, path, mode, rdev); + if (!err) + err = lookup_path(f, parent, name, path, &e, + NULL); + } + fuse_finish_interrupt(f, req, &d); + free_path(f, parent, path); + } + reply_entry(req, &e, err); +} + +static void fuse_lib_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e; + char *path; + int err; + + err = get_path_name(f, parent, name, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_mkdir(f->fs, path, mode); + if (!err) + err = lookup_path(f, parent, name, path, &e, NULL); + fuse_finish_interrupt(f, req, &d); + free_path(f, parent, path); + } + reply_entry(req, &e, err); +} + +static void fuse_lib_unlink(fuse_req_t req, fuse_ino_t parent, + const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + struct node *wnode; + char *path; + int err; + + err = get_path_wrlock(f, parent, name, &path, &wnode); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + if (!f->conf.hard_remove && is_open(f, parent, name)) { + err = hide_node(f, path, parent, name); + if (!err) { + /* we have hidden the node so now check again under a lock in case it is not used any more */ + if (!is_open(f, parent, wnode->name)) { + char *unlinkpath; + + /* get the hidden file path, to unlink it */ + if (try_get_path(f, wnode->nodeid, NULL, &unlinkpath, NULL, false) == 0) { + err = fuse_fs_unlink(f->fs, unlinkpath); + if (!err) + remove_node(f, parent, wnode->name); + free(unlinkpath); + } + } + } + } else { + err = fuse_fs_unlink(f->fs, path); + if (!err) + remove_node(f, parent, name); + } + fuse_finish_interrupt(f, req, &d); + free_path_wrlock(f, parent, wnode, path); + } + reply_err(req, err); +} + +static void fuse_lib_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + struct node *wnode; + char *path; + int err; + + err = get_path_wrlock(f, parent, name, &path, &wnode); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_rmdir(f->fs, path); + fuse_finish_interrupt(f, req, &d); + if (!err) + remove_node(f, parent, name); + free_path_wrlock(f, parent, wnode, path); + } + reply_err(req, err); +} + +static void fuse_lib_symlink(fuse_req_t req, const char *linkname, + fuse_ino_t parent, const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e; + char *path; + int err; + + err = get_path_name(f, parent, name, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_symlink(f->fs, linkname, path); + if (!err) + err = lookup_path(f, parent, name, path, &e, NULL); + fuse_finish_interrupt(f, req, &d); + free_path(f, parent, path); + } + reply_entry(req, &e, err); +} + +static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir, + const char *oldname, fuse_ino_t newdir, + const char *newname, unsigned int flags) +{ + struct fuse *f = req_fuse_prepare(req); + char *oldpath; + char *newpath; + struct node *wnode1; + struct node *wnode2; + int err; + + err = get_path2(f, olddir, oldname, newdir, newname, + &oldpath, &newpath, &wnode1, &wnode2); + if (!err) { + struct fuse_intr_data d; + err = 0; + fuse_prepare_interrupt(f, req, &d); + if (!f->conf.hard_remove && !(flags & RENAME_EXCHANGE) && + is_open(f, newdir, newname)) + err = hide_node(f, newpath, newdir, newname); + if (!err) { + err = fuse_fs_rename(f->fs, oldpath, newpath, flags); + if (!err) { + if (flags & RENAME_EXCHANGE) { + err = exchange_node(f, olddir, oldname, + newdir, newname); + } else { + err = rename_node(f, olddir, oldname, + newdir, newname, 0); + } + } + } + fuse_finish_interrupt(f, req, &d); + free_path2(f, olddir, newdir, wnode1, wnode2, oldpath, newpath); + } + reply_err(req, err); +} + +static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, + const char *newname) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e; + char *oldpath; + char *newpath; + int err; + + err = get_path2(f, ino, NULL, newparent, newname, + &oldpath, &newpath, NULL, NULL); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_link(f->fs, oldpath, newpath); + if (!err) + err = lookup_path(f, newparent, newname, newpath, + &e, NULL); + fuse_finish_interrupt(f, req, &d); + free_path2(f, ino, newparent, NULL, NULL, oldpath, newpath); + } + reply_entry(req, &e, err); +} + +static void fuse_do_release(struct fuse *f, fuse_ino_t ino, const char *path, + struct fuse_file_info *fi) +{ + struct node *node; + int unlink_hidden = 0; + + fuse_fs_release(f->fs, path, fi); + + pthread_mutex_lock(&f->lock); + node = get_node(f, ino); + assert(node->open_count > 0); + --node->open_count; + if (node->is_hidden && !node->open_count) { + unlink_hidden = 1; + node->is_hidden = 0; + } + pthread_mutex_unlock(&f->lock); + + if(unlink_hidden) { + if (path) { + fuse_fs_unlink(f->fs, path); + } else if (f->conf.nullpath_ok) { + char *unlinkpath; + + if (get_path(f, ino, &unlinkpath) == 0) + fuse_fs_unlink(f->fs, unlinkpath); + + free_path(f, ino, unlinkpath); + } + } +} + +static void fuse_lib_create(fuse_req_t req, fuse_ino_t parent, + const char *name, mode_t mode, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + struct fuse_entry_param e; + char *path; + int err; + + err = get_path_name(f, parent, name, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_create(f->fs, path, mode, fi); + if (!err) { + err = lookup_path(f, parent, name, path, &e, fi); + if (err) + fuse_fs_release(f->fs, path, fi); + else if (!S_ISREG(e.attr.st_mode)) { + err = -EIO; + fuse_fs_release(f->fs, path, fi); + forget_node(f, e.ino, 1); + } else { + if (f->conf.direct_io) + fi->direct_io = 1; + if (f->conf.kernel_cache) + fi->keep_cache = 1; + if (fi->direct_io && + f->conf.parallel_direct_writes) + fi->parallel_direct_writes = 1; + } + } + fuse_finish_interrupt(f, req, &d); + } + if (!err) { + pthread_mutex_lock(&f->lock); + get_node(f, e.ino)->open_count++; + pthread_mutex_unlock(&f->lock); + if (fuse_reply_create(req, &e, fi) == -ENOENT) { + /* The open syscall was interrupted, so it + must be cancelled */ + fuse_do_release(f, e.ino, path, fi); + forget_node(f, e.ino, 1); + } + } else { + reply_err(req, err); + } + + free_path(f, parent, path); +} + +static double diff_timespec(const struct timespec *t1, + const struct timespec *t2) +{ + return (t1->tv_sec - t2->tv_sec) + + ((double) t1->tv_nsec - (double) t2->tv_nsec) / 1000000000.0; +} + +static void open_auto_cache(struct fuse *f, fuse_ino_t ino, const char *path, + struct fuse_file_info *fi) +{ + struct node *node; + + pthread_mutex_lock(&f->lock); + node = get_node(f, ino); + if (node->cache_valid) { + struct timespec now; + + curr_time(&now); + if (diff_timespec(&now, &node->stat_updated) > + f->conf.ac_attr_timeout) { + struct stat stbuf; + int err; + pthread_mutex_unlock(&f->lock); + err = fuse_fs_getattr(f->fs, path, &stbuf, fi); + pthread_mutex_lock(&f->lock); + if (!err) + update_stat(node, &stbuf); + else + node->cache_valid = 0; + } + } + if (node->cache_valid) + fi->keep_cache = 1; + + node->cache_valid = 1; + pthread_mutex_unlock(&f->lock); +} + +static void fuse_lib_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_open(f->fs, path, fi); + if (!err) { + if (f->conf.direct_io) + fi->direct_io = 1; + if (f->conf.kernel_cache) + fi->keep_cache = 1; + + if (f->conf.auto_cache) + open_auto_cache(f, ino, path, fi); + + if (f->conf.no_rofd_flush && + (fi->flags & O_ACCMODE) == O_RDONLY) + fi->noflush = 1; + + if (fi->direct_io && f->conf.parallel_direct_writes) + fi->parallel_direct_writes = 1; + + } + fuse_finish_interrupt(f, req, &d); + } + if (!err) { + pthread_mutex_lock(&f->lock); + get_node(f, ino)->open_count++; + pthread_mutex_unlock(&f->lock); + if (fuse_reply_open(req, fi) == -ENOENT) { + /* The open syscall was interrupted, so it + must be cancelled */ + fuse_do_release(f, ino, path, fi); + } + } else + reply_err(req, err); + + free_path(f, ino, path); +} + +static void fuse_lib_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_bufvec *buf = NULL; + char *path; + int res; + + res = get_path_nullok(f, ino, &path); + if (res == 0) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + res = fuse_fs_read_buf(f->fs, path, &buf, size, off, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + + if (res == 0) + fuse_reply_data(req, buf, FUSE_BUF_SPLICE_MOVE); + else + reply_err(req, res); + + fuse_free_buf(buf); +} + +static void fuse_lib_write_buf(fuse_req_t req, fuse_ino_t ino, + struct fuse_bufvec *buf, off_t off, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int res; + + res = get_path_nullok(f, ino, &path); + if (res == 0) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + res = fuse_fs_write_buf(f->fs, path, buf, off, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + + if (res >= 0) + fuse_reply_write(req, res); + else + reply_err(req, res); +} + +static void fuse_lib_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path_nullok(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_fsync(f->fs, path, datasync, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static struct fuse_dh *get_dirhandle(const struct fuse_file_info *llfi, + struct fuse_file_info *fi) +{ + struct fuse_dh *dh = (struct fuse_dh *) (uintptr_t) llfi->fh; + memset(fi, 0, sizeof(struct fuse_file_info)); + fi->fh = dh->fh; + return dh; +} + +static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *llfi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + struct fuse_dh *dh; + struct fuse_file_info fi; + char *path; + int err; + + dh = (struct fuse_dh *) malloc(sizeof(struct fuse_dh)); + if (dh == NULL) { + reply_err(req, -ENOMEM); + return; + } + memset(dh, 0, sizeof(struct fuse_dh)); + dh->fuse = f; + dh->contents = NULL; + dh->first = NULL; + dh->len = 0; + dh->filled = 0; + dh->nodeid = ino; + pthread_mutex_init(&dh->lock, NULL); + + llfi->fh = (uintptr_t) dh; + + memset(&fi, 0, sizeof(fi)); + fi.flags = llfi->flags; + + err = get_path(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_opendir(f->fs, path, &fi); + fuse_finish_interrupt(f, req, &d); + dh->fh = fi.fh; + llfi->cache_readdir = fi.cache_readdir; + llfi->keep_cache = fi.keep_cache; + } + if (!err) { + if (fuse_reply_open(req, llfi) == -ENOENT) { + /* The opendir syscall was interrupted, so it + must be cancelled */ + fuse_fs_releasedir(f->fs, path, &fi); + pthread_mutex_destroy(&dh->lock); + free(dh); + } + } else { + reply_err(req, err); + pthread_mutex_destroy(&dh->lock); + free(dh); + } + free_path(f, ino, path); +} + +static int extend_contents(struct fuse_dh *dh, unsigned minsize) +{ + if (minsize > dh->size) { + char *newptr; + unsigned newsize = dh->size; + if (!newsize) + newsize = 1024; + while (newsize < minsize) { + if (newsize >= 0x80000000) + newsize = 0xffffffff; + else + newsize *= 2; + } + + newptr = (char *) realloc(dh->contents, newsize); + if (!newptr) { + dh->error = -ENOMEM; + return -1; + } + dh->contents = newptr; + dh->size = newsize; + } + return 0; +} + +static int fuse_add_direntry_to_dh(struct fuse_dh *dh, const char *name, + struct stat *st, enum fuse_fill_dir_flags flags) +{ + struct fuse_direntry *de; + + de = malloc(sizeof(struct fuse_direntry)); + if (!de) { + dh->error = -ENOMEM; + return -1; + } + de->name = strdup(name); + if (!de->name) { + dh->error = -ENOMEM; + free(de); + return -1; + } + de->flags = flags; + de->stat = *st; + de->next = NULL; + + *dh->last = de; + dh->last = &de->next; + + return 0; +} + +static fuse_ino_t lookup_nodeid(struct fuse *f, fuse_ino_t parent, + const char *name) +{ + struct node *node; + fuse_ino_t res = FUSE_UNKNOWN_INO; + + pthread_mutex_lock(&f->lock); + node = lookup_node(f, parent, name); + if (node) + res = node->nodeid; + pthread_mutex_unlock(&f->lock); + + return res; +} + +static int fill_dir(void *dh_, const char *name, const struct stat *statp, + off_t off, enum fuse_fill_dir_flags flags) +{ + struct fuse_dh *dh = (struct fuse_dh *) dh_; + struct stat stbuf; + + if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) { + dh->error = -EIO; + return 1; + } + + if (statp) + stbuf = *statp; + else { + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = FUSE_UNKNOWN_INO; + } + + if (!dh->fuse->conf.use_ino) { + stbuf.st_ino = FUSE_UNKNOWN_INO; + if (dh->fuse->conf.readdir_ino) { + stbuf.st_ino = (ino_t) + lookup_nodeid(dh->fuse, dh->nodeid, name); + } + } + + if (off) { + size_t newlen; + + if (dh->filled) { + dh->error = -EIO; + return 1; + } + + if (dh->first) { + dh->error = -EIO; + return 1; + } + + if (extend_contents(dh, dh->needlen) == -1) + return 1; + + newlen = dh->len + + fuse_add_direntry(dh->req, dh->contents + dh->len, + dh->needlen - dh->len, name, + &stbuf, off); + if (newlen > dh->needlen) + return 1; + + dh->len = newlen; + } else { + dh->filled = 1; + + if (fuse_add_direntry_to_dh(dh, name, &stbuf, flags) == -1) + return 1; + } + return 0; +} + +static int is_dot_or_dotdot(const char *name) +{ + return name[0] == '.' && (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0')); +} + +static int fill_dir_plus(void *dh_, const char *name, const struct stat *statp, + off_t off, enum fuse_fill_dir_flags flags) +{ + struct fuse_dh *dh = (struct fuse_dh *) dh_; + struct fuse_entry_param e = { + /* ino=0 tells the kernel to ignore readdirplus stat info */ + .ino = 0, + }; + struct fuse *f = dh->fuse; + int res; + + if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) { + dh->error = -EIO; + return 1; + } + + if (statp && (flags & FUSE_FILL_DIR_PLUS)) { + e.attr = *statp; + } + + e.attr.st_ino = FUSE_UNKNOWN_INO; + if (statp) { + e.attr.st_mode = statp->st_mode; + if (f->conf.use_ino) + e.attr.st_ino = statp->st_ino; + } + if (!f->conf.use_ino && f->conf.readdir_ino) { + e.attr.st_ino = (ino_t) + lookup_nodeid(f, dh->nodeid, name); + } + + if (off) { + size_t newlen; + + if (dh->filled) { + dh->error = -EIO; + return 1; + } + + if (dh->first) { + dh->error = -EIO; + return 1; + } + if (extend_contents(dh, dh->needlen) == -1) + return 1; + + if (statp && (flags & FUSE_FILL_DIR_PLUS)) { + if (!is_dot_or_dotdot(name)) { + res = do_lookup(f, dh->nodeid, name, &e); + if (res) { + dh->error = res; + return 1; + } + } + } + + newlen = dh->len + + fuse_add_direntry_plus(dh->req, dh->contents + dh->len, + dh->needlen - dh->len, name, + &e, off); + if (newlen > dh->needlen) + return 1; + dh->len = newlen; + } else { + dh->filled = 1; + + if (fuse_add_direntry_to_dh(dh, name, &e.attr, flags) == -1) + return 1; + } + + return 0; +} + +static void free_direntries(struct fuse_direntry *de) +{ + while (de) { + struct fuse_direntry *next = de->next; + free(de->name); + free(de); + de = next; + } +} + +static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino, + size_t size, off_t off, struct fuse_dh *dh, + struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + char *path; + int err; + + if (f->fs->op.readdir) + err = get_path_nullok(f, ino, &path); + else + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_fill_dir_t filler = fill_dir; + + if (flags & FUSE_READDIR_PLUS) + filler = fill_dir_plus; + + free_direntries(dh->first); + dh->first = NULL; + dh->last = &dh->first; + dh->len = 0; + dh->error = 0; + dh->needlen = size; + dh->filled = 0; + dh->req = req; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_readdir(f->fs, path, dh, filler, off, fi, flags); + fuse_finish_interrupt(f, req, &d); + dh->req = NULL; + if (!err) + err = dh->error; + if (err) + dh->filled = 0; + free_path(f, ino, path); + } + return err; +} + +static int readdir_fill_from_list(fuse_req_t req, struct fuse_dh *dh, + off_t off, enum fuse_readdir_flags flags) +{ + off_t pos; + struct fuse_direntry *de = dh->first; + int res; + + dh->len = 0; + + if (extend_contents(dh, dh->needlen) == -1) + return dh->error; + + for (pos = 0; pos < off; pos++) { + if (!de) + break; + + de = de->next; + } + while (de) { + char *p = dh->contents + dh->len; + unsigned rem = dh->needlen - dh->len; + unsigned thislen; + unsigned newlen; + pos++; + + if (flags & FUSE_READDIR_PLUS) { + struct fuse_entry_param e = { + .ino = 0, + .attr = de->stat, + }; + + if (de->flags & FUSE_FILL_DIR_PLUS && + !is_dot_or_dotdot(de->name)) { + res = do_lookup(dh->fuse, dh->nodeid, + de->name, &e); + if (res) { + dh->error = res; + return 1; + } + } + + thislen = fuse_add_direntry_plus(req, p, rem, + de->name, &e, pos); + } else { + thislen = fuse_add_direntry(req, p, rem, + de->name, &de->stat, pos); + } + newlen = dh->len + thislen; + if (newlen > dh->needlen) + break; + dh->len = newlen; + de = de->next; + } + return 0; +} + +static void fuse_readdir_common(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *llfi, + enum fuse_readdir_flags flags) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_file_info fi; + struct fuse_dh *dh = get_dirhandle(llfi, &fi); + int err; + + pthread_mutex_lock(&dh->lock); + /* According to SUS, directory contents need to be refreshed on + rewinddir() */ + if (!off) + dh->filled = 0; + + if (!dh->filled) { + err = readdir_fill(f, req, ino, size, off, dh, &fi, flags); + if (err) { + reply_err(req, err); + goto out; + } + } + if (dh->filled) { + dh->needlen = size; + err = readdir_fill_from_list(req, dh, off, flags); + if (err) { + reply_err(req, err); + goto out; + } + } + fuse_reply_buf(req, dh->contents, dh->len); +out: + pthread_mutex_unlock(&dh->lock); +} + +static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *llfi) +{ + fuse_readdir_common(req, ino, size, off, llfi, 0); +} + +static void fuse_lib_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *llfi) +{ + fuse_readdir_common(req, ino, size, off, llfi, FUSE_READDIR_PLUS); +} + +static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *llfi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + struct fuse_file_info fi; + struct fuse_dh *dh = get_dirhandle(llfi, &fi); + char *path; + + get_path_nullok(f, ino, &path); + + fuse_prepare_interrupt(f, req, &d); + fuse_fs_releasedir(f->fs, path, &fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + + pthread_mutex_lock(&dh->lock); + pthread_mutex_unlock(&dh->lock); + pthread_mutex_destroy(&dh->lock); + free_direntries(dh->first); + free(dh->contents); + free(dh); + reply_err(req, 0); +} + +static void fuse_lib_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *llfi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_file_info fi; + char *path; + int err; + + get_dirhandle(llfi, &fi); + + err = get_path_nullok(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_fsyncdir(f->fs, path, datasync, &fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static void fuse_lib_statfs(fuse_req_t req, fuse_ino_t ino) +{ + struct fuse *f = req_fuse_prepare(req); + struct statvfs buf; + char *path = NULL; + int err = 0; + + memset(&buf, 0, sizeof(buf)); + if (ino) + err = get_path(f, ino, &path); + + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_statfs(f->fs, path ? path : "/", &buf); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + + if (!err) + fuse_reply_statfs(req, &buf); + else + reply_err(req, err); +} + +static void fuse_lib_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_setxattr(f->fs, path, name, value, size, flags); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static int common_getxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino, + const char *name, char *value, size_t size) +{ + int err; + char *path; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_getxattr(f->fs, path, name, value, size); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + return err; +} + +static void fuse_lib_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size) +{ + struct fuse *f = req_fuse_prepare(req); + int res; + + if (size) { + char *value = (char *) malloc(size); + if (value == NULL) { + reply_err(req, -ENOMEM); + return; + } + res = common_getxattr(f, req, ino, name, value, size); + if (res > 0) + fuse_reply_buf(req, value, res); + else + reply_err(req, res); + free(value); + } else { + res = common_getxattr(f, req, ino, name, NULL, 0); + if (res >= 0) + fuse_reply_xattr(req, res); + else + reply_err(req, res); + } +} + +static int common_listxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino, + char *list, size_t size) +{ + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_listxattr(f->fs, path, list, size); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + return err; +} + +static void fuse_lib_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) +{ + struct fuse *f = req_fuse_prepare(req); + int res; + + if (size) { + char *list = (char *) malloc(size); + if (list == NULL) { + reply_err(req, -ENOMEM); + return; + } + res = common_listxattr(f, req, ino, list, size); + if (res > 0) + fuse_reply_buf(req, list, res); + else + reply_err(req, res); + free(list); + } else { + res = common_listxattr(f, req, ino, NULL, 0); + if (res >= 0) + fuse_reply_xattr(req, res); + else + reply_err(req, res); + } +} + +static void fuse_lib_removexattr(fuse_req_t req, fuse_ino_t ino, + const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_removexattr(f->fs, path, name); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static struct lock *locks_conflict(struct node *node, const struct lock *lock) +{ + struct lock *l; + + for (l = node->locks; l; l = l->next) + if (l->owner != lock->owner && + lock->start <= l->end && l->start <= lock->end && + (l->type == F_WRLCK || lock->type == F_WRLCK)) + break; + + return l; +} + +static void delete_lock(struct lock **lockp) +{ + struct lock *l = *lockp; + *lockp = l->next; + free(l); +} + +static void insert_lock(struct lock **pos, struct lock *lock) +{ + lock->next = *pos; + *pos = lock; +} + +static int locks_insert(struct node *node, struct lock *lock) +{ + struct lock **lp; + struct lock *newl1 = NULL; + struct lock *newl2 = NULL; + + if (lock->type != F_UNLCK || lock->start != 0 || + lock->end != OFFSET_MAX) { + newl1 = malloc(sizeof(struct lock)); + newl2 = malloc(sizeof(struct lock)); + + if (!newl1 || !newl2) { + free(newl1); + free(newl2); + return -ENOLCK; + } + } + + for (lp = &node->locks; *lp;) { + struct lock *l = *lp; + if (l->owner != lock->owner) + goto skip; + + if (lock->type == l->type) { + if (l->end < lock->start - 1) + goto skip; + if (lock->end < l->start - 1) + break; + if (l->start <= lock->start && lock->end <= l->end) + goto out; + if (l->start < lock->start) + lock->start = l->start; + if (lock->end < l->end) + lock->end = l->end; + goto delete; + } else { + if (l->end < lock->start) + goto skip; + if (lock->end < l->start) + break; + if (lock->start <= l->start && l->end <= lock->end) + goto delete; + if (l->end <= lock->end) { + l->end = lock->start - 1; + goto skip; + } + if (lock->start <= l->start) { + l->start = lock->end + 1; + break; + } + *newl2 = *l; + newl2->start = lock->end + 1; + l->end = lock->start - 1; + insert_lock(&l->next, newl2); + newl2 = NULL; + } + skip: + lp = &l->next; + continue; + + delete: + delete_lock(lp); + } + if (lock->type != F_UNLCK) { + *newl1 = *lock; + insert_lock(lp, newl1); + newl1 = NULL; + } +out: + free(newl1); + free(newl2); + return 0; +} + +static void flock_to_lock(struct flock *flock, struct lock *lock) +{ + memset(lock, 0, sizeof(struct lock)); + lock->type = flock->l_type; + lock->start = flock->l_start; + lock->end = + flock->l_len ? flock->l_start + flock->l_len - 1 : OFFSET_MAX; + lock->pid = flock->l_pid; +} + +static void lock_to_flock(struct lock *lock, struct flock *flock) +{ + flock->l_type = lock->type; + flock->l_start = lock->start; + flock->l_len = + (lock->end == OFFSET_MAX) ? 0 : lock->end - lock->start + 1; + flock->l_pid = lock->pid; +} + +static int fuse_flush_common(struct fuse *f, fuse_req_t req, fuse_ino_t ino, + const char *path, struct fuse_file_info *fi) +{ + struct fuse_intr_data d; + struct flock lock; + struct lock l; + int err; + int errlock; + + fuse_prepare_interrupt(f, req, &d); + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + err = fuse_fs_flush(f->fs, path, fi); + errlock = fuse_fs_lock(f->fs, path, fi, F_SETLK, &lock); + fuse_finish_interrupt(f, req, &d); + + if (errlock != -ENOSYS) { + flock_to_lock(&lock, &l); + l.owner = fi->lock_owner; + pthread_mutex_lock(&f->lock); + locks_insert(get_node(f, ino), &l); + pthread_mutex_unlock(&f->lock); + + /* if op.lock() is defined FLUSH is needed regardless + of op.flush() */ + if (err == -ENOSYS) + err = 0; + } + return err; +} + +static void fuse_lib_release(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err = 0; + + get_path_nullok(f, ino, &path); + if (fi->flush) { + err = fuse_flush_common(f, req, ino, path, fi); + if (err == -ENOSYS) + err = 0; + } + + fuse_prepare_interrupt(f, req, &d); + fuse_do_release(f, ino, path, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + + reply_err(req, err); +} + +static void fuse_lib_flush(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + get_path_nullok(f, ino, &path); + err = fuse_flush_common(f, req, ino, path, fi); + free_path(f, ino, path); + + reply_err(req, err); +} + +static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct flock *lock, + int cmd) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path_nullok(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_lock(f->fs, path, fi, cmd, lock); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + return err; +} + +static void fuse_lib_getlk(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct flock *lock) +{ + int err; + struct lock l; + struct lock *conflict; + struct fuse *f = req_fuse(req); + + flock_to_lock(lock, &l); + l.owner = fi->lock_owner; + pthread_mutex_lock(&f->lock); + conflict = locks_conflict(get_node(f, ino), &l); + if (conflict) + lock_to_flock(conflict, lock); + pthread_mutex_unlock(&f->lock); + if (!conflict) + err = fuse_lock_common(req, ino, fi, lock, F_GETLK); + else + err = 0; + + if (!err) + fuse_reply_lock(req, lock); + else + reply_err(req, err); +} + +static void fuse_lib_setlk(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct flock *lock, + int sleep) +{ + int err = fuse_lock_common(req, ino, fi, lock, + sleep ? F_SETLKW : F_SETLK); + if (!err) { + struct fuse *f = req_fuse(req); + struct lock l; + flock_to_lock(lock, &l); + l.owner = fi->lock_owner; + pthread_mutex_lock(&f->lock); + locks_insert(get_node(f, ino), &l); + pthread_mutex_unlock(&f->lock); + } + reply_err(req, err); +} + +static void fuse_lib_flock(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, int op) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path_nullok(f, ino, &path); + if (err == 0) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_flock(f->fs, path, fi, op); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize, + uint64_t idx) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_bmap(f->fs, path, blocksize, &idx); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) + fuse_reply_bmap(req, idx); + else + reply_err(req, err); +} + +static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, + void *arg, struct fuse_file_info *llfi, + unsigned int flags, const void *in_buf, + size_t in_bufsz, size_t out_bufsz) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + struct fuse_file_info fi; + char *path, *out_buf = NULL; + int err; + + err = -EPERM; + if (flags & FUSE_IOCTL_UNRESTRICTED) + goto err; + + if (flags & FUSE_IOCTL_DIR) + get_dirhandle(llfi, &fi); + else + fi = *llfi; + + if (out_bufsz) { + err = -ENOMEM; + out_buf = malloc(out_bufsz); + if (!out_buf) + goto err; + } + + assert(!in_bufsz || !out_bufsz || in_bufsz == out_bufsz); + if (out_buf && in_bufsz) + memcpy(out_buf, in_buf, in_bufsz); + + err = get_path_nullok(f, ino, &path); + if (err) + goto err; + + fuse_prepare_interrupt(f, req, &d); + + err = fuse_fs_ioctl(f->fs, path, cmd, arg, &fi, flags, + out_buf ? out_buf : (void *)in_buf); + + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + + if (err < 0) + goto err; + fuse_reply_ioctl(req, err, out_buf, out_bufsz); + goto out; +err: + reply_err(req, err); +out: + free(out_buf); +} + +static void fuse_lib_poll(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct fuse_pollhandle *ph) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + unsigned revents = 0; + + err = get_path_nullok(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_poll(f->fs, path, fi, ph, &revents); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) + fuse_reply_poll(req, revents); + else + reply_err(req, err); +} + +static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode, + off_t offset, off_t length, struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + + err = get_path_nullok(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_fallocate(f->fs, path, mode, offset, length, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static void fuse_lib_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in, + off_t off_in, struct fuse_file_info *fi_in, + fuse_ino_t nodeid_out, off_t off_out, + struct fuse_file_info *fi_out, size_t len, + int flags) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path_in, *path_out; + int err; + ssize_t res; + + err = get_path_nullok(f, nodeid_in, &path_in); + if (err) { + reply_err(req, err); + return; + } + + err = get_path_nullok(f, nodeid_out, &path_out); + if (err) { + free_path(f, nodeid_in, path_in); + reply_err(req, err); + return; + } + + fuse_prepare_interrupt(f, req, &d); + res = fuse_fs_copy_file_range(f->fs, path_in, fi_in, off_in, path_out, + fi_out, off_out, len, flags); + fuse_finish_interrupt(f, req, &d); + + if (res >= 0) + fuse_reply_write(req, res); + else + reply_err(req, res); + + free_path(f, nodeid_in, path_in); + free_path(f, nodeid_out, path_out); +} + +static void fuse_lib_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + off_t res; + + err = get_path(f, ino, &path); + if (err) { + reply_err(req, err); + return; + } + + fuse_prepare_interrupt(f, req, &d); + res = fuse_fs_lseek(f->fs, path, off, whence, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + if (res >= 0) + fuse_reply_lseek(req, res); + else + reply_err(req, res); +} + +#ifdef HAVE_STATX +static void fuse_lib_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct statx stxbuf; + char *path; + int err; + + memset(&stxbuf, 0, sizeof(stxbuf)); + + if (fi != NULL) + err = get_path_nullok(f, ino, &path); + else + err = get_path(f, ino, &path); + + if (!err) { + struct fuse_intr_data d; + + if (!path) + flags |= AT_EMPTY_PATH; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_statx(f->fs, path, flags, mask, &stxbuf, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) { + struct node *node; + + pthread_mutex_lock(&f->lock); + node = get_node(f, ino); + if (node->is_hidden && stxbuf.stx_nlink > 0) + stxbuf.stx_nlink--; + if (f->conf.auto_cache) { + struct stat stbuf; + + stbuf.st_mtime = stxbuf.stx_mtime.tv_nsec; + ST_MTIM_NSEC(&stbuf) = stxbuf.stx_mtime.tv_nsec; + stbuf.st_size = stxbuf.stx_size; + update_stat(node, &stbuf); + } + pthread_mutex_unlock(&f->lock); + set_statx(f, ino, &stxbuf); + fuse_reply_statx(req, 0, &stxbuf, f->conf.attr_timeout); + } else + reply_err(req, err); +} +#endif + +static int clean_delay(struct fuse *f) +{ + /* + * This is calculating the delay between clean runs. To + * reduce the number of cleans we are doing them 10 times + * within the remember window. + */ + int min_sleep = 60; + int max_sleep = 3600; + int sleep_time = f->conf.remember / 10; + + if (sleep_time > max_sleep) + return max_sleep; + if (sleep_time < min_sleep) + return min_sleep; + return sleep_time; +} + +int fuse_clean_cache(struct fuse *f) +{ + struct node_lru *lnode; + struct list_head *curr, *next; + struct node *node; + struct timespec now; + + pthread_mutex_lock(&f->lock); + + curr_time(&now); + + for (curr = f->lru_table.next; curr != &f->lru_table; curr = next) { + double age; + + next = curr->next; + lnode = list_entry(curr, struct node_lru, lru); + node = &lnode->node; + + age = diff_timespec(&now, &lnode->forget_time); + if (age <= f->conf.remember) + break; + + assert(node->nlookup == 1); + + /* Don't forget active directories */ + if (node->refctr > 1) + continue; + + node->nlookup = 0; + unhash_name(f, node); + unref_node(f, node); + } + pthread_mutex_unlock(&f->lock); + + return clean_delay(f); +} + +static struct fuse_lowlevel_ops fuse_path_ops = { + .init = fuse_lib_init, + .destroy = fuse_lib_destroy, + .lookup = fuse_lib_lookup, + .forget = fuse_lib_forget, + .forget_multi = fuse_lib_forget_multi, + .getattr = fuse_lib_getattr, + .setattr = fuse_lib_setattr, + .access = fuse_lib_access, + .readlink = fuse_lib_readlink, + .mknod = fuse_lib_mknod, + .mkdir = fuse_lib_mkdir, + .unlink = fuse_lib_unlink, + .rmdir = fuse_lib_rmdir, + .symlink = fuse_lib_symlink, + .rename = fuse_lib_rename, + .link = fuse_lib_link, + .create = fuse_lib_create, + .open = fuse_lib_open, + .read = fuse_lib_read, + .write_buf = fuse_lib_write_buf, + .flush = fuse_lib_flush, + .release = fuse_lib_release, + .fsync = fuse_lib_fsync, + .opendir = fuse_lib_opendir, + .readdir = fuse_lib_readdir, + .readdirplus = fuse_lib_readdirplus, + .releasedir = fuse_lib_releasedir, + .fsyncdir = fuse_lib_fsyncdir, + .statfs = fuse_lib_statfs, + .setxattr = fuse_lib_setxattr, + .getxattr = fuse_lib_getxattr, + .listxattr = fuse_lib_listxattr, + .removexattr = fuse_lib_removexattr, + .getlk = fuse_lib_getlk, + .setlk = fuse_lib_setlk, + .flock = fuse_lib_flock, + .bmap = fuse_lib_bmap, + .ioctl = fuse_lib_ioctl, + .poll = fuse_lib_poll, + .fallocate = fuse_lib_fallocate, + .copy_file_range = fuse_lib_copy_file_range, + .lseek = fuse_lib_lseek, +#ifdef HAVE_STATX + .statx = fuse_lib_statx, +#endif +}; + +int fuse_notify_poll(struct fuse_pollhandle *ph) +{ + return fuse_lowlevel_notify_poll(ph); +} + +struct fuse_session *fuse_get_session(struct fuse *f) +{ + return f->se; +} + +static int fuse_session_loop_remember(struct fuse *f) +{ + struct fuse_session *se = f->se; + int res = 0; + struct timespec now; + time_t next_clean; + struct pollfd fds = { + .fd = se->fd, + .events = POLLIN + }; + struct fuse_buf fbuf = { + .mem = NULL, + }; + + curr_time(&now); + next_clean = now.tv_sec; + while (!fuse_session_exited(se)) { + unsigned timeout; + + curr_time(&now); + if (now.tv_sec < next_clean) + timeout = next_clean - now.tv_sec; + else + timeout = 0; + + res = poll(&fds, 1, timeout * 1000); + if (res == -1) { + if (errno == EINTR) + continue; + else + break; + } else if (res > 0) { + res = fuse_session_receive_buf_internal(se, &fbuf, + NULL); + if (res == -EINTR) + continue; + if (res <= 0) + break; + + fuse_session_process_buf_internal(se, &fbuf, NULL); + } else { + timeout = fuse_clean_cache(f); + curr_time(&now); + next_clean = now.tv_sec + timeout; + } + } + + fuse_buf_free(&fbuf); + return res < 0 ? -1 : 0; +} + +int fuse_loop(struct fuse *f) +{ + if (!f) + return -1; + + if (lru_enabled(f)) + return fuse_session_loop_remember(f); + + return fuse_session_loop(f->se); +} + +FUSE_SYMVER("fuse_loop_mt_312", "fuse_loop_mt@@FUSE_3.12") +int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config) +{ + if (f == NULL) + return -1; + + int res = fuse_start_cleanup_thread(f); + if (res) + return -1; + + res = fuse_session_loop_mt_312(fuse_get_session(f), config); + fuse_stop_cleanup_thread(f); + return res; +} + +int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1); +FUSE_SYMVER("fuse_loop_mt_32", "fuse_loop_mt@FUSE_3.2") +int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1) +{ + struct fuse_loop_config *config = fuse_loop_cfg_create(); + if (config == NULL) + return ENOMEM; + + fuse_loop_cfg_convert(config, config_v1); + + int res = fuse_loop_mt_312(f, config); + + fuse_loop_cfg_destroy(config); + + return res; +} + +int fuse_loop_mt_31(struct fuse *f, int clone_fd); +FUSE_SYMVER("fuse_loop_mt_31", "fuse_loop_mt@FUSE_3.0") +int fuse_loop_mt_31(struct fuse *f, int clone_fd) +{ + int err; + struct fuse_loop_config *config = fuse_loop_cfg_create(); + + if (config == NULL) + return ENOMEM; + + fuse_loop_cfg_set_clone_fd(config, clone_fd); + + err = fuse_loop_mt_312(f, config); + + fuse_loop_cfg_destroy(config); + + return err; +} + +void fuse_exit(struct fuse *f) +{ + fuse_session_exit(f->se); +} + +struct fuse_context *fuse_get_context(void) +{ + struct fuse_context_i *c = fuse_get_context_internal(); + + if (c) + return &c->ctx; + else + return NULL; +} + +int fuse_getgroups(int size, gid_t list[]) +{ + struct fuse_context_i *c = fuse_get_context_internal(); + if (!c) + return -EINVAL; + + return fuse_req_getgroups(c->req, size, list); +} + +int fuse_interrupted(void) +{ + struct fuse_context_i *c = fuse_get_context_internal(); + + if (c) + return fuse_req_interrupted(c->req); + else + return 0; +} + +int fuse_invalidate_path(struct fuse *f, const char *path) { + fuse_ino_t ino; + int err = lookup_path_in_cache(f, path, &ino); + if (err) { + return err; + } + + return fuse_lowlevel_notify_inval_inode(f->se, ino, 0, 0); +} + +#define FUSE_LIB_OPT(t, p, v) { t, offsetof(struct fuse_config, p), v } + +static const struct fuse_opt fuse_lib_opts[] = { + FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP), + FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP), + FUSE_LIB_OPT("debug", debug, 1), + FUSE_LIB_OPT("-d", debug, 1), + FUSE_LIB_OPT("kernel_cache", kernel_cache, 1), + FUSE_LIB_OPT("auto_cache", auto_cache, 1), + FUSE_LIB_OPT("noauto_cache", auto_cache, 0), + FUSE_LIB_OPT("no_rofd_flush", no_rofd_flush, 1), + FUSE_LIB_OPT("umask=", set_mode, 1), + FUSE_LIB_OPT("umask=%o", umask, 0), + FUSE_LIB_OPT("fmask=", set_mode, 1), + FUSE_LIB_OPT("fmask=%o", fmask, 0), + FUSE_LIB_OPT("dmask=", set_mode, 1), + FUSE_LIB_OPT("dmask=%o", dmask, 0), + FUSE_LIB_OPT("uid=", set_uid, 1), + FUSE_LIB_OPT("uid=%d", uid, 0), + FUSE_LIB_OPT("gid=", set_gid, 1), + FUSE_LIB_OPT("gid=%d", gid, 0), + FUSE_LIB_OPT("entry_timeout=%lf", entry_timeout, 0), + FUSE_LIB_OPT("attr_timeout=%lf", attr_timeout, 0), + FUSE_LIB_OPT("ac_attr_timeout=%lf", ac_attr_timeout, 0), + FUSE_LIB_OPT("ac_attr_timeout=", ac_attr_timeout_set, 1), + FUSE_LIB_OPT("negative_timeout=%lf", negative_timeout, 0), + FUSE_LIB_OPT("noforget", remember, -1), + FUSE_LIB_OPT("remember=%u", remember, 0), + FUSE_LIB_OPT("modules=%s", modules, 0), + FUSE_LIB_OPT("parallel_direct_write=%d", parallel_direct_writes, 0), + FUSE_OPT_END +}; + +static int fuse_lib_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) arg; (void) outargs; (void) data; (void) key; + + /* Pass through unknown options */ + return 1; +} + + +static const struct fuse_opt fuse_help_opts[] = { + FUSE_LIB_OPT("modules=%s", modules, 1), + FUSE_OPT_KEY("modules=%s", FUSE_OPT_KEY_KEEP), + FUSE_OPT_END +}; + +static void print_module_help(const char *name, + fuse_module_factory_t *fac) +{ + struct fuse_args a = FUSE_ARGS_INIT(0, NULL); + if (fuse_opt_add_arg(&a, "") == -1 || + fuse_opt_add_arg(&a, "-h") == -1) + return; + printf("\nOptions for %s module:\n", name); + (*fac)(&a, NULL); + fuse_opt_free_args(&a); +} + +void fuse_lib_help(struct fuse_args *args) +{ + /* These are not all options, but only the ones that + may be of interest to an end-user */ + printf( +" -o kernel_cache cache files in kernel\n" +" -o [no]auto_cache enable caching based on modification times (off)\n" +" -o no_rofd_flush disable flushing of read-only fd on close (off)\n" +" -o umask=M set file permissions (octal)\n" +" -o fmask=M set file permissions (octal)\n" +" -o dmask=M set dir permissions (octal)\n" +" -o uid=N set file owner\n" +" -o gid=N set file group\n" +" -o entry_timeout=T cache timeout for names (1.0s)\n" +" -o negative_timeout=T cache timeout for deleted names (0.0s)\n" +" -o attr_timeout=T cache timeout for attributes (1.0s)\n" +" -o ac_attr_timeout=T auto cache timeout for attributes (attr_timeout)\n" +" -o noforget never forget cached inodes\n" +" -o remember=T remember cached inodes for T seconds (0s)\n" +" -o modules=M1[:M2...] names of modules to push onto filesystem stack\n"); + + + /* Print low-level help */ + fuse_lowlevel_help(); + + /* Print help for builtin modules */ + print_module_help("subdir", &fuse_module_subdir_factory); +#ifdef HAVE_ICONV + print_module_help("iconv", &fuse_module_iconv_factory); +#endif + + /* Parse command line options in case we need to + activate more modules */ + struct fuse_config conf = { .modules = NULL }; + if (fuse_opt_parse(args, &conf, fuse_help_opts, + fuse_lib_opt_proc) == -1 + || !conf.modules) + return; + + char *module; + char *next; + struct fuse_module *m; + + // Iterate over all modules + for (module = conf.modules; module; module = next) { + char *p; + for (p = module; *p && *p != ':'; p++); + next = *p ? p + 1 : NULL; + *p = '\0'; + + m = fuse_get_module(module); + if (m) + print_module_help(module, &m->factory); + } +} + +static int fuse_init_intr_signal(int signum, int *installed) +{ + struct sigaction old_sa; + + if (sigaction(signum, NULL, &old_sa) == -1) { + perror("fuse: cannot get old signal handler"); + return -1; + } + + if (old_sa.sa_handler == SIG_DFL) { + struct sigaction sa; + + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = fuse_intr_sighandler; + sigemptyset(&sa.sa_mask); + + if (sigaction(signum, &sa, NULL) == -1) { + perror("fuse: cannot set interrupt signal handler"); + return -1; + } + *installed = 1; + } + return 0; +} + +static void fuse_restore_intr_signal(int signum) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = SIG_DFL; + sigaction(signum, &sa, NULL); +} + + +static int fuse_push_module(struct fuse *f, const char *module, + struct fuse_args *args) +{ + struct fuse_fs *fs[2] = { f->fs, NULL }; + struct fuse_fs *newfs; + struct fuse_module *m = fuse_get_module(module); + + if (!m) + return -1; + + newfs = m->factory(args, fs); + if (!newfs) { + fuse_put_module(m); + return -1; + } + f->fs = newfs; + return 0; +} + +struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size, + void *user_data) +{ + struct fuse_fs *fs; + + if (sizeof(struct fuse_operations) < op_size) { + fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not not work\n"); + op_size = sizeof(struct fuse_operations); + } + + fs = (struct fuse_fs *) calloc(1, sizeof(struct fuse_fs)); + if (!fs) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse_fs object\n"); + return NULL; + } + + fs->user_data = user_data; + if (op) + memcpy(&fs->op, op, op_size); + return fs; +} + +static int node_table_init(struct node_table *t) +{ + t->size = NODE_TABLE_MIN_SIZE; + t->array = (struct node **) calloc(1, sizeof(struct node *) * t->size); + if (t->array == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + return -1; + } + t->use = 0; + t->split = 0; + + return 0; +} + +static void *fuse_prune_nodes(void *fuse) +{ + struct fuse *f = fuse; + int sleep_time; + + fuse_set_thread_name("fuse_prune_nodes"); + + while(1) { + sleep_time = fuse_clean_cache(f); + sleep(sleep_time); + } + return NULL; +} + +int fuse_start_cleanup_thread(struct fuse *f) +{ + if (lru_enabled(f)) + return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f); + + return 0; +} + +void fuse_stop_cleanup_thread(struct fuse *f) +{ + if (lru_enabled(f)) { + pthread_mutex_lock(&f->lock); + pthread_cancel(f->prune_thread); + pthread_mutex_unlock(&f->lock); + pthread_join(f->prune_thread, NULL); + } +} + +/* + * Not supposed to be called directly, but supposed to be called + * through the fuse_new macro + */ +struct fuse *_fuse_new_31(struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data) +{ + struct fuse *f; + struct node *root; + struct fuse_fs *fs; + struct fuse_lowlevel_ops llop = fuse_path_ops; + + f = (struct fuse *) calloc(1, sizeof(struct fuse)); + if (f == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n"); + goto out; + } + + f->conf.entry_timeout = 1.0; + f->conf.attr_timeout = 1.0; + f->conf.negative_timeout = 0.0; + f->conf.intr_signal = FUSE_DEFAULT_INTR_SIGNAL; + + /* Parse options */ + if (fuse_opt_parse(args, &f->conf, fuse_lib_opts, + fuse_lib_opt_proc) == -1) + goto out_free; + + pthread_mutex_lock(&fuse_context_lock); + static int builtin_modules_registered = 0; + /* Have the builtin modules already been registered? */ + if (builtin_modules_registered == 0) { + /* If not, register them. */ + fuse_register_module("subdir", fuse_module_subdir_factory, NULL); +#ifdef HAVE_ICONV + fuse_register_module("iconv", fuse_module_iconv_factory, NULL); +#endif + builtin_modules_registered= 1; + } + pthread_mutex_unlock(&fuse_context_lock); + + if (fuse_create_context_key() == -1) + goto out_free; + + fs = fuse_fs_new(op, op_size, user_data); + if (!fs) + goto out_delete_context_key; + + f->fs = fs; + + /* Oh f**k, this is ugly! */ + if (!fs->op.lock) { + llop.getlk = NULL; + llop.setlk = NULL; + } + + f->pagesize = getpagesize(); + init_list_head(&f->partial_slabs); + init_list_head(&f->full_slabs); + init_list_head(&f->lru_table); + + if (f->conf.modules) { + char *module; + char *next; + + for (module = f->conf.modules; module; module = next) { + char *p; + for (p = module; *p && *p != ':'; p++); + next = *p ? p + 1 : NULL; + *p = '\0'; + if (module[0] && + fuse_push_module(f, module, args) == -1) + goto out_free_fs; + } + } + + if (!f->conf.ac_attr_timeout_set) + f->conf.ac_attr_timeout = f->conf.attr_timeout; + +#if defined(__FreeBSD__) || defined(__NetBSD__) + /* + * In FreeBSD, we always use these settings as inode numbers + * are needed to make getcwd(3) work. + */ + f->conf.readdir_ino = 1; +#endif + + /* not declared globally, to restrict usage of this function */ + struct fuse_session *fuse_session_new_versioned( + struct fuse_args *args, const struct fuse_lowlevel_ops *op, + size_t op_size, struct libfuse_version *version, + void *userdata); + f->se = fuse_session_new_versioned(args, &llop, sizeof(llop), version, + f); + if (f->se == NULL) + goto out_free_fs; + + /* Trace topmost layer by default */ + f->fs->debug = f->conf.debug; + f->ctr = 0; + f->generation = 0; + if (node_table_init(&f->name_table) == -1) + goto out_free_session; + + if (node_table_init(&f->id_table) == -1) + goto out_free_name_table; + + pthread_mutex_init(&f->lock, NULL); + + root = alloc_node(f); + if (root == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + goto out_free_id_table; + } + if (lru_enabled(f)) { + struct node_lru *lnode = node_lru(root); + init_list_head(&lnode->lru); + } + + strcpy(root->inline_name, "/"); + root->name = root->inline_name; + root->parent = NULL; + root->nodeid = FUSE_ROOT_ID; + inc_nlookup(root); + hash_id(f, root); + + return f; + +out_free_id_table: + free(f->id_table.array); +out_free_name_table: + free(f->name_table.array); +out_free_session: + fuse_session_destroy(f->se); +out_free_fs: + free(f->fs); + free(f->conf.modules); +out_delete_context_key: + fuse_delete_context_key(); +out_free: + free(f); +out: + return NULL; +} + +/* Emulates 3.0-style fuse_new(), which processes --help */ +FUSE_SYMVER("_fuse_new_30", "_fuse_new@FUSE_3.0") +struct fuse *_fuse_new_30(struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, + struct libfuse_version *version, + void *user_data) +{ + struct fuse_config conf = {0}; + + const struct fuse_opt opts[] = { + FUSE_LIB_OPT("-h", show_help, 1), + FUSE_LIB_OPT("--help", show_help, 1), + FUSE_OPT_END + }; + + if (fuse_opt_parse(args, &conf, opts, + fuse_lib_opt_proc) == -1) + return NULL; + + if (conf.show_help) { + fuse_lib_help(args); + return NULL; + } else + return _fuse_new_31(args, op, op_size, version, user_data); +} + +/* ABI compat version */ +struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op, + size_t op_size, void *user_data); +FUSE_SYMVER("fuse_new_31", "fuse_new@FUSE_3.1") +struct fuse *fuse_new_31(struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + /* unknown version */ + struct libfuse_version version = { 0 }; + + return _fuse_new_31(args, op, op_size, &version, user_data); +} + +/* + * ABI compat version + * Emulates 3.0-style fuse_new(), which processes --help + */ +struct fuse *fuse_new_30(struct fuse_args *args, const struct fuse_operations *op, + size_t op_size, void *user_data); +FUSE_SYMVER("fuse_new_30", "fuse_new@FUSE_3.0") +struct fuse *fuse_new_30(struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + struct fuse_config conf = {0}; + + const struct fuse_opt opts[] = { + FUSE_LIB_OPT("-h", show_help, 1), + FUSE_LIB_OPT("--help", show_help, 1), + FUSE_OPT_END + }; + + if (fuse_opt_parse(args, &conf, opts, + fuse_lib_opt_proc) == -1) + return NULL; + + if (conf.show_help) { + fuse_lib_help(args); + return NULL; + } else + return fuse_new_31(args, op, op_size, user_data); +} + + +void fuse_destroy(struct fuse *f) +{ + size_t i; + + if (f->conf.intr && f->intr_installed) + fuse_restore_intr_signal(f->conf.intr_signal); + + if (f->fs) { + fuse_create_context(f); + + for (i = 0; i < f->id_table.size; i++) { + struct node *node; + + for (node = f->id_table.array[i]; node != NULL; + node = node->id_next) { + if (node->is_hidden) { + char *path; + if (try_get_path(f, node->nodeid, NULL, &path, NULL, false) == 0) { + fuse_fs_unlink(f->fs, path); + free(path); + } + } + } + } + } + for (i = 0; i < f->id_table.size; i++) { + struct node *node; + struct node *next; + + for (node = f->id_table.array[i]; node != NULL; node = next) { + next = node->id_next; + free_node(f, node); + f->id_table.use--; + } + } + assert(list_empty(&f->partial_slabs)); + assert(list_empty(&f->full_slabs)); + + while (fuse_modules) { + fuse_put_module(fuse_modules); + } + free(f->id_table.array); + free(f->name_table.array); + pthread_mutex_destroy(&f->lock); + fuse_session_destroy(f->se); + free(f->fs); + free(f->conf.modules); + free(f); + fuse_delete_context_key(); +} + +int fuse_mount(struct fuse *f, const char *mountpoint) { + return fuse_session_mount(fuse_get_session(f), mountpoint); +} + + +void fuse_unmount(struct fuse *f) { + fuse_session_unmount(fuse_get_session(f)); +} + +int fuse_version(void) +{ + return FUSE_VERSION; +} + +const char *fuse_pkgversion(void) +{ + return PACKAGE_VERSION; +} diff --git a/lib/fuse_i.h b/lib/fuse_i.h new file mode 100644 index 0000000..d35e1e5 --- /dev/null +++ b/lib/fuse_i.h @@ -0,0 +1,266 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#ifndef LIB_FUSE_I_H_ +#define LIB_FUSE_I_H_ + +#include "fuse.h" +#include "fuse_lowlevel.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include + +#define MIN(a, b) \ +({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a < _b ? _a : _b; \ +}) + +struct mount_opts; +struct fuse_ring_pool; + +struct fuse_req { + struct fuse_session *se; + uint64_t unique; + _Atomic int ref_cnt; + pthread_mutex_t lock; + struct fuse_ctx ctx; + struct fuse_chan *ch; + int interrupted; + struct { + unsigned int ioctl_64bit : 1; + unsigned int is_uring : 1; + unsigned int is_copy_file_range_64 : 1; + } flags; + union { + struct { + uint64_t unique; + } i; + struct { + fuse_interrupt_func_t func; + void *data; + } ni; + } u; + struct fuse_req *next; + struct fuse_req *prev; +}; + +struct fuse_notify_req { + uint64_t unique; + void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t, + const void *, const struct fuse_buf *); + struct fuse_notify_req *next; + struct fuse_notify_req *prev; +}; + +struct fuse_session_uring { + bool enable; + unsigned int q_depth; + struct fuse_ring_pool *pool; +}; + +struct fuse_session { + _Atomic(char *)mountpoint; + int fd; + struct fuse_custom_io *io; + struct mount_opts *mo; + int debug; + int deny_others; + struct fuse_lowlevel_ops op; + int got_init; + struct cuse_data *cuse_data; + void *userdata; + uid_t owner; + struct fuse_conn_info conn; + struct fuse_req list; + struct fuse_req interrupts; + pthread_mutex_t lock; + int got_destroy; + pthread_key_t pipe_key; + int broken_splice_nonblock; + uint64_t notify_ctr; + struct fuse_notify_req notify_list; + _Atomic size_t bufsize; + int error; + + /* + * This is useful if any kind of ABI incompatibility is found at + * a later version, to 'fix' it at run time. + */ + struct libfuse_version version; + + /* thread synchronization */ + _Atomic bool mt_exited; + pthread_mutex_t mt_lock; + sem_t mt_finish; + + /* true if reading requests from /dev/fuse are handled internally */ + bool buf_reallocable; + + /* io_uring */ + struct fuse_session_uring uring; + + /* + * conn->want and conn_want_ext options set by libfuse , needed + * to correctly convert want to want_ext + */ + uint32_t conn_want; + uint64_t conn_want_ext; +}; + +struct fuse_chan { + pthread_mutex_t lock; + int ctr; + int fd; +}; + +/** + * Filesystem module + * + * Filesystem modules are registered with the FUSE_REGISTER_MODULE() + * macro. + * + */ +struct fuse_module { + char *name; + fuse_module_factory_t factory; + struct fuse_module *next; + struct fusemod_so *so; + int ctr; +}; + +/** + * Configuration parameters passed to fuse_session_loop_mt() and + * fuse_loop_mt(). + * + * Internal API to avoid exposing the plain data structure and + * causing compat issues after adding or removing struct members. + * + */ +#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12) +struct fuse_loop_config +{ + /* verififier that a correct struct was was passed. This is especially + * needed, as versions below (3, 12) were using a public struct + * (now called fuse_loop_config_v1), which was hard to extend with + * additional parameters, without risking that file system implementations + * would not have noticed and might either pass uninitialized members + * or even too small structs. + * fuse_loop_config_v1 has clone_fd at this offset, which should be either 0 + * or 1. v2 or even higher version just need to set a value here + * which not conflicting and very unlikely as having been set by + * file system implementation. + */ + int version_id; + + /** + * whether to use separate device fds for each thread + * (may increase performance) + */ + int clone_fd; + /** + * The maximum number of available worker threads before they + * start to get deleted when they become idle. If not + * specified, the default is 10. + * + * Adjusting this has performance implications; a very small number + * of threads in the pool will cause a lot of thread creation and + * deletion overhead and performance may suffer. When set to 0, a new + * thread will be created to service every operation. + * The special value of -1 means that this parameter is disabled. + */ + int max_idle_threads; + + /** + * max number of threads taking and processing kernel requests + * + * As of now threads are created dynamically + */ + unsigned int max_threads; +}; +#endif + +/* ----------------------------------------------------------- * + * Channel interface (when using -o clone_fd) * + * ----------------------------------------------------------- */ + +/** + * Obtain counted reference to the channel + * + * @param ch the channel + * @return the channel + */ +struct fuse_chan *fuse_chan_get(struct fuse_chan *ch); + +/** + * Drop counted reference to a channel + * + * @param ch the channel + */ +void fuse_chan_put(struct fuse_chan *ch); + +struct mount_opts *parse_mount_opts(struct fuse_args *args); +void destroy_mount_opts(struct mount_opts *mo); +void fuse_mount_version(void); +unsigned get_max_read(struct mount_opts *o); +void fuse_kern_unmount(const char *mountpoint, int fd); +int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo); + +int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov, + int count); +void fuse_free_req(fuse_req_t req); +void list_init_req(struct fuse_req *req); + +void _cuse_lowlevel_init(fuse_req_t req, const fuse_ino_t nodeid, + const void *req_header, const void *req_payload); +void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg); + +int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg); + +void fuse_buf_free(struct fuse_buf *buf); + +int fuse_session_receive_buf_internal(struct fuse_session *se, + struct fuse_buf *buf, + struct fuse_chan *ch); +void fuse_session_process_buf_internal(struct fuse_session *se, + const struct fuse_buf *buf, + struct fuse_chan *ch); + +struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op, + size_t op_size, void *private_data); +int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config); +int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config); + +/** + * Internal verifier for the given config. + * + * @return negative standard error code or 0 on success + */ +int fuse_loop_cfg_verify(struct fuse_loop_config *config); + + +/* + * This can be changed dynamically on recent kernels through the + * /proc/sys/fs/fuse/max_pages_limit interface. + * + * Older kernels will always use the default value. + */ +#define FUSE_DEFAULT_MAX_PAGES_LIMIT 256 +#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32 + +/* room needed in buffer to accommodate header */ +#define FUSE_BUFFER_HEADER_SIZE 0x1000 + + +#endif /* LIB_FUSE_I_H_*/ diff --git a/lib/fuse_log.c b/lib/fuse_log.c new file mode 100644 index 0000000..66dcf8a --- /dev/null +++ b/lib/fuse_log.c @@ -0,0 +1,59 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2019 Red Hat, Inc. + + Logging API. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#include "fuse_log.h" + +#include +#include +#include +#include + +#define MAX_SYSLOG_LINE_LEN 512 + +static bool to_syslog = false; + +static void default_log_func(enum fuse_log_level level, const char *fmt, va_list ap) +{ + if (to_syslog) + vsyslog(level, fmt, ap); + else + vfprintf(stderr, fmt, ap); +} + +static fuse_log_func_t log_func = default_log_func; + +void fuse_set_log_func(fuse_log_func_t func) +{ + if (!func) + func = default_log_func; + + log_func = func; +} + +void fuse_log(enum fuse_log_level level, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_func(level, fmt, ap); + va_end(ap); +} + +void fuse_log_enable_syslog(const char *ident, int option, int facility) +{ + to_syslog = true; + + openlog(ident, option, facility); +} + +void fuse_log_close_syslog(void) +{ + closelog(); +} diff --git a/lib/fuse_loop.c b/lib/fuse_loop.c new file mode 100644 index 0000000..1ff075c --- /dev/null +++ b/lib/fuse_loop.c @@ -0,0 +1,48 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Implementation of the single-threaded FUSE session loop. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#include "fuse_config.h" +#include "fuse_lowlevel.h" +#include "fuse_i.h" +#include "fuse_uring_i.h" +#include +#include +#include + +int fuse_session_loop(struct fuse_session *se) +{ + int res = 0; + struct fuse_buf fbuf = { + .mem = NULL, + }; + + while (!fuse_session_exited(se)) { + res = fuse_session_receive_buf_internal(se, &fbuf, NULL); + + if (res == -EINTR) + continue; + if (res <= 0) + break; + + fuse_session_process_buf(se, &fbuf); + } + + fuse_buf_free(&fbuf); + if(res > 0) + /* No error, just the length of the most recently read + request */ + res = 0; + if(se->error != 0) + res = se->error; + + if (se->uring.pool) + fuse_uring_stop(se); + return res; +} diff --git a/lib/fuse_loop_mt.c b/lib/fuse_loop_mt.c new file mode 100644 index 0000000..ec4bb0b --- /dev/null +++ b/lib/fuse_loop_mt.c @@ -0,0 +1,535 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Implementation of the multi-threaded FUSE session loop. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#define _GNU_SOURCE + +#include "fuse_config.h" +#include "fuse_lowlevel.h" +#include "fuse_misc.h" +#include "fuse_kernel.h" +#include "fuse_i.h" +#include "fuse_uring_i.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Environment var controlling the thread stack size */ +#define ENVNAME_THREAD_STACK "FUSE_THREAD_STACK" + +#define FUSE_LOOP_MT_V2_IDENTIFIER INT_MAX - 2 +#define FUSE_LOOP_MT_DEF_CLONE_FD 0 +#define FUSE_LOOP_MT_DEF_MAX_THREADS 10 +#define FUSE_LOOP_MT_DEF_IDLE_THREADS -1 /* thread destruction is disabled + * by default */ + +/* an arbitrary large value that cannot be valid */ +#define FUSE_LOOP_MT_MAX_THREADS (100U * 1000) + +struct fuse_worker { + struct fuse_worker *prev; + struct fuse_worker *next; + pthread_t thread_id; + + // We need to include fuse_buf so that we can properly free + // it when a thread is terminated by pthread_cancel(). + struct fuse_buf fbuf; + struct fuse_chan *ch; + struct fuse_mt *mt; +}; + +/* synchronization via se->mt_lock */ +struct fuse_mt { + int numworker; + int numavail; + struct fuse_session *se; + struct fuse_worker main; + int error; + int clone_fd; + int max_idle; + int max_threads; +}; + +static struct fuse_chan *fuse_chan_new(int fd) +{ + struct fuse_chan *ch = (struct fuse_chan *) malloc(sizeof(*ch)); + if (ch == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate channel\n"); + return NULL; + } + + memset(ch, 0, sizeof(*ch)); + ch->fd = fd; + ch->ctr = 1; + pthread_mutex_init(&ch->lock, NULL); + + return ch; +} + +struct fuse_chan *fuse_chan_get(struct fuse_chan *ch) +{ + assert(ch->ctr > 0); + pthread_mutex_lock(&ch->lock); + ch->ctr++; + pthread_mutex_unlock(&ch->lock); + + return ch; +} + +void fuse_chan_put(struct fuse_chan *ch) +{ + if (ch == NULL) + return; + pthread_mutex_lock(&ch->lock); + ch->ctr--; + if (!ch->ctr) { + pthread_mutex_unlock(&ch->lock); + close(ch->fd); + pthread_mutex_destroy(&ch->lock); + free(ch); + } else + pthread_mutex_unlock(&ch->lock); +} + +static void list_add_worker(struct fuse_worker *w, struct fuse_worker *next) +{ + struct fuse_worker *prev = next->prev; + w->next = next; + w->prev = prev; + prev->next = w; + next->prev = w; +} + +static void list_del_worker(struct fuse_worker *w) +{ + struct fuse_worker *prev = w->prev; + struct fuse_worker *next = w->next; + prev->next = next; + next->prev = prev; +} + +static int fuse_loop_start_thread(struct fuse_mt *mt); + +static void *fuse_do_work(void *data) +{ + struct fuse_worker *w = (struct fuse_worker *) data; + struct fuse_mt *mt = w->mt; + struct fuse_session *se = mt->se; + + fuse_set_thread_name("fuse_worker"); + + while (!fuse_session_exited(se)) { + int isforget = 0; + int res; + + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + res = fuse_session_receive_buf_internal(se, &w->fbuf, w->ch); + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + if (res == -EINTR) + continue; + if (res <= 0) { + if (res < 0) { + fuse_session_exit(se); + mt->error = res; + } + break; + } + + pthread_mutex_lock(&se->mt_lock); + if (fuse_session_exited(se)) { + pthread_mutex_unlock(&se->mt_lock); + return NULL; + } + + /* + * This disgusting hack is needed so that zillions of threads + * are not created on a burst of FORGET messages + */ + if (!(w->fbuf.flags & FUSE_BUF_IS_FD)) { + struct fuse_in_header *in = w->fbuf.mem; + + if (in->opcode == FUSE_FORGET || + in->opcode == FUSE_BATCH_FORGET) + isforget = 1; + } + + if (!isforget) + mt->numavail--; + if (mt->numavail == 0 && mt->numworker < mt->max_threads && + likely(se->got_init)) + fuse_loop_start_thread(mt); + pthread_mutex_unlock(&se->mt_lock); + + fuse_session_process_buf_internal(se, &w->fbuf, w->ch); + + pthread_mutex_lock(&se->mt_lock); + if (!isforget) + mt->numavail++; + + /* creating and destroying threads is rather expensive - and there is + * not much gain from destroying existing threads. It is therefore + * discouraged to set max_idle to anything else than -1. If there + * is indeed a good reason to destruct threads it should be done + * delayed, a moving average might be useful for that. + */ + if (mt->max_idle != -1 && mt->numavail > mt->max_idle && mt->numworker > 1) { + if (fuse_session_exited(se)) { + pthread_mutex_unlock(&se->mt_lock); + return NULL; + } + list_del_worker(w); + mt->numavail--; + mt->numworker--; + pthread_mutex_unlock(&se->mt_lock); + + pthread_detach(w->thread_id); + fuse_buf_free(&w->fbuf); + fuse_chan_put(w->ch); + free(w); + return NULL; + } + pthread_mutex_unlock(&se->mt_lock); + } + + sem_post(&se->mt_finish); + return NULL; +} + +int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg) +{ + sigset_t oldset; + sigset_t newset; + int res; + pthread_attr_t attr; + char *stack_size; + + /* Override default stack size + * XXX: This should ideally be a parameter option. It is rather + * well hidden here. + */ + pthread_attr_init(&attr); + stack_size = getenv(ENVNAME_THREAD_STACK); + if (stack_size) { + long size; + + res = libfuse_strtol(stack_size, &size); + if (res) + fuse_log(FUSE_LOG_ERR, "fuse: invalid stack size: %s\n", + stack_size); + else if (pthread_attr_setstacksize(&attr, size)) + fuse_log(FUSE_LOG_ERR, "fuse: could not set stack size: %ld\n", + size); + } + + /* Disallow signal reception in worker threads */ + sigemptyset(&newset); + sigaddset(&newset, SIGTERM); + sigaddset(&newset, SIGINT); + sigaddset(&newset, SIGHUP); + sigaddset(&newset, SIGQUIT); + pthread_sigmask(SIG_BLOCK, &newset, &oldset); + res = pthread_create(thread_id, &attr, func, arg); + pthread_sigmask(SIG_SETMASK, &oldset, NULL); + pthread_attr_destroy(&attr); + if (res != 0) { + fuse_log(FUSE_LOG_ERR, "fuse: error creating thread: %s\n", + strerror(res)); + return -1; + } + + return 0; +} + +static int fuse_clone_chan_fd_default(struct fuse_session *se) +{ + int res; + int clonefd; + uint32_t masterfd; + const char *devname = "/dev/fuse"; + +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif + clonefd = open(devname, O_RDWR | O_CLOEXEC); + if (clonefd == -1) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n", devname, + strerror(errno)); + return -1; + } + if (!O_CLOEXEC) { + res = fcntl(clonefd, F_SETFD, FD_CLOEXEC); + if (res == -1) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to set CLOEXEC: %s\n", + strerror(errno)); + close(clonefd); + return -1; + } + } + + masterfd = se->fd; + res = ioctl(clonefd, FUSE_DEV_IOC_CLONE, &masterfd); + if (res == -1) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to clone device fd: %s\n", + strerror(errno)); + close(clonefd); + return -1; + } + return clonefd; +} + +static struct fuse_chan *fuse_clone_chan(struct fuse_mt *mt) +{ + int clonefd; + struct fuse_session *se = mt->se; + struct fuse_chan *newch; + + if (se->io != NULL) { + if (se->io->clone_fd != NULL) + clonefd = se->io->clone_fd(se->fd); + else + return NULL; + } else { + clonefd = fuse_clone_chan_fd_default(se); + } + if (clonefd < 0) + return NULL; + + newch = fuse_chan_new(clonefd); + if (newch == NULL) + close(clonefd); + + return newch; +} + +static int fuse_loop_start_thread(struct fuse_mt *mt) +{ + int res; + + struct fuse_worker *w = malloc(sizeof(struct fuse_worker)); + if (!w) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate worker structure\n"); + return -1; + } + memset(w, 0, sizeof(struct fuse_worker)); + w->fbuf.mem = NULL; + w->mt = mt; + + w->ch = NULL; + if (mt->clone_fd) { + w->ch = fuse_clone_chan(mt); + if(!w->ch) { + /* Don't attempt this again */ + fuse_log(FUSE_LOG_ERR, "fuse: trying to continue " + "without -o clone_fd.\n"); + mt->clone_fd = 0; + } + } + + res = fuse_start_thread(&w->thread_id, fuse_do_work, w); + if (res == -1) { + fuse_chan_put(w->ch); + free(w); + return -1; + } + list_add_worker(w, &mt->main); + mt->numavail ++; + mt->numworker ++; + + return 0; +} + +static void fuse_join_worker(struct fuse_mt *mt, struct fuse_worker *w) +{ + pthread_join(w->thread_id, NULL); + pthread_mutex_lock(&mt->se->mt_lock); + list_del_worker(w); + pthread_mutex_unlock(&mt->se->mt_lock); + fuse_buf_free(&w->fbuf); + fuse_chan_put(w->ch); + free(w); +} + +int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config); +FUSE_SYMVER("fuse_session_loop_mt_312", "fuse_session_loop_mt@@FUSE_3.12") +int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config) +{ +int err; + struct fuse_mt mt; + struct fuse_worker *w; + int created_config = 0; + + if (config) { + err = fuse_loop_cfg_verify(config); + if (err) + return err; + } else { + /* The caller does not care about parameters - use the default */ + config = fuse_loop_cfg_create(); + created_config = 1; + } + + + memset(&mt, 0, sizeof(struct fuse_mt)); + mt.se = se; + mt.clone_fd = config->clone_fd; + mt.error = 0; + mt.numworker = 0; + mt.numavail = 0; + mt.max_idle = config->max_idle_threads; + mt.max_threads = config->max_threads; + mt.main.thread_id = pthread_self(); + mt.main.prev = mt.main.next = &mt.main; + + pthread_mutex_lock(&se->mt_lock); + err = fuse_loop_start_thread(&mt); + pthread_mutex_unlock(&se->mt_lock); + if (!err) { + while (!fuse_session_exited(se)) + sem_wait(&se->mt_finish); + if (se->debug) + fuse_log(FUSE_LOG_DEBUG, + "fuse: session exited, terminating workers\n"); + + pthread_mutex_lock(&se->mt_lock); + for (w = mt.main.next; w != &mt.main; w = w->next) + pthread_cancel(w->thread_id); + pthread_mutex_unlock(&se->mt_lock); + + while (mt.main.next != &mt.main) + fuse_join_worker(&mt, mt.main.next); + + err = mt.error; + + if (se->uring.pool) + fuse_uring_stop(se); + } + + pthread_mutex_destroy(&se->mt_lock); + if(se->error != 0) + err = se->error; + + + if (created_config) { + fuse_loop_cfg_destroy(config); + config = NULL; + } + + return err; +} + +int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1); +FUSE_SYMVER("fuse_session_loop_mt_32", "fuse_session_loop_mt@FUSE_3.2") +int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1) +{ + int err; + struct fuse_loop_config *config = NULL; + + if (config_v1 != NULL) { + /* convert the given v1 config */ + config = fuse_loop_cfg_create(); + if (config == NULL) + return ENOMEM; + + fuse_loop_cfg_convert(config, config_v1); + } + + err = fuse_session_loop_mt_312(se, config); + + fuse_loop_cfg_destroy(config); + + return err; +} + + +int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd); +FUSE_SYMVER("fuse_session_loop_mt_31", "fuse_session_loop_mt@FUSE_3.0") +int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd) +{ + int err; + struct fuse_loop_config *config = fuse_loop_cfg_create(); + if (clone_fd > 0) + fuse_loop_cfg_set_clone_fd(config, clone_fd); + err = fuse_session_loop_mt_312(se, config); + + fuse_loop_cfg_destroy(config); + + return err; +} + +struct fuse_loop_config *fuse_loop_cfg_create(void) +{ + struct fuse_loop_config *config = calloc(1, sizeof(*config)); + if (config == NULL) + return NULL; + + config->version_id = FUSE_LOOP_MT_V2_IDENTIFIER; + config->max_idle_threads = FUSE_LOOP_MT_DEF_IDLE_THREADS; + config->max_threads = FUSE_LOOP_MT_DEF_MAX_THREADS; + config->clone_fd = FUSE_LOOP_MT_DEF_CLONE_FD; + + return config; +} + +void fuse_loop_cfg_destroy(struct fuse_loop_config *config) +{ + free(config); +} + +int fuse_loop_cfg_verify(struct fuse_loop_config *config) +{ + if (config->version_id != FUSE_LOOP_MT_V2_IDENTIFIER) + return -EINVAL; + + return 0; +} + +void fuse_loop_cfg_convert(struct fuse_loop_config *config, + struct fuse_loop_config_v1 *v1_conf) +{ + fuse_loop_cfg_set_idle_threads(config, v1_conf->max_idle_threads); + + fuse_loop_cfg_set_clone_fd(config, v1_conf->clone_fd); +} + +void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config, + unsigned int value) +{ + if (value > FUSE_LOOP_MT_MAX_THREADS) { + if (value != UINT_MAX) + fuse_log(FUSE_LOG_ERR, + "Ignoring invalid max threads value " + "%u > max (%u).\n", value, + FUSE_LOOP_MT_MAX_THREADS); + return; + } + config->max_idle_threads = value; +} + +void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config, + unsigned int value) +{ + config->max_threads = value; +} + +void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config, + unsigned int value) +{ + config->clone_fd = value; +} + diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c new file mode 100644 index 0000000..d420b25 --- /dev/null +++ b/lib/fuse_lowlevel.c @@ -0,0 +1,4491 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Implementation of (most of) the low-level FUSE API. The session loop + functions are implemented in separate files. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#define _GNU_SOURCE + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_kernel.h" +#include "fuse_opt.h" +#include "fuse_misc.h" +#include "mount_util.h" +#include "util.h" +#include "fuse_uring_i.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USDT_ENABLED +#include "usdt.h" +#endif + +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif +#ifndef F_SETPIPE_SZ +#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7) +#endif + +#define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg))) +#define OFFSET_MAX 0x7fffffffffffffffLL + +struct fuse_pollhandle { + uint64_t kh; + struct fuse_session *se; +}; + +static size_t pagesize; + +static __attribute__((constructor)) void fuse_ll_init_pagesize(void) +{ + pagesize = getpagesize(); +} + +#ifdef USDT_ENABLED +/* tracepoints */ +static void trace_request_receive(int err) +{ + USDT(libfuse, request_receive, err); +} + +static void trace_request_process(unsigned int opcode, unsigned int unique) +{ + USDT(libfuse, request_process, opcode, unique); +} + +static void trace_request_reply(uint64_t unique, unsigned int len, + int error, int reply_err) +{ + USDT(libfuse, request_reply, unique, len, error, reply_err); +} +#else +static void trace_request_receive(int err) +{ + (void)err; +} + +static void trace_request_process(unsigned int opcode, unsigned int unique) +{ + (void)opcode; + (void)unique; +} + +static void trace_request_reply(uint64_t unique, unsigned int len, + int error, int reply_err) +{ + (void)unique; + (void)len; + (void)error; + (void)reply_err; +} +#endif + +static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr) +{ + attr->ino = stbuf->st_ino; + attr->mode = stbuf->st_mode; + attr->nlink = stbuf->st_nlink; + attr->uid = stbuf->st_uid; + attr->gid = stbuf->st_gid; + attr->rdev = stbuf->st_rdev; + attr->size = stbuf->st_size; + attr->blksize = stbuf->st_blksize; + attr->blocks = stbuf->st_blocks; + attr->atime = stbuf->st_atime; + attr->mtime = stbuf->st_mtime; + attr->ctime = stbuf->st_ctime; + attr->atimensec = ST_ATIM_NSEC(stbuf); + attr->mtimensec = ST_MTIM_NSEC(stbuf); + attr->ctimensec = ST_CTIM_NSEC(stbuf); +} + +static void convert_attr(const struct fuse_setattr_in *attr, struct stat *stbuf) +{ + stbuf->st_mode = attr->mode; + stbuf->st_uid = attr->uid; + stbuf->st_gid = attr->gid; + stbuf->st_size = attr->size; + stbuf->st_atime = attr->atime; + stbuf->st_mtime = attr->mtime; + stbuf->st_ctime = attr->ctime; + ST_ATIM_NSEC_SET(stbuf, attr->atimensec); + ST_MTIM_NSEC_SET(stbuf, attr->mtimensec); + ST_CTIM_NSEC_SET(stbuf, attr->ctimensec); +} + +static size_t iov_length(const struct iovec *iov, size_t count) +{ + size_t seg; + size_t ret = 0; + + for (seg = 0; seg < count; seg++) + ret += iov[seg].iov_len; + return ret; +} + +void list_init_req(struct fuse_req *req) +{ + req->next = req; + req->prev = req; +} + +static void list_del_req(struct fuse_req *req) +{ + struct fuse_req *prev = req->prev; + struct fuse_req *next = req->next; + prev->next = next; + next->prev = prev; +} + +static void list_add_req(struct fuse_req *req, struct fuse_req *next) +{ + struct fuse_req *prev = next->prev; + req->next = next; + req->prev = prev; + prev->next = req; + next->prev = req; +} + +static void destroy_req(fuse_req_t req) +{ + if (req->flags.is_uring) { + fuse_log(FUSE_LOG_ERR, "Refusing to destruct uring req\n"); + return; + } + assert(req->ch == NULL); + pthread_mutex_destroy(&req->lock); + free(req); +} + +void fuse_free_req(fuse_req_t req) +{ + int ctr; + struct fuse_session *se = req->se; + + /* XXX: for now no support for interrupts with io-uring + * It actually might work already, though. But then would add + * a lock across ring queues. + */ + if (se->conn.no_interrupt || req->flags.is_uring) { + ctr = --req->ref_cnt; + fuse_chan_put(req->ch); + req->ch = NULL; + } else { + pthread_mutex_lock(&se->lock); + req->u.ni.func = NULL; + req->u.ni.data = NULL; + list_del_req(req); + ctr = --req->ref_cnt; + fuse_chan_put(req->ch); + req->ch = NULL; + pthread_mutex_unlock(&se->lock); + } + if (!ctr) + destroy_req(req); +} + +static struct fuse_req *fuse_ll_alloc_req(struct fuse_session *se) +{ + struct fuse_req *req; + + req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req)); + if (req == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate request\n"); + } else { + req->se = se; + req->ref_cnt = 1; + list_init_req(req); + pthread_mutex_init(&req->lock, NULL); + } + + return req; +} + +/* + * Send data to fuse-kernel using an fd of the fuse device. + */ +static int fuse_write_msg_dev(struct fuse_session *se, struct fuse_chan *ch, + struct iovec *iov, int count) +{ + ssize_t res; + int err; + + if (se->io != NULL) + + /* se->io->writev is never NULL if se->io is not NULL as + * specified by fuse_session_custom_io() + */ + res = se->io->writev(ch ? ch->fd : se->fd, iov, count, + se->userdata); + else + res = writev(ch ? ch->fd : se->fd, iov, count); + + if (res == -1) { + /* ENOENT means the operation was interrupted */ + err = errno; + if (!fuse_session_exited(se) && err != ENOENT) + perror("fuse: writing device"); + return -err; + } + + return 0; +} + +static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch, + struct iovec *iov, int count, fuse_req_t req) +{ + struct fuse_out_header *out = iov[0].iov_base; + int err; + bool is_uring = req && req->flags.is_uring ? true : false; + + if (!is_uring) + assert(se != NULL); + out->len = iov_length(iov, count); + + if (se->debug) { + if (out->unique == 0) { + fuse_log(FUSE_LOG_DEBUG, "NOTIFY: code=%d length=%u\n", + out->error, out->len); + } else if (out->error) { + fuse_log(FUSE_LOG_DEBUG, + " unique: %llu, error: %i (%s), outsize: %i\n", + (unsigned long long) out->unique, out->error, + strerror(-out->error), out->len); + } else { + fuse_log(FUSE_LOG_DEBUG, + " unique: %llu, success, outsize: %i\n", + (unsigned long long) out->unique, out->len); + } + } + + if (is_uring) + err = fuse_send_msg_uring(req, iov, count); + else + err = fuse_write_msg_dev(se, ch, iov, count); + + trace_request_reply(out->unique, out->len, out->error, err); + return err; +} + +int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov, + int count) +{ + struct fuse_out_header out; + +#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32 + const char *str = strerrordesc_np(error * -1); + if ((str == NULL && error != 0) || error > 0) { +#else + if (error <= -1000 || error > 0) { +#endif + fuse_log(FUSE_LOG_ERR, "fuse: bad error value: %i\n", error); + error = -ERANGE; + } + + out.unique = req->unique; + out.error = error; + + iov[0].iov_base = &out; + iov[0].iov_len = sizeof(struct fuse_out_header); + + return fuse_send_msg(req->se, req->ch, iov, count, req); +} + +static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov, + int count) +{ + int res; + + res = fuse_send_reply_iov_nofree(req, error, iov, count); + fuse_free_req(req); + return res; +} + +static int send_reply(fuse_req_t req, int error, const void *arg, + size_t argsize) +{ + if (req->flags.is_uring) + return send_reply_uring(req, error, arg, argsize); + + struct iovec iov[2]; + int count = 1; + if (argsize) { + iov[1].iov_base = (void *) arg; + iov[1].iov_len = argsize; + count++; + } + return send_reply_iov(req, error, iov, count); +} + +int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count) +{ + int res; + struct iovec *padded_iov; + + padded_iov = malloc((count + 1) * sizeof(struct iovec)); + if (padded_iov == NULL) + return fuse_reply_err(req, ENOMEM); + + memcpy(padded_iov + 1, iov, count * sizeof(struct iovec)); + count++; + + res = send_reply_iov(req, 0, padded_iov, count); + free(padded_iov); + + return res; +} + + +/* `buf` is allowed to be empty so that the proper size may be + allocated by the caller */ +size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, + const char *name, const struct stat *stbuf, off_t off) +{ + (void)req; + size_t namelen; + size_t entlen; + size_t entlen_padded; + struct fuse_dirent *dirent; + + namelen = strlen(name); + entlen = FUSE_NAME_OFFSET + namelen; + entlen_padded = FUSE_DIRENT_ALIGN(entlen); + + if ((buf == NULL) || (entlen_padded > bufsize)) + return entlen_padded; + + dirent = (struct fuse_dirent*) buf; + dirent->ino = stbuf->st_ino; + dirent->off = off; + dirent->namelen = namelen; + dirent->type = (stbuf->st_mode & S_IFMT) >> 12; + memcpy(dirent->name, name, namelen); + memset(dirent->name + namelen, 0, entlen_padded - entlen); + + return entlen_padded; +} + +static void convert_statfs(const struct statvfs *stbuf, + struct fuse_kstatfs *kstatfs) +{ + kstatfs->bsize = stbuf->f_bsize; + kstatfs->frsize = stbuf->f_frsize; + kstatfs->blocks = stbuf->f_blocks; + kstatfs->bfree = stbuf->f_bfree; + kstatfs->bavail = stbuf->f_bavail; + kstatfs->files = stbuf->f_files; + kstatfs->ffree = stbuf->f_ffree; + kstatfs->namelen = stbuf->f_namemax; +} + +static int send_reply_ok(fuse_req_t req, const void *arg, size_t argsize) +{ + return send_reply(req, 0, arg, argsize); +} + +int fuse_reply_err(fuse_req_t req, int err) +{ + return send_reply(req, -err, NULL, 0); +} + +void fuse_reply_none(fuse_req_t req) +{ + fuse_free_req(req); +} + +static unsigned long calc_timeout_sec(double t) +{ + if (t > (double) ULONG_MAX) + return ULONG_MAX; + else if (t < 0.0) + return 0; + else + return (unsigned long) t; +} + +static unsigned int calc_timeout_nsec(double t) +{ + double f = t - (double) calc_timeout_sec(t); + if (f < 0.0) + return 0; + else if (f >= 0.999999999) + return 999999999; + else + return (unsigned int) (f * 1.0e9); +} + +static void fill_entry(struct fuse_entry_out *arg, + const struct fuse_entry_param *e) +{ + arg->nodeid = e->ino; + arg->generation = e->generation; + arg->entry_valid = calc_timeout_sec(e->entry_timeout); + arg->entry_valid_nsec = calc_timeout_nsec(e->entry_timeout); + arg->attr_valid = calc_timeout_sec(e->attr_timeout); + arg->attr_valid_nsec = calc_timeout_nsec(e->attr_timeout); + convert_stat(&e->attr, &arg->attr); +} + +/* `buf` is allowed to be empty so that the proper size may be + allocated by the caller */ +size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, + const char *name, + const struct fuse_entry_param *e, off_t off) +{ + (void)req; + size_t namelen; + size_t entlen; + size_t entlen_padded; + + namelen = strlen(name); + entlen = FUSE_NAME_OFFSET_DIRENTPLUS + namelen; + entlen_padded = FUSE_DIRENT_ALIGN(entlen); + if ((buf == NULL) || (entlen_padded > bufsize)) + return entlen_padded; + + struct fuse_direntplus *dp = (struct fuse_direntplus *) buf; + memset(&dp->entry_out, 0, sizeof(dp->entry_out)); + fill_entry(&dp->entry_out, e); + + struct fuse_dirent *dirent = &dp->dirent; + dirent->ino = e->attr.st_ino; + dirent->off = off; + dirent->namelen = namelen; + dirent->type = (e->attr.st_mode & S_IFMT) >> 12; + memcpy(dirent->name, name, namelen); + memset(dirent->name + namelen, 0, entlen_padded - entlen); + + return entlen_padded; +} + +static void fill_open(struct fuse_open_out *arg, + const struct fuse_file_info *f) +{ + arg->fh = f->fh; + if (f->backing_id > 0) { + arg->backing_id = f->backing_id; + arg->open_flags |= FOPEN_PASSTHROUGH; + } + if (f->direct_io) + arg->open_flags |= FOPEN_DIRECT_IO; + if (f->keep_cache) + arg->open_flags |= FOPEN_KEEP_CACHE; + if (f->cache_readdir) + arg->open_flags |= FOPEN_CACHE_DIR; + if (f->nonseekable) + arg->open_flags |= FOPEN_NONSEEKABLE; + if (f->noflush) + arg->open_flags |= FOPEN_NOFLUSH; + if (f->parallel_direct_writes) + arg->open_flags |= FOPEN_PARALLEL_DIRECT_WRITES; +} + +int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e) +{ + struct fuse_entry_out arg; + size_t size = req->se->conn.proto_minor < 9 ? + FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg); + + /* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant + negative entry */ + if (!e->ino && req->se->conn.proto_minor < 4) + return fuse_reply_err(req, ENOENT); + + memset(&arg, 0, sizeof(arg)); + fill_entry(&arg, e); + return send_reply_ok(req, &arg, size); +} + +int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, + const struct fuse_file_info *f) +{ + alignas(uint64_t) char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)]; + size_t entrysize = req->se->conn.proto_minor < 9 ? + FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(struct fuse_entry_out); + struct fuse_entry_out *earg = (struct fuse_entry_out *) buf; + struct fuse_open_out *oarg = (struct fuse_open_out *) (buf + entrysize); + + memset(buf, 0, sizeof(buf)); + fill_entry(earg, e); + fill_open(oarg, f); + return send_reply_ok(req, buf, + entrysize + sizeof(struct fuse_open_out)); +} + +int fuse_reply_attr(fuse_req_t req, const struct stat *attr, + double attr_timeout) +{ + struct fuse_attr_out arg; + size_t size = req->se->conn.proto_minor < 9 ? + FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg); + + memset(&arg, 0, sizeof(arg)); + arg.attr_valid = calc_timeout_sec(attr_timeout); + arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout); + convert_stat(attr, &arg.attr); + + return send_reply_ok(req, &arg, size); +} + +int fuse_reply_readlink(fuse_req_t req, const char *linkname) +{ + return send_reply_ok(req, linkname, strlen(linkname)); +} + +int fuse_passthrough_open(fuse_req_t req, int fd) +{ + struct fuse_backing_map map = { .fd = fd }; + int ret; + + ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_OPEN, &map); + if (ret <= 0) { + fuse_log(FUSE_LOG_ERR, "fuse: passthrough_open: %s\n", strerror(errno)); + return 0; + } + + return ret; +} + +int fuse_passthrough_close(fuse_req_t req, int backing_id) +{ + int ret; + + ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_CLOSE, &backing_id); + if (ret < 0) + fuse_log(FUSE_LOG_ERR, "fuse: passthrough_close: %s\n", strerror(errno)); + + return ret; +} + +int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *f) +{ + struct fuse_open_out arg; + + memset(&arg, 0, sizeof(arg)); + fill_open(&arg, f); + return send_reply_ok(req, &arg, sizeof(arg)); +} + +static int do_fuse_reply_write(fuse_req_t req, size_t count) +{ + struct fuse_write_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.size = count; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +static int do_fuse_reply_copy(fuse_req_t req, size_t count) +{ + struct fuse_copy_file_range_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.bytes_copied = count; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_write(fuse_req_t req, size_t count) +{ + /* + * This function is also used by FUSE_COPY_FILE_RANGE and its 64-bit + * variant. + */ + if (req->flags.is_copy_file_range_64) + return do_fuse_reply_copy(req, count); + else + return do_fuse_reply_write(req, count); +} + +int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size) +{ + return send_reply_ok(req, buf, size); +} + +static int fuse_send_data_iov_fallback(struct fuse_session *se, + struct fuse_chan *ch, + struct iovec *iov, int iov_count, + struct fuse_bufvec *buf, + size_t len, fuse_req_t req) +{ + struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len); + void *mbuf; + int res; + + /* Optimize common case */ + if (buf->count == 1 && buf->idx == 0 && buf->off == 0 && + !(buf->buf[0].flags & FUSE_BUF_IS_FD)) { + /* FIXME: also avoid memory copy if there are multiple buffers + but none of them contain an fd */ + + iov[iov_count].iov_base = buf->buf[0].mem; + iov[iov_count].iov_len = len; + iov_count++; + return fuse_send_msg(se, ch, iov, iov_count, req); + } + + res = posix_memalign(&mbuf, pagesize, len); + if (res != 0) + return res; + + mem_buf.buf[0].mem = mbuf; + res = fuse_buf_copy(&mem_buf, buf, 0); + if (res < 0) { + free(mbuf); + return -res; + } + len = res; + + iov[iov_count].iov_base = mbuf; + iov[iov_count].iov_len = len; + iov_count++; + res = fuse_send_msg(se, ch, iov, iov_count, req); + free(mbuf); + + return res; +} + +struct fuse_ll_pipe { + size_t size; + int can_grow; + int pipe[2]; +}; + +static void fuse_ll_pipe_free(struct fuse_ll_pipe *llp) +{ + close(llp->pipe[0]); + close(llp->pipe[1]); + free(llp); +} + +#ifdef HAVE_SPLICE +#if !defined(HAVE_PIPE2) || !defined(O_CLOEXEC) +static int fuse_pipe(int fds[2]) +{ + int rv = pipe(fds); + + if (rv == -1) + return rv; + + if (fcntl(fds[0], F_SETFL, O_NONBLOCK) == -1 || + fcntl(fds[1], F_SETFL, O_NONBLOCK) == -1 || + fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 || + fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) { + close(fds[0]); + close(fds[1]); + rv = -1; + } + return rv; +} +#else +static int fuse_pipe(int fds[2]) +{ + return pipe2(fds, O_CLOEXEC | O_NONBLOCK); +} +#endif + +static struct fuse_ll_pipe *fuse_ll_get_pipe(struct fuse_session *se) +{ + struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key); + if (llp == NULL) { + int res; + + llp = malloc(sizeof(struct fuse_ll_pipe)); + if (llp == NULL) + return NULL; + + res = fuse_pipe(llp->pipe); + if (res == -1) { + free(llp); + return NULL; + } + + /* + *the default size is 16 pages on linux + */ + llp->size = pagesize * 16; + llp->can_grow = 1; + + pthread_setspecific(se->pipe_key, llp); + } + + return llp; +} +#endif + +static void fuse_ll_clear_pipe(struct fuse_session *se) +{ + struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key); + if (llp) { + pthread_setspecific(se->pipe_key, NULL); + fuse_ll_pipe_free(llp); + } +} + +#if defined(HAVE_SPLICE) && defined(HAVE_VMSPLICE) +static int read_back(int fd, char *buf, size_t len) +{ + int res; + + res = read(fd, buf, len); + if (res == -1) { + fuse_log(FUSE_LOG_ERR, + "fuse: internal error: failed to read back from pipe: %s\n", + strerror(errno)); + return -EIO; + } + if (res != len) { + fuse_log(FUSE_LOG_ERR, + "fuse: internal error: short read back from pipe: %i from %zd\n", + res, len); + return -EIO; + } + return 0; +} + +static int grow_pipe_to_max(int pipefd) +{ + int res; + long max; + long maxfd; + char buf[32]; + + maxfd = open("/proc/sys/fs/pipe-max-size", O_RDONLY); + if (maxfd < 0) + return -errno; + + res = read(maxfd, buf, sizeof(buf) - 1); + if (res < 0) { + int saved_errno; + + saved_errno = errno; + close(maxfd); + return -saved_errno; + } + close(maxfd); + buf[res] = '\0'; + + res = libfuse_strtol(buf, &max); + if (res) + return res; + res = fcntl(pipefd, F_SETPIPE_SZ, max); + if (res < 0) + return -errno; + return max; +} + +static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch, + struct iovec *iov, int iov_count, + struct fuse_bufvec *buf, unsigned int flags, + fuse_req_t req) +{ + int res; + size_t len = fuse_buf_size(buf); + struct fuse_out_header *out = iov[0].iov_base; + struct fuse_ll_pipe *llp; + int splice_flags; + size_t pipesize; + size_t total_buf_size; + size_t idx; + size_t headerlen; + struct fuse_bufvec pipe_buf = FUSE_BUFVEC_INIT(len); + + if (se->broken_splice_nonblock) + goto fallback; + + if (flags & FUSE_BUF_NO_SPLICE) + goto fallback; + + total_buf_size = 0; + for (idx = buf->idx; idx < buf->count; idx++) { + total_buf_size += buf->buf[idx].size; + if (idx == buf->idx) + total_buf_size -= buf->off; + } + if (total_buf_size < 2 * pagesize) + goto fallback; + + if (se->conn.proto_minor < 14 || + !(se->conn.want_ext & FUSE_CAP_SPLICE_WRITE)) + goto fallback; + + llp = fuse_ll_get_pipe(se); + if (llp == NULL) + goto fallback; + + + headerlen = iov_length(iov, iov_count); + + out->len = headerlen + len; + + /* + * Heuristic for the required pipe size, does not work if the + * source contains less than page size fragments + */ + pipesize = pagesize * (iov_count + buf->count + 1) + out->len; + + if (llp->size < pipesize) { + if (llp->can_grow) { + res = fcntl(llp->pipe[0], F_SETPIPE_SZ, pipesize); + if (res == -1) { + res = grow_pipe_to_max(llp->pipe[0]); + if (res > 0) + llp->size = res; + llp->can_grow = 0; + goto fallback; + } + llp->size = res; + } + if (llp->size < pipesize) + goto fallback; + } + + + res = vmsplice(llp->pipe[1], iov, iov_count, SPLICE_F_NONBLOCK); + if (res == -1) + goto fallback; + + if (res != headerlen) { + res = -EIO; + fuse_log(FUSE_LOG_ERR, "fuse: short vmsplice to pipe: %u/%zu\n", res, + headerlen); + goto clear_pipe; + } + + pipe_buf.buf[0].flags = FUSE_BUF_IS_FD; + pipe_buf.buf[0].fd = llp->pipe[1]; + + res = fuse_buf_copy(&pipe_buf, buf, + FUSE_BUF_FORCE_SPLICE | FUSE_BUF_SPLICE_NONBLOCK); + if (res < 0) { + if (res == -EAGAIN || res == -EINVAL) { + /* + * Should only get EAGAIN on kernels with + * broken SPLICE_F_NONBLOCK support (<= + * 2.6.35) where this error or a short read is + * returned even if the pipe itself is not + * full + * + * EINVAL might mean that splice can't handle + * this combination of input and output. + */ + if (res == -EAGAIN) + se->broken_splice_nonblock = 1; + + pthread_setspecific(se->pipe_key, NULL); + fuse_ll_pipe_free(llp); + goto fallback; + } + res = -res; + goto clear_pipe; + } + + if (res != 0 && res < len) { + struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len); + void *mbuf; + size_t now_len = res; + /* + * For regular files a short count is either + * 1) due to EOF, or + * 2) because of broken SPLICE_F_NONBLOCK (see above) + * + * For other inputs it's possible that we overflowed + * the pipe because of small buffer fragments. + */ + + res = posix_memalign(&mbuf, pagesize, len); + if (res != 0) + goto clear_pipe; + + mem_buf.buf[0].mem = mbuf; + mem_buf.off = now_len; + res = fuse_buf_copy(&mem_buf, buf, 0); + if (res > 0) { + char *tmpbuf; + size_t extra_len = res; + /* + * Trickiest case: got more data. Need to get + * back the data from the pipe and then fall + * back to regular write. + */ + tmpbuf = malloc(headerlen); + if (tmpbuf == NULL) { + free(mbuf); + res = ENOMEM; + goto clear_pipe; + } + res = read_back(llp->pipe[0], tmpbuf, headerlen); + free(tmpbuf); + if (res != 0) { + free(mbuf); + goto clear_pipe; + } + res = read_back(llp->pipe[0], mbuf, now_len); + if (res != 0) { + free(mbuf); + goto clear_pipe; + } + len = now_len + extra_len; + iov[iov_count].iov_base = mbuf; + iov[iov_count].iov_len = len; + iov_count++; + res = fuse_send_msg(se, ch, iov, iov_count, req); + free(mbuf); + return res; + } + free(mbuf); + res = now_len; + } + len = res; + out->len = headerlen + len; + + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, + " unique: %llu, success, outsize: %i (splice)\n", + (unsigned long long) out->unique, out->len); + } + + splice_flags = 0; + if ((flags & FUSE_BUF_SPLICE_MOVE) && + (se->conn.want_ext & FUSE_CAP_SPLICE_MOVE)) + splice_flags |= SPLICE_F_MOVE; + + if (se->io != NULL && se->io->splice_send != NULL) { + res = se->io->splice_send(llp->pipe[0], NULL, + ch ? ch->fd : se->fd, NULL, out->len, + splice_flags, se->userdata); + } else { + res = splice(llp->pipe[0], NULL, ch ? ch->fd : se->fd, NULL, + out->len, splice_flags); + } + if (res == -1) { + res = -errno; + perror("fuse: splice from pipe"); + goto clear_pipe; + } + if (res != out->len) { + res = -EIO; + fuse_log(FUSE_LOG_ERR, "fuse: short splice from pipe: %u/%u\n", + res, out->len); + goto clear_pipe; + } + return 0; + +clear_pipe: + fuse_ll_clear_pipe(se); + return res; + +fallback: + return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len, req); +} +#else +static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch, + struct iovec *iov, int iov_count, + struct fuse_bufvec *req_data, unsigned int flags, + fuse_req_t req) +{ + size_t len = fuse_buf_size(req_data); + (void) flags; + + return fuse_send_data_iov_fallback(se, ch, iov, iov_count, req_data, len, req); +} +#endif + +int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags) +{ + struct iovec iov[2]; + struct fuse_out_header out; + int res; + + if (req->flags.is_uring) + return fuse_reply_data_uring(req, bufv, flags); + + iov[0].iov_base = &out; + iov[0].iov_len = sizeof(struct fuse_out_header); + + out.unique = req->unique; + out.error = 0; + + res = fuse_send_data_iov(req->se, req->ch, iov, 1, bufv, flags, req); + if (res <= 0) { + fuse_free_req(req); + return res; + } else { + return fuse_reply_err(req, res); + } +} + +int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf) +{ + struct fuse_statfs_out arg; + size_t size = req->se->conn.proto_minor < 4 ? + FUSE_COMPAT_STATFS_SIZE : sizeof(arg); + + memset(&arg, 0, sizeof(arg)); + convert_statfs(stbuf, &arg.st); + + return send_reply_ok(req, &arg, size); +} + +int fuse_reply_xattr(fuse_req_t req, size_t count) +{ + struct fuse_getxattr_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.size = count; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_lock(fuse_req_t req, const struct flock *lock) +{ + struct fuse_lk_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.lk.type = lock->l_type; + if (lock->l_type != F_UNLCK) { + arg.lk.start = lock->l_start; + if (lock->l_len == 0) + arg.lk.end = OFFSET_MAX; + else + arg.lk.end = lock->l_start + lock->l_len - 1; + } + arg.lk.pid = lock->l_pid; + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_bmap(fuse_req_t req, uint64_t idx) +{ + struct fuse_bmap_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.block = idx; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +static struct fuse_ioctl_iovec *fuse_ioctl_iovec_copy(const struct iovec *iov, + size_t count) +{ + struct fuse_ioctl_iovec *fiov; + size_t i; + + fiov = malloc(sizeof(fiov[0]) * count); + if (!fiov) + return NULL; + + for (i = 0; i < count; i++) { + fiov[i].base = (uintptr_t) iov[i].iov_base; + fiov[i].len = iov[i].iov_len; + } + + return fiov; +} + +int fuse_reply_ioctl_retry(fuse_req_t req, + const struct iovec *in_iov, size_t in_count, + const struct iovec *out_iov, size_t out_count) +{ + struct fuse_ioctl_out arg; + struct fuse_ioctl_iovec *in_fiov = NULL; + struct fuse_ioctl_iovec *out_fiov = NULL; + struct iovec iov[4]; + size_t count = 1; + int res; + + memset(&arg, 0, sizeof(arg)); + arg.flags |= FUSE_IOCTL_RETRY; + arg.in_iovs = in_count; + arg.out_iovs = out_count; + iov[count].iov_base = &arg; + iov[count].iov_len = sizeof(arg); + count++; + + if (req->se->conn.proto_minor < 16) { + if (in_count) { + iov[count].iov_base = (void *)in_iov; + iov[count].iov_len = sizeof(in_iov[0]) * in_count; + count++; + } + + if (out_count) { + iov[count].iov_base = (void *)out_iov; + iov[count].iov_len = sizeof(out_iov[0]) * out_count; + count++; + } + } else { + /* Can't handle non-compat 64bit ioctls on 32bit */ + if (sizeof(void *) == 4 && req->flags.ioctl_64bit) { + res = fuse_reply_err(req, EINVAL); + goto out; + } + + if (in_count) { + in_fiov = fuse_ioctl_iovec_copy(in_iov, in_count); + if (!in_fiov) + goto enomem; + + iov[count].iov_base = (void *)in_fiov; + iov[count].iov_len = sizeof(in_fiov[0]) * in_count; + count++; + } + if (out_count) { + out_fiov = fuse_ioctl_iovec_copy(out_iov, out_count); + if (!out_fiov) + goto enomem; + + iov[count].iov_base = (void *)out_fiov; + iov[count].iov_len = sizeof(out_fiov[0]) * out_count; + count++; + } + } + + res = send_reply_iov(req, 0, iov, count); +out: + free(in_fiov); + free(out_fiov); + + return res; + +enomem: + res = fuse_reply_err(req, ENOMEM); + goto out; +} + +int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size) +{ + struct fuse_ioctl_out arg; + struct iovec iov[3]; + size_t count = 1; + + memset(&arg, 0, sizeof(arg)); + arg.result = result; + iov[count].iov_base = &arg; + iov[count].iov_len = sizeof(arg); + count++; + + if (size) { + iov[count].iov_base = (char *) buf; + iov[count].iov_len = size; + count++; + } + + return send_reply_iov(req, 0, iov, count); +} + +int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, + int count) +{ + struct iovec *padded_iov; + struct fuse_ioctl_out arg; + int res; + + padded_iov = malloc((count + 2) * sizeof(struct iovec)); + if (padded_iov == NULL) + return fuse_reply_err(req, ENOMEM); + + memset(&arg, 0, sizeof(arg)); + arg.result = result; + padded_iov[1].iov_base = &arg; + padded_iov[1].iov_len = sizeof(arg); + + memcpy(&padded_iov[2], iov, count * sizeof(struct iovec)); + + res = send_reply_iov(req, 0, padded_iov, count + 2); + free(padded_iov); + + return res; +} + +int fuse_reply_poll(fuse_req_t req, unsigned revents) +{ + struct fuse_poll_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.revents = revents; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_lseek(fuse_req_t req, off_t off) +{ + struct fuse_lseek_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.offset = off; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +#ifdef HAVE_STATX +int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, + double attr_timeout) +{ + struct fuse_statx_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.flags = flags; + arg.attr_valid = calc_timeout_sec(attr_timeout); + arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout); + memcpy(&arg.stat, statx, sizeof(arg.stat)); + + return send_reply_ok(req, &arg, sizeof(arg)); +} +#else +int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, + double attr_timeout) +{ + (void)req; + (void)flags; + (void)statx; + (void)attr_timeout; + + return -ENOSYS; +} +#endif + +static void _do_lookup(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)op_in; + + char *name = (char *)in_payload; + + if (req->se->op.lookup) + req->se->op.lookup(req, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_lookup(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_lookup(req, nodeid, NULL, inarg); +} + +static void _do_forget(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + + struct fuse_forget_in *arg = (struct fuse_forget_in *)op_in; + + if (req->se->op.forget) + req->se->op.forget(req, nodeid, arg->nlookup); + else + fuse_reply_none(req); +} + +static void do_forget(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_forget(req, nodeid, inarg, NULL); +} + +static void _do_batch_forget(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)nodeid; + unsigned int i; + + const struct fuse_batch_forget_in *arg = op_in; + const struct fuse_forget_one *forgets = in_payload; + + if (req->se->op.forget_multi) { + req->se->op.forget_multi(req, arg->count, + (struct fuse_forget_data *)in_payload); + } else if (req->se->op.forget) { + for (i = 0; i < arg->count; i++) { + const struct fuse_forget_one *forget = &forgets[i]; + struct fuse_req *dummy_req; + + dummy_req = fuse_ll_alloc_req(req->se); + if (dummy_req == NULL) + break; + + dummy_req->unique = req->unique; + dummy_req->ctx = req->ctx; + dummy_req->ch = NULL; + + req->se->op.forget(dummy_req, forget->nodeid, + forget->nlookup); + } + fuse_reply_none(req); + } else { + fuse_reply_none(req); + } +} + +static void do_batch_forget(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + struct fuse_batch_forget_in *arg = (void *)inarg; + struct fuse_forget_one *param = (void *)PARAM(arg); + + _do_batch_forget(req, nodeid, inarg, param); +} + +static void _do_getattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + struct fuse_getattr_in *arg = (struct fuse_getattr_in *)op_in; + (void)in_payload; + + struct fuse_file_info *fip = NULL; + struct fuse_file_info fi; + + if (req->se->conn.proto_minor >= 9) { + if (arg->getattr_flags & FUSE_GETATTR_FH) { + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fip = &fi; + } + } + + if (req->se->op.getattr) + req->se->op.getattr(req, nodeid, fip); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_getattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_getattr(req, nodeid, inarg, NULL); +} + +static void _do_setattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_setattr_in *arg = op_in; + uint32_t valid = arg->valid; + + if (req->se->op.setattr) { + struct fuse_file_info *fi = NULL; + struct fuse_file_info fi_store; + struct stat stbuf; + memset(&stbuf, 0, sizeof(stbuf)); + convert_attr(arg, &stbuf); + if (arg->valid & FATTR_FH) { + valid &= ~FATTR_FH; + memset(&fi_store, 0, sizeof(fi_store)); + fi = &fi_store; + fi->fh = arg->fh; + } + valid &= FUSE_SET_ATTR_MODE | FUSE_SET_ATTR_UID | + FUSE_SET_ATTR_GID | FUSE_SET_ATTR_SIZE | + FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME | + FUSE_SET_ATTR_KILL_SUID | FUSE_SET_ATTR_KILL_SGID | + FUSE_SET_ATTR_ATIME_NOW | FUSE_SET_ATTR_MTIME_NOW | + FUSE_SET_ATTR_CTIME; + + req->se->op.setattr(req, nodeid, &stbuf, valid, fi); + } else + fuse_reply_err(req, ENOSYS); +} + +static void do_setattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_setattr(req, nodeid, inarg, NULL); +} + +static void _do_access(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_access_in *arg = op_in; + + if (req->se->op.access) + req->se->op.access(req, nodeid, arg->mask); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_access(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_access(req, nodeid, inarg, NULL); +} + +static void _do_readlink(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)op_in; + (void)in_payload; + + if (req->se->op.readlink) + req->se->op.readlink(req, nodeid); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_readlink(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_readlink(req, nodeid, inarg, NULL); +} + +static void _do_mknod(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + const struct fuse_mknod_in *arg = (struct fuse_mknod_in *)op_in; + const char *name = in_payload; + + if (req->se->conn.proto_minor >= 12) + req->ctx.umask = arg->umask; + + if (req->se->op.mknod) + req->se->op.mknod(req, nodeid, name, arg->mode, arg->rdev); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_mknod(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_mknod_in *arg = (struct fuse_mknod_in *)inarg; + char *name = PARAM(arg); + + if (req->se->conn.proto_minor < 12) + name = (char *)inarg + FUSE_COMPAT_MKNOD_IN_SIZE; + + _do_mknod(req, nodeid, inarg, name); +} + +static void _do_mkdir(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + const char *name = in_payload; + const struct fuse_mkdir_in *arg = op_in; + + if (req->se->conn.proto_minor >= 12) + req->ctx.umask = arg->umask; + + if (req->se->op.mkdir) + req->se->op.mkdir(req, nodeid, name, arg->mode); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_mkdir(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + const struct fuse_mkdir_in *arg = inarg; + const char *name = PARAM(arg); + + _do_mkdir(req, nodeid, inarg, name); +} + +static void _do_unlink(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)op_in; + const char *name = in_payload; + + if (req->se->op.unlink) + req->se->op.unlink(req, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_unlink(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_unlink(req, nodeid, NULL, inarg); +} + +static void _do_rmdir(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)op_in; + const char *name = in_payload; + + if (req->se->op.rmdir) + req->se->op.rmdir(req, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_rmdir(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_rmdir(req, nodeid, NULL, inarg); +} + +static void _do_symlink(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)op_in; + const char *name = (char *)in_payload; + const char *linkname = name + strlen(name) + 1; + + if (req->se->op.symlink) + req->se->op.symlink(req, linkname, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_symlink(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_symlink(req, nodeid, NULL, inarg); +} + +static void _do_rename(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + const struct fuse_rename_in *arg = (struct fuse_rename_in *)op_in; + const char *oldname = in_payload; + const char *newname = oldname + strlen(oldname) + 1; + + if (req->se->op.rename) + req->se->op.rename(req, nodeid, oldname, arg->newdir, newname, + 0); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_rename(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + const struct fuse_rename_in *arg = inarg; + const void *payload = PARAM(arg); + + _do_rename(req, nodeid, arg, payload); +} + +static void _do_rename2(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + const struct fuse_rename2_in *arg = op_in; + const char *oldname = in_payload; + const char *newname = oldname + strlen(oldname) + 1; + + if (req->se->op.rename) + req->se->op.rename(req, nodeid, oldname, arg->newdir, newname, + arg->flags); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_rename2(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + const struct fuse_rename2_in *arg = inarg; + const void *payload = PARAM(arg); + + _do_rename2(req, nodeid, arg, payload); +} + +static void _do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *op_in, + const void *in_payload) +{ + (void)in_payload; + const struct fuse_create_in *arg = op_in; + + if (req->se->op.tmpfile) { + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + + if (req->se->conn.proto_minor >= 12) + req->ctx.umask = arg->umask; + + req->se->op.tmpfile(req, nodeid, arg->mode, &fi); + } else + fuse_reply_err(req, ENOSYS); +} + +static void do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_create_in *arg = (struct fuse_create_in *) inarg; + + _do_tmpfile(req, nodeid, arg, NULL); +} + +static void _do_link(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, + const void *in_payload) +{ + struct fuse_link_in *arg = (struct fuse_link_in *)op_in; + + if (req->se->op.link) + req->se->op.link(req, arg->oldnodeid, nodeid, in_payload); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + const struct fuse_link_in *arg = inarg; + const void *name = PARAM(arg); + + _do_link(req, nodeid, inarg, name); +} + +static void _do_create(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + const struct fuse_create_in *arg = op_in; + const char *name = in_payload; + + if (req->se->op.create) { + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + + if (req->se->conn.proto_minor >= 12) + req->ctx.umask = arg->umask; + + /* XXX: fuse_create_in::open_flags */ + + req->se->op.create(req, nodeid, name, arg->mode, &fi); + } else { + fuse_reply_err(req, ENOSYS); + } +} + +static void do_create(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + const struct fuse_create_in *arg = (struct fuse_create_in *)inarg; + void *payload = PARAM(arg); + + if (req->se->conn.proto_minor < 12) + payload = (char *)inarg + sizeof(struct fuse_open_in); + + _do_create(req, nodeid, arg, payload); +} + +static void _do_open(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, + const void *in_payload) +{ + (void)in_payload; + struct fuse_open_in *arg = (struct fuse_open_in *)op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + + /* XXX: fuse_open_in::open_flags */ + + if (req->se->op.open) + req->se->op.open(req, nodeid, &fi); + else if (req->se->conn.want_ext & FUSE_CAP_NO_OPEN_SUPPORT) + fuse_reply_err(req, ENOSYS); + else + fuse_reply_open(req, &fi); +} + +static void do_open(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_open(req, nodeid, inarg, NULL); +} + +static void _do_read(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, + const void *in_payload) +{ + (void)in_payload; + struct fuse_read_in *arg = (struct fuse_read_in *)op_in; + + if (req->se->op.read) { + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + if (req->se->conn.proto_minor >= 9) { + fi.lock_owner = arg->lock_owner; + fi.flags = arg->flags; + } + req->se->op.read(req, nodeid, arg->size, arg->offset, &fi); + } else + fuse_reply_err(req, ENOSYS); +} + +static void do_read(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_read(req, nodeid, inarg, NULL); +} + +static void _do_write(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + struct fuse_write_in *arg = (struct fuse_write_in *)op_in; + const char *buf = in_payload; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.writepage = (arg->write_flags & FUSE_WRITE_CACHE) != 0; + + if (req->se->conn.proto_minor >= 9) { + fi.lock_owner = arg->lock_owner; + fi.flags = arg->flags; + } + + if (req->se->op.write) + req->se->op.write(req, nodeid, buf, arg->size, arg->offset, + &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_write(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_write_in *arg = (struct fuse_write_in *)inarg; + const void *payload; + + if (req->se->conn.proto_minor < 9) + payload = ((char *)arg) + FUSE_COMPAT_WRITE_IN_SIZE; + else + payload = PARAM(arg); + + _do_write(req, nodeid, arg, payload); +} + +static void _do_write_buf(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, struct fuse_bufvec *bufv) +{ + struct fuse_session *se = req->se; + struct fuse_write_in *arg = (struct fuse_write_in *)op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.writepage = arg->write_flags & FUSE_WRITE_CACHE; + + if (se->conn.proto_minor >= 9) { + fi.lock_owner = arg->lock_owner; + fi.flags = arg->flags; + } + + se->op.write_buf(req, nodeid, bufv, arg->offset, &fi); +} + +static void do_write_buf(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg, const struct fuse_buf *ibuf) +{ + struct fuse_session *se = req->se; + struct fuse_bufvec bufv = { + .buf[0] = *ibuf, + .count = 1, + }; + struct fuse_write_in *arg = (struct fuse_write_in *)inarg; + + if (se->conn.proto_minor < 9) { + bufv.buf[0].mem = ((char *)arg) + FUSE_COMPAT_WRITE_IN_SIZE; + bufv.buf[0].size -= sizeof(struct fuse_in_header) + + FUSE_COMPAT_WRITE_IN_SIZE; + assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD)); + } else { + if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD)) + bufv.buf[0].mem = PARAM(arg); + + bufv.buf[0].size -= sizeof(struct fuse_in_header) + + sizeof(struct fuse_write_in); + } + if (bufv.buf[0].size < arg->size) { + fuse_log(FUSE_LOG_ERR, + "fuse: %s: buffer size too small\n", __func__); + fuse_reply_err(req, EIO); + goto out; + } + bufv.buf[0].size = arg->size; + + _do_write_buf(req, nodeid, inarg, &bufv); + +out: + /* Need to reset the pipe if ->write_buf() didn't consume all data */ + if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count) + fuse_ll_clear_pipe(se); +} + +static void _do_flush(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + struct fuse_flush_in *arg = (struct fuse_flush_in *)op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.flush = 1; + if (req->se->conn.proto_minor >= 7) + fi.lock_owner = arg->lock_owner; + + if (req->se->op.flush) + req->se->op.flush(req, nodeid, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_flush(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_flush(req, nodeid, inarg, NULL); +} + +static void _do_release(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_release_in *arg = op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + fi.fh = arg->fh; + if (req->se->conn.proto_minor >= 8) { + fi.flush = (arg->release_flags & FUSE_RELEASE_FLUSH) ? 1 : 0; + fi.lock_owner = arg->lock_owner; + } + if (arg->release_flags & FUSE_RELEASE_FLOCK_UNLOCK) { + fi.flock_release = 1; + fi.lock_owner = arg->lock_owner; + } + + if (req->se->op.release) + req->se->op.release(req, nodeid, &fi); + else + fuse_reply_err(req, 0); +} + +static void do_release(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_release(req, nodeid, inarg, NULL); +} + +static void _do_fsync(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_fsync_in *arg = op_in; + struct fuse_file_info fi; + int datasync = arg->fsync_flags & 1; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->se->op.fsync) + req->se->op.fsync(req, nodeid, datasync, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_fsync(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_fsync(req, nodeid, inarg, NULL); +} + +static void _do_opendir(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_open_in *arg = op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + /* XXX: fuse_open_in::open_flags */ + + if (req->se->op.opendir) + req->se->op.opendir(req, nodeid, &fi); + else if (req->se->conn.want_ext & FUSE_CAP_NO_OPENDIR_SUPPORT) + fuse_reply_err(req, ENOSYS); + else + fuse_reply_open(req, &fi); +} + +static void do_opendir(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_opendir(req, nodeid, inarg, NULL); +} + +static void _do_readdir(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + struct fuse_read_in *arg = (struct fuse_read_in *)op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->se->op.readdir) + req->se->op.readdir(req, nodeid, arg->size, arg->offset, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_readdir(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_readdir(req, nodeid, inarg, NULL); +} + +static void _do_readdirplus(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + struct fuse_read_in *arg = (struct fuse_read_in *)op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->se->op.readdirplus) + req->se->op.readdirplus(req, nodeid, arg->size, arg->offset, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_readdirplus(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_readdirplus(req, nodeid, inarg, NULL); +} + +static void _do_releasedir(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + struct fuse_release_in *arg = (struct fuse_release_in *)op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + fi.fh = arg->fh; + + if (req->se->op.releasedir) + req->se->op.releasedir(req, nodeid, &fi); + else + fuse_reply_err(req, 0); +} + +static void do_releasedir(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_releasedir(req, nodeid, inarg, NULL); +} + +static void _do_fsyncdir(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + struct fuse_fsync_in *arg = (struct fuse_fsync_in *)op_in; + struct fuse_file_info fi; + int datasync = arg->fsync_flags & 1; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->se->op.fsyncdir) + req->se->op.fsyncdir(req, nodeid, datasync, &fi); + else + fuse_reply_err(req, ENOSYS); +} +static void do_fsyncdir(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_fsyncdir(req, nodeid, inarg, NULL); +} + +static void _do_statfs(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void) nodeid; + (void)op_in; + (void)in_payload; + + if (req->se->op.statfs) + req->se->op.statfs(req, nodeid); + else { + struct statvfs buf = { + .f_namemax = 255, + .f_bsize = 512, + }; + fuse_reply_statfs(req, &buf); + } +} +static void do_statfs(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_statfs(req, nodeid, inarg, NULL); +} + +static void _do_setxattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + struct fuse_setxattr_in *arg = (struct fuse_setxattr_in *)op_in; + const char *name = in_payload; + const char *value = name + strlen(name) + 1; + + /* XXX:The API should be extended to support extra_flags/setxattr_flags */ + + if (req->se->op.setxattr) + req->se->op.setxattr(req, nodeid, name, value, arg->size, + arg->flags); + else + fuse_reply_err(req, ENOSYS); +} +static void do_setxattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + struct fuse_session *se = req->se; + unsigned int xattr_ext = !!(se->conn.want & FUSE_CAP_SETXATTR_EXT); + const struct fuse_setxattr_in *arg = inarg; + char *payload = xattr_ext ? PARAM(arg) : + (char *)arg + FUSE_COMPAT_SETXATTR_IN_SIZE; + + _do_setxattr(req, nodeid, arg, payload); +} + +static void _do_getxattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + const struct fuse_getxattr_in *arg = op_in; + + if (req->se->op.getxattr) + req->se->op.getxattr(req, nodeid, in_payload, arg->size); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_getxattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + const struct fuse_getxattr_in *arg = inarg; + const void *payload = PARAM(arg); + + _do_getxattr(req, nodeid, arg, payload); +} + +static void _do_listxattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg, const void *in_payload) +{ + (void)in_payload; + const struct fuse_getxattr_in *arg = inarg; + + if (req->se->op.listxattr) + req->se->op.listxattr(req, nodeid, arg->size); + else + fuse_reply_err(req, ENOSYS); +} +static void do_listxattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_listxattr(req, nodeid, inarg, NULL); +} + +static void _do_removexattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg, const void *in_payload) +{ + (void)inarg; + const char *name = in_payload; + + if (req->se->op.removexattr) + req->se->op.removexattr(req, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} +static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_removexattr(req, nodeid, NULL, inarg); +} + +static void convert_fuse_file_lock(const struct fuse_file_lock *fl, + struct flock *flock) +{ + memset(flock, 0, sizeof(struct flock)); + flock->l_type = fl->type; + flock->l_whence = SEEK_SET; + flock->l_start = fl->start; + if (fl->end == OFFSET_MAX) + flock->l_len = 0; + else + flock->l_len = fl->end - fl->start + 1; + flock->l_pid = fl->pid; +} + +static void _do_getlk(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_lk_in *arg = op_in; + struct fuse_file_info fi; + struct flock flock; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.lock_owner = arg->owner; + + convert_fuse_file_lock(&arg->lk, &flock); + if (req->se->op.getlk) + req->se->op.getlk(req, nodeid, &fi, &flock); + else + fuse_reply_err(req, ENOSYS); +} +static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_getlk(req, nodeid, inarg, NULL); +} + +static void do_setlk_common(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, int sleep) +{ + const struct fuse_lk_in *arg = op_in; + struct fuse_file_info fi; + struct flock flock; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.lock_owner = arg->owner; + + if (arg->lk_flags & FUSE_LK_FLOCK) { + int op = 0; + + switch (arg->lk.type) { + case F_RDLCK: + op = LOCK_SH; + break; + case F_WRLCK: + op = LOCK_EX; + break; + case F_UNLCK: + op = LOCK_UN; + break; + } + if (!sleep) + op |= LOCK_NB; + + if (req->se->op.flock) + req->se->op.flock(req, nodeid, &fi, op); + else + fuse_reply_err(req, ENOSYS); + } else { + convert_fuse_file_lock(&arg->lk, &flock); + if (req->se->op.setlk) + req->se->op.setlk(req, nodeid, &fi, &flock, sleep); + else + fuse_reply_err(req, ENOSYS); + } +} + +static void _do_setlk(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + do_setlk_common(req, nodeid, op_in, 0); +} + +static void do_setlk(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_setlk(req, nodeid, inarg, NULL); +} + +static void _do_setlkw(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + do_setlk_common(req, nodeid, op_in, 1); +} +static void do_setlkw(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_setlkw(req, nodeid, inarg, NULL); +} + +static int find_interrupted(struct fuse_session *se, struct fuse_req *req) +{ + struct fuse_req *curr; + + for (curr = se->list.next; curr != &se->list; curr = curr->next) { + if (curr->unique == req->u.i.unique) { + fuse_interrupt_func_t func; + void *data; + + curr->ref_cnt++; + pthread_mutex_unlock(&se->lock); + + /* Ugh, ugly locking */ + pthread_mutex_lock(&curr->lock); + pthread_mutex_lock(&se->lock); + curr->interrupted = 1; + func = curr->u.ni.func; + data = curr->u.ni.data; + pthread_mutex_unlock(&se->lock); + if (func) + func(curr, data); + pthread_mutex_unlock(&curr->lock); + + pthread_mutex_lock(&se->lock); + curr->ref_cnt--; + if (!curr->ref_cnt) { + destroy_req(curr); + } + + return 1; + } + } + for (curr = se->interrupts.next; curr != &se->interrupts; + curr = curr->next) { + if (curr->u.i.unique == req->u.i.unique) + return 1; + } + return 0; +} + +static void _do_interrupt(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_interrupt_in *arg = op_in; + struct fuse_session *se = req->se; + + (void) nodeid; + if (se->debug) + fuse_log(FUSE_LOG_DEBUG, "INTERRUPT: %llu\n", + (unsigned long long) arg->unique); + + req->u.i.unique = arg->unique; + + pthread_mutex_lock(&se->lock); + if (find_interrupted(se, req)) { + fuse_chan_put(req->ch); + req->ch = NULL; + destroy_req(req); + } else + list_add_req(req, &se->interrupts); + pthread_mutex_unlock(&se->lock); +} +static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_interrupt(req, nodeid, inarg, NULL); +} + +static struct fuse_req *check_interrupt(struct fuse_session *se, + struct fuse_req *req) +{ + struct fuse_req *curr; + + for (curr = se->interrupts.next; curr != &se->interrupts; + curr = curr->next) { + if (curr->u.i.unique == req->unique) { + req->interrupted = 1; + list_del_req(curr); + fuse_chan_put(curr->ch); + curr->ch = NULL; + destroy_req(curr); + return NULL; + } + } + curr = se->interrupts.next; + if (curr != &se->interrupts) { + list_del_req(curr); + list_init_req(curr); + return curr; + } else + return NULL; +} + +static void _do_bmap(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, + const void *in_payload) +{ + (void)in_payload; + const struct fuse_bmap_in *arg = op_in; + + if (req->se->op.bmap) + req->se->op.bmap(req, nodeid, arg->blocksize, arg->block); + else + fuse_reply_err(req, ENOSYS); +} +static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_bmap(req, nodeid, inarg, NULL); +} + +static void _do_ioctl(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *)op_in; + unsigned int flags = arg->flags; + const void *in_buf = in_payload; + struct fuse_file_info fi; + + if (flags & FUSE_IOCTL_DIR && + !(req->se->conn.want_ext & FUSE_CAP_IOCTL_DIR)) { + fuse_reply_err(req, ENOTTY); + return; + } + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (sizeof(void *) == 4 && req->se->conn.proto_minor >= 16 && + !(flags & FUSE_IOCTL_32BIT)) { + req->flags.ioctl_64bit = 1; + } + + if (req->se->op.ioctl) + req->se->op.ioctl(req, nodeid, arg->cmd, + (void *)(uintptr_t)arg->arg, &fi, flags, + in_buf, arg->in_size, arg->out_size); + else + fuse_reply_err(req, ENOSYS); +} +static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + const struct fuse_ioctl_in *arg = inarg; + void *in_buf = arg->in_size ? PARAM(arg) : NULL; + + _do_ioctl(req, nodeid, arg, in_buf); +} + +void fuse_pollhandle_destroy(struct fuse_pollhandle *ph) +{ + free(ph); +} + +static void _do_poll(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, + const void *in_payload) +{ + (void)in_payload; + struct fuse_poll_in *arg = (struct fuse_poll_in *)op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.poll_events = arg->events; + + if (req->se->op.poll) { + struct fuse_pollhandle *ph = NULL; + + if (arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) { + ph = malloc(sizeof(struct fuse_pollhandle)); + if (ph == NULL) { + fuse_reply_err(req, ENOMEM); + return; + } + ph->kh = arg->kh; + ph->se = req->se; + } + + req->se->op.poll(req, nodeid, &fi, ph); + } else { + fuse_reply_err(req, ENOSYS); + } +} + +static void do_poll(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_poll(req, nodeid, inarg, NULL); +} + +static void _do_fallocate(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_fallocate_in *arg = op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->se->op.fallocate) + req->se->op.fallocate(req, nodeid, arg->mode, arg->offset, + arg->length, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_fallocate(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_fallocate(req, nodeid, inarg, NULL); +} + +static void copy_file_range_common(fuse_req_t req, const fuse_ino_t nodeid_in, + const struct fuse_copy_file_range_in *arg) +{ + struct fuse_file_info fi_in, fi_out; + + memset(&fi_in, 0, sizeof(fi_in)); + fi_in.fh = arg->fh_in; + + memset(&fi_out, 0, sizeof(fi_out)); + fi_out.fh = arg->fh_out; + + if (req->se->op.copy_file_range) + req->se->op.copy_file_range(req, nodeid_in, arg->off_in, &fi_in, + arg->nodeid_out, arg->off_out, + &fi_out, arg->len, arg->flags); + else + fuse_reply_err(req, ENOSYS); +} + +static void _do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in, + const void *op_in, const void *in_payload) +{ + const struct fuse_copy_file_range_in *arg = op_in; + struct fuse_copy_file_range_in arg_tmp; + + (void) in_payload; + /* fuse_write_out can only handle 32bit copy size */ + if (arg->len > 0xfffff000) { + arg_tmp = *arg; + arg_tmp.len = 0xfffff000; + arg = &arg_tmp; + } + copy_file_range_common(req, nodeid_in, arg); +} + +static void do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in, + const void *inarg) +{ + _do_copy_file_range(req, nodeid_in, inarg, NULL); +} + +static void _do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in, + const void *op_in, const void *in_payload) +{ + (void) in_payload; + req->flags.is_copy_file_range_64 = 1; + /* Limit size on 32bit userspace to avoid conversion overflow */ + if (sizeof(size_t) == 4) + _do_copy_file_range(req, nodeid_in, op_in, NULL); + else + copy_file_range_common(req, nodeid_in, op_in); +} + +static void do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in, + const void *inarg) +{ + _do_copy_file_range_64(req, nodeid_in, inarg, NULL); +} + +/* + * Note that the uint64_t offset in struct fuse_lseek_in is derived from + * linux kernel loff_t and is therefore signed. + */ +static void _do_lseek(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_lseek_in *arg = op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->se->op.lseek) + req->se->op.lseek(req, nodeid, arg->offset, arg->whence, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_lseek(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_lseek(req, nodeid, inarg, NULL); +} + +#ifdef HAVE_STATX +static void _do_statx(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_statx_in *arg = op_in; + struct fuse_file_info *fip = NULL; + struct fuse_file_info fi; + + if (arg->getattr_flags & FUSE_GETATTR_FH) { + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fip = &fi; + } + + if (req->se->op.statx) + req->se->op.statx(req, nodeid, arg->sx_flags, arg->sx_mask, fip); + else + fuse_reply_err(req, ENOSYS); +} +#else +static void _do_statx(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + (void)req; + (void)nodeid; + (void)op_in; +} +#endif + +static void do_statx(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_statx(req, nodeid, inarg, NULL); +} + +static bool want_flags_valid(uint64_t capable, uint64_t want) +{ + uint64_t unknown_flags = want & (~capable); + if (unknown_flags != 0) { + fuse_log(FUSE_LOG_ERR, + "fuse: unknown connection 'want' flags: 0x%08llx\n", + (unsigned long long)unknown_flags); + return false; + } + return true; +} + +/** + * Get the wanted capability flags, converting from old format if necessary + */ +int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn) +{ + struct fuse_session *se = container_of(conn, struct fuse_session, conn); + + /* + * Convert want to want_ext if necessary. + * For the high level interface this function might be called + * twice, once from the high level interface and once from the + * low level interface. Both, with different want_ext_default and + * want_default values. In order to suppress a failure for the + * second call, we check if the lower 32 bits of want_ext are + * already set to the value of want. + */ + if (conn->want != se->conn_want && + fuse_lower_32_bits(conn->want_ext) != conn->want) { + if (conn->want_ext != se->conn_want_ext) { + fuse_log(FUSE_LOG_ERR, + "%s: Both conn->want_ext and conn->want are set.\n" + "want=%x want_ext=%llx, se->want=%x se->want_ext=%llx\n", + __func__, conn->want, + (unsigned long long)conn->want_ext, + se->conn_want, + (unsigned long long)se->conn_want_ext); + return -EINVAL; + } + + /* high bits from want_ext, low bits from want */ + conn->want_ext = fuse_higher_32_bits(conn->want_ext) | + conn->want; + } + + /* ensure there won't be a second conversion */ + conn->want = fuse_lower_32_bits(conn->want_ext); + + return 0; +} + +bool fuse_set_feature_flag(struct fuse_conn_info *conn, + uint64_t flag) +{ + struct fuse_session *se = container_of(conn, struct fuse_session, conn); + + if (conn->capable_ext & flag) { + conn->want_ext |= flag; + se->conn_want_ext |= flag; + conn->want |= flag; + se->conn_want |= flag; + return true; + } + return false; +} + +void fuse_unset_feature_flag(struct fuse_conn_info *conn, + uint64_t flag) +{ + struct fuse_session *se = container_of(conn, struct fuse_session, conn); + + conn->want_ext &= ~flag; + se->conn_want_ext &= ~flag; + conn->want &= ~flag; + se->conn_want &= ~flag; +} + +bool fuse_get_feature_flag(struct fuse_conn_info *conn, + uint64_t flag) +{ + return conn->capable_ext & flag ? true : false; +} + +/* Prevent bogus data races (bogus since "init" is called before + * multi-threading becomes relevant */ +static __attribute__((no_sanitize("thread"))) void +_do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, + const void *in_payload) +{ + (void)in_payload; + const struct fuse_init_in *arg = op_in; + struct fuse_init_out outarg; + struct fuse_session *se = req->se; + size_t bufsize = se->bufsize; + size_t outargsize = sizeof(outarg); + uint64_t inargflags = 0; + uint64_t outargflags = 0; + bool buf_reallocable = se->buf_reallocable; + (void) nodeid; + bool enable_io_uring = false; + + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, "INIT: %u.%u\n", arg->major, arg->minor); + if (arg->major == 7 && arg->minor >= 6) { + fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags); + fuse_log(FUSE_LOG_DEBUG, "max_readahead=0x%08x\n", + arg->max_readahead); + } + } + se->conn.proto_major = arg->major; + se->conn.proto_minor = arg->minor; + se->conn.capable_ext = 0; + se->conn.want_ext = 0; + + memset(&outarg, 0, sizeof(outarg)); + outarg.major = FUSE_KERNEL_VERSION; + outarg.minor = FUSE_KERNEL_MINOR_VERSION; + + if (arg->major < 7) { + fuse_log(FUSE_LOG_ERR, "fuse: unsupported protocol version: %u.%u\n", + arg->major, arg->minor); + fuse_reply_err(req, EPROTO); + return; + } + + if (arg->major > 7) { + /* Wait for a second INIT request with a 7.X version */ + send_reply_ok(req, &outarg, sizeof(outarg)); + return; + } + + if (arg->minor >= 6) { + if (arg->max_readahead < se->conn.max_readahead) + se->conn.max_readahead = arg->max_readahead; + inargflags = arg->flags; + if (inargflags & FUSE_INIT_EXT) + inargflags = inargflags | (uint64_t) arg->flags2 << 32; + if (inargflags & FUSE_ASYNC_READ) + se->conn.capable_ext |= FUSE_CAP_ASYNC_READ; + if (inargflags & FUSE_POSIX_LOCKS) + se->conn.capable_ext |= FUSE_CAP_POSIX_LOCKS; + if (inargflags & FUSE_ATOMIC_O_TRUNC) + se->conn.capable_ext |= FUSE_CAP_ATOMIC_O_TRUNC; + if (inargflags & FUSE_EXPORT_SUPPORT) + se->conn.capable_ext |= FUSE_CAP_EXPORT_SUPPORT; + if (inargflags & FUSE_DONT_MASK) + se->conn.capable_ext |= FUSE_CAP_DONT_MASK; + if (inargflags & FUSE_FLOCK_LOCKS) + se->conn.capable_ext |= FUSE_CAP_FLOCK_LOCKS; + if (inargflags & FUSE_AUTO_INVAL_DATA) + se->conn.capable_ext |= FUSE_CAP_AUTO_INVAL_DATA; + if (inargflags & FUSE_DO_READDIRPLUS) + se->conn.capable_ext |= FUSE_CAP_READDIRPLUS; + if (inargflags & FUSE_READDIRPLUS_AUTO) + se->conn.capable_ext |= FUSE_CAP_READDIRPLUS_AUTO; + if (inargflags & FUSE_ASYNC_DIO) + se->conn.capable_ext |= FUSE_CAP_ASYNC_DIO; + if (inargflags & FUSE_WRITEBACK_CACHE) + se->conn.capable_ext |= FUSE_CAP_WRITEBACK_CACHE; + if (inargflags & FUSE_NO_OPEN_SUPPORT) + se->conn.capable_ext |= FUSE_CAP_NO_OPEN_SUPPORT; + if (inargflags & FUSE_PARALLEL_DIROPS) + se->conn.capable_ext |= FUSE_CAP_PARALLEL_DIROPS; + if (inargflags & FUSE_POSIX_ACL) + se->conn.capable_ext |= FUSE_CAP_POSIX_ACL; + if (inargflags & FUSE_HANDLE_KILLPRIV) + se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV; + if (inargflags & FUSE_HANDLE_KILLPRIV_V2) + se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV_V2; + if (inargflags & FUSE_CACHE_SYMLINKS) + se->conn.capable_ext |= FUSE_CAP_CACHE_SYMLINKS; + if (inargflags & FUSE_NO_OPENDIR_SUPPORT) + se->conn.capable_ext |= FUSE_CAP_NO_OPENDIR_SUPPORT; + if (inargflags & FUSE_EXPLICIT_INVAL_DATA) + se->conn.capable_ext |= FUSE_CAP_EXPLICIT_INVAL_DATA; + if (inargflags & FUSE_SETXATTR_EXT) + se->conn.capable_ext |= FUSE_CAP_SETXATTR_EXT; + if (!(inargflags & FUSE_MAX_PAGES)) { + size_t max_bufsize = + FUSE_DEFAULT_MAX_PAGES_PER_REQ * getpagesize() + + FUSE_BUFFER_HEADER_SIZE; + if (bufsize > max_bufsize) { + bufsize = max_bufsize; + } + buf_reallocable = false; + } + if (inargflags & FUSE_DIRECT_IO_ALLOW_MMAP) + se->conn.capable_ext |= FUSE_CAP_DIRECT_IO_ALLOW_MMAP; + if (arg->minor >= 38 || (inargflags & FUSE_HAS_EXPIRE_ONLY)) + se->conn.capable_ext |= FUSE_CAP_EXPIRE_ONLY; + if (inargflags & FUSE_PASSTHROUGH) + se->conn.capable_ext |= FUSE_CAP_PASSTHROUGH; + if (inargflags & FUSE_NO_EXPORT_SUPPORT) + se->conn.capable_ext |= FUSE_CAP_NO_EXPORT_SUPPORT; + if (inargflags & FUSE_OVER_IO_URING) + se->conn.capable_ext |= FUSE_CAP_OVER_IO_URING; + + } else { + se->conn.max_readahead = 0; + } + + if (se->conn.proto_minor >= 14) { +#ifdef HAVE_SPLICE +#ifdef HAVE_VMSPLICE + if ((se->io == NULL) || (se->io->splice_send != NULL)) { + se->conn.capable_ext |= FUSE_CAP_SPLICE_WRITE | + FUSE_CAP_SPLICE_MOVE; + } +#endif + if ((se->io == NULL) || (se->io->splice_receive != NULL)) { + se->conn.capable_ext |= FUSE_CAP_SPLICE_READ; + } +#endif + } + if (se->conn.proto_minor >= 18) + se->conn.capable_ext |= FUSE_CAP_IOCTL_DIR; + + /* Default settings for modern filesystems. + * + * Most of these capabilities were disabled by default in + * libfuse2 for backwards compatibility reasons. In libfuse3, + * we can finally enable them by default (as long as they're + * supported by the kernel). + */ +#define LL_SET_DEFAULT(cond, cap) \ + if ((cond)) \ + fuse_set_feature_flag(&se->conn, cap) + + LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_READ); + LL_SET_DEFAULT(1, FUSE_CAP_AUTO_INVAL_DATA); + LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_DIO); + LL_SET_DEFAULT(1, FUSE_CAP_IOCTL_DIR); + LL_SET_DEFAULT(1, FUSE_CAP_ATOMIC_O_TRUNC); + LL_SET_DEFAULT(se->op.write_buf, FUSE_CAP_SPLICE_READ); + LL_SET_DEFAULT(se->op.getlk && se->op.setlk, + FUSE_CAP_POSIX_LOCKS); + LL_SET_DEFAULT(se->op.flock, FUSE_CAP_FLOCK_LOCKS); + LL_SET_DEFAULT(se->op.readdirplus, FUSE_CAP_READDIRPLUS); + LL_SET_DEFAULT(se->op.readdirplus && se->op.readdir, + FUSE_CAP_READDIRPLUS_AUTO); + LL_SET_DEFAULT(1, FUSE_CAP_OVER_IO_URING); + + /* This could safely become default, but libfuse needs an API extension + * to support it + * LL_SET_DEFAULT(1, FUSE_CAP_SETXATTR_EXT); + */ + + se->conn.time_gran = 1; + + if (se->op.init) { + // Apply the first 32 bits of capable_ext to capable + se->conn.capable = fuse_lower_32_bits(se->conn.capable_ext); + + se->op.init(se->userdata, &se->conn); + + /* + * se->conn.want is 32-bit value and deprecated in favour of + * se->conn.want_ext + * Userspace might still use conn.want - we need to convert it + */ + fuse_convert_to_conn_want_ext(&se->conn); + } + + if (!want_flags_valid(se->conn.capable_ext, se->conn.want_ext)) { + fuse_reply_err(req, EPROTO); + se->error = -EPROTO; + fuse_session_exit(se); + return; + } + + unsigned max_read_mo = get_max_read(se->mo); + if (se->conn.max_read != max_read_mo) { + fuse_log(FUSE_LOG_ERR, "fuse: error: init() and fuse_session_new() " + "requested different maximum read size (%u vs %u)\n", + se->conn.max_read, max_read_mo); + fuse_reply_err(req, EPROTO); + se->error = -EPROTO; + fuse_session_exit(se); + return; + } + + if (bufsize < FUSE_MIN_READ_BUFFER) { + fuse_log(FUSE_LOG_ERR, + "fuse: warning: buffer size too small: %zu\n", + bufsize); + bufsize = FUSE_MIN_READ_BUFFER; + } + + if (buf_reallocable) + bufsize = UINT_MAX; + se->conn.max_write = MIN(se->conn.max_write, bufsize - FUSE_BUFFER_HEADER_SIZE); + se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE; + + if (arg->flags & FUSE_MAX_PAGES) { + outarg.flags |= FUSE_MAX_PAGES; + outarg.max_pages = (se->conn.max_write - 1) / getpagesize() + 1; + } + outargflags = outarg.flags; + /* Always enable big writes, this is superseded + by the max_write option */ + outargflags |= FUSE_BIG_WRITES; + + if (se->conn.want_ext & FUSE_CAP_ASYNC_READ) + outargflags |= FUSE_ASYNC_READ; + if (se->conn.want_ext & FUSE_CAP_POSIX_LOCKS) + outargflags |= FUSE_POSIX_LOCKS; + if (se->conn.want_ext & FUSE_CAP_ATOMIC_O_TRUNC) + outargflags |= FUSE_ATOMIC_O_TRUNC; + if (se->conn.want_ext & FUSE_CAP_EXPORT_SUPPORT) + outargflags |= FUSE_EXPORT_SUPPORT; + if (se->conn.want_ext & FUSE_CAP_DONT_MASK) + outargflags |= FUSE_DONT_MASK; + if (se->conn.want_ext & FUSE_CAP_FLOCK_LOCKS) + outargflags |= FUSE_FLOCK_LOCKS; + if (se->conn.want_ext & FUSE_CAP_AUTO_INVAL_DATA) + outargflags |= FUSE_AUTO_INVAL_DATA; + if (se->conn.want_ext & FUSE_CAP_READDIRPLUS) + outargflags |= FUSE_DO_READDIRPLUS; + if (se->conn.want_ext & FUSE_CAP_READDIRPLUS_AUTO) + outargflags |= FUSE_READDIRPLUS_AUTO; + if (se->conn.want_ext & FUSE_CAP_ASYNC_DIO) + outargflags |= FUSE_ASYNC_DIO; + if (se->conn.want_ext & FUSE_CAP_WRITEBACK_CACHE) + outargflags |= FUSE_WRITEBACK_CACHE; + if (se->conn.want_ext & FUSE_CAP_PARALLEL_DIROPS) + outargflags |= FUSE_PARALLEL_DIROPS; + if (se->conn.want_ext & FUSE_CAP_POSIX_ACL) + outargflags |= FUSE_POSIX_ACL; + if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV) + outargflags |= FUSE_HANDLE_KILLPRIV; + if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2) + outargflags |= FUSE_HANDLE_KILLPRIV_V2; + if (se->conn.want_ext & FUSE_CAP_CACHE_SYMLINKS) + outargflags |= FUSE_CACHE_SYMLINKS; + if (se->conn.want_ext & FUSE_CAP_EXPLICIT_INVAL_DATA) + outargflags |= FUSE_EXPLICIT_INVAL_DATA; + if (se->conn.want_ext & FUSE_CAP_SETXATTR_EXT) + outargflags |= FUSE_SETXATTR_EXT; + if (se->conn.want_ext & FUSE_CAP_DIRECT_IO_ALLOW_MMAP) + outargflags |= FUSE_DIRECT_IO_ALLOW_MMAP; + if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH) { + outargflags |= FUSE_PASSTHROUGH; + /* + * outarg.max_stack_depth includes the fuse stack layer, + * so it is one more than max_backing_stack_depth. + */ + outarg.max_stack_depth = se->conn.max_backing_stack_depth + 1; + } + if (se->conn.want_ext & FUSE_CAP_NO_EXPORT_SUPPORT) + outargflags |= FUSE_NO_EXPORT_SUPPORT; + if (se->uring.enable && se->conn.want_ext & FUSE_CAP_OVER_IO_URING) { + outargflags |= FUSE_OVER_IO_URING; + enable_io_uring = true; + } + + if ((inargflags & FUSE_REQUEST_TIMEOUT) && se->conn.request_timeout) { + outargflags |= FUSE_REQUEST_TIMEOUT; + outarg.request_timeout = se->conn.request_timeout; + } + + outarg.max_readahead = se->conn.max_readahead; + outarg.max_write = se->conn.max_write; + if (se->conn.proto_minor >= 13) { + if (se->conn.max_background >= (1 << 16)) + se->conn.max_background = (1 << 16) - 1; + if (se->conn.congestion_threshold > se->conn.max_background) + se->conn.congestion_threshold = se->conn.max_background; + if (!se->conn.congestion_threshold) { + se->conn.congestion_threshold = + se->conn.max_background * 3 / 4; + } + + outarg.max_background = se->conn.max_background; + outarg.congestion_threshold = se->conn.congestion_threshold; + } + if (se->conn.proto_minor >= 23) + outarg.time_gran = se->conn.time_gran; + + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, " INIT: %u.%u\n", outarg.major, outarg.minor); + fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags); + fuse_log(FUSE_LOG_DEBUG, " max_readahead=0x%08x\n", + outarg.max_readahead); + fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write); + fuse_log(FUSE_LOG_DEBUG, " max_background=%i\n", + outarg.max_background); + fuse_log(FUSE_LOG_DEBUG, " congestion_threshold=%i\n", + outarg.congestion_threshold); + fuse_log(FUSE_LOG_DEBUG, " time_gran=%u\n", + outarg.time_gran); + if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH) + fuse_log(FUSE_LOG_DEBUG, " max_stack_depth=%u\n", + outarg.max_stack_depth); + } + if (arg->minor < 5) + outargsize = FUSE_COMPAT_INIT_OUT_SIZE; + else if (arg->minor < 23) + outargsize = FUSE_COMPAT_22_INIT_OUT_SIZE; + + /* XXX: Add an option to make non-available io-uring fatal */ + if (enable_io_uring) { + int ring_rc = fuse_uring_start(se); + + if (ring_rc != 0) { + fuse_log(FUSE_LOG_INFO, + "fuse: failed to start io-uring: %s\n", + strerror(ring_rc)); + outargflags &= ~FUSE_OVER_IO_URING; + enable_io_uring = false; + } + } + + if (inargflags & FUSE_INIT_EXT) { + outargflags |= FUSE_INIT_EXT; + outarg.flags2 = outargflags >> 32; + } + outarg.flags = outargflags; + + /* + * Has to be set before replying, as new kernel requests might + * immediately arrive and got_init is used for op-code sanity. + * Especially with external handlers, where we have no control + * over the thread scheduling. + */ + se->got_init = 1; + send_reply_ok(req, &outarg, outargsize); + if (enable_io_uring) + fuse_uring_wake_ring_threads(se); +} + +static __attribute__((no_sanitize("thread"))) void +do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_init(req, nodeid, inarg, NULL); +} + +static void _do_destroy(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + struct fuse_session *se = req->se; + char *mountpoint; + + (void) nodeid; + (void)op_in; + (void)in_payload; + + mountpoint = atomic_exchange(&se->mountpoint, NULL); + free(mountpoint); + + se->got_destroy = 1; + se->got_init = 0; + if (se->op.destroy) + se->op.destroy(se->userdata); + + send_reply_ok(req, NULL, 0); +} + +static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_destroy(req, nodeid, inarg, NULL); +} + +static void list_del_nreq(struct fuse_notify_req *nreq) +{ + struct fuse_notify_req *prev = nreq->prev; + struct fuse_notify_req *next = nreq->next; + prev->next = next; + next->prev = prev; +} + +static void list_add_nreq(struct fuse_notify_req *nreq, + struct fuse_notify_req *next) +{ + struct fuse_notify_req *prev = next->prev; + nreq->next = next; + nreq->prev = prev; + prev->next = nreq; + next->prev = nreq; +} + +static void list_init_nreq(struct fuse_notify_req *nreq) +{ + nreq->next = nreq; + nreq->prev = nreq; +} + +static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid, + const void *inarg, const struct fuse_buf *buf) +{ + struct fuse_session *se = req->se; + struct fuse_notify_req *nreq; + struct fuse_notify_req *head; + + pthread_mutex_lock(&se->lock); + head = &se->notify_list; + for (nreq = head->next; nreq != head; nreq = nreq->next) { + if (nreq->unique == req->unique) { + list_del_nreq(nreq); + break; + } + } + pthread_mutex_unlock(&se->lock); + + if (nreq != head) + nreq->reply(nreq, req, nodeid, inarg, buf); +} + +static int send_notify_iov(struct fuse_session *se, int notify_code, + struct iovec *iov, int count) +{ + struct fuse_out_header out; + struct fuse_req *req = NULL; + + if (!se->got_init) + return -ENOTCONN; + + out.unique = 0; + out.error = notify_code; + iov[0].iov_base = &out; + iov[0].iov_len = sizeof(struct fuse_out_header); + + return fuse_send_msg(se, NULL, iov, count, req); +} + +int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph) +{ + if (ph != NULL) { + struct fuse_notify_poll_wakeup_out outarg; + struct iovec iov[2]; + + outarg.kh = ph->kh; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + + return send_notify_iov(ph->se, FUSE_NOTIFY_POLL, iov, 2); + } else { + return 0; + } +} + +int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, + off_t off, off_t len) +{ + struct fuse_notify_inval_inode_out outarg; + struct iovec iov[2]; + + if (!se) + return -EINVAL; + + if (se->conn.proto_minor < 12) + return -ENOSYS; + + outarg.ino = ino; + outarg.off = off; + outarg.len = len; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + + return send_notify_iov(se, FUSE_NOTIFY_INVAL_INODE, iov, 2); +} + +int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se) +{ + struct iovec iov[1]; + + if (!se) + return -EINVAL; + + if (se->conn.proto_minor < 44) + return -ENOSYS; + + return send_notify_iov(se, FUSE_NOTIFY_INC_EPOCH, iov, 1); +} + +/** + * Notify parent attributes and the dentry matching parent/name + * + * Underlying base function for fuse_lowlevel_notify_inval_entry() and + * fuse_lowlevel_notify_expire_entry(). + * + * @warning + * Only checks if fuse_lowlevel_notify_inval_entry() is supported by + * the kernel. All other flags will fall back to + * fuse_lowlevel_notify_inval_entry() if not supported! + * DO THE PROPER CHECKS IN THE DERIVED FUNCTION! + * + * @param se the session object + * @param parent inode number + * @param name file name + * @param namelen strlen() of file name + * @param flags flags to control if the entry should be expired or invalidated + * @return zero for success, -errno for failure +*/ +static int fuse_lowlevel_notify_entry(struct fuse_session *se, fuse_ino_t parent, + const char *name, size_t namelen, + enum fuse_notify_entry_flags flags) +{ + struct fuse_notify_inval_entry_out outarg; + struct iovec iov[3]; + + if (!se) + return -EINVAL; + + if (se->conn.proto_minor < 12) + return -ENOSYS; + + outarg.parent = parent; + outarg.namelen = namelen; + outarg.flags = 0; + if (flags & FUSE_LL_EXPIRE_ONLY) + outarg.flags |= FUSE_EXPIRE_ONLY; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + iov[2].iov_base = (void *)name; + iov[2].iov_len = namelen + 1; + + return send_notify_iov(se, FUSE_NOTIFY_INVAL_ENTRY, iov, 3); +} + +int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, + const char *name, size_t namelen) +{ + return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_INVALIDATE); +} + +int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, + const char *name, size_t namelen) +{ + if (!se) + return -EINVAL; + + if (!(se->conn.capable_ext & FUSE_CAP_EXPIRE_ONLY)) + return -ENOSYS; + + return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_EXPIRE_ONLY); +} + + +int fuse_lowlevel_notify_delete(struct fuse_session *se, + fuse_ino_t parent, fuse_ino_t child, + const char *name, size_t namelen) +{ + struct fuse_notify_delete_out outarg; + struct iovec iov[3]; + + if (!se) + return -EINVAL; + + if (se->conn.proto_minor < 18) + return -ENOSYS; + + outarg.parent = parent; + outarg.child = child; + outarg.namelen = namelen; + outarg.padding = 0; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + iov[2].iov_base = (void *)name; + iov[2].iov_len = namelen + 1; + + return send_notify_iov(se, FUSE_NOTIFY_DELETE, iov, 3); +} + +int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, + off_t offset, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags) +{ + struct fuse_out_header out; + struct fuse_notify_store_out outarg; + struct iovec iov[3]; + size_t size = fuse_buf_size(bufv); + int res; + struct fuse_req *req = NULL; + + if (!se) + return -EINVAL; + + if (se->conn.proto_minor < 15) + return -ENOSYS; + + out.unique = 0; + out.error = FUSE_NOTIFY_STORE; + + outarg.nodeid = ino; + outarg.offset = offset; + outarg.size = size; + outarg.padding = 0; + + iov[0].iov_base = &out; + iov[0].iov_len = sizeof(out); + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + + res = fuse_send_data_iov(se, NULL, iov, 2, bufv, flags, req); + if (res > 0) + res = -res; + + return res; +} + +struct fuse_retrieve_req { + struct fuse_notify_req nreq; + void *cookie; +}; + +static void fuse_ll_retrieve_reply(struct fuse_notify_req *nreq, + fuse_req_t req, fuse_ino_t ino, + const void *inarg, + const struct fuse_buf *ibuf) +{ + struct fuse_session *se = req->se; + struct fuse_retrieve_req *rreq = + container_of(nreq, struct fuse_retrieve_req, nreq); + const struct fuse_notify_retrieve_in *arg = inarg; + struct fuse_bufvec bufv = { + .buf[0] = *ibuf, + .count = 1, + }; + + if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD)) + bufv.buf[0].mem = PARAM(arg); + + bufv.buf[0].size -= sizeof(struct fuse_in_header) + + sizeof(struct fuse_notify_retrieve_in); + + if (bufv.buf[0].size < arg->size) { + fuse_log(FUSE_LOG_ERR, "fuse: retrieve reply: buffer size too small\n"); + fuse_reply_none(req); + goto out; + } + bufv.buf[0].size = arg->size; + + if (se->op.retrieve_reply) { + se->op.retrieve_reply(req, rreq->cookie, ino, + arg->offset, &bufv); + } else { + fuse_reply_none(req); + } +out: + free(rreq); + if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count) + fuse_ll_clear_pipe(se); +} + +int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, + size_t size, off_t offset, void *cookie) +{ + struct fuse_notify_retrieve_out outarg; + struct iovec iov[2]; + struct fuse_retrieve_req *rreq; + int err; + + if (!se) + return -EINVAL; + + if (se->conn.proto_minor < 15) + return -ENOSYS; + + rreq = malloc(sizeof(*rreq)); + if (rreq == NULL) + return -ENOMEM; + + pthread_mutex_lock(&se->lock); + rreq->cookie = cookie; + rreq->nreq.unique = se->notify_ctr++; + rreq->nreq.reply = fuse_ll_retrieve_reply; + list_add_nreq(&rreq->nreq, &se->notify_list); + pthread_mutex_unlock(&se->lock); + + outarg.notify_unique = rreq->nreq.unique; + outarg.nodeid = ino; + outarg.offset = offset; + outarg.size = size; + outarg.padding = 0; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + + err = send_notify_iov(se, FUSE_NOTIFY_RETRIEVE, iov, 2); + if (err) { + pthread_mutex_lock(&se->lock); + list_del_nreq(&rreq->nreq); + pthread_mutex_unlock(&se->lock); + free(rreq); + } + + return err; +} + +void *fuse_req_userdata(fuse_req_t req) +{ + return req->se->userdata; +} + +const struct fuse_ctx *fuse_req_ctx(fuse_req_t req) +{ + return &req->ctx; +} + +void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, + void *data) +{ + pthread_mutex_lock(&req->lock); + pthread_mutex_lock(&req->se->lock); + req->u.ni.func = func; + req->u.ni.data = data; + pthread_mutex_unlock(&req->se->lock); + if (req->interrupted && func) + func(req, data); + pthread_mutex_unlock(&req->lock); +} + +int fuse_req_interrupted(fuse_req_t req) +{ + int interrupted; + + pthread_mutex_lock(&req->se->lock); + interrupted = req->interrupted; + pthread_mutex_unlock(&req->se->lock); + + return interrupted; +} + +bool fuse_req_is_uring(fuse_req_t req) +{ + return req->flags.is_uring; +} + +#ifndef HAVE_URING +int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, + void **mr) +{ + (void)req; + (void)payload; + (void)payload_sz; + (void)mr; + return -ENOTSUP; +} +#endif + +static struct { + void (*func)(fuse_req_t req, const fuse_ino_t node, const void *arg); + const char *name; +} fuse_ll_ops[] = { + [FUSE_LOOKUP] = { do_lookup, "LOOKUP" }, + [FUSE_FORGET] = { do_forget, "FORGET" }, + [FUSE_GETATTR] = { do_getattr, "GETATTR" }, + [FUSE_SETATTR] = { do_setattr, "SETATTR" }, + [FUSE_READLINK] = { do_readlink, "READLINK" }, + [FUSE_SYMLINK] = { do_symlink, "SYMLINK" }, + [FUSE_MKNOD] = { do_mknod, "MKNOD" }, + [FUSE_MKDIR] = { do_mkdir, "MKDIR" }, + [FUSE_UNLINK] = { do_unlink, "UNLINK" }, + [FUSE_RMDIR] = { do_rmdir, "RMDIR" }, + [FUSE_RENAME] = { do_rename, "RENAME" }, + [FUSE_LINK] = { do_link, "LINK" }, + [FUSE_OPEN] = { do_open, "OPEN" }, + [FUSE_READ] = { do_read, "READ" }, + [FUSE_WRITE] = { do_write, "WRITE" }, + [FUSE_STATFS] = { do_statfs, "STATFS" }, + [FUSE_RELEASE] = { do_release, "RELEASE" }, + [FUSE_FSYNC] = { do_fsync, "FSYNC" }, + [FUSE_SETXATTR] = { do_setxattr, "SETXATTR" }, + [FUSE_GETXATTR] = { do_getxattr, "GETXATTR" }, + [FUSE_LISTXATTR] = { do_listxattr, "LISTXATTR" }, + [FUSE_REMOVEXATTR] = { do_removexattr, "REMOVEXATTR" }, + [FUSE_FLUSH] = { do_flush, "FLUSH" }, + [FUSE_INIT] = { do_init, "INIT" }, + [FUSE_OPENDIR] = { do_opendir, "OPENDIR" }, + [FUSE_READDIR] = { do_readdir, "READDIR" }, + [FUSE_RELEASEDIR] = { do_releasedir, "RELEASEDIR" }, + [FUSE_FSYNCDIR] = { do_fsyncdir, "FSYNCDIR" }, + [FUSE_GETLK] = { do_getlk, "GETLK" }, + [FUSE_SETLK] = { do_setlk, "SETLK" }, + [FUSE_SETLKW] = { do_setlkw, "SETLKW" }, + [FUSE_ACCESS] = { do_access, "ACCESS" }, + [FUSE_CREATE] = { do_create, "CREATE" }, + [FUSE_TMPFILE] = { do_tmpfile, "TMPFILE" }, + [FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" }, + [FUSE_BMAP] = { do_bmap, "BMAP" }, + [FUSE_IOCTL] = { do_ioctl, "IOCTL" }, + [FUSE_POLL] = { do_poll, "POLL" }, + [FUSE_FALLOCATE] = { do_fallocate, "FALLOCATE" }, + [FUSE_DESTROY] = { do_destroy, "DESTROY" }, + [FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" }, + [FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" }, + [FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"}, + [FUSE_RENAME2] = { do_rename2, "RENAME2" }, + [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" }, + [FUSE_COPY_FILE_RANGE_64] = { do_copy_file_range_64, "COPY_FILE_RANGE_64" }, + [FUSE_LSEEK] = { do_lseek, "LSEEK" }, + [FUSE_STATX] = { do_statx, "STATX" }, + [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" }, +}; + +static struct { + void (*func)(fuse_req_t req, const fuse_ino_t ino, const void *op_in, + const void *op_payload); + const char *name; +} fuse_ll_ops2[] __attribute__((unused)) = { + [FUSE_LOOKUP] = { _do_lookup, "LOOKUP" }, + [FUSE_FORGET] = { _do_forget, "FORGET" }, + [FUSE_GETATTR] = { _do_getattr, "GETATTR" }, + [FUSE_SETATTR] = { _do_setattr, "SETATTR" }, + [FUSE_READLINK] = { _do_readlink, "READLINK" }, + [FUSE_SYMLINK] = { _do_symlink, "SYMLINK" }, + [FUSE_MKNOD] = { _do_mknod, "MKNOD" }, + [FUSE_MKDIR] = { _do_mkdir, "MKDIR" }, + [FUSE_UNLINK] = { _do_unlink, "UNLINK" }, + [FUSE_RMDIR] = { _do_rmdir, "RMDIR" }, + [FUSE_RENAME] = { _do_rename, "RENAME" }, + [FUSE_LINK] = { _do_link, "LINK" }, + [FUSE_OPEN] = { _do_open, "OPEN" }, + [FUSE_READ] = { _do_read, "READ" }, + [FUSE_WRITE] = { _do_write, "WRITE" }, + [FUSE_STATFS] = { _do_statfs, "STATFS" }, + [FUSE_RELEASE] = { _do_release, "RELEASE" }, + [FUSE_FSYNC] = { _do_fsync, "FSYNC" }, + [FUSE_SETXATTR] = { _do_setxattr, "SETXATTR" }, + [FUSE_GETXATTR] = { _do_getxattr, "GETXATTR" }, + [FUSE_LISTXATTR] = { _do_listxattr, "LISTXATTR" }, + [FUSE_REMOVEXATTR] = { _do_removexattr, "REMOVEXATTR" }, + [FUSE_FLUSH] = { _do_flush, "FLUSH" }, + [FUSE_INIT] = { _do_init, "INIT" }, + [FUSE_OPENDIR] = { _do_opendir, "OPENDIR" }, + [FUSE_READDIR] = { _do_readdir, "READDIR" }, + [FUSE_RELEASEDIR] = { _do_releasedir, "RELEASEDIR" }, + [FUSE_FSYNCDIR] = { _do_fsyncdir, "FSYNCDIR" }, + [FUSE_GETLK] = { _do_getlk, "GETLK" }, + [FUSE_SETLK] = { _do_setlk, "SETLK" }, + [FUSE_SETLKW] = { _do_setlkw, "SETLKW" }, + [FUSE_ACCESS] = { _do_access, "ACCESS" }, + [FUSE_CREATE] = { _do_create, "CREATE" }, + [FUSE_TMPFILE] = { _do_tmpfile, "TMPFILE" }, + [FUSE_INTERRUPT] = { _do_interrupt, "INTERRUPT" }, + [FUSE_BMAP] = { _do_bmap, "BMAP" }, + [FUSE_IOCTL] = { _do_ioctl, "IOCTL" }, + [FUSE_POLL] = { _do_poll, "POLL" }, + [FUSE_FALLOCATE] = { _do_fallocate, "FALLOCATE" }, + [FUSE_DESTROY] = { _do_destroy, "DESTROY" }, + [FUSE_NOTIFY_REPLY] = { (void *)1, "NOTIFY_REPLY" }, + [FUSE_BATCH_FORGET] = { _do_batch_forget, "BATCH_FORGET" }, + [FUSE_READDIRPLUS] = { _do_readdirplus, "READDIRPLUS" }, + [FUSE_RENAME2] = { _do_rename2, "RENAME2" }, + [FUSE_COPY_FILE_RANGE] = { _do_copy_file_range, "COPY_FILE_RANGE" }, + [FUSE_COPY_FILE_RANGE_64] = { _do_copy_file_range_64, "COPY_FILE_RANGE_64" }, + [FUSE_LSEEK] = { _do_lseek, "LSEEK" }, + [FUSE_STATX] = { _do_statx, "STATX" }, + [CUSE_INIT] = { _cuse_lowlevel_init, "CUSE_INIT" }, +}; + +/* + * For ABI compatibility we cannot allow higher values than CUSE_INIT. + * Without ABI compatibility we could use the size of the array. + * #define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0])) + */ +#define FUSE_MAXOP (CUSE_INIT + 1) + + +/** + * + * @return 0 if sanity is ok, error otherwise + */ +static inline int +fuse_req_opcode_sanity_ok(struct fuse_session *se, enum fuse_opcode in_op) +{ + int err = EIO; + + if (!se->got_init) { + enum fuse_opcode expected; + + expected = se->cuse_data ? CUSE_INIT : FUSE_INIT; + if (in_op != expected) + return err; + } else if (in_op == FUSE_INIT || in_op == CUSE_INIT) + return err; + + return 0; +} + +static inline void +fuse_session_in2req(struct fuse_req *req, struct fuse_in_header *in) +{ + req->unique = in->unique; + req->ctx.uid = in->uid; + req->ctx.gid = in->gid; + req->ctx.pid = in->pid; +} + +/** + * Implement -o allow_root + */ +static inline int +fuse_req_check_allow_root(struct fuse_session *se, enum fuse_opcode in_op, + uid_t in_uid) +{ + int err = EACCES; + + if (se->deny_others && in_uid != se->owner && in_uid != 0 && + in_op != FUSE_INIT && in_op != FUSE_READ && + in_op != FUSE_WRITE && in_op != FUSE_FSYNC && + in_op != FUSE_RELEASE && in_op != FUSE_READDIR && + in_op != FUSE_FSYNCDIR && in_op != FUSE_RELEASEDIR && + in_op != FUSE_NOTIFY_REPLY && + in_op != FUSE_READDIRPLUS) + return err; + + return 0; +} + +static const char *opname(enum fuse_opcode opcode) +{ + if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name) + return "???"; + else + return fuse_ll_ops[opcode].name; +} + +static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst, + struct fuse_bufvec *src) +{ + ssize_t res = fuse_buf_copy(dst, src, 0); + if (res < 0) { + fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n", strerror(-res)); + return res; + } + if ((size_t)res < fuse_buf_size(dst)) { + fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: short read\n"); + return -1; + } + return 0; +} + +void fuse_session_process_buf(struct fuse_session *se, + const struct fuse_buf *buf) +{ + fuse_session_process_buf_internal(se, buf, NULL); +} + +/* libfuse internal handler */ +void fuse_session_process_buf_internal(struct fuse_session *se, + const struct fuse_buf *buf, struct fuse_chan *ch) +{ + const size_t write_header_size = sizeof(struct fuse_in_header) + + sizeof(struct fuse_write_in); + struct fuse_bufvec bufv = { .buf[0] = *buf, .count = 1 }; + struct fuse_bufvec tmpbuf = FUSE_BUFVEC_INIT(write_header_size); + struct fuse_in_header *in; + const void *inarg; + struct fuse_req *req; + void *mbuf = NULL; + int err; + int res; + + if (buf->flags & FUSE_BUF_IS_FD) { + if (buf->size < tmpbuf.buf[0].size) + tmpbuf.buf[0].size = buf->size; + + mbuf = malloc(tmpbuf.buf[0].size); + if (mbuf == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate header\n"); + goto clear_pipe; + } + tmpbuf.buf[0].mem = mbuf; + + res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv); + if (res < 0) + goto clear_pipe; + + in = mbuf; + } else { + in = buf->mem; + } + + trace_request_process(in->opcode, in->unique); + + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, + "dev unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n", + (unsigned long long) in->unique, + opname((enum fuse_opcode) in->opcode), in->opcode, + (unsigned long long) in->nodeid, buf->size, in->pid); + } + + req = fuse_ll_alloc_req(se); + if (req == NULL) { + struct fuse_out_header out = { + .unique = in->unique, + .error = -ENOMEM, + }; + struct iovec iov = { + .iov_base = &out, + .iov_len = sizeof(struct fuse_out_header), + }; + + fuse_send_msg(se, ch, &iov, 1, NULL); + goto clear_pipe; + } + + fuse_session_in2req(req, in); + req->ch = ch ? fuse_chan_get(ch) : NULL; + + err = fuse_req_opcode_sanity_ok(se, in->opcode); + if (err) + goto reply_err; + + err = fuse_req_check_allow_root(se, in->opcode, in->uid); + if (err) + goto reply_err; + + err = ENOSYS; + if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func) + goto reply_err; + /* Do not process interrupt request */ + if (se->conn.no_interrupt && in->opcode == FUSE_INTERRUPT) { + if (se->debug) + fuse_log(FUSE_LOG_DEBUG, "FUSE_INTERRUPT: reply to kernel to disable interrupt\n"); + goto reply_err; + } + if (!se->conn.no_interrupt && in->opcode != FUSE_INTERRUPT) { + struct fuse_req *intr; + pthread_mutex_lock(&se->lock); + intr = check_interrupt(se, req); + list_add_req(req, &se->list); + pthread_mutex_unlock(&se->lock); + if (intr) + fuse_reply_err(intr, EAGAIN); + } + + if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size && + (in->opcode != FUSE_WRITE || !se->op.write_buf) && + in->opcode != FUSE_NOTIFY_REPLY) { + void *newmbuf; + + err = ENOMEM; + newmbuf = realloc(mbuf, buf->size); + if (newmbuf == NULL) + goto reply_err; + mbuf = newmbuf; + + tmpbuf = FUSE_BUFVEC_INIT(buf->size - write_header_size); + tmpbuf.buf[0].mem = (char *)mbuf + write_header_size; + + res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv); + err = -res; + if (res < 0) + goto reply_err; + + in = mbuf; + } + + inarg = (void *) &in[1]; + if (in->opcode == FUSE_WRITE && se->op.write_buf) + do_write_buf(req, in->nodeid, inarg, buf); + else if (in->opcode == FUSE_NOTIFY_REPLY) + do_notify_reply(req, in->nodeid, inarg, buf); + else + fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg); + +out_free: + free(mbuf); + return; + +reply_err: + fuse_reply_err(req, err); +clear_pipe: + if (buf->flags & FUSE_BUF_IS_FD) + fuse_ll_clear_pipe(se); + goto out_free; +} + +void fuse_session_process_uring_cqe(struct fuse_session *se, + struct fuse_req *req, + struct fuse_in_header *in, void *op_in, + void *op_payload, size_t payload_len) +{ + int err; + + fuse_session_in2req(req, in); + + err = fuse_req_opcode_sanity_ok(se, in->opcode); + if (err) + goto reply_err; + + err = fuse_req_check_allow_root(se, in->opcode, in->uid); + if (err) + goto reply_err; + + err = ENOSYS; + if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func) + goto reply_err; + + if (se->debug) { + fuse_log( + FUSE_LOG_DEBUG, + "cqe unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n", + (unsigned long long)in->unique, + opname((enum fuse_opcode)in->opcode), in->opcode, + (unsigned long long)in->nodeid, payload_len, in->pid); + } + + if (in->opcode == FUSE_WRITE && se->op.write_buf) { + struct fuse_bufvec bufv = { + .buf[0] = { .size = payload_len, + .flags = 0, + .mem = op_payload }, + .count = 1, + }; + _do_write_buf(req, in->nodeid, op_in, &bufv); + } else if (in->opcode == FUSE_NOTIFY_REPLY) { + struct fuse_buf buf = { .size = payload_len, + .mem = op_payload }; + do_notify_reply(req, in->nodeid, op_in, &buf); + } else { + fuse_ll_ops2[in->opcode].func(req, in->nodeid, op_in, + op_payload); + } + + return; + +reply_err: + fuse_reply_err(req, err); +} + +#define LL_OPTION(n,o,v) \ + { n, offsetof(struct fuse_session, o), v } + +static const struct fuse_opt fuse_ll_opts[] = { + LL_OPTION("debug", debug, 1), + LL_OPTION("-d", debug, 1), + LL_OPTION("--debug", debug, 1), + LL_OPTION("allow_root", deny_others, 1), + LL_OPTION("io_uring", uring.enable, 1), + LL_OPTION("io_uring_q_depth=%u", uring.q_depth, -1), + FUSE_OPT_END +}; + +void fuse_lowlevel_version(void) +{ + printf("using FUSE kernel interface version %i.%i\n", + FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION); + fuse_mount_version(); +} + +void fuse_lowlevel_help(void) +{ + /* These are not all options, but the ones that are + potentially of interest to an end-user */ + printf( +" -o allow_other allow access by all users\n" +" -o allow_root allow access by root\n" +" -o auto_unmount auto unmount on process termination\n" +" -o io_uring enable io-uring\n" +" -o io_uring_q_depth= io-uring queue depth\n" +); +} + +void fuse_session_destroy(struct fuse_session *se) +{ + struct fuse_ll_pipe *llp; + + if (se->got_init && !se->got_destroy) { + if (se->op.destroy) + se->op.destroy(se->userdata); + } + llp = pthread_getspecific(se->pipe_key); + if (llp != NULL) + fuse_ll_pipe_free(llp); + pthread_key_delete(se->pipe_key); + sem_destroy(&se->mt_finish); + pthread_mutex_destroy(&se->mt_lock); + pthread_mutex_destroy(&se->lock); + free(se->cuse_data); + if (se->fd != -1) + close(se->fd); + if (se->io != NULL) + free(se->io); + destroy_mount_opts(se->mo); + free(se); +} + + +static void fuse_ll_pipe_destructor(void *data) +{ + struct fuse_ll_pipe *llp = data; + fuse_ll_pipe_free(llp); +} + +void fuse_buf_free(struct fuse_buf *buf) +{ + if (buf->mem == NULL) + return; + + size_t write_header_sz = + sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in); + + char *ptr = (char *)buf->mem - pagesize + write_header_sz; + free(ptr); + buf->mem = NULL; +} + +/* + * This is used to allocate buffers that hold fuse requests + */ +static void *buf_alloc(size_t size, bool internal) +{ + /* + * For libfuse internal caller add in alignment. That cannot be done + * for an external caller, as it is not guaranteed that the external + * caller frees the raw pointer. + */ + if (internal) { + size_t write_header_sz = sizeof(struct fuse_in_header) + + sizeof(struct fuse_write_in); + size_t new_size = ROUND_UP(size + write_header_sz, pagesize); + + char *buf = aligned_alloc(pagesize, new_size); + if (buf == NULL) + return NULL; + + buf += pagesize - write_header_sz; + + return buf; + } else { + return malloc(size); + } +} + +/* + *@param internal true if called from libfuse internal code + */ +static int _fuse_session_receive_buf(struct fuse_session *se, + struct fuse_buf *buf, struct fuse_chan *ch, + bool internal) +{ + int err; + ssize_t res; + size_t bufsize; +#ifdef HAVE_SPLICE + struct fuse_ll_pipe *llp; + struct fuse_buf tmpbuf; + +pipe_retry: + bufsize = se->bufsize; + + if (se->conn.proto_minor < 14 || + !(se->conn.want_ext & FUSE_CAP_SPLICE_READ)) + goto fallback; + + llp = fuse_ll_get_pipe(se); + if (llp == NULL) + goto fallback; + + if (llp->size < bufsize) { + if (llp->can_grow) { + res = fcntl(llp->pipe[0], F_SETPIPE_SZ, bufsize); + if (res == -1) { + llp->can_grow = 0; + res = grow_pipe_to_max(llp->pipe[0]); + if (res > 0) + llp->size = res; + goto fallback; + } + llp->size = res; + } + if (llp->size < bufsize) + goto fallback; + } + + if (se->io != NULL && se->io->splice_receive != NULL) { + res = se->io->splice_receive(ch ? ch->fd : se->fd, NULL, + llp->pipe[1], NULL, bufsize, 0, + se->userdata); + } else { + res = splice(ch ? ch->fd : se->fd, NULL, llp->pipe[1], NULL, + bufsize, 0); + } + err = errno; + trace_request_receive(err); + + if (fuse_session_exited(se)) + return 0; + + if (res == -1) { + if (err == ENODEV) { + /* Filesystem was unmounted, or connection was aborted + via /sys/fs/fuse/connections */ + fuse_session_exit(se); + return 0; + } + + /* FUSE_INIT might have increased the required bufsize */ + if (err == EINVAL && bufsize < se->bufsize) { + fuse_ll_clear_pipe(se); + goto pipe_retry; + } + + if (err != EINTR && err != EAGAIN) + perror("fuse: splice from device"); + return -err; + } + + if (res < sizeof(struct fuse_in_header)) { + fuse_log(FUSE_LOG_ERR, "short splice from fuse device\n"); + return -EIO; + } + + tmpbuf = (struct fuse_buf){ + .size = res, + .flags = FUSE_BUF_IS_FD, + .fd = llp->pipe[0], + }; + + /* + * Don't bother with zero copy for small requests. + * fuse_loop_mt() needs to check for FORGET so this more than + * just an optimization. + */ + if (res < sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) + + pagesize) { + struct fuse_bufvec src = { .buf[0] = tmpbuf, .count = 1 }; + struct fuse_bufvec dst = { .count = 1 }; + + if (!buf->mem) { + buf->mem = buf_alloc(bufsize, internal); + if (!buf->mem) { + fuse_log( + FUSE_LOG_ERR, + "fuse: failed to allocate read buffer\n"); + return -ENOMEM; + } + buf->mem_size = bufsize; + } + buf->size = bufsize; + buf->flags = 0; + dst.buf[0] = *buf; + + res = fuse_buf_copy(&dst, &src, 0); + if (res < 0) { + fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n", + strerror(-res)); + fuse_ll_clear_pipe(se); + return res; + } + if (res < tmpbuf.size) { + fuse_log(FUSE_LOG_ERR, + "fuse: copy from pipe: short read\n"); + fuse_ll_clear_pipe(se); + return -EIO; + } + assert(res == tmpbuf.size); + + } else { + /* Don't overwrite buf->mem, as that would cause a leak */ + buf->fd = tmpbuf.fd; + buf->flags = tmpbuf.flags; + } + buf->size = tmpbuf.size; + + return res; + +fallback: +#endif + bufsize = internal ? buf->mem_size : se->bufsize; + if (!buf->mem) { + bufsize = se->bufsize; /* might have changed */ + buf->mem = buf_alloc(bufsize, internal); + if (!buf->mem) { + fuse_log(FUSE_LOG_ERR, + "fuse: failed to allocate read buffer\n"); + return -ENOMEM; + } + + if (internal) + buf->mem_size = bufsize; + } + +restart: + if (se->io != NULL) { + /* se->io->read is never NULL if se->io is not NULL as + specified by fuse_session_custom_io()*/ + res = se->io->read(ch ? ch->fd : se->fd, buf->mem, bufsize, + se->userdata); + } else { + res = read(ch ? ch->fd : se->fd, buf->mem, bufsize); + } + err = errno; + trace_request_receive(err); + + if (fuse_session_exited(se)) + return 0; + if (res == -1) { + if (err == EINVAL && internal && se->bufsize > bufsize) { + /* FUSE_INIT might have increased the required bufsize */ + bufsize = se->bufsize; + void *newbuf = buf_alloc(bufsize, internal); + if (!newbuf) { + fuse_log( + FUSE_LOG_ERR, + "fuse: failed to (re)allocate read buffer\n"); + return -ENOMEM; + } + fuse_buf_free(buf); + buf->mem = newbuf; + buf->mem_size = bufsize; + goto restart; + } + + /* ENOENT means the operation was interrupted, it's safe + to restart */ + if (err == ENOENT) + goto restart; + + if (err == ENODEV) { + /* Filesystem was unmounted, or connection was aborted + via /sys/fs/fuse/connections */ + fuse_session_exit(se); + return 0; + } + /* Errors occurring during normal operation: EINTR (read + interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem + umounted) */ + if (err != EINTR && err != EAGAIN) + perror("fuse: reading device"); + return -err; + } + if ((size_t)res < sizeof(struct fuse_in_header)) { + fuse_log(FUSE_LOG_ERR, "short read on fuse device\n"); + return -EIO; + } + + buf->size = res; + + return res; +} + +int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf) +{ + return _fuse_session_receive_buf(se, buf, NULL, false); +} + +/* libfuse internal handler */ +int fuse_session_receive_buf_internal(struct fuse_session *se, + struct fuse_buf *buf, + struct fuse_chan *ch) +{ + /* + * if run internally thread buffers are from libfuse - we can + * reallocate them + */ + if (unlikely(!se->got_init) && !se->buf_reallocable) + se->buf_reallocable = true; + + return _fuse_session_receive_buf(se, buf, ch, true); +} + +struct fuse_session * +fuse_session_new_versioned(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, size_t op_size, + struct libfuse_version *version, void *userdata) +{ + int err; + struct fuse_session *se; + struct mount_opts *mo; + + if (op == NULL || op_size == 0) { + fuse_log(FUSE_LOG_ERR, + "fuse: warning: empty op list passed to fuse_session_new()\n"); + return NULL; + } + + if (version == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: warning: version not passed to fuse_session_new()\n"); + return NULL; + } + + if (sizeof(struct fuse_lowlevel_ops) < op_size) { + fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n"); + op_size = sizeof(struct fuse_lowlevel_ops); + } + + if (args == NULL || args->argc == 0) { + fuse_log(FUSE_LOG_ERR, "fuse: empty argv passed to fuse_session_new().\n"); + return NULL; + } + + se = (struct fuse_session *) calloc(1, sizeof(struct fuse_session)); + if (se == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n"); + goto out1; + } + se->fd = -1; + se->conn.max_write = FUSE_DEFAULT_MAX_PAGES_LIMIT * getpagesize(); + se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE; + se->conn.max_readahead = UINT_MAX; + + /* + * Allow overriding with env, mostly to avoid the need to modify + * all tests. I.e. to test with and without io-uring being enabled. + */ + se->uring.enable = getenv("FUSE_URING_ENABLE") ? + atoi(getenv("FUSE_URING_ENABLE")) : + SESSION_DEF_URING_ENABLE; + se->uring.q_depth = getenv("FUSE_URING_QUEUE_DEPTH") ? + atoi(getenv("FUSE_URING_QUEUE_DEPTH")) : + SESSION_DEF_URING_Q_DEPTH; + + /* Parse options */ + if(fuse_opt_parse(args, se, fuse_ll_opts, NULL) == -1) + goto out2; + if(se->deny_others) { + /* Allowing access only by root is done by instructing + * kernel to allow access by everyone, and then restricting + * access to root and mountpoint owner in libfuse. + */ + // We may be adding the option a second time, but + // that doesn't hurt. + if(fuse_opt_add_arg(args, "-oallow_other") == -1) + goto out2; + } + mo = parse_mount_opts(args); + if (mo == NULL) + goto out3; + + if(args->argc == 1 && + args->argv[0][0] == '-') { + fuse_log(FUSE_LOG_ERR, "fuse: warning: argv[0] looks like an option, but " + "will be ignored\n"); + } else if (args->argc != 1) { + int i; + fuse_log(FUSE_LOG_ERR, "fuse: unknown option(s): `"); + for(i = 1; i < args->argc-1; i++) + fuse_log(FUSE_LOG_ERR, "%s ", args->argv[i]); + fuse_log(FUSE_LOG_ERR, "%s'\n", args->argv[i]); + goto out4; + } + + if (se->debug) + fuse_log(FUSE_LOG_DEBUG, "FUSE library version: %s\n", PACKAGE_VERSION); + + list_init_req(&se->list); + list_init_req(&se->interrupts); + list_init_nreq(&se->notify_list); + se->notify_ctr = 1; + pthread_mutex_init(&se->lock, NULL); + sem_init(&se->mt_finish, 0, 0); + pthread_mutex_init(&se->mt_lock, NULL); + + err = pthread_key_create(&se->pipe_key, fuse_ll_pipe_destructor); + if (err) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n", + strerror(err)); + goto out5; + } + + memcpy(&se->op, op, op_size); + se->owner = getuid(); + se->userdata = userdata; + + se->mo = mo; + + /* Fuse server application should pass the version it was compiled + * against and pass it. If a libfuse version accidentally introduces an + * ABI incompatibility, it might be possible to 'fix' that at run time, + * by checking the version numbers. + */ + se->version = *version; + + return se; + +out5: + sem_destroy(&se->mt_finish); + pthread_mutex_destroy(&se->mt_lock); + pthread_mutex_destroy(&se->lock); +out4: + fuse_opt_free_args(args); +out3: + if (mo != NULL) + destroy_mount_opts(mo); +out2: + free(se); +out1: + return NULL; +} + +struct fuse_session *fuse_session_new_30(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata); +struct fuse_session *fuse_session_new_30(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, + void *userdata) +{ + struct fuse_lowlevel_ops null_ops = { 0 }; + + /* unknown version */ + struct libfuse_version version = { 0 }; + + /* + * This function is the ABI interface function from fuse_session_new in + * compat.c. External libraries like "fuser" might call fuse_session_new() + * with NULL ops and then pass that session to fuse_session_mount(). + * The actual FUSE operations are handled in their own library. + */ + if (op == NULL) { + op = &null_ops; + op_size = sizeof(null_ops); + } + + return fuse_session_new_versioned(args, op, op_size, &version, + userdata); +} + +FUSE_SYMVER("fuse_session_custom_io_317", "fuse_session_custom_io@@FUSE_3.17") +int fuse_session_custom_io_317(struct fuse_session *se, + const struct fuse_custom_io *io, size_t op_size, int fd) +{ + if (sizeof(struct fuse_custom_io) < op_size) { + fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n"); + op_size = sizeof(struct fuse_custom_io); + } + + if (fd < 0) { + fuse_log(FUSE_LOG_ERR, "Invalid file descriptor value %d passed to " + "fuse_session_custom_io()\n", fd); + return -EBADF; + } + if (io == NULL) { + fuse_log(FUSE_LOG_ERR, "No custom IO passed to " + "fuse_session_custom_io()\n"); + return -EINVAL; + } else if (io->read == NULL || io->writev == NULL) { + /* If the user provides their own file descriptor, we can't + guarantee that the default behavior of the io operations made + in libfuse will function properly. Therefore, we enforce the + user to implement these io operations when using custom io. */ + fuse_log(FUSE_LOG_ERR, "io passed to fuse_session_custom_io() must " + "implement both io->read() and io->writev\n"); + return -EINVAL; + } + + se->io = calloc(1, sizeof(struct fuse_custom_io)); + if (se->io == NULL) { + fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for custom io. " + "Error: %s\n", strerror(errno)); + return -errno; + } + + se->fd = fd; + memcpy(se->io, io, op_size); + return 0; +} + +int fuse_session_custom_io_30(struct fuse_session *se, + const struct fuse_custom_io *io, int fd); +FUSE_SYMVER("fuse_session_custom_io_30", "fuse_session_custom_io@FUSE_3.0") +int fuse_session_custom_io_30(struct fuse_session *se, + const struct fuse_custom_io *io, int fd) +{ + return fuse_session_custom_io_317(se, io, + offsetof(struct fuse_custom_io, clone_fd), fd); +} + +int fuse_session_mount(struct fuse_session *se, const char *_mountpoint) +{ + int fd; + char *mountpoint; + + if (_mountpoint == NULL) { + fuse_log(FUSE_LOG_ERR, "Invalid null-ptr mountpoint!\n"); + return -1; + } + + mountpoint = strdup(_mountpoint); + if (mountpoint == NULL) { + fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for mountpoint. Error: %s\n", + strerror(errno)); + return -1; + } + + /* + * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos + * would ensue. + */ + do { + fd = open("/dev/null", O_RDWR); + if (fd > 2) + close(fd); + } while (fd >= 0 && fd <= 2); + + /* + * To allow FUSE daemons to run without privileges, the caller may open + * /dev/fuse before launching the file system and pass on the file + * descriptor by specifying /dev/fd/N as the mount point. Note that the + * parent process takes care of performing the mount in this case. + */ + fd = fuse_mnt_parse_fuse_fd(mountpoint); + if (fd != -1) { + if (fcntl(fd, F_GETFD) == -1) { + fuse_log(FUSE_LOG_ERR, + "fuse: Invalid file descriptor /dev/fd/%u\n", + fd); + goto error_out; + } + se->fd = fd; + return 0; + } + + /* Open channel */ + fd = fuse_kern_mount(mountpoint, se->mo); + if (fd == -1) + goto error_out; + se->fd = fd; + + /* Save mountpoint */ + se->mountpoint = mountpoint; + + return 0; + +error_out: + free(mountpoint); + return -1; +} + +int fuse_session_fd(struct fuse_session *se) +{ + return se->fd; +} + +void fuse_session_unmount(struct fuse_session *se) +{ + if (se->mountpoint != NULL) { + char *mountpoint = atomic_exchange(&se->mountpoint, NULL); + + fuse_kern_unmount(mountpoint, se->fd); + se->fd = -1; + free(mountpoint); + } +} + +#ifdef linux +int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]) +{ + char *buf; + size_t bufsize = 1024; + char path[128]; + int ret; + int fd; + unsigned long pid = req->ctx.pid; + char *s; + + sprintf(path, "/proc/%lu/task/%lu/status", pid, pid); + +retry: + buf = malloc(bufsize); + if (buf == NULL) + return -ENOMEM; + + ret = -EIO; + fd = open(path, O_RDONLY); + if (fd == -1) + goto out_free; + + ret = read(fd, buf, bufsize); + close(fd); + if (ret < 0) { + ret = -EIO; + goto out_free; + } + + if ((size_t)ret == bufsize) { + free(buf); + bufsize *= 4; + goto retry; + } + + buf[ret] = '\0'; + ret = -EIO; + s = strstr(buf, "\nGroups:"); + if (s == NULL) + goto out_free; + + s += 8; + ret = 0; + while (1) { + char *end; + unsigned long val = strtoul(s, &end, 0); + if (end == s) + break; + + s = end; + if (ret < size) + list[ret] = val; + ret++; + } + +out_free: + free(buf); + return ret; +} +#else /* linux */ +/* + * This is currently not implemented on other than Linux... + */ +int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]) +{ + (void) req; (void) size; (void) list; + return -ENOSYS; +} +#endif + +/* Prevent spurious data race warning - we don't care + * about races for this flag */ +__attribute__((no_sanitize_thread)) +void fuse_session_exit(struct fuse_session *se) +{ + atomic_store_explicit(&se->mt_exited, 1, memory_order_relaxed); + sem_post(&se->mt_finish); +} + +__attribute__((no_sanitize_thread)) +void fuse_session_reset(struct fuse_session *se) +{ + se->mt_exited = false; + se->error = 0; +} + +__attribute__((no_sanitize_thread)) +int fuse_session_exited(struct fuse_session *se) +{ + bool exited = + atomic_load_explicit(&se->mt_exited, memory_order_relaxed); + + return exited ? 1 : 0; +} diff --git a/lib/fuse_misc.h b/lib/fuse_misc.h new file mode 100644 index 0000000..1452593 --- /dev/null +++ b/lib/fuse_misc.h @@ -0,0 +1,51 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#include + +/* + Versioned symbols cannot be used in some cases because it + - not supported on MacOSX (in MachO binary format) + + Note: "@@" denotes the default symbol, "@" is binary a compat version. + +*/ +#ifdef LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS +# if HAVE_SYMVER_ATTRIBUTE +# define FUSE_SYMVER(sym1, sym2) __attribute__ ((symver (sym2))) +# else +# define FUSE_SYMVER(sym1, sym2) __asm__("\t.symver " sym1 "," sym2); +# endif +#else +#define FUSE_SYMVER(sym1, sym2) +#endif + +#ifdef HAVE_STRUCT_STAT_ST_ATIM +/* Linux */ +#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec) +#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec) +#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec) +#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atim.tv_nsec = (val) +#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctim.tv_nsec = (val) +#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtim.tv_nsec = (val) +#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC) +/* FreeBSD */ +#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimespec.tv_nsec) +#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimespec.tv_nsec) +#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimespec.tv_nsec) +#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atimespec.tv_nsec = (val) +#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctimespec.tv_nsec = (val) +#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtimespec.tv_nsec = (val) +#else +#define ST_ATIM_NSEC(stbuf) 0 +#define ST_CTIM_NSEC(stbuf) 0 +#define ST_MTIM_NSEC(stbuf) 0 +#define ST_ATIM_NSEC_SET(stbuf, val) do { } while (0) +#define ST_CTIM_NSEC_SET(stbuf, val) do { } while (0) +#define ST_MTIM_NSEC_SET(stbuf, val) do { } while (0) +#endif diff --git a/lib/fuse_opt.c b/lib/fuse_opt.c new file mode 100644 index 0000000..046e577 --- /dev/null +++ b/lib/fuse_opt.c @@ -0,0 +1,423 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Implementation of option parsing routines (dealing with `struct + fuse_args`). + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_opt.h" +#include "fuse_misc.h" + +#include +#include +#include +#include + +struct fuse_opt_context { + void *data; + const struct fuse_opt *opt; + fuse_opt_proc_t proc; + int argctr; + int argc; + char **argv; + struct fuse_args outargs; + char *opts; + int nonopt; +}; + +void fuse_opt_free_args(struct fuse_args *args) +{ + if (args) { + if (args->argv && args->allocated) { + int i; + for (i = 0; i < args->argc; i++) + free(args->argv[i]); + free(args->argv); + } + args->argc = 0; + args->argv = NULL; + args->allocated = 0; + } +} + +static int alloc_failed(void) +{ + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + return -1; +} + +int fuse_opt_add_arg(struct fuse_args *args, const char *arg) +{ + char **newargv; + char *newarg; + + assert(!args->argv || args->allocated); + + newarg = strdup(arg); + if (!newarg) + return alloc_failed(); + + newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *)); + if (!newargv) { + free(newarg); + return alloc_failed(); + } + + args->argv = newargv; + args->allocated = 1; + args->argv[args->argc++] = newarg; + args->argv[args->argc] = NULL; + return 0; +} + +static int fuse_opt_insert_arg_common(struct fuse_args *args, int pos, + const char *arg) +{ + assert(pos <= args->argc); + if (fuse_opt_add_arg(args, arg) == -1) + return -1; + + if (pos != args->argc - 1) { + char *newarg = args->argv[args->argc - 1]; + memmove(&args->argv[pos + 1], &args->argv[pos], + sizeof(char *) * (args->argc - pos - 1)); + args->argv[pos] = newarg; + } + return 0; +} + +int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg) +{ + return fuse_opt_insert_arg_common(args, pos, arg); +} + +static int next_arg(struct fuse_opt_context *ctx, const char *opt) +{ + if (ctx->argctr + 1 >= ctx->argc) { + fuse_log(FUSE_LOG_ERR, "fuse: missing argument after `%s'\n", opt); + return -1; + } + ctx->argctr++; + return 0; +} + +static int add_arg(struct fuse_opt_context *ctx, const char *arg) +{ + return fuse_opt_add_arg(&ctx->outargs, arg); +} + +static int add_opt_common(char **opts, const char *opt, int esc) +{ + unsigned oldlen = *opts ? strlen(*opts) : 0; + char *d = realloc(*opts, oldlen + 1 + strlen(opt) * 2 + 1); + + if (!d) + return alloc_failed(); + + *opts = d; + if (oldlen) { + d += oldlen; + *d++ = ','; + } + + for (; *opt; opt++) { + if (esc && (*opt == ',' || *opt == '\\')) + *d++ = '\\'; + *d++ = *opt; + } + *d = '\0'; + + return 0; +} + +int fuse_opt_add_opt(char **opts, const char *opt) +{ + return add_opt_common(opts, opt, 0); +} + +int fuse_opt_add_opt_escaped(char **opts, const char *opt) +{ + return add_opt_common(opts, opt, 1); +} + +static int add_opt(struct fuse_opt_context *ctx, const char *opt) +{ + return add_opt_common(&ctx->opts, opt, 1); +} + +static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key, + int iso) +{ + if (key == FUSE_OPT_KEY_DISCARD) + return 0; + + if (key != FUSE_OPT_KEY_KEEP && ctx->proc) { + int res = ctx->proc(ctx->data, arg, key, &ctx->outargs); + if (res == -1 || !res) + return res; + } + if (iso) + return add_opt(ctx, arg); + else + return add_arg(ctx, arg); +} + +static int match_template(const char *t, const char *arg, unsigned *sepp) +{ + int arglen = strlen(arg); + const char *sep = strchr(t, '='); + sep = sep ? sep : strchr(t, ' '); + if (sep && (!sep[1] || sep[1] == '%')) { + int tlen = sep - t; + if (sep[0] == '=') + tlen ++; + if (arglen >= tlen && strncmp(arg, t, tlen) == 0) { + *sepp = sep - t; + return 1; + } + } + if (strcmp(t, arg) == 0) { + *sepp = 0; + return 1; + } + return 0; +} + +static const struct fuse_opt *find_opt(const struct fuse_opt *opt, + const char *arg, unsigned *sepp) +{ + for (; opt && opt->templ; opt++) + if (match_template(opt->templ, arg, sepp)) + return opt; + return NULL; +} + +int fuse_opt_match(const struct fuse_opt *opts, const char *opt) +{ + unsigned dummy; + return find_opt(opts, opt, &dummy) ? 1 : 0; +} + +static int process_opt_param(void *var, const char *format, const char *param, + const char *arg) +{ + assert(format[0] == '%'); + if (format[1] == 's') { + char **s = var; + char *copy = strdup(param); + if (!copy) + return alloc_failed(); + + free(*s); + *s = copy; + } else { + if (sscanf(param, format, var) != 1) { + fuse_log(FUSE_LOG_ERR, "fuse: invalid parameter in option `%s'\n", arg); + return -1; + } + } + return 0; +} + +static int process_opt(struct fuse_opt_context *ctx, + const struct fuse_opt *opt, unsigned sep, + const char *arg, int iso) +{ + if (opt->offset == -1U) { + if (call_proc(ctx, arg, opt->value, iso) == -1) + return -1; + } else { + void *var = (char *)ctx->data + opt->offset; + if (sep && opt->templ[sep + 1]) { + const char *param = arg + sep; + if (opt->templ[sep] == '=') + param ++; + if (process_opt_param(var, opt->templ + sep + 1, + param, arg) == -1) + return -1; + } else + *(int *)var = opt->value; + } + return 0; +} + +static int process_opt_sep_arg(struct fuse_opt_context *ctx, + const struct fuse_opt *opt, unsigned sep, + const char *arg, int iso) +{ + int res; + char *newarg; + char *param; + + if (next_arg(ctx, arg) == -1) + return -1; + + param = ctx->argv[ctx->argctr]; + newarg = malloc(sep + strlen(param) + 1); + if (!newarg) + return alloc_failed(); + + memcpy(newarg, arg, sep); + strcpy(newarg + sep, param); + res = process_opt(ctx, opt, sep, newarg, iso); + free(newarg); + + return res; +} + +static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso) +{ + unsigned sep; + const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep); + if (opt) { + for (; opt; opt = find_opt(opt + 1, arg, &sep)) { + int res; + if (sep && opt->templ[sep] == ' ' && !arg[sep]) + res = process_opt_sep_arg(ctx, opt, sep, arg, + iso); + else + res = process_opt(ctx, opt, sep, arg, iso); + if (res == -1) + return -1; + } + return 0; + } else + return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso); +} + +static int process_real_option_group(struct fuse_opt_context *ctx, char *opts) +{ + char *s = opts; + char *d = s; + int end = 0; + + while (!end) { + if (*s == '\0') + end = 1; + if (*s == ',' || end) { + int res; + + *d = '\0'; + res = process_gopt(ctx, opts, 1); + if (res == -1) + return -1; + d = opts; + } else { + if (s[0] == '\\' && s[1] != '\0') { + s++; + if (s[0] >= '0' && s[0] <= '3' && + s[1] >= '0' && s[1] <= '7' && + s[2] >= '0' && s[2] <= '7') { + *d++ = (s[0] - '0') * 0100 + + (s[1] - '0') * 0010 + + (s[2] - '0'); + s += 2; + } else { + *d++ = *s; + } + } else { + *d++ = *s; + } + } + s++; + } + + return 0; +} + +static int process_option_group(struct fuse_opt_context *ctx, const char *opts) +{ + int res; + char *copy = strdup(opts); + + if (!copy) { + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + return -1; + } + res = process_real_option_group(ctx, copy); + free(copy); + return res; +} + +static int process_one(struct fuse_opt_context *ctx, const char *arg) +{ + if (ctx->nonopt || arg[0] != '-') + return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0); + else if (arg[1] == 'o') { + if (arg[2]) + return process_option_group(ctx, arg + 2); + else { + if (next_arg(ctx, arg) == -1) + return -1; + + return process_option_group(ctx, + ctx->argv[ctx->argctr]); + } + } else if (arg[1] == '-' && !arg[2]) { + if (add_arg(ctx, arg) == -1) + return -1; + ctx->nonopt = ctx->outargs.argc; + return 0; + } else + return process_gopt(ctx, arg, 0); +} + +static int opt_parse(struct fuse_opt_context *ctx) +{ + if (ctx->argc) { + if (add_arg(ctx, ctx->argv[0]) == -1) + return -1; + } + + for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++) + if (process_one(ctx, ctx->argv[ctx->argctr]) == -1) + return -1; + + if (ctx->opts) { + if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 || + fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1) + return -1; + } + + /* If option separator ("--") is the last argument, remove it */ + if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc && + strcmp(ctx->outargs.argv[ctx->outargs.argc - 1], "--") == 0) { + free(ctx->outargs.argv[ctx->outargs.argc - 1]); + ctx->outargs.argv[--ctx->outargs.argc] = NULL; + } + + return 0; +} + +int fuse_opt_parse(struct fuse_args *args, void *data, + const struct fuse_opt opts[], fuse_opt_proc_t proc) +{ + int res; + struct fuse_opt_context ctx = { + .data = data, + .opt = opts, + .proc = proc, + }; + + if (!args || !args->argv || !args->argc) + return 0; + + ctx.argc = args->argc; + ctx.argv = args->argv; + + res = opt_parse(&ctx); + if (res != -1) { + struct fuse_args tmp = *args; + *args = ctx.outargs; + ctx.outargs = tmp; + } + free(ctx.opts); + fuse_opt_free_args(&ctx.outargs); + return res; +} diff --git a/lib/fuse_signals.c b/lib/fuse_signals.c new file mode 100644 index 0000000..03b0d8f --- /dev/null +++ b/lib/fuse_signals.c @@ -0,0 +1,216 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Utility functions for setting signal handlers. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#include "fuse_config.h" +#include "fuse_lowlevel.h" +#include "fuse_i.h" + +#include +#include +#include +#include +#include + +#ifdef HAVE_BACKTRACE +#include +#endif + +/* + * Must not handle SIGCANCEL, as that is used to wake up threads from + * syscalls reading requests from /dev/fuse + */ +static int teardown_sigs[] = { SIGHUP, SIGINT, SIGTERM }; + +static int ignore_sigs[] = { SIGPIPE}; +static int fail_sigs[] = { SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGSEGV }; +static struct fuse_session *fuse_instance; + +#ifdef HAVE_BACKTRACE +#define BT_STACK_SZ (1024 * 1024) +static void *backtrace_buffer[BT_STACK_SZ]; +#endif + +static void dump_stack(void) +{ +#ifdef HAVE_BACKTRACE + char **strings; + + int nptrs = backtrace(backtrace_buffer, BT_STACK_SZ); + strings = backtrace_symbols(backtrace_buffer, nptrs); + + if (strings == NULL) { + fuse_log(FUSE_LOG_ERR, "Failed to get backtrace symbols: %s\n", + strerror(errno)); + return; + } + + for (int idx = 0; idx < nptrs; idx++) + fuse_log(FUSE_LOG_ERR, "%s\n", strings[idx]); + + free(strings); +#endif +} + +static void exit_handler(int sig) +{ + if (fuse_instance == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse_instance is NULL\n"); + return; + } + + if (fuse_instance->debug) + fuse_log(FUSE_LOG_ERR, "exit_handler called with sig %d\n", + sig); + + fuse_session_exit(fuse_instance); + + if (sig < 0) { + fuse_log(FUSE_LOG_ERR, + "assertion error: signal value <= 0\n"); + dump_stack(); + abort(); + fuse_instance->error = sig; + } + + fuse_instance->error = sig; +} + +static void exit_backtrace(int sig) +{ + if (fuse_instance == NULL) + return; + + fuse_session_exit(fuse_instance); + + fuse_remove_signal_handlers(fuse_instance); + fuse_log(FUSE_LOG_ERR, "Got signal: %d\n", sig); + dump_stack(); + abort(); +} + + +static void do_nothing(int sig) +{ + (void) sig; +} + +static int set_one_signal_handler(int sig, void (*handler)(int), int remove) +{ + struct sigaction sa; + struct sigaction old_sa; + + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = remove ? SIG_DFL : handler; + sigemptyset(&(sa.sa_mask)); + sa.sa_flags = 0; + + if (sigaction(sig, NULL, &old_sa) == -1) { + perror("fuse: cannot get old signal handler"); + return -1; + } + + if (old_sa.sa_handler == (remove ? handler : SIG_DFL) && + sigaction(sig, &sa, NULL) == -1) { + perror("fuse: cannot set signal handler"); + return -1; + } + return 0; +} + +static int _fuse_set_signal_handlers(int signals[], int nr_signals, + void (*handler)(int)) +{ + for (int idx = 0; idx < nr_signals; idx++) { + int signal = signals[idx]; + + /* + * If we used SIG_IGN instead of the do_nothing function, + * then we would be unable to tell if we set SIG_IGN (and + * thus should reset to SIG_DFL in fuse_remove_signal_handlers) + * or if it was already set to SIG_IGN (and should be left + * untouched. + */ + if (set_one_signal_handler(signal, handler, 0) == -1) { + fuse_log(FUSE_LOG_ERR, + "Failed to install signal handler for sig %d\n", + signal); + return -1; + } + } + + return 0; +} + +int fuse_set_signal_handlers(struct fuse_session *se) +{ + size_t nr_signals; + int rc; + + nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]); + rc = _fuse_set_signal_handlers(teardown_sigs, nr_signals, exit_handler); + if (rc < 0) + return rc; + + nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]); + rc = _fuse_set_signal_handlers(ignore_sigs, nr_signals, do_nothing); + if (rc < 0) + return rc; + + /* + * needs to be set independently if already set, as some applications + * may have multiple sessions and might rely on traditional behavior + * that the last session is used. + */ + fuse_instance = se; + + return 0; +} + +int fuse_set_fail_signal_handlers(struct fuse_session *se) +{ + size_t nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]); + + int rc = _fuse_set_signal_handlers(fail_sigs, nr_signals, + exit_backtrace); + if (rc < 0) + return rc; + + /* See fuse_set_signal_handlers, why set unconditionally */ + fuse_instance = se; + + return 0; +} + +static void _fuse_remove_signal_handlers(int signals[], int nr_signals, + void (*handler)(int)) +{ + for (int idx = 0; idx < nr_signals; idx++) + set_one_signal_handler(signals[idx], handler, 1); +} + +void fuse_remove_signal_handlers(struct fuse_session *se) +{ + size_t nr_signals; + + if (fuse_instance != se) + fuse_log(FUSE_LOG_ERR, + "fuse: fuse_remove_signal_handlers: unknown session\n"); + else + fuse_instance = NULL; + + nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]); + _fuse_remove_signal_handlers(teardown_sigs, nr_signals, exit_handler); + + nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]); + _fuse_remove_signal_handlers(ignore_sigs, nr_signals, do_nothing); + + nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]); + _fuse_remove_signal_handlers(fail_sigs, nr_signals, exit_backtrace); +} diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c new file mode 100644 index 0000000..c3127b7 --- /dev/null +++ b/lib/fuse_uring.c @@ -0,0 +1,954 @@ +/* + * FUSE: Filesystem in Userspace + * Copyright (C) 2025 Bernd Schubert + * + * Implementation of (most of) FUSE-over-io-uring. + * + * This program can be distributed under the terms of the GNU LGPLv2. + * See the file LGPL2.txt + */ + +#define _GNU_SOURCE + +#include "fuse_i.h" +#include "fuse_kernel.h" +#include "fuse_uring_i.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Size of command data area in SQE when IORING_SETUP_SQE128 is used */ +#define FUSE_URING_MAX_SQE128_CMD_DATA 80 + +struct fuse_ring_ent { + struct fuse_ring_queue *ring_queue; /* back pointer */ + struct fuse_req req; + + struct fuse_uring_req_header *req_header; + void *op_payload; + size_t req_payload_sz; + + /* commit id of a fuse request */ + uint64_t req_commit_id; + + enum fuse_uring_cmd last_cmd; + + /* header and payload */ + struct iovec iov[2]; +}; + +struct fuse_ring_queue { + /* back pointer */ + struct fuse_ring_pool *ring_pool; + int qid; + int numa_node; + pthread_t tid; + int eventfd; + size_t req_header_sz; + struct io_uring ring; + + pthread_mutex_t ring_lock; + bool cqe_processing; + + /* size depends on queue depth */ + struct fuse_ring_ent ent[]; +}; + +/** + * Main fuse_ring structure, holds all fuse-ring data + */ +struct fuse_ring_pool { + struct fuse_session *se; + + /* number of queues */ + size_t nr_queues; + + /* number of per queue entries */ + size_t queue_depth; + + /* max payload size for fuse requests*/ + size_t max_req_payload_sz; + + /* size of a single queue */ + size_t queue_mem_size; + + unsigned int started_threads; + unsigned int failed_threads; + + /* Avoid sending queue entries before FUSE_INIT reply*/ + sem_t init_sem; + + pthread_cond_t thread_start_cond; + pthread_mutex_t thread_start_mutex; + + /* pointer to the first queue */ + struct fuse_ring_queue *queues; +}; + +static size_t +fuse_ring_queue_size(const size_t q_depth) +{ + const size_t req_size = sizeof(struct fuse_ring_ent) * q_depth; + + return sizeof(struct fuse_ring_queue) + req_size; +} + +static struct fuse_ring_queue * +fuse_uring_get_queue(struct fuse_ring_pool *fuse_ring, int qid) +{ + void *ptr = + ((char *)fuse_ring->queues) + (qid * fuse_ring->queue_mem_size); + + return ptr; +} + +/** + * return a pointer to the 80B area + */ +static void *fuse_uring_get_sqe_cmd(struct io_uring_sqe *sqe) +{ + return (void *)&sqe->cmd[0]; +} + +static void fuse_uring_sqe_set_req_data(struct fuse_uring_cmd_req *req, + const unsigned int qid, + const uint64_t commit_id) +{ + req->qid = qid; + req->commit_id = commit_id; + req->flags = 0; +} + +static void +fuse_uring_sqe_prepare(struct io_uring_sqe *sqe, struct fuse_ring_ent *req, + __u32 cmd_op) +{ + /* These fields should be written once, never change */ + sqe->opcode = IORING_OP_URING_CMD; + + /* + * IOSQE_FIXED_FILE: fd is the index to the fd *array* + * given to io_uring_register_files() + */ + sqe->flags = IOSQE_FIXED_FILE; + sqe->fd = 0; + + sqe->rw_flags = 0; + sqe->ioprio = 0; + sqe->off = 0; + + io_uring_sqe_set_data(sqe, req); + + sqe->cmd_op = cmd_op; + sqe->__pad1 = 0; +} + +static int fuse_uring_commit_sqe(struct fuse_ring_pool *ring_pool, + struct fuse_ring_queue *queue, + struct fuse_ring_ent *ring_ent) +{ + bool locked = false; + struct fuse_session *se = ring_pool->se; + struct fuse_uring_req_header *rrh = ring_ent->req_header; + struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out; + struct fuse_uring_ent_in_out *ent_in_out = + (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out; + struct io_uring_sqe *sqe; + + if (pthread_self() != queue->tid) { + pthread_mutex_lock(&queue->ring_lock); + locked = true; + } + + sqe = io_uring_get_sqe(&queue->ring); + + if (sqe == NULL) { + /* This is an impossible condition, unless there is a bug. + * The kernel sent back an SQEs, which is assigned to a request. + * There is no way to get out of SQEs, as the number of + * SQEs matches the number tof requests. + */ + + se->error = -EIO; + fuse_log(FUSE_LOG_ERR, "Failed to get a ring SQEs\n"); + + return -EIO; + } + + ring_ent->last_cmd = FUSE_IO_URING_CMD_COMMIT_AND_FETCH; + fuse_uring_sqe_prepare(sqe, ring_ent, ring_ent->last_cmd); + fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), queue->qid, + ring_ent->req_commit_id); + + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, " unique: %" PRIu64 ", result=%d\n", + out->unique, ent_in_out->payload_sz); + } + + if (!queue->cqe_processing) + io_uring_submit(&queue->ring); + + if (locked) + pthread_mutex_unlock(&queue->ring_lock); + + return 0; +} + +int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, + void **mr) +{ + struct fuse_ring_ent *ring_ent; + + /* Not possible without io-uring interface */ + if (!req->flags.is_uring) + return -EINVAL; + + ring_ent = container_of(req, struct fuse_ring_ent, req); + + *payload = ring_ent->op_payload; + *payload_sz = ring_ent->req_payload_sz; + + /* + * For now unused, but will be used later when the application can + * allocate the buffers itself and register them for rdma. + */ + if (mr) + *mr = NULL; + + return 0; +} + +int send_reply_uring(fuse_req_t req, int error, const void *arg, size_t argsize) +{ + int res; + struct fuse_ring_ent *ring_ent = + container_of(req, struct fuse_ring_ent, req); + struct fuse_uring_req_header *rrh = ring_ent->req_header; + struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out; + struct fuse_uring_ent_in_out *ent_in_out = + (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out; + + struct fuse_ring_queue *queue = ring_ent->ring_queue; + struct fuse_ring_pool *ring_pool = queue->ring_pool; + size_t max_payload_sz = ring_pool->max_req_payload_sz; + + if (argsize > max_payload_sz) { + fuse_log(FUSE_LOG_ERR, "argsize %zu exceeds buffer size %zu", + argsize, max_payload_sz); + error = -EINVAL; + } else if (argsize) { + if (arg != ring_ent->op_payload) + memcpy(ring_ent->op_payload, arg, argsize); + } + ent_in_out->payload_sz = argsize; + + out->error = error; + out->unique = req->unique; + + res = fuse_uring_commit_sqe(ring_pool, queue, ring_ent); + + fuse_free_req(req); + + return res; +} + +int fuse_reply_data_uring(fuse_req_t req, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags) +{ + struct fuse_ring_ent *ring_ent = + container_of(req, struct fuse_ring_ent, req); + + struct fuse_ring_queue *queue = ring_ent->ring_queue; + struct fuse_ring_pool *ring_pool = queue->ring_pool; + struct fuse_uring_req_header *rrh = ring_ent->req_header; + struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out; + struct fuse_uring_ent_in_out *ent_in_out = + (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out; + size_t max_payload_sz = ring_ent->req_payload_sz; + struct fuse_bufvec dest_vec = FUSE_BUFVEC_INIT(max_payload_sz); + int res; + + dest_vec.buf[0].mem = ring_ent->op_payload; + dest_vec.buf[0].size = max_payload_sz; + + res = fuse_buf_copy(&dest_vec, bufv, flags); + + out->error = res < 0 ? res : 0; + out->unique = req->unique; + + ent_in_out->payload_sz = res > 0 ? res : 0; + + res = fuse_uring_commit_sqe(ring_pool, queue, ring_ent); + + fuse_free_req(req); + + return res; +} + +/** + * Copy the iov into the ring buffer and submit and commit/fetch sqe + */ +int fuse_send_msg_uring(fuse_req_t req, struct iovec *iov, int count) +{ + struct fuse_ring_ent *ring_ent = + container_of(req, struct fuse_ring_ent, req); + + struct fuse_ring_queue *queue = ring_ent->ring_queue; + struct fuse_ring_pool *ring_pool = queue->ring_pool; + struct fuse_uring_req_header *rrh = ring_ent->req_header; + struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out; + struct fuse_uring_ent_in_out *ent_in_out = + (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out; + size_t max_buf = ring_pool->max_req_payload_sz; + size_t len = 0; + int res = 0; + + /* copy iov into the payload, idx=0 is the header section */ + for (int idx = 1; idx < count; idx++) { + struct iovec *cur = &iov[idx]; + + if (len + cur->iov_len > max_buf) { + fuse_log(FUSE_LOG_ERR, + "iov[%d] exceeds buffer size %zu", + idx, max_buf); + res = -EINVAL; /* Gracefully handle this? */ + break; + } + + memcpy(ring_ent->op_payload + len, cur->iov_base, cur->iov_len); + len += cur->iov_len; + } + + ent_in_out->payload_sz = len; + + out->error = res; + out->unique = req->unique; + out->len = len; + + return fuse_uring_commit_sqe(ring_pool, queue, ring_ent); +} + +static int fuse_queue_setup_io_uring(struct io_uring *ring, size_t qid, + size_t depth, int fd, int evfd) +{ + int rc; + struct io_uring_params params = {0}; + int files[2] = { fd, evfd }; + + depth += 1; /* for the eventfd poll SQE */ + + params.flags = IORING_SETUP_SQE128; + + /* Avoid cq overflow */ + params.flags |= IORING_SETUP_CQSIZE; + params.cq_entries = depth * 2; + + /* These flags should help to increase performance, but actually + * make it a bit slower - reason should get investigated. + */ + if (0) { + /* Has the main slow down effect */ + params.flags |= IORING_SETUP_SINGLE_ISSUER; + + // params.flags |= IORING_SETUP_DEFER_TASKRUN; + params.flags |= IORING_SETUP_TASKRUN_FLAG; + + /* Second main effect to make it slower */ + params.flags |= IORING_SETUP_COOP_TASKRUN; + } + + rc = io_uring_queue_init_params(depth, ring, ¶ms); + if (rc != 0) { + fuse_log(FUSE_LOG_ERR, "Failed to setup qid %zu: %d (%s)\n", + qid, rc, strerror(-rc)); + return rc; + } + + rc = io_uring_register_files(ring, files, 1); + if (rc != 0) { + rc = -errno; + fuse_log(FUSE_LOG_ERR, + "Failed to register files for ring idx %zu: %s", + qid, strerror(errno)); + return rc; + } + + return 0; +} + +static void fuse_session_destruct_uring(struct fuse_ring_pool *fuse_ring) +{ + for (size_t qid = 0; qid < fuse_ring->nr_queues; qid++) { + struct fuse_ring_queue *queue = + fuse_uring_get_queue(fuse_ring, qid); + + if (queue->tid != 0) { + uint64_t value = 1ULL; + int rc; + + rc = write(queue->eventfd, &value, sizeof(value)); + if (rc != sizeof(value)) + fprintf(stderr, + "Wrote to eventfd=%d err=%s: rc=%d\n", + queue->eventfd, strerror(errno), rc); + pthread_cancel(queue->tid); + pthread_join(queue->tid, NULL); + queue->tid = 0; + } + + if (queue->eventfd >= 0) { + close(queue->eventfd); + queue->eventfd = -1; + } + + if (queue->ring.ring_fd != -1) + io_uring_queue_exit(&queue->ring); + + for (size_t idx = 0; idx < fuse_ring->queue_depth; idx++) { + struct fuse_ring_ent *ent = &queue->ent[idx]; + + numa_free(ent->op_payload, ent->req_payload_sz); + numa_free(ent->req_header, queue->req_header_sz); + } + + pthread_mutex_destroy(&queue->ring_lock); + } + + free(fuse_ring->queues); + pthread_cond_destroy(&fuse_ring->thread_start_cond); + pthread_mutex_destroy(&fuse_ring->thread_start_mutex); + free(fuse_ring); +} + +static int fuse_uring_register_ent(struct fuse_ring_queue *queue, + struct fuse_ring_ent *ent) +{ + struct io_uring_sqe *sqe; + + sqe = io_uring_get_sqe(&queue->ring); + if (sqe == NULL) { + /* + * All SQEs are idle here - no good reason this + * could fail + */ + fuse_log(FUSE_LOG_ERR, "Failed to get all ring SQEs"); + return -EIO; + } + + ent->last_cmd = FUSE_IO_URING_CMD_REGISTER; + fuse_uring_sqe_prepare(sqe, ent, ent->last_cmd); + + /* only needed for fetch */ + ent->iov[0].iov_base = ent->req_header; + ent->iov[0].iov_len = queue->req_header_sz; + + ent->iov[1].iov_base = ent->op_payload; + ent->iov[1].iov_len = ent->req_payload_sz; + + sqe->addr = (uint64_t)(ent->iov); + sqe->len = 2; + + /* this is a fetch, kernel does not read commit id */ + fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), queue->qid, 0); + + return 0; + +} + +static int fuse_uring_register_queue(struct fuse_ring_queue *queue) +{ + struct fuse_ring_pool *ring_pool = queue->ring_pool; + unsigned int sq_ready; + struct io_uring_sqe *sqe; + int res; + + for (size_t idx = 0; idx < ring_pool->queue_depth; idx++) { + struct fuse_ring_ent *ent = &queue->ent[idx]; + + res = fuse_uring_register_ent(queue, ent); + if (res != 0) + return res; + } + + sq_ready = io_uring_sq_ready(&queue->ring); + if (sq_ready != ring_pool->queue_depth) { + fuse_log(FUSE_LOG_ERR, + "SQE ready mismatch, expected %zu got %u\n", + ring_pool->queue_depth, sq_ready); + return -EINVAL; + } + + /* Poll SQE for the eventfd to wake up on teardown */ + sqe = io_uring_get_sqe(&queue->ring); + if (sqe == NULL) { + fuse_log(FUSE_LOG_ERR, "Failed to get eventfd SQE"); + return -EIO; + } + + io_uring_prep_poll_add(sqe, queue->eventfd, POLLIN); + io_uring_sqe_set_data(sqe, (void *)(uintptr_t)queue->eventfd); + + /* Only preparation until here, no submission yet */ + + return 0; +} + +static struct fuse_ring_pool *fuse_create_ring(struct fuse_session *se) +{ + struct fuse_ring_pool *fuse_ring = NULL; + const size_t nr_queues = get_nprocs_conf(); + size_t payload_sz = se->bufsize - FUSE_BUFFER_HEADER_SIZE; + size_t queue_sz; + + if (se->debug) + fuse_log(FUSE_LOG_DEBUG, "starting io-uring q-depth=%d\n", + se->uring.q_depth); + + fuse_ring = calloc(1, sizeof(*fuse_ring)); + if (fuse_ring == NULL) { + fuse_log(FUSE_LOG_ERR, "Allocating the ring failed\n"); + goto err; + } + + queue_sz = fuse_ring_queue_size(se->uring.q_depth); + fuse_ring->queues = calloc(1, queue_sz * nr_queues); + if (fuse_ring->queues == NULL) { + fuse_log(FUSE_LOG_ERR, "Allocating the queues failed\n"); + goto err; + } + + fuse_ring->se = se; + fuse_ring->nr_queues = nr_queues; + fuse_ring->queue_depth = se->uring.q_depth; + fuse_ring->max_req_payload_sz = payload_sz; + fuse_ring->queue_mem_size = queue_sz; + + /* + * very basic queue initialization, that cannot fail and will + * allow easy cleanup if something (like mmap) fails in the middle + * below + */ + for (size_t qid = 0; qid < nr_queues; qid++) { + struct fuse_ring_queue *queue = + fuse_uring_get_queue(fuse_ring, qid); + + queue->ring.ring_fd = -1; + queue->numa_node = numa_node_of_cpu(qid); + queue->qid = qid; + queue->ring_pool = fuse_ring; + queue->eventfd = -1; + pthread_mutex_init(&queue->ring_lock, NULL); + } + + pthread_cond_init(&fuse_ring->thread_start_cond, NULL); + pthread_mutex_init(&fuse_ring->thread_start_mutex, NULL); + sem_init(&fuse_ring->init_sem, 0, 0); + + return fuse_ring; + +err: + if (fuse_ring) + fuse_session_destruct_uring(fuse_ring); + + return NULL; +} + +static void fuse_uring_resubmit(struct fuse_ring_queue *queue, + struct fuse_ring_ent *ent) +{ + struct io_uring_sqe *sqe; + + sqe = io_uring_get_sqe(&queue->ring); + if (sqe == NULL) { + /* This is an impossible condition, unless there is a bug. + * The kernel sent back an SQEs, which is assigned to a request. + * There is no way to get out of SQEs, as the number of + * SQEs matches the number tof requests. + */ + + queue->ring_pool->se->error = -EIO; + fuse_log(FUSE_LOG_ERR, "Failed to get a ring SQEs\n"); + + return; + } + + fuse_uring_sqe_prepare(sqe, ent, ent->last_cmd); + + switch (ent->last_cmd) { + case FUSE_IO_URING_CMD_REGISTER: + sqe->addr = (uint64_t)(ent->iov); + sqe->len = 2; + fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), + queue->qid, 0); + break; + case FUSE_IO_URING_CMD_COMMIT_AND_FETCH: + fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), + queue->qid, ent->req_commit_id); + break; + default: + fuse_log(FUSE_LOG_ERR, "Unknown command type: %d\n", + ent->last_cmd); + queue->ring_pool->se->error = -EINVAL; + break; + } + + /* caller submits */ +} + +static void fuse_uring_handle_cqe(struct fuse_ring_queue *queue, + struct io_uring_cqe *cqe) +{ + struct fuse_ring_ent *ent = io_uring_cqe_get_data(cqe); + + if (!ent) { + fuse_log(FUSE_LOG_ERR, + "cqe=%p io_uring_cqe_get_data returned NULL\n", cqe); + return; + } + + struct fuse_req *req = &ent->req; + struct fuse_ring_pool *fuse_ring = queue->ring_pool; + struct fuse_uring_req_header *rrh = ent->req_header; + + struct fuse_in_header *in = (struct fuse_in_header *)&rrh->in_out; + struct fuse_uring_ent_in_out *ent_in_out = &rrh->ring_ent_in_out; + + ent->req_commit_id = ent_in_out->commit_id; + if (unlikely(ent->req_commit_id == 0)) { + /* + * If this happens kernel will not find the response - it will + * be stuck forever - better to abort immediately. + */ + fuse_log(FUSE_LOG_ERR, "Received invalid commit_id=0\n"); + abort(); + } + + memset(&req->flags, 0, sizeof(req->flags)); + memset(&req->u, 0, sizeof(req->u)); + req->flags.is_uring = 1; + req->ref_cnt++; + req->ch = NULL; /* not needed for uring */ + req->interrupted = 0; + list_init_req(req); + + fuse_session_process_uring_cqe(fuse_ring->se, req, in, &rrh->op_in, + ent->op_payload, ent_in_out->payload_sz); +} + +static int fuse_uring_queue_handle_cqes(struct fuse_ring_queue *queue) +{ + struct fuse_ring_pool *ring_pool = queue->ring_pool; + struct fuse_session *se = ring_pool->se; + size_t num_completed = 0; + struct io_uring_cqe *cqe; + unsigned int head; + struct fuse_ring_ent *ent; + int ret = 0; + + io_uring_for_each_cqe(&queue->ring, head, cqe) { + int err = 0; + + num_completed++; + + err = cqe->res; + if (unlikely(err != 0)) { + if (err > 0 && ((uintptr_t)io_uring_cqe_get_data(cqe) == + (unsigned int)queue->eventfd)) { + /* teardown from eventfd */ + return -ENOTCONN; + } + + + switch (err) { + case -EAGAIN: + fallthrough; + case -EINTR: + ent = io_uring_cqe_get_data(cqe); + fuse_uring_resubmit(queue, ent); + continue; + default: + break; + } + + /* -ENOTCONN is ok on umount */ + if (err != -ENOTCONN) { + se->error = cqe->res; + + /* return first error */ + if (ret == 0) + ret = err; + } + + } else { + fuse_uring_handle_cqe(queue, cqe); + } + } + + if (num_completed) + io_uring_cq_advance(&queue->ring, num_completed); + + return ret == 0 ? 0 : num_completed; +} + +/** + * In per-core-queue configuration we have thread per core - the thread + * to that core + */ +static void fuse_uring_set_thread_core(int qid) +{ + cpu_set_t mask; + int rc; + + CPU_ZERO(&mask); + CPU_SET(qid, &mask); + rc = sched_setaffinity(0, sizeof(cpu_set_t), &mask); + if (rc != 0) + fuse_log(FUSE_LOG_ERR, "Failed to bind qid=%d to its core: %s\n", + qid, strerror(errno)); + + if (0) { + const int policy = SCHED_IDLE; + const struct sched_param param = { + .sched_priority = sched_get_priority_min(policy), + }; + + /* Set the lowest possible priority, so that the application + * submitting requests is not moved away from the current core. + */ + rc = sched_setscheduler(0, policy, ¶m); + if (rc != 0) + fuse_log(FUSE_LOG_ERR, "Failed to set scheduler: %s\n", + strerror(errno)); + } +} + +/* + * @return negative error code or io-uring file descriptor + */ +static int fuse_uring_init_queue(struct fuse_ring_queue *queue) +{ + struct fuse_ring_pool *ring = queue->ring_pool; + struct fuse_session *se = ring->se; + int res; + size_t page_sz = sysconf(_SC_PAGESIZE); + + queue->eventfd = eventfd(0, EFD_CLOEXEC); + if (queue->eventfd < 0) { + res = -errno; + fuse_log(FUSE_LOG_ERR, + "Failed to create eventfd for qid %d: %s\n", + queue->qid, strerror(errno)); + return res; + } + + res = fuse_queue_setup_io_uring(&queue->ring, queue->qid, + ring->queue_depth, se->fd, + queue->eventfd); + if (res != 0) { + fuse_log(FUSE_LOG_ERR, "qid=%d io_uring init failed\n", + queue->qid); + return res; + } + + queue->req_header_sz = ROUND_UP(sizeof(struct fuse_ring_ent), + page_sz); + + for (size_t idx = 0; idx < ring->queue_depth; idx++) { + struct fuse_ring_ent *ring_ent = &queue->ent[idx]; + struct fuse_req *req = &ring_ent->req; + + ring_ent->ring_queue = queue; + + /* + * Also allocate the header to have it page aligned, which + * is a requirement for page pinning + */ + ring_ent->req_header = + numa_alloc_local(queue->req_header_sz); + if (!ring_ent->req_header) + return -ENOMEM; + ring_ent->req_payload_sz = ring->max_req_payload_sz; + + ring_ent->op_payload = + numa_alloc_local(ring_ent->req_payload_sz); + if (!ring_ent->op_payload) + return -ENOMEM; + + req->se = se; + pthread_mutex_init(&req->lock, NULL); + req->flags.is_uring = 1; + req->ref_cnt = 1; /* extra ref to avoid destruction */ + list_init_req(req); + } + + res = fuse_uring_register_queue(queue); + if (res != 0) { + fuse_log( + FUSE_LOG_ERR, + "Grave fuse-uring error on preparing SQEs, aborting\n"); + se->error = -EIO; + fuse_session_exit(se); + return res; + } + + return queue->ring.ring_fd; +} + +static void *fuse_uring_thread(void *arg) +{ + struct fuse_ring_queue *queue = arg; + struct fuse_ring_pool *ring_pool = queue->ring_pool; + struct fuse_session *se = ring_pool->se; + int err; + char thread_name[16] = { 0 }; + + snprintf(thread_name, 16, "fuse-ring-%d", queue->qid); + thread_name[15] = '\0'; + fuse_set_thread_name(thread_name); + + fuse_uring_set_thread_core(queue->qid); + + err = fuse_uring_init_queue(queue); + pthread_mutex_lock(&ring_pool->thread_start_mutex); + if (err < 0) + ring_pool->failed_threads++; + ring_pool->started_threads++; + pthread_cond_broadcast(&ring_pool->thread_start_cond); + pthread_mutex_unlock(&ring_pool->thread_start_mutex); + + if (err < 0) { + fuse_log(FUSE_LOG_ERR, "qid=%d queue setup failed\n", + queue->qid); + goto err_non_fatal; + } + + sem_wait(&ring_pool->init_sem); + + /* Not using fuse_session_exited(se), as that cannot be inlined */ + while (!atomic_load_explicit(&se->mt_exited, memory_order_relaxed)) { + io_uring_submit_and_wait(&queue->ring, 1); + + pthread_mutex_lock(&queue->ring_lock); + queue->cqe_processing = true; + err = fuse_uring_queue_handle_cqes(queue); + queue->cqe_processing = false; + pthread_mutex_unlock(&queue->ring_lock); + if (err < 0) + goto err; + } + + return NULL; + +err: + fuse_session_exit(se); +err_non_fatal: + return NULL; +} + +static int fuse_uring_start_ring_threads(struct fuse_ring_pool *ring) +{ + int rc = 0; + + for (size_t qid = 0; qid < ring->nr_queues; qid++) { + struct fuse_ring_queue *queue = fuse_uring_get_queue(ring, qid); + + rc = pthread_create(&queue->tid, NULL, fuse_uring_thread, queue); + if (rc != 0) + break; + } + + return rc; +} + +static int fuse_uring_sanity_check(struct fuse_session *se) +{ + if (se->uring.q_depth == 0) { + fuse_log(FUSE_LOG_ERR, "io-uring queue depth must be > 0\n"); + return -EINVAL; + } + + _Static_assert(sizeof(struct fuse_uring_cmd_req) <= + FUSE_URING_MAX_SQE128_CMD_DATA, + "SQE128_CMD_DATA has 80B cmd data"); + + return 0; +} + +int fuse_uring_start(struct fuse_session *se) +{ + int err = 0; + struct fuse_ring_pool *fuse_ring; + + fuse_uring_sanity_check(se); + + fuse_ring = fuse_create_ring(se); + if (fuse_ring == NULL) { + err = -EADDRNOTAVAIL; + goto err; + } + + se->uring.pool = fuse_ring; + + /* Hold off threads from send fuse ring entries (SQEs) */ + sem_init(&fuse_ring->init_sem, 0, 0); + pthread_cond_init(&fuse_ring->thread_start_cond, NULL); + pthread_mutex_init(&fuse_ring->thread_start_mutex, NULL); + + err = fuse_uring_start_ring_threads(fuse_ring); + if (err) + goto err; + + /* + * Wait for all threads to start or to fail + */ + pthread_mutex_lock(&fuse_ring->thread_start_mutex); + while (fuse_ring->started_threads < fuse_ring->nr_queues) + pthread_cond_wait(&fuse_ring->thread_start_cond, + &fuse_ring->thread_start_mutex); + + if (fuse_ring->failed_threads != 0) + err = -EADDRNOTAVAIL; + pthread_mutex_unlock(&fuse_ring->thread_start_mutex); + +err: + if (err) { + /* Note all threads need to have been started */ + if (fuse_ring) + fuse_session_destruct_uring(fuse_ring); + se->uring.pool = NULL; + } + return err; +} + +int fuse_uring_stop(struct fuse_session *se) +{ + struct fuse_ring_pool *ring = se->uring.pool; + + if (ring == NULL) + return 0; + + fuse_session_destruct_uring(ring); + + return 0; +} + +void fuse_uring_wake_ring_threads(struct fuse_session *se) +{ + struct fuse_ring_pool *ring = se->uring.pool; + + /* Wake up the threads to let them send SQEs */ + for (size_t qid = 0; qid < ring->nr_queues; qid++) + sem_post(&ring->init_sem); +} diff --git a/lib/fuse_uring_i.h b/lib/fuse_uring_i.h new file mode 100644 index 0000000..c1da73a --- /dev/null +++ b/lib/fuse_uring_i.h @@ -0,0 +1,86 @@ +/* + * FUSE: Filesystem in Userspace + * Copyright (C) 2025 Bernd Schubert + * This program can be distributed under the terms of the GNU LGPLv2. + * See the file LGPL2.txt + */ + +#ifndef FUSE_URING_I_H_ +#define FUSE_URING_I_H_ + +#include "fuse_config.h" +#include "fuse_lowlevel.h" +#include "fuse_kernel.h" + +#ifndef HAVE_URING +#include "util.h" +#endif + +#include // IWYU pragma: keep + +/* io-uring defaults */ +#define SESSION_DEF_URING_ENABLE (0) +#define SESSION_DEF_URING_Q_DEPTH (8) + +void fuse_session_process_uring_cqe(struct fuse_session *se, + struct fuse_req *req, + struct fuse_in_header *in, void *in_header, + void *in_payload, size_t payload_len); + +#ifdef HAVE_URING + +struct fuse_in_header; + +int fuse_uring_start(struct fuse_session *se); +void fuse_uring_wake_ring_threads(struct fuse_session *se); +int fuse_uring_stop(struct fuse_session *se); +int send_reply_uring(fuse_req_t req, int error, const void *arg, + size_t argsize); + +int fuse_reply_data_uring(fuse_req_t req, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags); +int fuse_send_msg_uring(fuse_req_t req, struct iovec *iov, int count); + +#else // HAVE_URING + +static inline int fuse_uring_start(struct fuse_session *se FUSE_VAR_UNUSED) +{ + return -ENOTSUP; +} + +static inline void +fuse_uring_wake_ring_threads(struct fuse_session *se FUSE_VAR_UNUSED) +{ +} + +static inline int fuse_uring_stop(struct fuse_session *se FUSE_VAR_UNUSED) +{ + return -ENOTSUP; +} + +static inline int send_reply_uring(fuse_req_t req FUSE_VAR_UNUSED, + int error FUSE_VAR_UNUSED, + const void *arg FUSE_VAR_UNUSED, + size_t argsize FUSE_VAR_UNUSED) +{ + return -ENOTSUP; +} + +static inline int +fuse_reply_data_uring(fuse_req_t req FUSE_VAR_UNUSED, + struct fuse_bufvec *bufv FUSE_VAR_UNUSED, + enum fuse_buf_copy_flags flags FUSE_VAR_UNUSED) +{ + return -ENOTSUP; +} + +static inline int fuse_send_msg_uring(fuse_req_t req FUSE_VAR_UNUSED, + struct iovec *iov FUSE_VAR_UNUSED, + int count FUSE_VAR_UNUSED) +{ + return -ENOTSUP; +} + +#endif // HAVE_URING + +#endif // FUSE_URING_I_H_ diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript new file mode 100644 index 0000000..b58360f --- /dev/null +++ b/lib/fuse_versionscript @@ -0,0 +1,227 @@ +FUSE_3.0 { + global: + fuse_destroy; + fuse_exit; + fuse_loop; + fuse_loop_mt; + fuse_reply_attr; + fuse_reply_buf; + fuse_reply_entry; + fuse_reply_err; + fuse_reply_none; + fuse_reply_readlink; + fuse_reply_write; + fuse_reply_xattr; + fuse_req_userdata; + fuse_session_destroy; + fuse_session_exit; + fuse_session_exited; + fuse_session_loop; + fuse_session_loop_mt; + fuse_session_reset; + fuse_session_fd; + fuse_opt_parse; + fuse_opt_add_opt; + fuse_opt_add_arg; + fuse_opt_free_args; + fuse_opt_match; + fuse_parse_cmdline; + fuse_remove_signal_handlers; + fuse_reply_create; + fuse_reply_open; + fuse_reply_statfs; + fuse_set_signal_handlers; + fuse_add_direntry; + fuse_add_direntry_plus; + fuse_daemonize; + fuse_get_session; + fuse_interrupted; + fuse_session_new; + fuse_main_real; + fuse_mount; + fuse_session_custom_io; + fuse_session_mount; + fuse_new; + fuse_opt_insert_arg; + fuse_reply_lock; + fuse_req_interrupt_func; + fuse_req_interrupted; + fuse_unmount; + fuse_session_unmount; + fuse_fs_access; + fuse_fs_bmap; + fuse_fs_chmod; + fuse_fs_chown; + fuse_fs_create; + fuse_fs_destroy; + fuse_fs_flush; + fuse_fs_fsync; + fuse_fs_fsyncdir; + fuse_fs_getattr; + fuse_fs_getxattr; + fuse_fs_init; + fuse_fs_link; + fuse_fs_listxattr; + fuse_fs_lock; + fuse_fs_mkdir; + fuse_fs_mknod; + fuse_fs_new; + fuse_fs_open; + fuse_fs_opendir; + fuse_fs_read; + fuse_fs_readdir; + fuse_fs_readlink; + fuse_fs_release; + fuse_fs_releasedir; + fuse_fs_removexattr; + fuse_fs_rename; + fuse_fs_rmdir; + fuse_fs_setxattr; + fuse_fs_statfs; + fuse_fs_symlink; + fuse_fs_truncate; + fuse_fs_unlink; + fuse_fs_utimens; + fuse_fs_write; + fuse_reply_iov; + fuse_version; + fuse_pkgversion; + fuse_reply_bmap; + cuse_lowlevel_new; + cuse_lowlevel_main; + cuse_lowlevel_setup; + cuse_lowlevel_teardown; + fuse_fs_ioctl; + fuse_fs_poll; + fuse_get_context; + fuse_getgroups; + fuse_lowlevel_notify_inval_entry; + fuse_lowlevel_notify_inval_inode; + fuse_lowlevel_notify_poll; + fuse_notify_poll; + fuse_opt_add_opt_escaped; + fuse_pollhandle_destroy; + fuse_reply_ioctl; + fuse_reply_ioctl_iov; + fuse_reply_ioctl_retry; + fuse_reply_poll; + fuse_req_ctx; + fuse_req_getgroups; + fuse_buf_copy; + fuse_buf_size; + fuse_fs_read_buf; + fuse_fs_write_buf; + fuse_lowlevel_notify_retrieve; + fuse_lowlevel_notify_store; + fuse_reply_data; + fuse_session_process_buf; + fuse_session_receive_buf; + fuse_start_cleanup_thread; + fuse_stop_cleanup_thread; + fuse_clean_cache; + fuse_lowlevel_notify_delete; + fuse_fs_flock; + fuse_fs_fallocate; + fuse_lowlevel_help; + fuse_lowlevel_version; + fuse_cmdline_help; + fuse_apply_conn_info_opts; + fuse_parse_conn_info_opts; + fuse_fs_lseek; + fuse_reply_lseek; + + local: + *; +}; + +FUSE_3.1 { + global: + fuse_lib_help; + fuse_invalidate_path; + fuse_new_30; + fuse_new_31; + fuse_new; +} FUSE_3.0; + +FUSE_3.2 { + global: + fuse_session_loop_mt; + fuse_session_loop_mt_31; + fuse_session_loop_mt_32; + fuse_loop_mt; + fuse_loop_mt_31; +} FUSE_3.1; + +FUSE_3.3 { + global: + fuse_open_channel; +} FUSE_3.2; + +FUSE_3.4 { + global: + fuse_fs_copy_file_range; +} FUSE_3.3; + +FUSE_3.7 { + global: + fuse_set_log_func; + fuse_log; +} FUSE_3.4; + +FUSE_3.12 { + global: + fuse_session_loop_mt; + fuse_session_loop_mt_312; + fuse_loop_mt; + fuse_loop_mt_32; + fuse_loop_mt_312; + fuse_loop_cfg_create; + fuse_loop_cfg_destroy; + fuse_loop_cfg_set_idle_threads; + fuse_loop_cfg_set_max_threads; + fuse_loop_cfg_set_clone_fd; + fuse_loop_cfg_convert; + fuse_parse_cmdline; + fuse_parse_cmdline_30; + fuse_parse_cmdline_312; + fuse_lowlevel_notify_expire_entry; +} FUSE_3.4; + +FUSE_3.17 { + global: + fuse_main_real_versioned; + fuse_session_new_versioned; + _fuse_new_30; + _fuse_new_31; + fuse_passthrough_open; + fuse_passthrough_close; + fuse_session_custom_io_30; + fuse_session_custom_io_317; + fuse_set_fail_signal_handlers; + fuse_log_enable_syslog; + fuse_log_close_syslog; +} FUSE_3.12; + +FUSE_3.17.3 { + global: + fuse_set_feature_flag; + fuse_unset_feature_flag; + fuse_get_feature_flag; + + # Not part of public API, for internal testing only + fuse_convert_to_conn_want_ext; +} FUSE_3.17; + +FUSE_3.18 { + global: + fuse_req_is_uring; + fuse_req_get_payload; + fuse_lowlevel_notify_increment_epoch; + + fuse_reply_statx; + fuse_fs_statx; +} FUSE_3.17; + +# Local Variables: +# indent-tabs-mode: t +# End: diff --git a/lib/helper.c b/lib/helper.c new file mode 100644 index 0000000..5c13b93 --- /dev/null +++ b/lib/helper.c @@ -0,0 +1,498 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Helper functions to create (simple) standalone programs. With the + aid of these functions it should be possible to create full FUSE + file system by implementing nothing but the request handlers. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_misc.h" +#include "fuse_opt.h" +#include "fuse_lowlevel.h" +#include "mount_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define FUSE_HELPER_OPT(t, p) \ + { t, offsetof(struct fuse_cmdline_opts, p), 1 } + +static const struct fuse_opt fuse_helper_opts[] = { + FUSE_HELPER_OPT("-h", show_help), + FUSE_HELPER_OPT("--help", show_help), + FUSE_HELPER_OPT("-V", show_version), + FUSE_HELPER_OPT("--version", show_version), + FUSE_HELPER_OPT("-d", debug), + FUSE_HELPER_OPT("debug", debug), + FUSE_HELPER_OPT("-d", foreground), + FUSE_HELPER_OPT("debug", foreground), + FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP), + FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP), + FUSE_HELPER_OPT("-f", foreground), + FUSE_HELPER_OPT("-s", singlethread), + FUSE_HELPER_OPT("fsname=", nodefault_subtype), + FUSE_OPT_KEY("fsname=", FUSE_OPT_KEY_KEEP), +#ifndef __FreeBSD__ + FUSE_HELPER_OPT("subtype=", nodefault_subtype), + FUSE_OPT_KEY("subtype=", FUSE_OPT_KEY_KEEP), +#endif + FUSE_HELPER_OPT("clone_fd", clone_fd), + FUSE_HELPER_OPT("max_idle_threads=%u", max_idle_threads), + FUSE_HELPER_OPT("max_threads=%u", max_threads), + FUSE_OPT_END +}; + +struct fuse_conn_info_opts { + int atomic_o_trunc; + int no_remote_posix_lock; + int no_remote_flock; + int splice_write; + int splice_move; + int splice_read; + int no_splice_write; + int no_splice_move; + int no_splice_read; + int auto_inval_data; + int no_auto_inval_data; + int no_readdirplus; + int no_readdirplus_auto; + int async_dio; + int no_async_dio; + int writeback_cache; + int no_writeback_cache; + int async_read; + int sync_read; + unsigned max_write; + unsigned max_readahead; + unsigned max_background; + unsigned congestion_threshold; + unsigned time_gran; + int set_max_write; + int set_max_readahead; + int set_max_background; + int set_congestion_threshold; + int set_time_gran; +}; + +#define CONN_OPTION(t, p, v) \ + { t, offsetof(struct fuse_conn_info_opts, p), v } +static const struct fuse_opt conn_info_opt_spec[] = { + CONN_OPTION("max_write=%u", max_write, 0), + CONN_OPTION("max_write=", set_max_write, 1), + CONN_OPTION("max_readahead=%u", max_readahead, 0), + CONN_OPTION("max_readahead=", set_max_readahead, 1), + CONN_OPTION("max_background=%u", max_background, 0), + CONN_OPTION("max_background=", set_max_background, 1), + CONN_OPTION("congestion_threshold=%u", congestion_threshold, 0), + CONN_OPTION("congestion_threshold=", set_congestion_threshold, 1), + CONN_OPTION("sync_read", sync_read, 1), + CONN_OPTION("async_read", async_read, 1), + CONN_OPTION("atomic_o_trunc", atomic_o_trunc, 1), + CONN_OPTION("no_remote_lock", no_remote_posix_lock, 1), + CONN_OPTION("no_remote_lock", no_remote_flock, 1), + CONN_OPTION("no_remote_flock", no_remote_flock, 1), + CONN_OPTION("no_remote_posix_lock", no_remote_posix_lock, 1), + CONN_OPTION("splice_write", splice_write, 1), + CONN_OPTION("no_splice_write", no_splice_write, 1), + CONN_OPTION("splice_move", splice_move, 1), + CONN_OPTION("no_splice_move", no_splice_move, 1), + CONN_OPTION("splice_read", splice_read, 1), + CONN_OPTION("no_splice_read", no_splice_read, 1), + CONN_OPTION("auto_inval_data", auto_inval_data, 1), + CONN_OPTION("no_auto_inval_data", no_auto_inval_data, 1), + CONN_OPTION("readdirplus=no", no_readdirplus, 1), + CONN_OPTION("readdirplus=yes", no_readdirplus, 0), + CONN_OPTION("readdirplus=yes", no_readdirplus_auto, 1), + CONN_OPTION("readdirplus=auto", no_readdirplus, 0), + CONN_OPTION("readdirplus=auto", no_readdirplus_auto, 0), + CONN_OPTION("async_dio", async_dio, 1), + CONN_OPTION("no_async_dio", no_async_dio, 1), + CONN_OPTION("writeback_cache", writeback_cache, 1), + CONN_OPTION("no_writeback_cache", no_writeback_cache, 1), + CONN_OPTION("time_gran=%u", time_gran, 0), + CONN_OPTION("time_gran=", set_time_gran, 1), + FUSE_OPT_END +}; + + +void fuse_cmdline_help(void) +{ + printf(" -h --help print help\n" + " -V --version print version\n" + " -d -o debug enable debug output (implies -f)\n" + " -f foreground operation\n" + " -s disable multi-threaded operation\n" + " -o clone_fd use separate fuse device fd for each thread\n" + " (may improve performance)\n" + " -o max_idle_threads the maximum number of idle worker threads\n" + " allowed (default: -1)\n" + " -o max_threads the maximum number of worker threads\n" + " allowed (default: 10)\n"); +} + +static int fuse_helper_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) outargs; + struct fuse_cmdline_opts *opts = data; + + switch (key) { + case FUSE_OPT_KEY_NONOPT: + if (!opts->mountpoint) { + if (fuse_mnt_parse_fuse_fd(arg) != -1) { + return fuse_opt_add_opt(&opts->mountpoint, arg); + } + + char mountpoint[PATH_MAX] = ""; + if (realpath(arg, mountpoint) == NULL) { + fuse_log(FUSE_LOG_ERR, + "fuse: bad mount point `%s': %s\n", + arg, strerror(errno)); + return -1; + } + return fuse_opt_add_opt(&opts->mountpoint, mountpoint); + } else { + fuse_log(FUSE_LOG_ERR, "fuse: invalid argument `%s'\n", arg); + return -1; + } + + default: + /* Pass through unknown options */ + return 1; + } +} + +/* Under FreeBSD, there is no subtype option so this + function actually sets the fsname */ +static int add_default_subtype(const char *progname, struct fuse_args *args) +{ + int res; + char *subtype_opt; + + const char *basename = strrchr(progname, '/'); + if (basename == NULL) + basename = progname; + else if (basename[1] != '\0') + basename++; + + subtype_opt = (char *) malloc(strlen(basename) + 64); + if (subtype_opt == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + return -1; + } +#ifdef __FreeBSD__ + sprintf(subtype_opt, "-ofsname=%s", basename); +#else + sprintf(subtype_opt, "-osubtype=%s", basename); +#endif + res = fuse_opt_add_arg(args, subtype_opt); + free(subtype_opt); + return res; +} + +int fuse_parse_cmdline_312(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +FUSE_SYMVER("fuse_parse_cmdline_312", "fuse_parse_cmdline@@FUSE_3.12") +int fuse_parse_cmdline_312(struct fuse_args *args, + struct fuse_cmdline_opts *opts) +{ + memset(opts, 0, sizeof(struct fuse_cmdline_opts)); + + opts->max_idle_threads = UINT_MAX; /* new default in fuse version 3.12 */ + opts->max_threads = 10; + + if (fuse_opt_parse(args, opts, fuse_helper_opts, + fuse_helper_opt_proc) == -1) + return -1; + + /* *Linux*: if neither -o subtype nor -o fsname are specified, + set subtype to program's basename. + *FreeBSD*: if fsname is not specified, set to program's + basename. */ + if (!opts->nodefault_subtype) + if (add_default_subtype(args->argv[0], args) == -1) + return -1; + + return 0; +} + +/** + * struct fuse_cmdline_opts got extended in libfuse-3.12 + */ +int fuse_parse_cmdline_30(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +FUSE_SYMVER("fuse_parse_cmdline_30", "fuse_parse_cmdline@FUSE_3.0") +int fuse_parse_cmdline_30(struct fuse_args *args, + struct fuse_cmdline_opts *out_opts) +{ + struct fuse_cmdline_opts opts; + + int rc = fuse_parse_cmdline_312(args, &opts); + if (rc == 0) { + /* copy up to the size of the old pre 3.12 struct */ + memcpy(out_opts, &opts, + offsetof(struct fuse_cmdline_opts, max_idle_threads) + + sizeof(opts.max_idle_threads)); + } + + return rc; +} + +int fuse_daemonize(int foreground) +{ + if (!foreground) { + int nullfd; + int waiter[2]; + char completed; + + if (pipe(waiter)) { + perror("fuse_daemonize: pipe"); + return -1; + } + + /* + * demonize current process by forking it and killing the + * parent. This makes current process as a child of 'init'. + */ + switch(fork()) { + case -1: + perror("fuse_daemonize: fork"); + return -1; + case 0: + break; + default: + (void) read(waiter[0], &completed, sizeof(completed)); + _exit(0); + } + + if (setsid() == -1) { + perror("fuse_daemonize: setsid"); + return -1; + } + + (void) chdir("/"); + + nullfd = open("/dev/null", O_RDWR, 0); + if (nullfd != -1) { + (void) dup2(nullfd, 0); + (void) dup2(nullfd, 1); + (void) dup2(nullfd, 2); + if (nullfd > 2) + close(nullfd); + } + + /* Propagate completion of daemon initialization */ + completed = 1; + (void) write(waiter[1], &completed, sizeof(completed)); + close(waiter[0]); + close(waiter[1]); + } else { + (void) chdir("/"); + } + return 0; +} + +int fuse_main_real_versioned(int argc, char *argv[], + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse *fuse; + struct fuse_cmdline_opts opts; + int res; + struct fuse_loop_config *loop_config = NULL; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + + if (opts.show_version) { + printf("FUSE library version %s\n", PACKAGE_VERSION); + fuse_lowlevel_version(); + res = 0; + goto out1; + } + + if (opts.show_help) { + if(args.argv[0][0] != '\0') + printf("usage: %s [options] \n\n", + args.argv[0]); + printf("FUSE options:\n"); + fuse_cmdline_help(); + fuse_lib_help(&args); + res = 0; + goto out1; + } + + if (!opts.show_help && + !opts.mountpoint) { + fuse_log(FUSE_LOG_ERR, "error: no mountpoint specified\n"); + res = 2; + goto out1; + } + + struct fuse *_fuse_new_31(struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, + void *user_data); + fuse = _fuse_new_31(&args, op, op_size, version, user_data); + if (fuse == NULL) { + res = 3; + goto out1; + } + + if (fuse_mount(fuse,opts.mountpoint) != 0) { + res = 4; + goto out2; + } + + if (fuse_daemonize(opts.foreground) != 0) { + res = 5; + goto out3; + } + + struct fuse_session *se = fuse_get_session(fuse); + if (fuse_set_signal_handlers(se) != 0) { + res = 6; + goto out3; + } + + if (opts.singlethread) + res = fuse_loop(fuse); + else { + loop_config = fuse_loop_cfg_create(); + if (loop_config == NULL) { + res = 7; + goto out3; + } + + fuse_loop_cfg_set_clone_fd(loop_config, opts.clone_fd); + + fuse_loop_cfg_set_idle_threads(loop_config, opts.max_idle_threads); + fuse_loop_cfg_set_max_threads(loop_config, opts.max_threads); + res = fuse_loop_mt(fuse, loop_config); + } + if (res) + res = 8; + + fuse_remove_signal_handlers(se); +out3: + fuse_unmount(fuse); +out2: + fuse_destroy(fuse); +out1: + fuse_loop_cfg_destroy(loop_config); + free(opts.mountpoint); + fuse_opt_free_args(&args); + return res; +} + +/* Not symboled, as not part of the official API */ +int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data); +int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + struct libfuse_version version = { 0 }; + return fuse_main_real_versioned(argc, argv, op, op_size, &version, + user_data); +} + +void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, + struct fuse_conn_info *conn) +{ + if(opts->set_max_write) + conn->max_write = opts->max_write; + if(opts->set_max_background) + conn->max_background = opts->max_background; + if(opts->set_congestion_threshold) + conn->congestion_threshold = opts->congestion_threshold; + if(opts->set_time_gran) + conn->time_gran = opts->time_gran; + if(opts->set_max_readahead) + conn->max_readahead = opts->max_readahead; + +#define LL_ENABLE(cond, cap) \ + do { \ + if (cond) \ + fuse_set_feature_flag(conn, cap); \ + } while (0) + +#define LL_DISABLE(cond, cap) \ + do { \ + if (cond) \ + fuse_unset_feature_flag(conn, cap); \ + } while (0) + + LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ); + LL_DISABLE(opts->no_splice_read, FUSE_CAP_SPLICE_READ); + + LL_ENABLE(opts->splice_write, FUSE_CAP_SPLICE_WRITE); + LL_DISABLE(opts->no_splice_write, FUSE_CAP_SPLICE_WRITE); + + LL_ENABLE(opts->splice_move, FUSE_CAP_SPLICE_MOVE); + LL_DISABLE(opts->no_splice_move, FUSE_CAP_SPLICE_MOVE); + + LL_ENABLE(opts->auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA); + LL_DISABLE(opts->no_auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA); + + LL_DISABLE(opts->no_readdirplus, FUSE_CAP_READDIRPLUS); + LL_DISABLE(opts->no_readdirplus_auto, FUSE_CAP_READDIRPLUS_AUTO); + + LL_ENABLE(opts->async_dio, FUSE_CAP_ASYNC_DIO); + LL_DISABLE(opts->no_async_dio, FUSE_CAP_ASYNC_DIO); + + LL_ENABLE(opts->writeback_cache, FUSE_CAP_WRITEBACK_CACHE); + LL_DISABLE(opts->no_writeback_cache, FUSE_CAP_WRITEBACK_CACHE); + + LL_ENABLE(opts->async_read, FUSE_CAP_ASYNC_READ); + LL_DISABLE(opts->sync_read, FUSE_CAP_ASYNC_READ); + + LL_DISABLE(opts->no_remote_posix_lock, FUSE_CAP_POSIX_LOCKS); + LL_DISABLE(opts->no_remote_flock, FUSE_CAP_FLOCK_LOCKS); +} + +struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args) +{ + struct fuse_conn_info_opts *opts; + + opts = calloc(1, sizeof(struct fuse_conn_info_opts)); + if(opts == NULL) { + fuse_log(FUSE_LOG_ERR, "calloc failed\n"); + return NULL; + } + if(fuse_opt_parse(args, opts, conn_info_opt_spec, NULL) == -1) { + free(opts); + return NULL; + } + return opts; +} + +int fuse_open_channel(const char *mountpoint, const char* options) +{ + struct mount_opts *opts = NULL; + int fd = -1; + const char *argv[] = { "", "-o", options }; + int argc = sizeof(argv) / sizeof(argv[0]); + struct fuse_args args = FUSE_ARGS_INIT(argc, (char**) argv); + + opts = parse_mount_opts(&args); + if (opts == NULL) + return -1; + + fd = fuse_kern_mount(mountpoint, opts); + destroy_mount_opts(opts); + + return fd; +} diff --git a/lib/meson.build b/lib/meson.build new file mode 100644 index 0000000..fcd9574 --- /dev/null +++ b/lib/meson.build @@ -0,0 +1,66 @@ +libfuse_sources = ['fuse.c', 'fuse_i.h', 'fuse_loop.c', 'fuse_loop_mt.c', + 'fuse_lowlevel.c', 'fuse_misc.h', 'fuse_opt.c', + 'fuse_signals.c', 'buffer.c', 'cuse_lowlevel.c', + 'helper.c', 'modules/subdir.c', 'mount_util.c', + 'fuse_log.c', 'compat.c', 'util.c', 'util.h' ] + +if host_machine.system().startswith('linux') + libfuse_sources += [ 'mount.c' ] +else + libfuse_sources += [ 'mount_bsd.c' ] +endif + +deps = [ thread_dep ] +if private_cfg.get('HAVE_ICONV') + libfuse_sources += [ 'modules/iconv.c' ] + libiconv = cc.find_library('iconv', required: false) + if libiconv.found() + deps += [ libiconv ] + endif +endif + +if private_cfg.get('HAVE_URING', false) + libfuse_sources += [ 'fuse_uring.c' ] + deps += [ dependency('liburing') ] + deps += [ dependency('numa') ] +endif + + + +libdl = cc.find_library('dl', required: false) +if libdl.found() + deps += [ libdl ] +endif + +if host_machine.system().startswith('netbsd') + deps += [ cc.find_library('perfuse'), + cc.find_library('puffs') ] +else + # Required for clock_gettime before glibc 2.17 + deps += cc.find_library('rt') +endif + +fusermount_path = join_paths(get_option('prefix'), get_option('bindir')) +libfuse = library('fuse3', + libfuse_sources, + version: base_version, + soversion: '4', + include_directories: include_dirs, + dependencies: deps, + install: true, + link_depends: 'fuse_versionscript', + c_args: [ '-DFUSE_USE_VERSION=317', + '-DFUSERMOUNT_DIR="@0@"'.format(fusermount_path) ], + link_args: ['-Wl,--version-script,' + meson.current_source_dir() + + '/fuse_versionscript' ]) + +pkg = import('pkgconfig') +pkg.generate(libraries: [ libfuse, '-lpthread' ], + libraries_private: '-ldl', + version: meson.project_version(), + name: 'fuse3', + description: 'Filesystem in Userspace', + subdirs: 'fuse3') + +libfuse_dep = declare_dependency(include_directories: include_dirs, + link_with: libfuse, dependencies: deps) diff --git a/lib/modules/iconv.c b/lib/modules/iconv.c new file mode 100644 index 0000000..417c904 --- /dev/null +++ b/lib/modules/iconv.c @@ -0,0 +1,756 @@ +/* + fuse iconv module: file name charset conversion + Copyright (C) 2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct iconv { + struct fuse_fs *next; + pthread_mutex_t lock; + char *from_code; + char *to_code; + iconv_t tofs; + iconv_t fromfs; +}; + +struct iconv_dh { + struct iconv *ic; + void *prev_buf; + fuse_fill_dir_t prev_filler; +}; + +static struct iconv *iconv_get(void) +{ + return fuse_get_context()->private_data; +} + +static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp, + int fromfs) +{ + size_t pathlen; + size_t newpathlen; + char *newpath; + size_t plen; + char *p; + size_t res; + int err; + + if (path == NULL) { + *newpathp = NULL; + return 0; + } + + pathlen = strlen(path); + newpathlen = pathlen * 4; + newpath = malloc(newpathlen + 1); + if (!newpath) + return -ENOMEM; + + plen = newpathlen; + p = newpath; + pthread_mutex_lock(&ic->lock); + do { + res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path, + &pathlen, &p, &plen); + if (res == (size_t) -1) { + char *tmp; + size_t inc; + + err = -EILSEQ; + if (errno != E2BIG) + goto err; + + inc = (pathlen + 1) * 4; + newpathlen += inc; + int dp = p - newpath; + tmp = realloc(newpath, newpathlen + 1); + err = -ENOMEM; + if (!tmp) + goto err; + + p = tmp + dp; + plen += inc; + newpath = tmp; + } + } while (res == (size_t) -1); + pthread_mutex_unlock(&ic->lock); + *p = '\0'; + *newpathp = newpath; + return 0; + +err: + iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL); + pthread_mutex_unlock(&ic->lock); + free(newpath); + return err; +} + +static int iconv_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_getattr(ic->next, newpath, stbuf, fi); + free(newpath); + } + return err; +} + +static int iconv_access(const char *path, int mask) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_access(ic->next, newpath, mask); + free(newpath); + } + return err; +} + +static int iconv_readlink(const char *path, char *buf, size_t size) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_readlink(ic->next, newpath, buf, size); + if (!err) { + char *newlink; + err = iconv_convpath(ic, buf, &newlink, 1); + if (!err) { + strncpy(buf, newlink, size - 1); + buf[size - 1] = '\0'; + free(newlink); + } + } + free(newpath); + } + return err; +} + +static int iconv_opendir(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_opendir(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_dir_fill(void *buf, const char *name, + const struct stat *stbuf, off_t off, + enum fuse_fill_dir_flags flags) +{ + struct iconv_dh *dh = buf; + char *newname; + int res = 0; + if (iconv_convpath(dh->ic, name, &newname, 1) == 0) { + res = dh->prev_filler(dh->prev_buf, newname, stbuf, off, flags); + free(newname); + } + return res; +} + +static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + struct iconv_dh dh; + dh.ic = ic; + dh.prev_buf = buf; + dh.prev_filler = filler; + err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill, + offset, fi, flags); + free(newpath); + } + return err; +} + +static int iconv_releasedir(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_releasedir(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_mknod(const char *path, mode_t mode, dev_t rdev) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_mknod(ic->next, newpath, mode, rdev); + free(newpath); + } + return err; +} + +static int iconv_mkdir(const char *path, mode_t mode) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_mkdir(ic->next, newpath, mode); + free(newpath); + } + return err; +} + +static int iconv_unlink(const char *path) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_unlink(ic->next, newpath); + free(newpath); + } + return err; +} + +static int iconv_rmdir(const char *path) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_rmdir(ic->next, newpath); + free(newpath); + } + return err; +} + +static int iconv_symlink(const char *from, const char *to) +{ + struct iconv *ic = iconv_get(); + char *newfrom; + char *newto; + int err = iconv_convpath(ic, from, &newfrom, 0); + if (!err) { + err = iconv_convpath(ic, to, &newto, 0); + if (!err) { + err = fuse_fs_symlink(ic->next, newfrom, newto); + free(newto); + } + free(newfrom); + } + return err; +} + +static int iconv_rename(const char *from, const char *to, unsigned int flags) +{ + struct iconv *ic = iconv_get(); + char *newfrom; + char *newto; + int err = iconv_convpath(ic, from, &newfrom, 0); + if (!err) { + err = iconv_convpath(ic, to, &newto, 0); + if (!err) { + err = fuse_fs_rename(ic->next, newfrom, newto, flags); + free(newto); + } + free(newfrom); + } + return err; +} + +static int iconv_link(const char *from, const char *to) +{ + struct iconv *ic = iconv_get(); + char *newfrom; + char *newto; + int err = iconv_convpath(ic, from, &newfrom, 0); + if (!err) { + err = iconv_convpath(ic, to, &newto, 0); + if (!err) { + err = fuse_fs_link(ic->next, newfrom, newto); + free(newto); + } + free(newfrom); + } + return err; +} + +static int iconv_chmod(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_chmod(ic->next, newpath, mode, fi); + free(newpath); + } + return err; +} + +static int iconv_chown(const char *path, uid_t uid, gid_t gid, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_chown(ic->next, newpath, uid, gid, fi); + free(newpath); + } + return err; +} + +static int iconv_truncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_truncate(ic->next, newpath, size, fi); + free(newpath); + } + return err; +} + +static int iconv_utimens(const char *path, const struct timespec ts[2], + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_utimens(ic->next, newpath, ts, fi); + free(newpath); + } + return err; +} + +static int iconv_create(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_create(ic->next, newpath, mode, fi); + free(newpath); + } + return err; +} + +static int iconv_open_file(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_open(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp, + size_t size, off_t offset, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi); + free(newpath); + } + return err; +} + +static int iconv_write_buf(const char *path, struct fuse_bufvec *buf, + off_t offset, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi); + free(newpath); + } + return err; +} + +static int iconv_statfs(const char *path, struct statvfs *stbuf) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_statfs(ic->next, newpath, stbuf); + free(newpath); + } + return err; +} + +static int iconv_flush(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_flush(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_release(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_release(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_fsync(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi); + free(newpath); + } + return err; +} + +static int iconv_fsyncdir(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi); + free(newpath); + } + return err; +} + +static int iconv_setxattr(const char *path, const char *name, + const char *value, size_t size, int flags) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_setxattr(ic->next, newpath, name, value, size, + flags); + free(newpath); + } + return err; +} + +static int iconv_getxattr(const char *path, const char *name, char *value, + size_t size) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_getxattr(ic->next, newpath, name, value, size); + free(newpath); + } + return err; +} + +static int iconv_listxattr(const char *path, char *list, size_t size) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_listxattr(ic->next, newpath, list, size); + free(newpath); + } + return err; +} + +static int iconv_removexattr(const char *path, const char *name) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_removexattr(ic->next, newpath, name); + free(newpath); + } + return err; +} + +static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd, + struct flock *lock) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock); + free(newpath); + } + return err; +} + +static int iconv_flock(const char *path, struct fuse_file_info *fi, int op) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_flock(ic->next, newpath, fi, op); + free(newpath); + } + return err; +} + +static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_bmap(ic->next, newpath, blocksize, idx); + free(newpath); + } + return err; +} + +static off_t iconv_lseek(const char *path, off_t off, int whence, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int res = iconv_convpath(ic, path, &newpath, 0); + if (!res) { + res = fuse_fs_lseek(ic->next, newpath, off, whence, fi); + free(newpath); + } + return res; +} + +#ifdef HAVE_STATX +static int iconv_statx(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int res = iconv_convpath(ic, path, &newpath, 0); + + if (!res) { + res = fuse_fs_statx(ic->next, newpath, flags, mask, stxbuf, fi); + free(newpath); + } + return res; +} +#endif + +static void *iconv_init(struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + struct iconv *ic = iconv_get(); + fuse_fs_init(ic->next, conn, cfg); + /* Don't touch cfg->nullpath_ok, we can work with + either */ + return ic; +} + +static void iconv_destroy(void *data) +{ + struct iconv *ic = data; + fuse_fs_destroy(ic->next); + iconv_close(ic->tofs); + iconv_close(ic->fromfs); + pthread_mutex_destroy(&ic->lock); + free(ic->from_code); + free(ic->to_code); + free(ic); +} + +static const struct fuse_operations iconv_oper = { + .destroy = iconv_destroy, + .init = iconv_init, + .getattr = iconv_getattr, + .access = iconv_access, + .readlink = iconv_readlink, + .opendir = iconv_opendir, + .readdir = iconv_readdir, + .releasedir = iconv_releasedir, + .mknod = iconv_mknod, + .mkdir = iconv_mkdir, + .symlink = iconv_symlink, + .unlink = iconv_unlink, + .rmdir = iconv_rmdir, + .rename = iconv_rename, + .link = iconv_link, + .chmod = iconv_chmod, + .chown = iconv_chown, + .truncate = iconv_truncate, + .utimens = iconv_utimens, + .create = iconv_create, + .open = iconv_open_file, + .read_buf = iconv_read_buf, + .write_buf = iconv_write_buf, + .statfs = iconv_statfs, + .flush = iconv_flush, + .release = iconv_release, + .fsync = iconv_fsync, + .fsyncdir = iconv_fsyncdir, + .setxattr = iconv_setxattr, + .getxattr = iconv_getxattr, + .listxattr = iconv_listxattr, + .removexattr = iconv_removexattr, + .lock = iconv_lock, + .flock = iconv_flock, + .bmap = iconv_bmap, + .lseek = iconv_lseek, +#ifdef HAVE_STATX + .statx = iconv_statx, +#endif +}; + +static const struct fuse_opt iconv_opts[] = { + FUSE_OPT_KEY("-h", 0), + FUSE_OPT_KEY("--help", 0), + { "from_code=%s", offsetof(struct iconv, from_code), 0 }, + { "to_code=%s", offsetof(struct iconv, to_code), 1 }, + FUSE_OPT_END +}; + +static void iconv_help(void) +{ + char *charmap; + const char *old = setlocale(LC_CTYPE, ""); + + charmap = strdup(nl_langinfo(CODESET)); + if (old) + setlocale(LC_CTYPE, old); + else + perror("setlocale"); + + printf( +" -o from_code=CHARSET original encoding of file names (default: UTF-8)\n" +" -o to_code=CHARSET new encoding of the file names (default: %s)\n", + charmap); + free(charmap); +} + +static int iconv_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) data; (void) arg; (void) outargs; + + if (!key) { + iconv_help(); + return -1; + } + + return 1; +} + +static struct fuse_fs *iconv_new(struct fuse_args *args, + struct fuse_fs *next[]) +{ + struct fuse_fs *fs; + struct iconv *ic; + const char *old = NULL; + const char *from; + const char *to; + + ic = calloc(1, sizeof(struct iconv)); + if (ic == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse-iconv: memory allocation failed\n"); + return NULL; + } + + if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1) + goto out_free; + + if (!next[0] || next[1]) { + fuse_log(FUSE_LOG_ERR, "fuse-iconv: exactly one next filesystem required\n"); + goto out_free; + } + + from = ic->from_code ? ic->from_code : "UTF-8"; + to = ic->to_code ? ic->to_code : ""; + /* FIXME: detect charset equivalence? */ + if (!to[0]) + old = setlocale(LC_CTYPE, ""); + ic->tofs = iconv_open(from, to); + if (ic->tofs == (iconv_t) -1) { + fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n", + to, from); + goto out_free; + } + ic->fromfs = iconv_open(to, from); + if (ic->tofs == (iconv_t) -1) { + fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n", + from, to); + goto out_iconv_close_to; + } + if (old) { + setlocale(LC_CTYPE, old); + old = NULL; + } + + ic->next = next[0]; + fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic); + if (!fs) + goto out_iconv_close_from; + + return fs; + +out_iconv_close_from: + iconv_close(ic->fromfs); +out_iconv_close_to: + iconv_close(ic->tofs); +out_free: + free(ic->from_code); + free(ic->to_code); + free(ic); + if (old) { + setlocale(LC_CTYPE, old); + } + return NULL; +} + +FUSE_REGISTER_MODULE(iconv, iconv_new); diff --git a/lib/modules/subdir.c b/lib/modules/subdir.c new file mode 100644 index 0000000..67c4697 --- /dev/null +++ b/lib/modules/subdir.c @@ -0,0 +1,708 @@ +/* + fuse subdir module: offset paths with a base directory + Copyright (C) 2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#include + +#include +#include +#include +#include +#include +#include + +struct subdir { + char *base; + size_t baselen; + int rellinks; + struct fuse_fs *next; +}; + +static struct subdir *subdir_get(void) +{ + return fuse_get_context()->private_data; +} + +static int subdir_addpath(struct subdir *d, const char *path, char **newpathp) +{ + char *newpath = NULL; + + if (path != NULL) { + unsigned newlen = d->baselen + strlen(path); + + newpath = malloc(newlen + 2); + if (!newpath) + return -ENOMEM; + + if (path[0] == '/') + path++; + strcpy(newpath, d->base); + strcpy(newpath + d->baselen, path); + if (!newpath[0]) + strcpy(newpath, "."); + } + *newpathp = newpath; + + return 0; +} + +static int subdir_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_getattr(d->next, newpath, stbuf, fi); + free(newpath); + } + return err; +} + +static int subdir_access(const char *path, int mask) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_access(d->next, newpath, mask); + free(newpath); + } + return err; +} + + +static int count_components(const char *p) +{ + int ctr; + + for (; *p == '/'; p++); + for (ctr = 0; *p; ctr++) { + for (; *p && *p != '/'; p++); + for (; *p == '/'; p++); + } + return ctr; +} + +static void strip_common(const char **sp, const char **tp) +{ + const char *s = *sp; + const char *t = *tp; + do { + for (; *s == '/'; s++); + for (; *t == '/'; t++); + *tp = t; + *sp = s; + for (; *s == *t && *s && *s != '/'; s++, t++); + } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t)); +} + +static void transform_symlink(struct subdir *d, const char *path, + char *buf, size_t size) +{ + const char *l = buf; + size_t llen; + char *s; + int dotdots; + int i; + + if (l[0] != '/' || d->base[0] != '/') + return; + + strip_common(&l, &path); + if (l - buf < (long) d->baselen) + return; + + dotdots = count_components(path); + if (!dotdots) + return; + dotdots--; + + llen = strlen(l); + if (dotdots * 3 + llen + 2 > size) + return; + + s = buf + dotdots * 3; + if (llen) + memmove(s, l, llen + 1); + else if (!dotdots) + strcpy(s, "."); + else + *s = '\0'; + + for (s = buf, i = 0; i < dotdots; i++, s += 3) + memcpy(s, "../", 3); +} + + +static int subdir_readlink(const char *path, char *buf, size_t size) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_readlink(d->next, newpath, buf, size); + if (!err && d->rellinks) + transform_symlink(d, newpath, buf, size); + free(newpath); + } + return err; +} + +static int subdir_opendir(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_opendir(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_readdir(const char *path, void *buf, + fuse_fill_dir_t filler, off_t offset, + struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_readdir(d->next, newpath, buf, filler, offset, + fi, flags); + free(newpath); + } + return err; +} + +static int subdir_releasedir(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_releasedir(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_mknod(const char *path, mode_t mode, dev_t rdev) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_mknod(d->next, newpath, mode, rdev); + free(newpath); + } + return err; +} + +static int subdir_mkdir(const char *path, mode_t mode) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_mkdir(d->next, newpath, mode); + free(newpath); + } + return err; +} + +static int subdir_unlink(const char *path) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_unlink(d->next, newpath); + free(newpath); + } + return err; +} + +static int subdir_rmdir(const char *path) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_rmdir(d->next, newpath); + free(newpath); + } + return err; +} + +static int subdir_symlink(const char *from, const char *path) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_symlink(d->next, from, newpath); + free(newpath); + } + return err; +} + +static int subdir_rename(const char *from, const char *to, unsigned int flags) +{ + struct subdir *d = subdir_get(); + char *newfrom; + char *newto; + int err = subdir_addpath(d, from, &newfrom); + if (!err) { + err = subdir_addpath(d, to, &newto); + if (!err) { + err = fuse_fs_rename(d->next, newfrom, newto, flags); + free(newto); + } + free(newfrom); + } + return err; +} + +static int subdir_link(const char *from, const char *to) +{ + struct subdir *d = subdir_get(); + char *newfrom; + char *newto; + int err = subdir_addpath(d, from, &newfrom); + if (!err) { + err = subdir_addpath(d, to, &newto); + if (!err) { + err = fuse_fs_link(d->next, newfrom, newto); + free(newto); + } + free(newfrom); + } + return err; +} + +static int subdir_chmod(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_chmod(d->next, newpath, mode, fi); + free(newpath); + } + return err; +} + +static int subdir_chown(const char *path, uid_t uid, gid_t gid, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_chown(d->next, newpath, uid, gid, fi); + free(newpath); + } + return err; +} + +static int subdir_truncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_truncate(d->next, newpath, size, fi); + free(newpath); + } + return err; +} + +static int subdir_utimens(const char *path, const struct timespec ts[2], + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_utimens(d->next, newpath, ts, fi); + free(newpath); + } + return err; +} + +static int subdir_create(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_create(d->next, newpath, mode, fi); + free(newpath); + } + return err; +} + +static int subdir_open(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_open(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_read_buf(const char *path, struct fuse_bufvec **bufp, + size_t size, off_t offset, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_read_buf(d->next, newpath, bufp, size, offset, fi); + free(newpath); + } + return err; +} + +static int subdir_write_buf(const char *path, struct fuse_bufvec *buf, + off_t offset, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_write_buf(d->next, newpath, buf, offset, fi); + free(newpath); + } + return err; +} + +static int subdir_statfs(const char *path, struct statvfs *stbuf) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_statfs(d->next, newpath, stbuf); + free(newpath); + } + return err; +} + +static int subdir_flush(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_flush(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_release(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_release(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_fsync(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_fsync(d->next, newpath, isdatasync, fi); + free(newpath); + } + return err; +} + +static int subdir_fsyncdir(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_fsyncdir(d->next, newpath, isdatasync, fi); + free(newpath); + } + return err; +} + +static int subdir_setxattr(const char *path, const char *name, + const char *value, size_t size, int flags) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_setxattr(d->next, newpath, name, value, size, + flags); + free(newpath); + } + return err; +} + +static int subdir_getxattr(const char *path, const char *name, char *value, + size_t size) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_getxattr(d->next, newpath, name, value, size); + free(newpath); + } + return err; +} + +static int subdir_listxattr(const char *path, char *list, size_t size) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_listxattr(d->next, newpath, list, size); + free(newpath); + } + return err; +} + +static int subdir_removexattr(const char *path, const char *name) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_removexattr(d->next, newpath, name); + free(newpath); + } + return err; +} + +static int subdir_lock(const char *path, struct fuse_file_info *fi, int cmd, + struct flock *lock) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_lock(d->next, newpath, fi, cmd, lock); + free(newpath); + } + return err; +} + +static int subdir_flock(const char *path, struct fuse_file_info *fi, int op) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_flock(d->next, newpath, fi, op); + free(newpath); + } + return err; +} + +static int subdir_bmap(const char *path, size_t blocksize, uint64_t *idx) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_bmap(d->next, newpath, blocksize, idx); + free(newpath); + } + return err; +} + +static off_t subdir_lseek(const char *path, off_t off, int whence, + struct fuse_file_info *fi) +{ + struct subdir *ic = subdir_get(); + char *newpath; + int res = subdir_addpath(ic, path, &newpath); + if (!res) { + res = fuse_fs_lseek(ic->next, newpath, off, whence, fi); + free(newpath); + } + return res; +} + +#ifdef HAVE_STATX +static int subdir_statx(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi) +{ + struct subdir *ic = subdir_get(); + char *newpath; + int res = subdir_addpath(ic, path, &newpath); + + if (!res) { + res = fuse_fs_statx(ic->next, newpath, flags, mask, stxbuf, fi); + free(newpath); + } + return res; +} +#endif + +static void *subdir_init(struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + struct subdir *d = subdir_get(); + fuse_fs_init(d->next, conn, cfg); + /* Don't touch cfg->nullpath_ok, we can work with + either */ + return d; +} + +static void subdir_destroy(void *data) +{ + struct subdir *d = data; + fuse_fs_destroy(d->next); + free(d->base); + free(d); +} + +static const struct fuse_operations subdir_oper = { + .destroy = subdir_destroy, + .init = subdir_init, + .getattr = subdir_getattr, + .access = subdir_access, + .readlink = subdir_readlink, + .opendir = subdir_opendir, + .readdir = subdir_readdir, + .releasedir = subdir_releasedir, + .mknod = subdir_mknod, + .mkdir = subdir_mkdir, + .symlink = subdir_symlink, + .unlink = subdir_unlink, + .rmdir = subdir_rmdir, + .rename = subdir_rename, + .link = subdir_link, + .chmod = subdir_chmod, + .chown = subdir_chown, + .truncate = subdir_truncate, + .utimens = subdir_utimens, + .create = subdir_create, + .open = subdir_open, + .read_buf = subdir_read_buf, + .write_buf = subdir_write_buf, + .statfs = subdir_statfs, + .flush = subdir_flush, + .release = subdir_release, + .fsync = subdir_fsync, + .fsyncdir = subdir_fsyncdir, + .setxattr = subdir_setxattr, + .getxattr = subdir_getxattr, + .listxattr = subdir_listxattr, + .removexattr = subdir_removexattr, + .lock = subdir_lock, + .flock = subdir_flock, + .bmap = subdir_bmap, + .lseek = subdir_lseek, +#ifdef HAVE_STATX + .statx = subdir_statx, +#endif +}; + +static const struct fuse_opt subdir_opts[] = { + FUSE_OPT_KEY("-h", 0), + FUSE_OPT_KEY("--help", 0), + { "subdir=%s", offsetof(struct subdir, base), 0 }, + { "rellinks", offsetof(struct subdir, rellinks), 1 }, + { "norellinks", offsetof(struct subdir, rellinks), 0 }, + FUSE_OPT_END +}; + +static void subdir_help(void) +{ + printf( +" -o subdir=DIR prepend this directory to all paths (mandatory)\n" +" -o [no]rellinks transform absolute symlinks to relative\n"); +} + +static int subdir_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) data; (void) arg; (void) outargs; + + if (!key) { + subdir_help(); + return -1; + } + + return 1; +} + +static struct fuse_fs *subdir_new(struct fuse_args *args, + struct fuse_fs *next[]) +{ + struct fuse_fs *fs; + struct subdir *d; + + d = calloc(1, sizeof(struct subdir)); + if (d == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n"); + return NULL; + } + + if (fuse_opt_parse(args, d, subdir_opts, subdir_opt_proc) == -1) + goto out_free; + + if (!next[0] || next[1]) { + fuse_log(FUSE_LOG_ERR, "fuse-subdir: exactly one next filesystem required\n"); + goto out_free; + } + + if (!d->base) { + fuse_log(FUSE_LOG_ERR, "fuse-subdir: missing 'subdir' option\n"); + goto out_free; + } + + if (d->base[0] && d->base[strlen(d->base)-1] != '/') { + char *tmp = realloc(d->base, strlen(d->base) + 2); + if (!tmp) { + fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n"); + goto out_free; + } + d->base = tmp; + strcat(d->base, "/"); + } + d->baselen = strlen(d->base); + d->next = next[0]; + fs = fuse_fs_new(&subdir_oper, sizeof(subdir_oper), d); + if (!fs) + goto out_free; + return fs; + +out_free: + free(d->base); + free(d); + return NULL; +} + +FUSE_REGISTER_MODULE(subdir, subdir_new); diff --git a/lib/mount.c b/lib/mount.c new file mode 100644 index 0000000..7a856c1 --- /dev/null +++ b/lib/mount.c @@ -0,0 +1,725 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Architecture specific file system mounting (Linux). + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +/* For environ */ +#define _GNU_SOURCE + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_misc.h" +#include "fuse_opt.h" +#include "mount_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fuse_mount_compat.h" + +#ifdef __NetBSD__ +#include + +#define MS_RDONLY MNT_RDONLY +#define MS_NOSUID MNT_NOSUID +#define MS_NODEV MNT_NODEV +#define MS_NOEXEC MNT_NOEXEC +#define MS_SYNCHRONOUS MNT_SYNCHRONOUS +#define MS_NOATIME MNT_NOATIME +#define MS_NOSYMFOLLOW MNT_NOSYMFOLLOW + +#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0) +#endif + +#define FUSERMOUNT_PROG "fusermount3" +#define FUSE_COMMFD_ENV "_FUSE_COMMFD" +#define FUSE_COMMFD2_ENV "_FUSE_COMMFD2" +#define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE" + +#ifndef MS_DIRSYNC +#define MS_DIRSYNC 128 +#endif + +enum { + KEY_KERN_FLAG, + KEY_KERN_OPT, + KEY_FUSERMOUNT_OPT, + KEY_SUBTYPE_OPT, + KEY_MTAB_OPT, + KEY_ALLOW_OTHER, + KEY_RO, +}; + +struct mount_opts { + int allow_other; + int flags; + int auto_unmount; + int blkdev; + char *fsname; + char *subtype; + char *subtype_opt; + char *mtab_opts; + char *fusermount_opts; + char *kernel_opts; + unsigned max_read; +}; + +#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 } + +static const struct fuse_opt fuse_mount_opts[] = { + FUSE_MOUNT_OPT("allow_other", allow_other), + FUSE_MOUNT_OPT("blkdev", blkdev), + FUSE_MOUNT_OPT("auto_unmount", auto_unmount), + FUSE_MOUNT_OPT("fsname=%s", fsname), + FUSE_MOUNT_OPT("max_read=%u", max_read), + FUSE_MOUNT_OPT("subtype=%s", subtype), + FUSE_OPT_KEY("allow_other", KEY_KERN_OPT), + FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT), + FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT), + FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT), + FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT), + FUSE_OPT_KEY("blksize=", KEY_KERN_OPT), + FUSE_OPT_KEY("default_permissions", KEY_KERN_OPT), + FUSE_OPT_KEY("context=", KEY_KERN_OPT), + FUSE_OPT_KEY("fscontext=", KEY_KERN_OPT), + FUSE_OPT_KEY("defcontext=", KEY_KERN_OPT), + FUSE_OPT_KEY("rootcontext=", KEY_KERN_OPT), + FUSE_OPT_KEY("max_read=", KEY_KERN_OPT), + FUSE_OPT_KEY("user=", KEY_MTAB_OPT), + FUSE_OPT_KEY("-n", KEY_MTAB_OPT), + FUSE_OPT_KEY("-r", KEY_RO), + FUSE_OPT_KEY("ro", KEY_KERN_FLAG), + FUSE_OPT_KEY("rw", KEY_KERN_FLAG), + FUSE_OPT_KEY("suid", KEY_KERN_FLAG), + FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG), + FUSE_OPT_KEY("dev", KEY_KERN_FLAG), + FUSE_OPT_KEY("nodev", KEY_KERN_FLAG), + FUSE_OPT_KEY("exec", KEY_KERN_FLAG), + FUSE_OPT_KEY("noexec", KEY_KERN_FLAG), + FUSE_OPT_KEY("async", KEY_KERN_FLAG), + FUSE_OPT_KEY("sync", KEY_KERN_FLAG), + FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG), + FUSE_OPT_KEY("noatime", KEY_KERN_FLAG), + FUSE_OPT_KEY("nodiratime", KEY_KERN_FLAG), + FUSE_OPT_KEY("nostrictatime", KEY_KERN_FLAG), + FUSE_OPT_KEY("symfollow", KEY_KERN_FLAG), + FUSE_OPT_KEY("nosymfollow", KEY_KERN_FLAG), + FUSE_OPT_END +}; + +/* + * Running fusermount by calling 'posix_spawn' + * + * @param out_pid might be NULL + */ +static int fusermount_posix_spawn(posix_spawn_file_actions_t *action, + char const * const argv[], pid_t *out_pid) +{ + const char *full_path = FUSERMOUNT_DIR "/" FUSERMOUNT_PROG; + pid_t pid; + + /* See man 7 environ for the global environ pointer */ + + /* first try the install path */ + int status = posix_spawn(&pid, full_path, action, NULL, + (char * const *) argv, environ); + if (status != 0) { + /* if that fails, try a system install */ + status = posix_spawnp(&pid, FUSERMOUNT_PROG, action, NULL, + (char * const *) argv, environ); + } + + if (status != 0) { + fuse_log(FUSE_LOG_ERR, "Failed to call '%s': %s\n", + FUSERMOUNT_PROG, strerror(status)); + return -status; + } + + if (out_pid) + *out_pid = pid; + else + waitpid(pid, NULL, 0); /* FIXME: check exit code and return error if any */ + + return 0; +} + +void fuse_mount_version(void) +{ + char const *const argv[] = {FUSERMOUNT_PROG, "--version", NULL}; + int status = fusermount_posix_spawn(NULL, argv, NULL); + + if(status != 0) + fuse_log(FUSE_LOG_ERR, "Running '%s --version' failed", + FUSERMOUNT_PROG); +} + +struct mount_flags { + const char *opt; + unsigned long flag; + int on; +}; + +static const struct mount_flags mount_flags[] = { + {"rw", MS_RDONLY, 0}, + {"ro", MS_RDONLY, 1}, + {"suid", MS_NOSUID, 0}, + {"nosuid", MS_NOSUID, 1}, + {"dev", MS_NODEV, 0}, + {"nodev", MS_NODEV, 1}, + {"exec", MS_NOEXEC, 0}, + {"noexec", MS_NOEXEC, 1}, + {"async", MS_SYNCHRONOUS, 0}, + {"sync", MS_SYNCHRONOUS, 1}, + {"noatime", MS_NOATIME, 1}, + {"nodiratime", MS_NODIRATIME, 1}, + {"norelatime", MS_RELATIME, 0}, + {"nostrictatime", MS_STRICTATIME, 0}, + {"symfollow", MS_NOSYMFOLLOW, 0}, + {"nosymfollow", MS_NOSYMFOLLOW, 1}, +#ifndef __NetBSD__ + {"dirsync", MS_DIRSYNC, 1}, +#endif + {NULL, 0, 0} +}; + +unsigned get_max_read(struct mount_opts *o) +{ + return o->max_read; +} + +static void set_mount_flag(const char *s, int *flags) +{ + int i; + + for (i = 0; mount_flags[i].opt != NULL; i++) { + const char *opt = mount_flags[i].opt; + if (strcmp(opt, s) == 0) { + if (mount_flags[i].on) + *flags |= mount_flags[i].flag; + else + *flags &= ~mount_flags[i].flag; + return; + } + } + fuse_log(FUSE_LOG_ERR, "fuse: internal error, can't find mount flag\n"); + abort(); +} + +static int fuse_mount_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) outargs; + struct mount_opts *mo = data; + + switch (key) { + case KEY_RO: + arg = "ro"; + /* fall through */ + case KEY_KERN_FLAG: + set_mount_flag(arg, &mo->flags); + return 0; + + case KEY_KERN_OPT: + return fuse_opt_add_opt(&mo->kernel_opts, arg); + + case KEY_FUSERMOUNT_OPT: + return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg); + + case KEY_SUBTYPE_OPT: + return fuse_opt_add_opt(&mo->subtype_opt, arg); + + case KEY_MTAB_OPT: + return fuse_opt_add_opt(&mo->mtab_opts, arg); + + /* Third party options like 'x-gvfs-notrash' */ + case FUSE_OPT_KEY_OPT: + return (strncmp("x-", arg, 2) == 0) ? + fuse_opt_add_opt(&mo->mtab_opts, arg) : + 1; + } + + /* Pass through unknown options */ + return 1; +} + +/* return value: + * >= 0 => fd + * -1 => error + */ +static int receive_fd(int fd) +{ + struct msghdr msg; + struct iovec iov; + char buf[1]; + int rv; + size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)]; + struct cmsghdr *cmsg; + + iov.iov_base = buf; + iov.iov_len = 1; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = 0; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + /* old BSD implementations should use msg_accrights instead of + * msg_control; the interface is different. */ + msg.msg_control = ccmsg; + msg.msg_controllen = sizeof(ccmsg); + + while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR); + if (rv == -1) { + fuse_log(FUSE_LOG_ERR, "recvmsg failed: %s", strerror(errno)); + return -1; + } + if(!rv) { + /* EOF */ + return -1; + } + + cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg->cmsg_type != SCM_RIGHTS) { + fuse_log(FUSE_LOG_ERR, "got control message of unknown type %d\n", + cmsg->cmsg_type); + return -1; + } + return *(int*)CMSG_DATA(cmsg); +} + +void fuse_kern_unmount(const char *mountpoint, int fd) +{ + int res; + + if (fd != -1) { + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = 0; + res = poll(&pfd, 1, 0); + + /* Need to close file descriptor, otherwise synchronous umount + would recurse into filesystem, and deadlock. + + Caller expects fuse_kern_unmount to close the fd, so close it + anyway. */ + close(fd); + + /* If file poll returns POLLERR on the device file descriptor, + then the filesystem is already unmounted or the connection + was severed via /sys/fs/fuse/connections/NNN/abort */ + if (res == 1 && (pfd.revents & POLLERR)) + return; + } + + if (geteuid() == 0) { + fuse_mnt_umount("fuse", mountpoint, mountpoint, 1); + return; + } + + res = umount2(mountpoint, 2); + if (res == 0) + return; + + char const * const argv[] = + { FUSERMOUNT_PROG, "--unmount", "--quiet", "--lazy", + "--", mountpoint, NULL }; + int status = fusermount_posix_spawn(NULL, argv, NULL); + if(status != 0) { + fuse_log(FUSE_LOG_ERR, "Spawning %s to unmount failed: %s", + FUSERMOUNT_PROG, strerror(-status)); + return; + } +} + +static int setup_auto_unmount(const char *mountpoint, int quiet) +{ + int fds[2]; + pid_t pid; + int res; + + if (!mountpoint) { + fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n"); + return -1; + } + + res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds); + if(res == -1) { + fuse_log(FUSE_LOG_ERR, "Setting up auto-unmount socketpair() failed: %s\n", + strerror(errno)); + return -1; + } + + char arg_fd_entry[30]; + snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]); + setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1); + /* + * This helps to identify the FD hold by parent process. + * In auto-unmount case, parent process can close this FD explicitly to do unmount. + * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV). + * One potential use case is to satisfy FD-Leak checks. + */ + snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]); + setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1); + + char const *const argv[] = { + FUSERMOUNT_PROG, + "--auto-unmount", + "--", + mountpoint, + NULL, + }; + + // TODO: add error handling for all manipulations of action. + posix_spawn_file_actions_t action; + posix_spawn_file_actions_init(&action); + + if (quiet) { + posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0); + posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0); + } + posix_spawn_file_actions_addclose(&action, fds[1]); + + /* + * auto-umount runs in the background - it is not waiting for the + * process + */ + int status = fusermount_posix_spawn(&action, argv, &pid); + + posix_spawn_file_actions_destroy(&action); + + if(status != 0) { + close(fds[0]); + close(fds[1]); + fuse_log(FUSE_LOG_ERR, "fuse: Setting up auto-unmount failed (spawn): %s", + strerror(-status)); + return -1; + } + // passed to child now, so can close here. + close(fds[0]); + + // Now fusermount3 will only exit when fds[1] closes automatically when our + // process exits. + return 0; + // Note: fds[1] is leakend and doesn't get FD_CLOEXEC +} + +static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo, + const char *opts, int quiet) +{ + int fds[2]; + pid_t pid; + int res; + + if (!mountpoint) { + fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n"); + return -1; + } + + res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds); + if(res == -1) { + fuse_log(FUSE_LOG_ERR, "Running %s: socketpair() failed: %s\n", + FUSERMOUNT_PROG, strerror(errno)); + return -1; + } + + char arg_fd_entry[30]; + snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]); + setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1); + /* + * This helps to identify the FD hold by parent process. + * In auto-unmount case, parent process can close this FD explicitly to do unmount. + * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV). + * One potential use case is to satisfy FD-Leak checks. + */ + snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]); + setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1); + + char const *const argv[] = { + FUSERMOUNT_PROG, + "-o", opts ? opts : "", + "--", + mountpoint, + NULL, + }; + + + posix_spawn_file_actions_t action; + posix_spawn_file_actions_init(&action); + + if (quiet) { + posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0); + posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0); + } + posix_spawn_file_actions_addclose(&action, fds[1]); + + int status = fusermount_posix_spawn(&action, argv, &pid); + + posix_spawn_file_actions_destroy(&action); + + if(status != 0) { + close(fds[0]); + close(fds[1]); + fuse_log(FUSE_LOG_ERR, "posix_spawn(p)() for %s failed: %s", + FUSERMOUNT_PROG, strerror(-status)); + return -1; + } + + // passed to child now, so can close here. + close(fds[0]); + + int fd = receive_fd(fds[1]); + + if (!mo->auto_unmount) { + /* with auto_unmount option fusermount3 will not exit until + this socket is closed */ + close(fds[1]); + waitpid(pid, NULL, 0); /* bury zombie */ + } + + if (fd >= 0) + fcntl(fd, F_SETFD, FD_CLOEXEC); + + return fd; +} + +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif + +static int fuse_mount_sys(const char *mnt, struct mount_opts *mo, + const char *mnt_opts) +{ + char tmp[128]; + const char *devname = getenv(FUSE_KERN_DEVICE_ENV) ?: "/dev/fuse"; + char *source = NULL; + char *type = NULL; + struct stat stbuf; + int fd; + int res; + + if (!mnt) { + fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n"); + return -1; + } + + res = stat(mnt, &stbuf); + if (res == -1) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to access mountpoint %s: %s\n", + mnt, strerror(errno)); + return -1; + } + + fd = open(devname, O_RDWR | O_CLOEXEC); + if (fd == -1) { + if (errno == ENODEV || errno == ENOENT) + fuse_log(FUSE_LOG_ERR, + "fuse: device %s not found. Kernel module not loaded?\n", + devname); + else + fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n", + devname, strerror(errno)); + return -1; + } + if (!O_CLOEXEC) + fcntl(fd, F_SETFD, FD_CLOEXEC); + + snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u", + fd, stbuf.st_mode & S_IFMT, getuid(), getgid()); + + res = fuse_opt_add_opt(&mo->kernel_opts, tmp); + if (res == -1) + goto out_close; + + source = malloc((mo->fsname ? strlen(mo->fsname) : 0) + + (mo->subtype ? strlen(mo->subtype) : 0) + + strlen(devname) + 32); + + type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32); + if (!type || !source) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate memory\n"); + goto out_close; + } + + strcpy(type, mo->blkdev ? "fuseblk" : "fuse"); + if (mo->subtype) { + strcat(type, "."); + strcat(type, mo->subtype); + } + strcpy(source, + mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname)); + + res = mount(source, mnt, type, mo->flags, mo->kernel_opts); + if (res == -1 && errno == ENODEV && mo->subtype) { + /* Probably missing subtype support */ + strcpy(type, mo->blkdev ? "fuseblk" : "fuse"); + if (mo->fsname) { + if (!mo->blkdev) + sprintf(source, "%s#%s", mo->subtype, + mo->fsname); + } else { + strcpy(source, type); + } + res = mount(source, mnt, type, mo->flags, mo->kernel_opts); + } + if (res == -1) { + /* + * Maybe kernel doesn't support unprivileged mounts, in this + * case try falling back to fusermount3 + */ + if (errno == EPERM) { + res = -2; + } else { + int errno_save = errno; + if (mo->blkdev && errno == ENODEV && + !fuse_mnt_check_fuseblk()) + fuse_log(FUSE_LOG_ERR, + "fuse: 'fuseblk' support missing\n"); + else + fuse_log(FUSE_LOG_ERR, "fuse: mount failed: %s\n", + strerror(errno_save)); + } + + goto out_close; + } + +#ifndef IGNORE_MTAB + if (geteuid() == 0) { + char *newmnt = fuse_mnt_resolve_path("fuse", mnt); + res = -1; + if (!newmnt) + goto out_umount; + + res = fuse_mnt_add_mount("fuse", source, newmnt, type, + mnt_opts); + free(newmnt); + if (res == -1) + goto out_umount; + } +#endif /* IGNORE_MTAB */ + free(type); + free(source); + + return fd; + +out_umount: + umount2(mnt, 2); /* lazy umount */ +out_close: + free(type); + free(source); + close(fd); + return res; +} + +static int get_mnt_flag_opts(char **mnt_optsp, int flags) +{ + int i; + + if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1) + return -1; + + for (i = 0; mount_flags[i].opt != NULL; i++) { + if (mount_flags[i].on && (flags & mount_flags[i].flag) && + fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1) + return -1; + } + return 0; +} + +struct mount_opts *parse_mount_opts(struct fuse_args *args) +{ + struct mount_opts *mo; + + mo = (struct mount_opts*) malloc(sizeof(struct mount_opts)); + if (mo == NULL) + return NULL; + + memset(mo, 0, sizeof(struct mount_opts)); + mo->flags = MS_NOSUID | MS_NODEV; + + if (args && + fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1) + goto err_out; + + return mo; + +err_out: + destroy_mount_opts(mo); + return NULL; +} + +void destroy_mount_opts(struct mount_opts *mo) +{ + free(mo->fsname); + free(mo->subtype); + free(mo->fusermount_opts); + free(mo->subtype_opt); + free(mo->kernel_opts); + free(mo->mtab_opts); + free(mo); +} + + +int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo) +{ + int res = -1; + char *mnt_opts = NULL; + + res = -1; + if (get_mnt_flag_opts(&mnt_opts, mo->flags) == -1) + goto out; + if (mo->kernel_opts && fuse_opt_add_opt(&mnt_opts, mo->kernel_opts) == -1) + goto out; + if (mo->mtab_opts && fuse_opt_add_opt(&mnt_opts, mo->mtab_opts) == -1) + goto out; + + res = fuse_mount_sys(mountpoint, mo, mnt_opts); + if (res >= 0 && mo->auto_unmount) { + if(0 > setup_auto_unmount(mountpoint, 0)) { + // Something went wrong, let's umount like in fuse_mount_sys. + umount2(mountpoint, MNT_DETACH); /* lazy umount */ + res = -1; + } + } else if (res == -2) { + if (mo->fusermount_opts && + fuse_opt_add_opt(&mnt_opts, mo->fusermount_opts) == -1) + goto out; + + if (mo->subtype) { + char *tmp_opts = NULL; + + res = -1; + if (fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1 || + fuse_opt_add_opt(&tmp_opts, mo->subtype_opt) == -1) { + free(tmp_opts); + goto out; + } + + res = fuse_mount_fusermount(mountpoint, mo, tmp_opts, 1); + free(tmp_opts); + if (res == -1) + res = fuse_mount_fusermount(mountpoint, mo, + mnt_opts, 0); + } else { + res = fuse_mount_fusermount(mountpoint, mo, mnt_opts, 0); + } + } +out: + free(mnt_opts); + return res; +} diff --git a/lib/mount_bsd.c b/lib/mount_bsd.c new file mode 100644 index 0000000..c12ab32 --- /dev/null +++ b/lib/mount_bsd.c @@ -0,0 +1,269 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2005-2008 Csaba Henk + + Architecture specific file system mounting (FreeBSD). + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_misc.h" +#include "fuse_opt.h" +#include "util.h" + +#include +#include "fuse_mount_compat.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define FUSERMOUNT_PROG "mount_fusefs" +#define FUSE_DEV_TRUNK "/dev/fuse" + +enum { + KEY_RO, + KEY_KERN +}; + +struct mount_opts { + int allow_other; + char *kernel_opts; + unsigned max_read; +}; + +#define FUSE_DUAL_OPT_KEY(templ, key) \ + FUSE_OPT_KEY(templ, key), FUSE_OPT_KEY("no" templ, key) + +static const struct fuse_opt fuse_mount_opts[] = { + { "allow_other", offsetof(struct mount_opts, allow_other), 1 }, + { "max_read=%u", offsetof(struct mount_opts, max_read), 1 }, + FUSE_OPT_KEY("-r", KEY_RO), + /* standard FreeBSD mount options */ + FUSE_DUAL_OPT_KEY("dev", KEY_KERN), + FUSE_DUAL_OPT_KEY("async", KEY_KERN), + FUSE_DUAL_OPT_KEY("atime", KEY_KERN), + FUSE_DUAL_OPT_KEY("dev", KEY_KERN), + FUSE_DUAL_OPT_KEY("exec", KEY_KERN), + FUSE_DUAL_OPT_KEY("suid", KEY_KERN), + FUSE_DUAL_OPT_KEY("symfollow", KEY_KERN), + FUSE_DUAL_OPT_KEY("rdonly", KEY_KERN), + FUSE_DUAL_OPT_KEY("sync", KEY_KERN), + FUSE_DUAL_OPT_KEY("union", KEY_KERN), + FUSE_DUAL_OPT_KEY("userquota", KEY_KERN), + FUSE_DUAL_OPT_KEY("groupquota", KEY_KERN), + FUSE_DUAL_OPT_KEY("clusterr", KEY_KERN), + FUSE_DUAL_OPT_KEY("clusterw", KEY_KERN), + FUSE_DUAL_OPT_KEY("suiddir", KEY_KERN), + FUSE_DUAL_OPT_KEY("snapshot", KEY_KERN), + FUSE_DUAL_OPT_KEY("multilabel", KEY_KERN), + FUSE_DUAL_OPT_KEY("acls", KEY_KERN), + FUSE_DUAL_OPT_KEY("force", KEY_KERN), + FUSE_DUAL_OPT_KEY("update", KEY_KERN), + FUSE_DUAL_OPT_KEY("ro", KEY_KERN), + FUSE_DUAL_OPT_KEY("rw", KEY_KERN), + FUSE_DUAL_OPT_KEY("auto", KEY_KERN), + FUSE_DUAL_OPT_KEY("automounted", KEY_KERN), + /* options supported under both Linux and FBSD */ + FUSE_DUAL_OPT_KEY("allow_other", KEY_KERN), + FUSE_DUAL_OPT_KEY("default_permissions",KEY_KERN), + FUSE_OPT_KEY("max_read=", KEY_KERN), + FUSE_OPT_KEY("subtype=", KEY_KERN), + /* FBSD FUSE specific mount options */ + FUSE_DUAL_OPT_KEY("private", KEY_KERN), + FUSE_DUAL_OPT_KEY("neglect_shares", KEY_KERN), + FUSE_DUAL_OPT_KEY("push_symlinks_in", KEY_KERN), +#if __FreeBSD_version >= 1200519 + FUSE_DUAL_OPT_KEY("intr", KEY_KERN), +#endif + /* stock FBSD mountopt parsing routine lets anything be negated... */ + /* + * Linux specific mount options, but let just the mount util + * handle them + */ + FUSE_OPT_KEY("fsname=", KEY_KERN), + FUSE_OPT_END +}; + +void fuse_mount_version(void) +{ + system(FUSERMOUNT_PROG " --version"); +} + +unsigned get_max_read(struct mount_opts *o) +{ + return o->max_read; +} + +static int fuse_mount_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) outargs; + struct mount_opts *mo = data; + + switch (key) { + case KEY_RO: + arg = "ro"; + /* fall through */ + + case KEY_KERN: + return fuse_opt_add_opt(&mo->kernel_opts, arg); + } + + /* Pass through unknown options */ + return 1; +} + +void fuse_kern_unmount(const char *mountpoint, int fd) +{ + if (close(fd) < 0) + fuse_log(FUSE_LOG_ERR, "closing FD %d failed: %s", fd, strerror(errno)); + if (unmount(mountpoint, MNT_FORCE) < 0) + fuse_log(FUSE_LOG_ERR, "unmounting %s failed: %s", + mountpoint, strerror(errno)); +} + +static int fuse_mount_core(const char *mountpoint, const char *opts) +{ + const char *mountprog = FUSERMOUNT_PROG; + long fd; + char *fdnam, *dev; + pid_t pid, cpid; + int status; + int err; + + fdnam = getenv("FUSE_DEV_FD"); + + if (fdnam) { + err = libfuse_strtol(fdnam, &fd); + if (err || fd < 0) { + fuse_log(FUSE_LOG_ERR, "invalid value given in FUSE_DEV_FD\n"); + return -1; + } + + goto mount; + } + + dev = getenv("FUSE_DEV_NAME"); + + if (! dev) + dev = (char *)FUSE_DEV_TRUNK; + + if ((fd = open(dev, O_RDWR)) < 0) { + perror("fuse: failed to open fuse device"); + return -1; + } + +mount: + if (getenv("FUSE_NO_MOUNT") || ! mountpoint) + goto out; + + pid = fork(); + cpid = pid; + + if (pid == -1) { + perror("fuse: fork() failed"); + close(fd); + return -1; + } + + if (pid == 0) { + pid = fork(); + + if (pid == -1) { + perror("fuse: fork() failed"); + close(fd); + _exit(EXIT_FAILURE); + } + + if (pid == 0) { + const char *argv[32]; + int a = 0; + int ret = -1; + + if (! fdnam) + { + ret = asprintf(&fdnam, "%ld", fd); + if(ret == -1) + { + perror("fuse: failed to assemble mount arguments"); + close(fd); + _exit(EXIT_FAILURE); + } + } + + argv[a++] = mountprog; + if (opts) { + argv[a++] = "-o"; + argv[a++] = opts; + } + argv[a++] = fdnam; + argv[a++] = mountpoint; + argv[a++] = NULL; + execvp(mountprog, (char **) argv); + perror("fuse: failed to exec mount program"); + free(fdnam); + _exit(EXIT_FAILURE); + } + + waitpid(pid, &status, 0); + if (!WIFEXITED(status)) + _exit(EXIT_FAILURE); + _exit(WEXITSTATUS(status)); + } + + if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) { + perror("fuse: failed to mount file system"); + if (close(fd) < 0) + perror("fuse: closing FD"); + return -1; + } + +out: + return fd; +} + +struct mount_opts *parse_mount_opts(struct fuse_args *args) +{ + struct mount_opts *mo; + + mo = (struct mount_opts*) malloc(sizeof(struct mount_opts)); + if (mo == NULL) + return NULL; + + memset(mo, 0, sizeof(struct mount_opts)); + + if (args && + fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1) + goto err_out; + + return mo; + +err_out: + destroy_mount_opts(mo); + return NULL; +} + +void destroy_mount_opts(struct mount_opts *mo) +{ + free(mo->kernel_opts); + free(mo); +} + +int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo) +{ + /* mount util should not try to spawn the daemon */ + setenv("MOUNT_FUSEFS_SAFE", "1", 1); + /* to notify the mount util it's called from lib */ + setenv("MOUNT_FUSEFS_CALL_BY_LIB", "1", 1); + + return fuse_mount_core(mountpoint, mo->kernel_opts); +} diff --git a/lib/mount_util.c b/lib/mount_util.c new file mode 100644 index 0000000..8c0cdf7 --- /dev/null +++ b/lib/mount_util.c @@ -0,0 +1,377 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Architecture-independent mounting code. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#include "fuse_config.h" +#include "mount_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if !defined( __NetBSD__) && !defined(__FreeBSD__) && !defined(__DragonFly__) && !defined(__ANDROID__) +#include +#else +#define IGNORE_MTAB +#endif +#include +#include + +#include "fuse_mount_compat.h" + +#include + +#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__) +#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0) +#endif + +#ifdef IGNORE_MTAB +#define mtab_needs_update(mnt) 0 +#else +static int mtab_needs_update(const char *mnt) +{ + int res; + struct stat stbuf; + + /* If mtab is within new mount, don't touch it */ + if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 && + _PATH_MOUNTED[strlen(mnt)] == '/') + return 0; + + /* + * Skip mtab update if /etc/mtab: + * + * - doesn't exist, + * - is on a read-only filesystem. + */ + res = lstat(_PATH_MOUNTED, &stbuf); + if (res == -1) { + if (errno == ENOENT) + return 0; + } else { + uid_t ruid; + int err; + + ruid = getuid(); + if (ruid != 0) + setreuid(0, -1); + + res = access(_PATH_MOUNTED, W_OK); + err = (res == -1) ? errno : 0; + if (ruid != 0) + setreuid(ruid, -1); + + if (err == EROFS) + return 0; + + res = access("/run/mount/utab", F_OK); + if (res == -1) + return 0; + } + + return 1; +} +#endif /* IGNORE_MTAB */ + +static int add_mount(const char *progname, const char *fsname, + const char *mnt, const char *type, const char *opts) +{ + int res; + int status; + sigset_t blockmask; + sigset_t oldmask; + + sigemptyset(&blockmask); + sigaddset(&blockmask, SIGCHLD); + res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask); + if (res == -1) { + fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno)); + return -1; + } + + res = fork(); + if (res == -1) { + fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); + goto out_restore; + } + if (res == 0) { + char *env = NULL; + + sigprocmask(SIG_SETMASK, &oldmask, NULL); + + if(setuid(geteuid()) == -1) { + fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno)); + res = -1; + goto out_restore; + } + + execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i", + "-f", "-t", type, "-o", opts, fsname, mnt, NULL, &env); + fprintf(stderr, "%s: failed to execute /bin/mount: %s\n", + progname, strerror(errno)); + exit(1); + } + res = waitpid(res, &status, 0); + if (res == -1) + fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno)); + + if (status != 0) + res = -1; + + out_restore: + sigprocmask(SIG_SETMASK, &oldmask, NULL); + + return res; +} + +int fuse_mnt_add_mount(const char *progname, const char *fsname, + const char *mnt, const char *type, const char *opts) +{ + if (!mtab_needs_update(mnt)) + return 0; + + return add_mount(progname, fsname, mnt, type, opts); +} + +static int exec_umount(const char *progname, const char *rel_mnt, int lazy) +{ + int res; + int status; + sigset_t blockmask; + sigset_t oldmask; + + sigemptyset(&blockmask); + sigaddset(&blockmask, SIGCHLD); + res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask); + if (res == -1) { + fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno)); + return -1; + } + + res = fork(); + if (res == -1) { + fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); + goto out_restore; + } + if (res == 0) { + char *env = NULL; + + sigprocmask(SIG_SETMASK, &oldmask, NULL); + + if(setuid(geteuid()) == -1) { + fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno)); + res = -1; + goto out_restore; + } + + if (lazy) { + execle("/bin/umount", "/bin/umount", "-i", rel_mnt, + "-l", NULL, &env); + } else { + execle("/bin/umount", "/bin/umount", "-i", rel_mnt, + NULL, &env); + } + fprintf(stderr, "%s: failed to execute /bin/umount: %s\n", + progname, strerror(errno)); + exit(1); + } + res = waitpid(res, &status, 0); + if (res == -1) + fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno)); + + if (status != 0) { + res = -1; + } + + out_restore: + sigprocmask(SIG_SETMASK, &oldmask, NULL); + return res; + +} + +int fuse_mnt_umount(const char *progname, const char *abs_mnt, + const char *rel_mnt, int lazy) +{ + int res; + + if (!mtab_needs_update(abs_mnt)) { + res = umount2(rel_mnt, lazy ? 2 : 0); + if (res == -1) + fprintf(stderr, "%s: failed to unmount %s: %s\n", + progname, abs_mnt, strerror(errno)); + return res; + } + + return exec_umount(progname, rel_mnt, lazy); +} + +static int remove_mount(const char *progname, const char *mnt) +{ + int res; + int status; + sigset_t blockmask; + sigset_t oldmask; + + sigemptyset(&blockmask); + sigaddset(&blockmask, SIGCHLD); + res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask); + if (res == -1) { + fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno)); + return -1; + } + + res = fork(); + if (res == -1) { + fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); + goto out_restore; + } + if (res == 0) { + char *env = NULL; + + sigprocmask(SIG_SETMASK, &oldmask, NULL); + + if(setuid(geteuid()) == -1) { + fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno)); + res = -1; + goto out_restore; + } + + execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i", + "--fake", mnt, NULL, &env); + fprintf(stderr, "%s: failed to execute /bin/umount: %s\n", + progname, strerror(errno)); + exit(1); + } + res = waitpid(res, &status, 0); + if (res == -1) + fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno)); + + if (status != 0) + res = -1; + + out_restore: + sigprocmask(SIG_SETMASK, &oldmask, NULL); + return res; +} + +int fuse_mnt_remove_mount(const char *progname, const char *mnt) +{ + if (!mtab_needs_update(mnt)) + return 0; + + return remove_mount(progname, mnt); +} + +char *fuse_mnt_resolve_path(const char *progname, const char *orig) +{ + char buf[PATH_MAX]; + char *copy; + char *dst; + char *end; + char *lastcomp; + const char *toresolv; + + if (!orig[0]) { + fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname, + orig); + return NULL; + } + + copy = strdup(orig); + if (copy == NULL) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return NULL; + } + + toresolv = copy; + lastcomp = NULL; + for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --); + if (end[0] != '/') { + char *tmp; + end[1] = '\0'; + tmp = strrchr(copy, '/'); + if (tmp == NULL) { + lastcomp = copy; + toresolv = "."; + } else { + lastcomp = tmp + 1; + if (tmp == copy) + toresolv = "/"; + } + if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) { + lastcomp = NULL; + toresolv = copy; + } + else if (tmp) + tmp[0] = '\0'; + } + if (realpath(toresolv, buf) == NULL) { + fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig, + strerror(errno)); + free(copy); + return NULL; + } + if (lastcomp == NULL) + dst = strdup(buf); + else { + dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1); + if (dst) { + unsigned buflen = strlen(buf); + if (buflen && buf[buflen-1] == '/') + sprintf(dst, "%s%s", buf, lastcomp); + else + sprintf(dst, "%s/%s", buf, lastcomp); + } + } + free(copy); + if (dst == NULL) + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return dst; +} + +int fuse_mnt_check_fuseblk(void) +{ + char buf[256]; + FILE *f = fopen("/proc/filesystems", "r"); + if (!f) + return 1; + + while (fgets(buf, sizeof(buf), f)) + if (strstr(buf, "fuseblk\n")) { + fclose(f); + return 1; + } + + fclose(f); + return 0; +} + +int fuse_mnt_parse_fuse_fd(const char *mountpoint) +{ + int fd = -1; + int len = 0; + + if (mountpoint == NULL) { + fprintf(stderr, "Invalid null-ptr mount-point!\n"); + return -1; + } + + if (sscanf(mountpoint, "/dev/fd/%u%n", &fd, &len) == 1 && + len == strlen(mountpoint)) { + return fd; + } + + return -1; +} diff --git a/lib/mount_util.h b/lib/mount_util.h new file mode 100644 index 0000000..9cb9077 --- /dev/null +++ b/lib/mount_util.h @@ -0,0 +1,18 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#include + +int fuse_mnt_add_mount(const char *progname, const char *fsname, + const char *mnt, const char *type, const char *opts); +int fuse_mnt_remove_mount(const char *progname, const char *mnt); +int fuse_mnt_umount(const char *progname, const char *abs_mnt, + const char *rel_mnt, int lazy); +char *fuse_mnt_resolve_path(const char *progname, const char *orig); +int fuse_mnt_check_fuseblk(void); +int fuse_mnt_parse_fuse_fd(const char *mountpoint); diff --git a/lib/usdt.h b/lib/usdt.h new file mode 100644 index 0000000..6a0bc42 --- /dev/null +++ b/lib/usdt.h @@ -0,0 +1,540 @@ +/* + * Copied from https://github.com/libbpf/usdt/ + */ + +// SPDX-License-Identifier: BSD-2-Clause +/* + * This single-header library defines a collection of variadic macros for + * defining and triggering USDTs (User Statically-Defined Tracepoints): + * + * - For USDTs without associated semaphore: + * USDT(group, name, args...) + * + * - For USDTs with implicit (transparent to the user) semaphore: + * USDT_WITH_SEMA(group, name, args...) + * USDT_IS_ACTIVE(group, name) + * + * - For USDTs with explicit (user-defined and provided) semaphore: + * USDT_WITH_EXPLICIT_SEMA(sema, group, name, args...) + * USDT_SEMA_IS_ACTIVE(sema) + * + * all of which emit a NOP instruction into the instruction stream, and so + * have *zero* overhead for the surrounding code. USDTs are identified by + * a combination of `group` and `name` identifiers, which is used by external + * tracing tooling (tracers) for identifying exact USDTs of interest. + * + * USDTs can have an associated (2-byte) activity counter (USDT semaphore), + * automatically maintained by Linux kernel whenever any correctly written + * BPF-based tracer is attached to the USDT. This USDT semaphore can be used + * to check whether there is a need to do any extra data collection and + * processing for a given USDT (if necessary), and otherwise avoid extra work + * for a common case of USDT not being traced ("active"). + * + * See documentation for USDT_WITH_SEMA()/USDT_IS_ACTIVE() or + * USDT_WITH_EXPLICIT_SEMA()/USDT_SEMA_IS_ACTIVE() APIs below for details on + * working with USDTs with implicitly or explicitly associated + * USDT semaphores, respectively. + * + * There is also some additional data recorded into an auxiliary note + * section. The data in the note section describes the operands, in terms of + * size and location, used by tracing tooling to know where to find USDT + * arguments. Each location is encoded as an assembler operand string. + * Tracing tools (bpftrace and BPF-based tracers, systemtap, etc) insert + * breakpoints on top of the nop, and decode the location operand-strings, + * like an assembler, to find the values being passed. + * + * The operand strings are selected by the compiler for each operand. + * They are constrained by inline-assembler codes.The default is: + * + * #define USDT_ARG_CONSTRAINT nor + * + * This is a good default if the operands tend to be integral and + * moderate in number (smaller than number of registers). In other + * cases, the compiler may report "'asm' requires impossible reload" or + * similar. In this case, consider simplifying the macro call (fewer + * and simpler operands), reduce optimization, or override the default + * constraints string via: + * + * #define USDT_ARG_CONSTRAINT g + * #include + * + * For some historical description of USDT v3 format (the one used by this + * library and generally recognized and assumed by BPF-based tracing tools) + * see [0]. The more formal specification can be found at [1]. Additional + * argument constraints information can be found at [2]. + * + * Original SystemTap's sys/sdt.h implementation ([3]) was used as a base for + * this USDT library implementation. Current implementation differs *a lot* in + * terms of exposed user API and general usability, which was the main goal + * and focus of the reimplementation work. Nevertheless, underlying recorded + * USDT definitions are fully binary compatible and any USDT-based tooling + * should work equally well with USDTs defined by either SystemTap's or this + * library's USDT implementation. + * + * [0] https://ecos.sourceware.org/ml/systemtap/2010-q3/msg00145.html + * [1] https://sourceware.org/systemtap/wiki/UserSpaceProbeImplementation + * [2] https://gcc.gnu.org/onlinedocs/gcc/Constraints.html + * [3] https://sourceware.org/git/?p=systemtap.git;a=blob;f=includes/sys/sdt.h + */ +#ifndef __USDT_H +#define __USDT_H + +/* + * Changelog: + * + * 0.1.0 + * ----- + * - Initial release + */ +#define USDT_MAJOR_VERSION 0 +#define USDT_MINOR_VERSION 1 +#define USDT_PATCH_VERSION 0 + +/* C++20 and C23 added __VA_OPT__ as a standard replacement for non-standard `##__VA_ARGS__` extension */ +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ > 201710L) || (defined(__cplusplus) && __cplusplus > 201703L) +#define __usdt_va_opt 1 +#define __usdt_va_args(...) __VA_OPT__(,) __VA_ARGS__ +#else +#define __usdt_va_args(...) , ##__VA_ARGS__ +#endif + +/* + * Trigger USDT with `group`:`name` identifier and pass through `args` as its + * arguments. Zero arguments are acceptable as well. No USDT semaphore is + * associated with this USDT. + * + * Such "semaphoreless" USDTs are commonly used when there is no extra data + * collection or processing needed to collect and prepare USDT arguments and + * they are just available in the surrounding code. USDT() macro will just + * record their locations in CPU registers or in memory for tracing tooling to + * be able to access them, if necessary. + */ +#ifdef __usdt_va_opt +#define USDT(group, name, ...) \ + __usdt_probe(group, name, __usdt_sema_none, 0 __VA_OPT__(,) __VA_ARGS__) +#else +#define USDT(group, name, ...) \ + __usdt_probe(group, name, __usdt_sema_none, 0, ##__VA_ARGS__) +#endif + +/* + * Trigger USDT with `group`:`name` identifier and pass through `args` as its + * arguments. Zero arguments are acceptable as well. USDT also get an + * implicitly-defined associated USDT semaphore, which will be "activated" by + * tracing tooling and can be used to check whether USDT is being actively + * observed. + * + * USDTs with semaphore are commonly used when there is a need to perform + * additional data collection and processing to prepare USDT arguments, which + * otherwise might not be necessary for the rest of application logic. In such + * case, USDT semaphore can be used to avoid unnecessary extra work. If USDT + * is not traced (which is presumed to be a common situation), the associated + * USDT semaphore is "inactive", and so there is no need to waste resources to + * prepare USDT arguments. Use USDT_IS_ACTIVE(group, name) to check whether + * USDT is "active". + * + * N.B. There is an inherent (albeit short) gap between checking whether USDT + * is active and triggering corresponding USDT, in which external tracer can + * be attached to an USDT and activate USDT semaphore after the activity check. + * If such a race occurs, tracers might miss one USDT execution. Tracers are + * expected to accommodate such possibility and this is expected to not be + * a problem for applications and tracers. + * + * N.B. Implicit USDT semaphore defined by USDT_WITH_SEMA() is contained + * within a single executable or shared library and is not shared outside + * them. I.e., if you use USDT_WITH_SEMA() with the same USDT group and name + * identifier across executable and shared library, it will work and won't + * conflict, per se, but will define independent USDT semaphores, one for each + * shared library/executable in which USDT_WITH_SEMA(group, name) is used. + * That is, if you attach to this USDT in one shared library (or executable), + * then only USDT semaphore within that shared library (or executable) will be + * updated by the kernel, while other libraries (or executable) will not see + * activated USDT semaphore. In short, it's best to use unique USDT group:name + * identifiers across different shared libraries (and, equivalently, between + * executable and shared library). This is advanced consideration and is + * rarely (if ever) seen in practice, but just to avoid surprises this is + * called out here. (Static libraries become a part of final executable, once + * linked by linker, so the above considerations don't apply to them.) + */ +#ifdef __usdt_va_opt +#define USDT_WITH_SEMA(group, name, ...) \ + __usdt_probe(group, name, \ + __usdt_sema_implicit, __usdt_sema_name(group, name) \ + __VA_OPT__(,) __VA_ARGS__) +#else +#define USDT_WITH_SEMA(group, name, ...) \ + __usdt_probe(group, name, \ + __usdt_sema_implicit, __usdt_sema_name(group, name), \ + ##__VA_ARGS__) +#endif + +struct usdt_sema { volatile unsigned short active; }; + +/* + * Check if USDT with `group`:`name` identifier is "active" (i.e., whether it + * is attached to by external tracing tooling and is actively observed). + * + * This macro can be used to decide whether any additional and potentially + * expensive data collection or processing should be done to pass extra + * information into the given USDT. It is assumed that USDT is triggered with + * USDT_WITH_SEMA() macro which will implicitly define associated USDT + * semaphore. (If one needs more control over USDT semaphore, see + * USDT_DEFINE_SEMA() and USDT_WITH_EXPLICIT_SEMA() macros below.) + * + * N.B. Such checks are necessarily racy and speculative. Between checking + * whether USDT is active and triggering the USDT itself, tracer can be + * detached with no notification. This race should be extremely rare and worst + * case should result in one-time wasted extra data collection and processing. + */ +#define USDT_IS_ACTIVE(group, name) ({ \ + extern struct usdt_sema __usdt_sema_name(group, name) \ + __usdt_asm_name(__usdt_sema_name(group, name)); \ + __usdt_sema_implicit(__usdt_sema_name(group, name)); \ + __usdt_sema_name(group, name).active > 0; \ +}) + +/* + * APIs for working with user-defined explicit USDT semaphores. + * + * This is a less commonly used advanced API for use cases in which user needs + * an explicit control over (potentially shared across multiple USDTs) USDT + * semaphore instance. This can be used when there is a group of logically + * related USDTs that all need extra data collection and processing whenever + * any of a family of related USDTs are "activated" (i.e., traced). In such + * a case, all such related USDTs will be associated with the same shared USDT + * semaphore defined with USDT_DEFINE_SEMA() and the USDTs themselves will be + * triggered with USDT_WITH_EXPLICIT_SEMA() macros, taking an explicit extra + * USDT semaphore identifier as an extra parameter. + */ + +/** + * Underlying C global variable name for user-defined USDT semaphore with + * `sema` identifier. Could be useful for debugging, but normally shouldn't be + * used explicitly. + */ +#define USDT_SEMA(sema) __usdt_sema_##sema + +/* + * Define storage for user-defined USDT semaphore `sema`. + * + * Should be used only once in non-header source file to let compiler allocate + * space for the semaphore variable. Just like with any other global variable. + * + * This macro can be used anywhere where global variable declaration is + * allowed. Just like with global variable definitions, there should be only + * one definition of user-defined USDT semaphore with given `sema` identifier, + * otherwise compiler or linker will complain about duplicate variable + * definition. + * + * For C++, it is allowed to use USDT_DEFINE_SEMA() both in global namespace + * and inside namespaces (including nested namespaces). Just make sure that + * USDT_DECLARE_SEMA() is placed within the namespace where this semaphore is + * referenced, or any of its parent namespaces, so the C++ language-level + * identifier is visible to the code that needs to reference the semaphore. + * At the lowest layer, USDT semaphores have global naming and visibility + * (they have a corresponding `__usdt_sema_` symbol, which can be linked + * against from C or C++ code, if necessary). To keep it simple, putting + * USDT_DECLARE_SEMA() declarations into global namespaces is the simplest + * no-brainer solution. All these aspects are irrelevant for plain C, because + * C doesn't have namespaces and everything is always in the global namespace. + * + * N.B. Due to USDT metadata being recorded in non-allocatable ELF note + * section, it has limitations when it comes to relocations, which, in + * practice, means that it's not possible to correctly share USDT semaphores + * between main executable and shared libraries, or even between multiple + * shared libraries. USDT semaphore has to be contained to individual shared + * library or executable to avoid unpleasant surprises with half-working USDT + * semaphores. We enforce this by marking semaphore ELF symbols as having + * a hidden visibility. This is quite an advanced use case and consideration + * and for most users this should have no consequences whatsoever. + */ +#define USDT_DEFINE_SEMA(sema) \ + struct usdt_sema __usdt_sema_sec USDT_SEMA(sema) \ + __usdt_asm_name(USDT_SEMA(sema)) \ + __attribute__((visibility("hidden"))) = { 0 } + +/* + * Declare extern reference to user-defined USDT semaphore `sema`. + * + * Refers to a variable defined in another compilation unit by + * USDT_DEFINE_SEMA() and allows to use the same USDT semaphore across + * multiple compilation units (i.e., .c and .cpp files). + * + * See USDT_DEFINE_SEMA() notes above for C++ language usage peculiarities. + */ +#define USDT_DECLARE_SEMA(sema) \ + extern struct usdt_sema USDT_SEMA(sema) __usdt_asm_name(USDT_SEMA(sema)) + +/* + * Check if user-defined USDT semaphore `sema` is "active" (i.e., whether it + * is attached to by external tracing tooling and is actively observed). + * + * This macro can be used to decide whether any additional and potentially + * expensive data collection or processing should be done to pass extra + * information into USDT(s) associated with USDT semaphore `sema`. + * + * N.B. Such checks are necessarily racy. Between checking the state of USDT + * semaphore and triggering associated USDT(s), the active tracer might attach + * or detach. This race should be extremely rare and worst case should result + * in one-time missed USDT event or wasted extra data collection and + * processing. USDT-using tracers should be written with this in mind and is + * not a concern of the application defining USDTs with associated semaphore. + */ +#define USDT_SEMA_IS_ACTIVE(sema) (USDT_SEMA(sema).active > 0) + +/* + * Invoke USDT specified by `group` and `name` identifiers and associate + * explicitly user-defined semaphore `sema` with it. Pass through `args` as + * USDT arguments. `args` are optional and zero arguments are acceptable. + * + * Semaphore is defined with the help of USDT_DEFINE_SEMA() macro and can be + * checked whether active with USDT_SEMA_IS_ACTIVE(). + */ +#ifdef __usdt_va_opt +#define USDT_WITH_EXPLICIT_SEMA(sema, group, name, ...) \ + __usdt_probe(group, name, __usdt_sema_explicit, USDT_SEMA(sema), ##__VA_ARGS__) +#else +#define USDT_WITH_EXPLICIT_SEMA(sema, group, name, ...) \ + __usdt_probe(group, name, __usdt_sema_explicit, USDT_SEMA(sema) __VA_OPT__(,) __VA_ARGS__) +#endif + +/* + * Adjustable implementation aspects + */ +#ifndef USDT_ARG_CONSTRAINT +#if defined __powerpc__ +#define USDT_ARG_CONSTRAINT nZr +#elif defined __arm__ +#define USDT_ARG_CONSTRAINT g +#elif defined __loongarch__ +#define USDT_ARG_CONSTRAINT nmr +#else +#define USDT_ARG_CONSTRAINT nor +#endif +#endif /* USDT_ARG_CONSTRAINT */ + +#ifndef USDT_NOP +#if defined(__ia64__) || defined(__s390__) || defined(__s390x__) +#define USDT_NOP nop 0 +#else +#define USDT_NOP nop +#endif +#endif /* USDT_NOP */ + +/* + * Implementation details + */ +/* USDT name for implicitly-defined USDT semaphore, derived from group:name */ +#define __usdt_sema_name(group, name) __usdt_sema_##group##__##name +/* ELF section into which USDT semaphores are put */ +#define __usdt_sema_sec __attribute__((section(".probes"))) + +#define __usdt_concat(a, b) a ## b +#define __usdt_apply(fn, n) __usdt_concat(fn, n) + +#ifndef __usdt_nth +#define __usdt_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, N, ...) N +#endif + +#ifndef __usdt_narg +#ifdef __usdt_va_opt +#define __usdt_narg(...) __usdt_nth(_ __VA_OPT__(,) __VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#else +#define __usdt_narg(...) __usdt_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#endif +#endif /* __usdt_narg */ + +#define __usdt_hash # +#define __usdt_str_(x) #x +#define __usdt_str(x) __usdt_str_(x) + +#ifndef __usdt_asm_name +#define __usdt_asm_name(name) __asm__(__usdt_str(name)) +#endif + +#define __usdt_asm1(a) __usdt_str(a) "\n" +#define __usdt_asm2(a,b) __usdt_str(a) "," __usdt_str(b) "\n" +#define __usdt_asm3(a,b,c) __usdt_str(a) "," __usdt_str(b) "," __usdt_str(c) "\n" +#define __usdt_asm5(a,b,c,d,e) __usdt_str(a) "," __usdt_str(b) "," __usdt_str(c) "," \ + __usdt_str(d) "," __usdt_str(e) "\n" + +#ifdef __LP64__ +#define __usdt_asm_addr .8byte +#else +#define __usdt_asm_addr .4byte +#endif + +#define __usdt_asm_strz_(x) __usdt_asm1(.asciz #x) +#define __usdt_asm_strz(x) __usdt_asm_strz_(x) +#define __usdt_asm_str_(x) __usdt_asm1(.ascii #x) +#define __usdt_asm_str(x) __usdt_asm_str_(x) + +/* "semaphoreless" USDT case */ +#ifndef __usdt_sema_none +#define __usdt_sema_none(sema) +#endif + +/* implicitly defined __usdt_sema__group__name semaphore (using weak symbols) */ +#ifndef __usdt_sema_implicit +#define __usdt_sema_implicit(sema) \ + __asm__ __volatile__ ( \ + __usdt_asm1(.ifndef sema) \ + __usdt_asm3( .pushsection .probes, "aw", "progbits") \ + __usdt_asm1( .weak sema) \ + __usdt_asm1( .hidden sema) \ + __usdt_asm1( .align 2) \ + __usdt_asm1(sema:) \ + __usdt_asm1( .zero 2) \ + __usdt_asm2( .type sema, @object) \ + __usdt_asm2( .size sema, 2) \ + __usdt_asm1( .popsection) \ + __usdt_asm1(.endif) \ + ); +#endif + +/* externally defined semaphore using USDT_DEFINE_SEMA() and passed explicitly by user */ +#ifndef __usdt_sema_explicit +#define __usdt_sema_explicit(sema) \ + __asm__ __volatile__ ("" :: "m" (sema)); +#endif + +/* main USDT definition (nop and .note.stapsdt metadata) */ +#define __usdt_probe(group, name, sema_def, sema, ...) do { \ + sema_def(sema) \ + __asm__ __volatile__ ( \ + __usdt_asm1(990: USDT_NOP) \ + __usdt_asm3( .pushsection .note.stapsdt, "", "note") \ + __usdt_asm1( .balign 4) \ + __usdt_asm3( .4byte 992f-991f,994f-993f,3) \ + __usdt_asm1(991: .asciz "stapsdt") \ + __usdt_asm1(992: .balign 4) \ + __usdt_asm1(993: __usdt_asm_addr 990b) \ + __usdt_asm1( __usdt_asm_addr _.stapsdt.base) \ + __usdt_asm1( __usdt_asm_addr sema) \ + __usdt_asm_strz(group) \ + __usdt_asm_strz(name) \ + __usdt_asm_args(__VA_ARGS__) \ + __usdt_asm1( .ascii "\0") \ + __usdt_asm1(994: .balign 4) \ + __usdt_asm1( .popsection) \ + __usdt_asm1(.ifndef _.stapsdt.base) \ + __usdt_asm5( .pushsection .stapsdt.base,"aG","progbits",.stapsdt.base,comdat)\ + __usdt_asm1( .weak _.stapsdt.base) \ + __usdt_asm1( .hidden _.stapsdt.base) \ + __usdt_asm1(_.stapsdt.base:) \ + __usdt_asm1( .space 1) \ + __usdt_asm2( .size _.stapsdt.base, 1) \ + __usdt_asm1( .popsection) \ + __usdt_asm1(.endif) \ + :: __usdt_asm_ops(__VA_ARGS__) \ + ); \ +} while (0) + +/* + * NB: gdb PR24541 highlighted an unspecified corner of the sdt.h + * operand note format. + * + * The named register may be a longer or shorter (!) alias for the + * storage where the value in question is found. For example, on + * i386, 64-bit value may be put in register pairs, and a register + * name stored would identify just one of them. Previously, gcc was + * asked to emit the %w[id] (16-bit alias of some registers holding + * operands), even when a wider 32-bit value was used. + * + * Bottom line: the byte-width given before the @ sign governs. If + * there is a mismatch between that width and that of the named + * register, then a sys/sdt.h note consumer may need to employ + * architecture-specific heuristics to figure out where the compiler + * has actually put the complete value. + */ +#if defined(__powerpc__) || defined(__powerpc64__) +#define __usdt_argref(id) %I[id]%[id] +#elif defined(__i386__) +#define __usdt_argref(id) %k[id] /* gcc.gnu.org/PR80115 sourceware.org/PR24541 */ +#else +#define __usdt_argref(id) %[id] +#endif + +#define __usdt_asm_arg(n) __usdt_asm_str(%c[__usdt_asz##n]) \ + __usdt_asm1(.ascii "@") \ + __usdt_asm_str(__usdt_argref(__usdt_aval##n)) + +#define __usdt_asm_args0 /* no arguments */ +#define __usdt_asm_args1 __usdt_asm_arg(1) +#define __usdt_asm_args2 __usdt_asm_args1 __usdt_asm1(.ascii " ") __usdt_asm_arg(2) +#define __usdt_asm_args3 __usdt_asm_args2 __usdt_asm1(.ascii " ") __usdt_asm_arg(3) +#define __usdt_asm_args4 __usdt_asm_args3 __usdt_asm1(.ascii " ") __usdt_asm_arg(4) +#define __usdt_asm_args5 __usdt_asm_args4 __usdt_asm1(.ascii " ") __usdt_asm_arg(5) +#define __usdt_asm_args6 __usdt_asm_args5 __usdt_asm1(.ascii " ") __usdt_asm_arg(6) +#define __usdt_asm_args7 __usdt_asm_args6 __usdt_asm1(.ascii " ") __usdt_asm_arg(7) +#define __usdt_asm_args8 __usdt_asm_args7 __usdt_asm1(.ascii " ") __usdt_asm_arg(8) +#define __usdt_asm_args9 __usdt_asm_args8 __usdt_asm1(.ascii " ") __usdt_asm_arg(9) +#define __usdt_asm_args10 __usdt_asm_args9 __usdt_asm1(.ascii " ") __usdt_asm_arg(10) +#define __usdt_asm_args11 __usdt_asm_args10 __usdt_asm1(.ascii " ") __usdt_asm_arg(11) +#define __usdt_asm_args12 __usdt_asm_args11 __usdt_asm1(.ascii " ") __usdt_asm_arg(12) +#define __usdt_asm_args(...) __usdt_apply(__usdt_asm_args, __usdt_narg(__VA_ARGS__)) + +#define __usdt_is_arr(x) (__builtin_classify_type(x) == 14 || __builtin_classify_type(x) == 5) +#define __usdt_arg_size(x) (__usdt_is_arr(x) ? sizeof(void *) : sizeof(x)) + +/* + * We can't use __builtin_choose_expr() in C++, so fall back to table-based + * signedness determination for known types, utilizing templates magic. + */ +#ifdef __cplusplus + +#define __usdt_is_signed(x) (!__usdt_is_arr(x) && __usdt_t<__typeof(x)>::is_signed) + +#include + +template struct __usdt_t { static const bool is_signed = false; }; +template struct __usdt_t : public __usdt_t {}; +template struct __usdt_t : public __usdt_t {}; + +#define __usdt_def_signed(T) \ +template<> struct __usdt_t { static const bool is_signed = true; }; \ +template<> struct __usdt_t { static const bool is_signed = true; }; \ +template<> struct __usdt_t { static const bool is_signed = true; }; \ +template<> struct __usdt_t { static const bool is_signed = true; } +#define __usdt_maybe_signed(T) \ +template<> struct __usdt_t { static const bool is_signed = (T)-1 < (T)1; }; \ +template<> struct __usdt_t { static const bool is_signed = (T)-1 < (T)1; }; \ +template<> struct __usdt_t { static const bool is_signed = (T)-1 < (T)1; }; \ +template<> struct __usdt_t { static const bool is_signed = (T)-1 < (T)1; } + +__usdt_def_signed(signed char); +__usdt_def_signed(short); +__usdt_def_signed(int); +__usdt_def_signed(long); +__usdt_def_signed(long long); +__usdt_maybe_signed(char); +__usdt_maybe_signed(wchar_t); + +#else /* !__cplusplus */ + +#define __usdt_is_inttype(x) (__builtin_classify_type(x) >= 1 && __builtin_classify_type(x) <= 4) +#define __usdt_inttype(x) __typeof(__builtin_choose_expr(__usdt_is_inttype(x), (x), 0U)) +#define __usdt_is_signed(x) ((__usdt_inttype(x))-1 < (__usdt_inttype(x))1) + +#endif /* __cplusplus */ + +#define __usdt_asm_op(n, x) \ + [__usdt_asz##n] "n" ((__usdt_is_signed(x) ? (int)-1 : 1) * (int)__usdt_arg_size(x)), \ + [__usdt_aval##n] __usdt_str(USDT_ARG_CONSTRAINT)(x) + +#define __usdt_asm_ops0() [__usdt_dummy] "g" (0) +#define __usdt_asm_ops1(x) __usdt_asm_op(1, x) +#define __usdt_asm_ops2(a,x) __usdt_asm_ops1(a), __usdt_asm_op(2, x) +#define __usdt_asm_ops3(a,b,x) __usdt_asm_ops2(a,b), __usdt_asm_op(3, x) +#define __usdt_asm_ops4(a,b,c,x) __usdt_asm_ops3(a,b,c), __usdt_asm_op(4, x) +#define __usdt_asm_ops5(a,b,c,d,x) __usdt_asm_ops4(a,b,c,d), __usdt_asm_op(5, x) +#define __usdt_asm_ops6(a,b,c,d,e,x) __usdt_asm_ops5(a,b,c,d,e), __usdt_asm_op(6, x) +#define __usdt_asm_ops7(a,b,c,d,e,f,x) __usdt_asm_ops6(a,b,c,d,e,f), __usdt_asm_op(7, x) +#define __usdt_asm_ops8(a,b,c,d,e,f,g,x) __usdt_asm_ops7(a,b,c,d,e,f,g), __usdt_asm_op(8, x) +#define __usdt_asm_ops9(a,b,c,d,e,f,g,h,x) __usdt_asm_ops8(a,b,c,d,e,f,g,h), __usdt_asm_op(9, x) +#define __usdt_asm_ops10(a,b,c,d,e,f,g,h,i,x) __usdt_asm_ops9(a,b,c,d,e,f,g,h,i), __usdt_asm_op(10, x) +#define __usdt_asm_ops11(a,b,c,d,e,f,g,h,i,j,x) __usdt_asm_ops10(a,b,c,d,e,f,g,h,i,j), __usdt_asm_op(11, x) +#define __usdt_asm_ops12(a,b,c,d,e,f,g,h,i,j,k,x) __usdt_asm_ops11(a,b,c,d,e,f,g,h,i,j,k), __usdt_asm_op(12, x) +#define __usdt_asm_ops(...) __usdt_apply(__usdt_asm_ops, __usdt_narg(__VA_ARGS__))(__VA_ARGS__) + +#endif /* __USDT_H */ diff --git a/lib/util.c b/lib/util.c new file mode 100644 index 0000000..2914f1c --- /dev/null +++ b/lib/util.c @@ -0,0 +1,53 @@ + +#include "fuse_config.h" + +#ifdef HAVE_PTHREAD_SETNAME_NP +#define _GNU_SOURCE +#include +#endif + +#include +#include +#include + +#ifndef FUSE_USE_VERSION +#define FUSE_USE_VERSION (FUSE_MAKE_VERSION(3, 18)) +#endif + +#include "util.h" +#include "fuse_log.h" +#include "fuse_lowlevel.h" +#include + +int libfuse_strtol(const char *str, long *res) +{ + char *endptr; + int base = 10; + long val; + + errno = 0; + + if (!str) + return -EINVAL; + + val = strtol(str, &endptr, base); + + if (errno) + return -errno; + + if (endptr == str || *endptr != '\0') + return -EINVAL; + + *res = val; + return 0; +} + +void fuse_set_thread_name(const char *name) +{ +#ifdef HAVE_PTHREAD_SETNAME_NP + pthread_setname_np(pthread_self(), name); +#else + (void)name; +#endif +} + diff --git a/lib/util.h b/lib/util.h new file mode 100644 index 0000000..107a2bf --- /dev/null +++ b/lib/util.h @@ -0,0 +1,49 @@ +#ifndef FUSE_UTIL_H_ +#define FUSE_UTIL_H_ + +#include +#include + +#define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1)) + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +struct fuse_conn_info; + +int libfuse_strtol(const char *str, long *res); +void fuse_set_thread_name(const char *name); + +/** + * Return the low bits of a number + */ +static inline uint32_t fuse_lower_32_bits(uint64_t nr) +{ + return (uint32_t)(nr & 0xffffffff); +} + +/** + * Return the high bits of a number + */ +static inline uint64_t fuse_higher_32_bits(uint64_t nr) +{ + return nr & ~0xffffffffULL; +} + +#ifndef FUSE_VAR_UNUSED +#define FUSE_VAR_UNUSED __attribute__((__unused__)) +#endif + +#define container_of(ptr, type, member) \ + ({ \ + unsigned long __mptr = (unsigned long)(ptr); \ + ((type *)(__mptr - offsetof(type, member))); \ + }) + +#if __has_attribute(__fallthrough__) +#define fallthrough __attribute__((__fallthrough__)) +#else +#define fallthrough do {} while (0) +#endif + +#endif /* FUSE_UTIL_H_ */ diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..f0570dc --- /dev/null +++ b/meson.build @@ -0,0 +1,332 @@ +project('libfuse3', ['c'], + version: '3.18.2', + meson_version: '>= 0.60.0', + default_options: [ + 'buildtype=debugoptimized', + 'c_std=gnu11', + 'cpp_std=c++20', + 'warning_level=2', + ]) + +# Would be better to create the version string +# from integers, i.e. concatenating strings instead +# of splitting a string, but 'project' needs to be +# the first meson.build keyword... + +# split version into base and rc +version_parts = meson.project_version().split('-') +base_version = version_parts[0] + +version_list = base_version.split('.') +FUSE_MAJOR_VERSION = version_list[0] +FUSE_MINOR_VERSION = version_list[1] +FUSE_HOTFIX_VERSION = version_list[2] +FUSE_RC_VERSION = version_parts.length() > 1 ? version_parts[1] : '' + +platform = host_machine.system() +if platform == 'darwin' + error('libfuse does not support OS-X.\n' + + 'Take a look at http://osxfuse.github.io/ or the more recent\n' + + 'https://www.fuse-t.org/ instead') +elif platform == 'cygwin' or platform == 'windows' + error('libfuse does not support Windows.\n' + + 'Take a look at http://www.secfs.net/winfsp/ instead') +endif + +cc = meson.get_compiler('c') + +# +# Feature detection, the resulting config file is installed +# with the package. +# Note: Symbols need to be care fully named, to avoid conflicts +# with applications linking to libfuse and including +# this config. +# +public_cfg = configuration_data() + +public_cfg.set('FUSE_MAJOR_VERSION', FUSE_MAJOR_VERSION) +public_cfg.set('FUSE_MINOR_VERSION', FUSE_MINOR_VERSION) +public_cfg.set('FUSE_HOTFIX_VERSION', FUSE_HOTFIX_VERSION) +public_cfg.set('FUSE_RC_VERSION', FUSE_RC_VERSION) + +# Default includes when checking for presence of functions and +# struct members +include_default = ''' +#include +#include +#include +#include +#include +#include +#include +#include /* For pthread_setname_np */ +''' +args_default = [ '-D_GNU_SOURCE' ] + +# +# Feature detection, only available at libfuse compilation time, +# but not for application linking to libfuse. +# +private_cfg = configuration_data() +private_cfg.set_quoted('PACKAGE_VERSION', meson.project_version()) + +# Test for presence of some functions +test_funcs = [ 'fork', 'fstatat', 'openat', 'readlinkat', 'pipe2', + 'splice', 'vmsplice', 'posix_fallocate', 'fdatasync', + 'utimensat', 'copy_file_range', 'fallocate', 'fspacectl' ] +foreach func : test_funcs + private_cfg.set('HAVE_' + func.to_upper(), + cc.has_function(func, prefix: include_default, args: args_default)) +endforeach + +# Special case checks that need custom code +special_funcs = { + 'pthread_setname_np': ''' + #include + int main(void) { + pthread_t thread = pthread_self(); + pthread_setname_np(thread, "test"); + return 0; + } + ''', + 'close_range': ''' + #include + #include + #ifdef linux + #include + #endif + int main(void) { + unsigned int flags = CLOSE_RANGE_UNSHARE; + return close_range(3, ~0U, flags); + } + ''', + 'listmount': ''' + #include + #include + #include + #include + + int main(int argc, char *argv[]) { + struct mnt_id_req req = { + .size = sizeof(struct mnt_id_req), + .mnt_id = LSMT_ROOT, + }; + uint64_t mnt_ids[1]; + + int n = syscall(SYS_listmount, &req, &mnt_ids, 1, 0); + if (n == -1) { + return -1; + } + } + ''' +} + +foreach name, code : special_funcs + private_cfg.set('HAVE_' + name.to_upper(), + cc.links(code, args: args_default, + name: name + ' check')) +endforeach + +# Headers checks +test_headers = [ 'sys/xattr.h', 'linux/limits.h' ] +foreach header : test_headers + private_cfg.set('HAVE_' + header.underscorify().to_upper(), + cc.has_header(header)) +endforeach + +# Regular function checks +private_cfg.set('HAVE_SETXATTR', + cc.has_function('setxattr', prefix: '#include ')) +private_cfg.set('HAVE_ICONV', + cc.has_function('iconv', prefix: '#include ')) +private_cfg.set('HAVE_BACKTRACE', + cc.has_function('backtrace', prefix: '#include ')) +private_cfg.set('HAVE_STATX', + cc.has_function('statx', prefix : '#define _GNU_SOURCE\n#include ')) + +# Struct member checks +private_cfg.set('HAVE_STRUCT_STAT_ST_ATIM', + cc.has_member('struct stat', 'st_atim', + prefix: include_default + '#include ', + args: args_default)) +private_cfg.set('HAVE_STRUCT_STAT_ST_ATIMESPEC', + cc.has_member('struct stat', 'st_atimespec', + prefix: include_default + '#include ', + args: args_default)) + +private_cfg.set('USDT_ENABLED', get_option('enable-usdt')) + +# Check for liburing with SQE128 support +code = ''' +#include +#include +int main(void) { + struct io_uring ring; + int ret = io_uring_queue_init(1, &ring, 0); +#ifndef IORING_SETUP_SQE128 +#error "No SQE128 support" +#endif + return ret; +}''' + +liburing = dependency('liburing', required: false) +libnuma = dependency('numa', required: false) + +if get_option('enable-io-uring') and liburing.found() and libnuma.found() + if cc.links(code, + name: 'liburing linking and SQE128 support', + dependencies: [liburing]) + private_cfg.set('HAVE_URING', true) + endif +endif + +# +# Compiler configuration +# +add_project_arguments('-D_REENTRANT', '-DHAVE_LIBFUSE_PRIVATE_CONFIG_H', '-Wno-sign-compare', '-D_FILE_OFFSET_BITS=64', + '-Wstrict-prototypes', '-Wmissing-declarations', '-Wwrite-strings', + '-fno-strict-aliasing', language: 'c') +add_project_arguments('-D_REENTRANT', '-DHAVE_LIBFUSE_PRIVATE_CONFIG_H', '-D_GNU_SOURCE', '-D_FILE_OFFSET_BITS=64', + '-Wno-sign-compare', '-Wmissing-declarations', + '-Wwrite-strings', '-fno-strict-aliasing', language: 'cpp') + +# Some (stupid) GCC versions warn about unused return values even when they are +# casted to void. This makes -Wunused-result pretty useless, since there is no +# way to suppress the warning when we really *want* to ignore the value. +code = ''' +__attribute__((warn_unused_result)) int get_4() { + return 4; +} +int main(void) { + (void) get_4(); + return 0; +}''' +if not cc.compiles(code, args: [ '-O0', '-Werror=unused-result' ]) + message('Compiler warns about unused result even when casting to void') + add_project_arguments('-Wno-unused-result', language: 'c') +endif + +# It is hard to detect if the libc supports versioned symbols. Only gnu-libc +# seems to provide that, but then glibc is the main target for libfuse, so +# enable it by default +versioned_symbols = 1 + +# This is an attempt to detect if another libc is used. +code = ''' +int main(void) { +#if (defined(__UCLIBC__) || defined(__APPLE__)) +#error /* libc does not have versioned symbols */ +#endif + return 0; +}''' +if not cc.compiles(code, args: [ '-O0' ]) + versioned_symbols = 0 +endif + +# The detection can be overridden, which is useful for other (above unhandled) +# libcs and also especially useful for testing +if get_option('disable-libc-symbol-version') + versioned_symbols = 0 +endif + +if versioned_symbols == 1 + message('Enabling versioned libc symbols') + public_cfg.set('LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS', 1) + + # gcc-10 and newer support the symver attribute which we need to use if we + # want to support LTO + # recent clang and gcc both support __has_attribute (and if they are too old + # to have __has_attribute, then they are too old to support symver) + # other compilers might not have __has_attribute, but in those cases + # it is safe for this check to fail and for us to fallback to the old _asm_ + # method for symver. Anyway the attributes not supported by __has_attribute() + # unfortunately return true giving a false positive. So let's try to build + # using __attribute__ ((symver )) and see the result. + code = ''' + __attribute__ ((symver ("test@TEST"))) + void foo(void) { + } + + int main(void) { + return 0; + }''' + if cc.compiles(code, args: [ '-O0', '-c', '-Werror']) + message('Compiler supports symver attribute') + add_project_arguments('-DHAVE_SYMVER_ATTRIBUTE', language: 'c') + else + message('Compiler does not support symver attribute') + endif +else + message('Disabling versioned libc symbols') +endif + +# Older versions of musl libc don't unescape entries in /etc/mtab +# Try to detect this behaviour, and work around, if necessary. +detect_getmntent_needs_unescape = ''' +#define _GNU_SOURCE +#include +#include +#include +#include + +#define dir_space_tab "dir\\040space\\011tab" + +int main() +{ + const char *fake_mtab = "name " dir_space_tab " type opts 0 0\n"; + FILE *f = fmemopen((void *)fake_mtab, strlen(fake_mtab) + 1, "r"); + struct mntent *entp = getmntent(f); + fclose(f); + if(NULL == entp) + exit(EXIT_FAILURE); + if (0 == strcmp(entp->mnt_dir, dir_space_tab)) + printf("needs escaping\n"); + else + printf("no need to escape\n"); +} +''' + +if not meson.is_cross_build() + result = cc.run(detect_getmntent_needs_unescape) + if result.compiled() and result.returncode() == 0 and result.stdout().strip() == 'needs escaping' + message('getmntent does not unescape') + add_project_arguments('-DGETMNTENT_NEEDS_UNESCAPING', language: 'c') + endif +endif + +# Write private test results into fuse_config.h (stored in build directory) +configure_file(output: 'fuse_config.h', configuration : private_cfg) + +# Write the test results, installed with the package, +# symbols need to be properly prefixed to avoid +# symbol (define) conflicts +configure_file(output: 'libfuse_config.h', + configuration : public_cfg, + install: true, install_dir: join_paths(get_option('includedir'), 'fuse3')) + +# '.' will refer to current build directory, which contains config.h +include_dirs = include_directories('include', 'lib', '.') + +# Common dependencies +thread_dep = dependency('threads') + +# +# Read build files from sub-directories +# +subdirs = [ 'lib', 'include'] +if get_option('utils') and not platform.endswith('bsd') and platform != 'dragonfly' + subdirs += [ 'util', 'doc' ] +endif + +if get_option('examples') + subdirs += 'example' +endif + +if get_option('tests') + subdirs += 'test' +endif + +foreach n : subdirs + subdir(n) +endforeach + diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..c1f8fe6 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,29 @@ +option('disable-mtab', type : 'boolean', value : false, + description: 'Disable and ignore usage of /etc/mtab') + +option('udevrulesdir', type : 'string', value : '', + description: 'Where to install udev rules (if empty, query pkg-config(1))') + +option('initscriptdir', type : 'string', value : '/etc/init.d/', + description: 'Init script installation location (if empty, disable init script installation)') + +option('utils', type : 'boolean', value : true, + description: 'Whether or not to build and install helper programs') + +option('examples', type : 'boolean', value : true, + description: 'Whether or not to build example programs') + +option('useroot', type : 'boolean', value : true, + description: 'Set owner and setuid bits on installed files') + +option('tests', type : 'boolean', value : true, + description: 'Compile the test files') + +option('disable-libc-symbol-version', type : 'boolean', value : false, + description: 'Disable versioned symbols through libc') + +option('enable-usdt', type : 'boolean', value : false, + description: 'Enable user statically defined tracepoints for extra observability') + +option('enable-io-uring', type: 'boolean', value: true, + description: 'Enable fuse-over-io-uring support') diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2c6a4a0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +# Packages required for building and testing libfuse only, +# no python code is required when using libfuse. +# Build packages: +meson +ninja +# Test packages: +pytest diff --git a/signify/fuse-3.15.pub b/signify/fuse-3.15.pub new file mode 100644 index 0000000..b8c688c --- /dev/null +++ b/signify/fuse-3.15.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWRsNRJIAGSHE0C6WuVmusQlvudAPlmOOkCw1LbuOLuu+25DuAmPCpDf diff --git a/signify/fuse-3.16.pub b/signify/fuse-3.16.pub new file mode 100644 index 0000000..13fdcb3 --- /dev/null +++ b/signify/fuse-3.16.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWQtnc3WSoYwHGAdfvtTTVX8RsAXrNwMb8xqVwlY8lYY2Fxn2QUDiPYK diff --git a/signify/fuse-3.17.pub b/signify/fuse-3.17.pub new file mode 100644 index 0000000..02525e1 --- /dev/null +++ b/signify/fuse-3.17.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWQqzcI/bjQ4/nouPgwUbs0WorZncrBxJHmiCb2N+GrMs9L6YAcGSFY/ diff --git a/signify/fuse-3.18.pub b/signify/fuse-3.18.pub new file mode 100644 index 0000000..eeee6bf --- /dev/null +++ b/signify/fuse-3.18.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWS6gMnNrKp/zRSYWv13J+KwXE26vCUsbC/hVZmjQ8PA3xjixGLjodz3 diff --git a/signify/fuse-3.19.pub b/signify/fuse-3.19.pub new file mode 100644 index 0000000..317f8f7 --- /dev/null +++ b/signify/fuse-3.19.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWR4cEcMGJhD3Dnd3NOeJck3WiuVt9A7mrkq+nQYwrwwmMdDDAan/YiU diff --git a/test/ci-build.sh b/test/ci-build.sh new file mode 100755 index 0000000..2bced37 --- /dev/null +++ b/test/ci-build.sh @@ -0,0 +1,168 @@ +#!/bin/bash -x + +set -e + +TEST_CMD="pytest -v --maxfail=1 --log-level=INFO --log-cli-level=INFO test/" +SAN="-Db_sanitize=address,undefined" + +# not default +export UBSAN_OPTIONS=halt_on_error=1 + +# Make sure binaries can be accessed when invoked by root. +umask 0022 + +# There are tests that run as root but without CAP_DAC_OVERRIDE. To allow these +# to launch built binaries, the directory tree must be accessible to the root +# user. Since the source directory isn't necessarily accessible to root, we +# build and run tests in a temporary directory that we can set up to be world +# readable/executable. +SOURCE_DIR="$(readlink -f .)" +TEST_DIR="$(mktemp -dt libfuse-build-XXXXXX)" + +PREFIX_DIR="$(mktemp -dt libfuse-install-XXXXXXX)" + +chmod 0755 "${TEST_DIR}" +cd "${TEST_DIR}" +echo "Running in ${TEST_DIR}" + +cp -v "${SOURCE_DIR}/test/lsan_suppress.txt" . +export LSAN_OPTIONS="suppressions=$(pwd)/lsan_suppress.txt" +export ASAN_OPTIONS="detect_leaks=1" +export CC + +non_sanitized_build() +( + echo "Standard build (without sanitizers)" + for CC in gcc gcc-9 gcc-10 clang; do + echo "=== Building with ${CC} ===" + mkdir build-${CC}; pushd build-${CC} + if [ "${CC}" == "clang" ]; then + export CXX="clang++" + export TEST_WITH_VALGRIND=false + else + unset CXX + export TEST_WITH_VALGRIND=true + fi + if [ ${CC} == 'gcc-7' ]; then + build_opts='-D b_lundef=false' + else + build_opts='' + fi + if [ ${CC} == 'gcc-10' ]; then + build_opts='-Dc_args=-flto=auto' + else + build_opts='' + fi + + meson setup -Dprefix=${PREFIX_DIR} -D werror=true ${build_opts} "${SOURCE_DIR}" || (cat meson-logs/meson-log.txt; false) + ninja + sudo env PATH=$PATH ninja install + + # libfuse will first try the install path and then system defaults + sudo chmod 4755 ${PREFIX_DIR}/bin/fusermount3 + + # also needed for some of the tests + sudo chown root:root util/fusermount3 + sudo chmod 4755 util/fusermount3 + + ${TEST_CMD} + popd + rm -fr build-${CC} + sudo rm -fr ${PREFIX_DIR} + + done +) + +sanitized_build() +( + echo "=== Building with clang and sanitizers" + + mkdir build-san; pushd build-san + + meson setup -Dprefix=${PREFIX_DIR} -D werror=true\ + "${SOURCE_DIR}" \ + || (cat meson-logs/meson-log.txt; false) + meson configure $SAN + + # b_lundef=false is required to work around clang + # bug, cf. https://groups.google.com/forum/#!topic/mesonbuild/tgEdAXIIdC4 + meson configure -D b_lundef=false + + # additional options + if [[ $# -gt 0 ]]; then + meson configure "$@" + fi + + # print all options + meson configure --no-pager + + # reconfigure to ensure it uses all additional options + meson setup --reconfigure "${SOURCE_DIR}" + ninja + sudo env PATH=$PATH ninja install + sudo chmod 4755 ${PREFIX_DIR}/bin/fusermount3 + + # also needed for some of the tests + sudo chown root:root util/fusermount3 + sudo chmod 4755 util/fusermount3 + + # Test as root and regular user + sudo env PATH=$PATH ${TEST_CMD} + # Cleanup temporary files (since they are now owned by root) + sudo rm -rf test/.pytest_cache/ test/__pycache__ + + ${TEST_CMD} + + popd + rm -fr build-san + sudo rm -fr ${PREFIX_DIR} +) + +# Sanitized with io-uring +export CC=clang +export CXX=clang++ +export FUSE_URING_ENABLE=1 +sanitized_build +unset FUSE_URING_ENABLE + +# 32-bit sanitized build +export CC=clang +export CXX=clang++ +export CFLAGS="-m32" +export CXXFLAGS="-m32" +export LDFLAGS="-m32" +export PKG_CONFIG_PATH="/usr/lib/i386-linux-gnu/pkgconfig" +TEST_WITH_VALGRIND=false +sanitized_build +unset CFLAGS +unset CXXFLAGS +unset LDFLAGS +unset PKG_CONFIG_PATH +unset TEST_WITH_VALGRIND +unset CC +unset CXX + +# Sanitized build +export CC=clang +export CXX=clang++ +TEST_WITH_VALGRIND=false +sanitized_build + +# Sanitized build without libc versioned symbols +export CC=clang +export CXX=clang++ +sanitized_build "-Ddisable-libc-symbol-version=true" + +# Sanitized build without fuse-io-uring +export CC=clang +export CXX=clang++ +sanitized_build "-Denable-io-uring=false" + +# Build without any sanitizer +non_sanitized_build + +# Documentation. +(cd "${SOURCE_DIR}"; doxygen doc/Doxyfile) + +# Clean up. +rm -rf "${TEST_DIR}" diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..291c919 --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 + +import sys +import pytest +import time +import re +import os +import threading + + +# If a test fails, wait a moment before retrieving the captured +# stdout/stderr. When using a server process, this makes sure that we capture +# any potential output of the server that comes *after* a test has failed. For +# example, if a request handler raises an exception, the server first signals an +# error to FUSE (causing the test to fail), and then logs the exception. Without +# the extra delay, the exception will go into nowhere. +@pytest.hookimpl(hookwrapper=True) +def pytest_pyfunc_call(pyfuncitem): + outcome = yield + failed = outcome.excinfo is not None + if failed: + time.sleep(1) + + +class OutputChecker: + '''Check output data for suspicious patterns. + + Everything written to check_output.fd will be scanned for suspicious + messages and then written to sys.stdout. + ''' + + def __init__(self): + (fd_r, fd_w) = os.pipe() + self.fd = fd_w + self._false_positives = [] + self._buf = bytearray() + self._thread = threading.Thread(target=self._loop, daemon=True, args=(fd_r,)) + self._thread.start() + + def register_output(self, pattern, count=1, flags=re.MULTILINE): + '''Register *pattern* as false positive for output checking + + This prevents the test from failing because the output otherwise + appears suspicious. + ''' + + self._false_positives.append((pattern, flags, count)) + + def _loop(self, ifd): + BUFSIZE = 128*1024 + ofd = sys.stdout.fileno() + while True: + buf = os.read(ifd, BUFSIZE) + if not buf: + break + os.write(ofd, buf) + self._buf += buf + + def _check(self): + os.close(self.fd) + self._thread.join() + + buf = self._buf.decode('utf8', errors='replace') + + # Strip out false positives + for (pattern, flags, count) in self._false_positives: + cp = re.compile(pattern, flags) + (buf, cnt) = cp.subn('', buf, count=count) + + patterns = [ r'\b{}\b'.format(x) for x in + ('exception', 'error', 'warning', 'fatal', 'traceback', + 'fault', 'crash(?:ed)?', 'abort(?:ed)', + 'uninitiali[zs]ed') ] + patterns += ['^==[0-9]+== '] + + for pattern in patterns: + cp = re.compile(pattern, re.IGNORECASE | re.MULTILINE) + hit = cp.search(buf) + if hit: + # Skip FUSE error messages in the format "unique: X, error: -Y (...), outsize: Z" + # These are no errors, but just fuse debug messages with the return code + if re.search(r'unique: \d+, error: -\d+ \(.*\), outsize: \d+', hit.group(0)): + continue + raise AssertionError(f'Suspicious output to stderr (matched "{hit.group(0)}")') + +@pytest.fixture() +def output_checker(request): + checker = OutputChecker() + yield checker + checker._check() + + +# Make test outcome available to fixtures +# (from https://github.com/pytest-dev/pytest/issues/230) +@pytest.hookimpl(hookwrapper=True, tryfirst=True) +def pytest_runtest_makereport(item, call): + outcome = yield + rep = outcome.get_result() + setattr(item, "rep_" + rep.when, rep) + return rep diff --git a/test/hello.c b/test/hello.c new file mode 100644 index 0000000..cf57668 --- /dev/null +++ b/test/hello.c @@ -0,0 +1,180 @@ +/* + * FUSE: Filesystem in Userspace + * Copyright (C) 2001-2007 Miklos Szeredi + * + * This program can be distributed under the terms of the GNU GPLv2. + * See the file GPL2.txt. + */ + +/** @file + * + * minimal example filesystem using high-level API + * + * Compile with: + * + * gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello + * + * ## Source code ## + * \include hello.c + */ + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include +#include +#include + +/* + * Command line options + * + * We can't set default values for the char* fields here because + * fuse_opt_parse would attempt to free() them when the user specifies + * different values on the command line. + */ +static struct options { + const char *filename; + const char *contents; + int show_help; +} options; + +#define OPTION(t, p) { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--name=%s", filename), OPTION("--contents=%s", contents), + OPTION("-h", show_help), OPTION("--help", show_help), FUSE_OPT_END +}; + +static void *hello_init(struct fuse_conn_info *conn, struct fuse_config *cfg) +{ + (void)conn; + cfg->kernel_cache = 1; + + /* Test setting flags the old way */ + conn->want = FUSE_CAP_ASYNC_READ; + conn->want &= ~FUSE_CAP_ASYNC_READ; + + return NULL; +} + +static int hello_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void)fi; + int res = 0; + + memset(stbuf, 0, sizeof(struct stat)); + if (strcmp(path, "/") == 0) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + } else if (strcmp(path + 1, options.filename) == 0) { + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = strlen(options.contents); + } else + res = -ENOENT; + + return res; +} + +static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + (void)offset; + (void)fi; + (void)flags; + + if (strcmp(path, "/") != 0) + return -ENOENT; + + filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS); + + return 0; +} + +static int hello_open(const char *path, struct fuse_file_info *fi) +{ + if (strcmp(path + 1, options.filename) != 0) + return -ENOENT; + + if ((fi->flags & O_ACCMODE) != O_RDONLY) + return -EACCES; + + return 0; +} + +static int hello_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + size_t len; + (void)fi; + if (strcmp(path + 1, options.filename) != 0) + return -ENOENT; + + len = strlen(options.contents); + if (offset < len) { + if (offset + size > len) + size = len - offset; + memcpy(buf, options.contents + offset, size); + } else + size = 0; + + return size; +} + +static const struct fuse_operations hello_oper = { + .init = hello_init, + .getattr = hello_getattr, + .readdir = hello_readdir, + .open = hello_open, + .read = hello_read, +}; + +static void show_help(const char *progname) +{ + printf("usage: %s [options] \n\n", progname); + printf("File-system specific options:\n" + " --name= Name of the \"hello\" file\n" + " (default: \"hello\")\n" + " --contents= Contents \"hello\" file\n" + " (default \"Hello, World!\\n\")\n" + "\n"); +} + +int main(int argc, char *argv[]) +{ + int ret; + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + + /* Set defaults -- we have to use strdup so that + * fuse_opt_parse can free the defaults if other + * values are specified + */ + options.filename = strdup("hello"); + options.contents = strdup("Hello World!\n"); + + /* Parse options */ + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + /* When --help is specified, first print our own file-system + * specific help text, then signal fuse_main to show + * additional help (by adding `--help` to the options again) + * without usage: line (by setting argv[0] to the empty + * string) + */ + if (options.show_help) { + show_help(argv[0]); + assert(fuse_opt_add_arg(&args, "--help") == 0); + args.argv[0][0] = '\0'; + } + + ret = fuse_main(args.argc, args.argv, &hello_oper, NULL); + fuse_opt_free_args(&args); + return ret; +} diff --git a/test/lsan_suppress.txt b/test/lsan_suppress.txt new file mode 100644 index 0000000..44703fc --- /dev/null +++ b/test/lsan_suppress.txt @@ -0,0 +1 @@ +# Suppression file for address sanitizer. diff --git a/test/meson.build b/test/meson.build new file mode 100644 index 0000000..9f6f409 --- /dev/null +++ b/test/meson.build @@ -0,0 +1,45 @@ +# Compile helper programs +td = [] +foreach prog: [ 'test_write_cache', 'test_setattr', 'hello' ] + td += executable(prog, prog + '.c', + include_directories: include_dirs, + link_with: [ libfuse ], + dependencies: thread_dep, + install: false) +endforeach +td += executable('test_syscalls', 'test_syscalls.c', + include_directories: include_dirs, + install: false) +td += executable('readdir_inode', 'readdir_inode.c', + include_directories: include_dirs, + install: false) +td += executable('release_unlink_race', 'release_unlink_race.c', + dependencies: [ libfuse_dep ], + install: false) +td += executable('test_want_conversion', 'test_want_conversion.c', + dependencies: [ libfuse_dep ], + install: false) +td += executable('test_signals', 'test_signals.c', + dependencies: [ libfuse_dep, thread_dep ], + install: false) +td += executable('test_abi', 'test_abi.c', + dependencies: [ libfuse_dep ], + install: false) + +test_scripts = [ 'conftest.py', 'pytest.ini', 'test_examples.py', + 'util.py', 'test_ctests.py', 'test_custom_io.py' ] +td += custom_target('test_scripts', input: test_scripts, + output: test_scripts, build_by_default: true, + command: ['cp', '-fPp', + '@INPUT@', meson.current_build_dir() ]) + +# Provide something helpful when running 'ninja test' + +if meson.is_subproject() + test('libfuse is a subproject, skipping tests', executable('wrong_command', + 'wrong_command.c', install: false, + c_args: [ '-DMESON_IS_SUBPROJECT' ])) +else + test('wrong_command', executable('wrong_command', 'wrong_command.c', + install: false)) +endif diff --git a/test/pytest.ini b/test/pytest.ini new file mode 100644 index 0000000..bbc8de8 --- /dev/null +++ b/test/pytest.ini @@ -0,0 +1,6 @@ +[pytest] +addopts = --verbose --assert=rewrite --tb=native -x -r a +markers = + uses_fuse: Indicates that FUSE is supported. +log_cli=true +faulthandler_timeout=60 diff --git a/test/readdir_inode.c b/test/readdir_inode.c new file mode 100644 index 0000000..99f95ff --- /dev/null +++ b/test/readdir_inode.c @@ -0,0 +1,57 @@ +/* + * Prints each directory entry, its inode and d_type as returned by 'readdir'. + * Skips '.' and '..' because readdir is not required to return them and + * some of our examples don't. However if they are returned, their d_type + * should be valid. + */ + +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) +{ + DIR* dirp; + struct dirent* dent; + + if (argc != 2) { + fprintf(stderr, "Usage: readdir_inode dir\n"); + return 1; + } + + dirp = opendir(argv[1]); + if (dirp == NULL) { + perror("failed to open directory"); + return 2; + } + + errno = 0; + dent = readdir(dirp); + while (dent != NULL) { + if (strcmp(dent->d_name, ".") != 0 && strcmp(dent->d_name, "..") != 0) { + printf("%llu %d %s\n", (unsigned long long)dent->d_ino, + (int)dent->d_type, dent->d_name); + if ((long long)dent->d_ino < 0) + fprintf(stderr,"%s : bad d_ino %llu\n", + dent->d_name, (unsigned long long)dent->d_ino); + if ((dent->d_type < 1) || (dent->d_type > 15)) + fprintf(stderr,"%s : bad d_type %d\n", + dent->d_name, (int)dent->d_type); + } else { + if (dent->d_type != DT_DIR) + fprintf(stderr,"%s : bad d_type %d\n", + dent->d_name, (int)dent->d_type); + } + dent = readdir(dirp); + } + if (errno != 0) { + perror("failed to read directory entry"); + return 3; + } + + closedir(dirp); + + return 0; +} diff --git a/test/release_unlink_race.c b/test/release_unlink_race.c new file mode 100644 index 0000000..f7b7b81 --- /dev/null +++ b/test/release_unlink_race.c @@ -0,0 +1,111 @@ +/* + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +#define FUSE_USE_VERSION 31 + +#define _GNU_SOURCE + +#include + +#include +#include +#include +#include + +static void *xmp_init(struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + (void) conn; + + cfg->use_ino = 1; + cfg->nullpath_ok = 1; + cfg->entry_timeout = 0; + cfg->attr_timeout = 0; + cfg->negative_timeout = 0; + + return NULL; +} + +static int xmp_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + int res; + + (void) path; + + if(fi) + res = fstat(fi->fh, stbuf); + else + res = lstat(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_unlink(const char *path) +{ + int res; + + res = unlink(path); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_rename(const char *from, const char *to, unsigned int flags) +{ + int res; + + if (flags) + return -EINVAL; + + if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000); + + res = rename(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi) +{ + int fd; + + fd = open(path, fi->flags, mode); + if (fd == -1) + return -errno; + + fi->fh = fd; + return 0; +} + +static int xmp_release(const char *path, struct fuse_file_info *fi) +{ + (void) path; + + if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000); + + close(fi->fh); + + return 0; +} + +static const struct fuse_operations xmp_oper = { + .init = xmp_init, + .getattr = xmp_getattr, + .unlink = xmp_unlink, + .rename = xmp_rename, + .create = xmp_create, + .release = xmp_release, +}; + +int main(int argc, char *argv[]) +{ + umask(0); + return fuse_main(argc, argv, &xmp_oper, NULL); +} diff --git a/test/stracedecode.c b/test/stracedecode.c new file mode 100644 index 0000000..01bf523 --- /dev/null +++ b/test/stracedecode.c @@ -0,0 +1,199 @@ +#include +#include +#include "fuse_kernel.h" + +static struct { + const char *name; +} fuse_ll_ops[] = { + [FUSE_LOOKUP] = { "LOOKUP" }, + [FUSE_FORGET] = { "FORGET" }, + [FUSE_GETATTR] = { "GETATTR" }, + [FUSE_SETATTR] = { "SETATTR" }, + [FUSE_READLINK] = { "READLINK" }, + [FUSE_SYMLINK] = { "SYMLINK" }, + [FUSE_MKNOD] = { "MKNOD" }, + [FUSE_MKDIR] = { "MKDIR" }, + [FUSE_UNLINK] = { "UNLINK" }, + [FUSE_RMDIR] = { "RMDIR" }, + [FUSE_RENAME] = { "RENAME" }, + [FUSE_LINK] = { "LINK" }, + [FUSE_OPEN] = { "OPEN" }, + [FUSE_READ] = { "READ" }, + [FUSE_WRITE] = { "WRITE" }, + [FUSE_STATFS] = { "STATFS" }, + [FUSE_RELEASE] = { "RELEASE" }, + [FUSE_FSYNC] = { "FSYNC" }, + [FUSE_SETXATTR] = { "SETXATTR" }, + [FUSE_GETXATTR] = { "GETXATTR" }, + [FUSE_LISTXATTR] = { "LISTXATTR" }, + [FUSE_REMOVEXATTR] = { "REMOVEXATTR" }, + [FUSE_FLUSH] = { "FLUSH" }, + [FUSE_INIT] = { "INIT" }, + [FUSE_OPENDIR] = { "OPENDIR" }, + [FUSE_READDIR] = { "READDIR" }, + [FUSE_RELEASEDIR] = { "RELEASEDIR" }, + [FUSE_FSYNCDIR] = { "FSYNCDIR" }, + [FUSE_GETLK] = { "GETLK" }, + [FUSE_SETLK] = { "SETLK" }, + [FUSE_SETLKW] = { "SETLKW" }, + [FUSE_ACCESS] = { "ACCESS" }, + [FUSE_CREATE] = { "CREATE" }, + [FUSE_TMPFILE] = { "TMPFILE" }, + [FUSE_INTERRUPT] = { "INTERRUPT" }, + [FUSE_BMAP] = { "BMAP" }, + [FUSE_DESTROY] = { "DESTROY" }, + [FUSE_READDIRPLUS] = { "READDIRPLUS" }, +}; + +#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0])) + +static const char *opname(enum fuse_opcode opcode) +{ + if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name) + return "???"; + else + return fuse_ll_ops[opcode].name; +} + + +static void process_buf(int dir, char *buf, int len) +{ + static unsigned long long prevuniq = -1; + static int prevopcode; + + if (!dir) { + struct fuse_in_header *in = (struct fuse_in_header *) buf; + buf += sizeof(struct fuse_in_header); + + printf("unique: %llu, opcode: %s (%i), nodeid: %lu, len: %i, insize: %i\n", + (unsigned long long) in->unique, + opname((enum fuse_opcode) in->opcode), in->opcode, + (unsigned long) in->nodeid, in->len, len); + + switch (in->opcode) { + case FUSE_READ: { + struct fuse_read_in *arg = (struct fuse_read_in *) buf; + printf("-READ fh:%llu off:%llu siz:%u rfl:%u own:%llu fl:%u\n", + arg->fh, arg->offset, arg->size, arg->read_flags, + arg->lock_owner, arg->flags); + break; + } + case FUSE_WRITE: { + struct fuse_write_in *arg = (struct fuse_write_in *) buf; + printf("-WRITE fh:%llu off:%llu siz:%u wfl:%u own:%llu fl:%u\n", + arg->fh, arg->offset, arg->size, arg->write_flags, + arg->lock_owner, arg->flags); + break; + } + } + prevuniq = in->unique; + prevopcode = in->opcode; + } else { + struct fuse_out_header *out = (struct fuse_out_header *) buf; + buf += sizeof(struct fuse_out_header); + + printf(" unique: %llu, error: %i (%s), len: %i, outsize: %i\n", + (unsigned long long) out->unique, out->error, + strerror(-out->error), out->len, len); + + if (out->unique == prevuniq) { + switch (prevopcode) { + case FUSE_GETATTR: { + struct fuse_attr_out *arg = (struct fuse_attr_out *) buf; + printf("+ATTR v:%llu.%09u i:%llu s:%llu b:%llu\n", + arg->attr_valid, arg->attr_valid_nsec, + arg->attr.ino, arg->attr.size, arg->attr.blocks); + break; + } + case FUSE_LOOKUP: { + struct fuse_entry_out *arg = (struct fuse_entry_out *) buf; + printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n", + arg->nodeid, arg->attr_valid, arg->attr_valid_nsec, + arg->attr.ino, arg->attr.size, arg->attr.blocks); + break; + } + } + } + } + +} + +int main(void) +{ + FILE *in = stdin; + while (1) { + int dir; + int res; + char buf[1048576]; + unsigned len = 0; + + memset(buf, 0, sizeof(buf)); + while (1) { + char str[32]; + + res = fscanf(in, "%30s", str); + if (res != 1 && feof(in)) + return 0; + + if (res == 0) + continue; + + if (strncmp(str, "read(", 5) == 0) { + dir = 0; + break; + } else if (strncmp(str, "writev(", 7) == 0) { + dir = 1; + break; + } + } + + while (1) { + int c = getc(in); + if (c == '"') { + while (1) { + int val; + + c = getc(in); + if (c == EOF) { + fprintf(stderr, "eof in string\n"); + break; + } + if (c == '\n') { + fprintf(stderr, "eol in string\n"); + break; + } + if (c == '"') + break; + if (c != '\\') { + val = c; + } else { + c = getc(in); + switch (c) { + case 'n': val = '\n'; break; + case 'r': val = '\r'; break; + case 't': val = '\t'; break; + case '"': val = '"'; break; + case '\\': val = '\\'; break; + case 'x': + res = scanf("%x", &val); + if (res != 1) { + fprintf(stderr, "parse error\n"); + continue; + } + break; + default: + fprintf(stderr, "unknown sequence: '\\%c'\n", c); + continue; + } + } + buf[len++] = val; + } + } + if (c == '\n') + break; + } + process_buf(dir, buf, len); + memset(buf, 0, len); + len = 0; + } +} diff --git a/test/test_abi.c b/test/test_abi.c new file mode 100644 index 0000000..99daa09 --- /dev/null +++ b/test/test_abi.c @@ -0,0 +1,18 @@ +#define FUSE_USE_VERSION 30 + +#include "fuse.h" + +#include +#include + +int main(void) +{ + if (sizeof(struct fuse_file_info) != 64) { + fprintf(stderr, "struct fuse_file_info size mismatch\n"); + exit(1); + } + if (sizeof(struct fuse_conn_info) != 128) { + fprintf(stderr, "struct fuse_conn_info size mismatch\n"); + exit(1); + } +} diff --git a/test/test_ctests.py b/test/test_ctests.py new file mode 100644 index 0000000..b3863e0 --- /dev/null +++ b/test/test_ctests.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 + +if __name__ == '__main__': + import pytest + import sys + sys.exit(pytest.main([__file__] + sys.argv[1:])) + +import subprocess +import pytest +import platform +import sys +import os +import logging +from packaging import version +from util import (wait_for_mount, umount, cleanup, base_cmdline, + safe_sleep, basename, fuse_test_marker, fuse_caps, + fuse_proto, create_tmpdir, parse_kernel_version) +from os.path import join as pjoin +import os.path + +pytestmark = fuse_test_marker() + +def test_abi(): + cmdline = [ pjoin(basename, 'test', 'test_abi') ] + subprocess.check_call(cmdline) + +@pytest.mark.skipif('FUSE_CAP_WRITEBACK_CACHE' not in fuse_caps, + reason='not supported by running kernel') +@pytest.mark.parametrize("writeback", (False, True)) +def test_write_cache(tmpdir, writeback, output_checker): + if writeback and parse_kernel_version(platform.release()) < version.parse('3.14'): + pytest.skip('Requires kernel 3.14 or newer') + # This test hangs under Valgrind when running close(fd) + # test_write_cache.c:test_fs(). Most likely this is because of an internal + # deadlock in valgrind, it probably assumes that until close() returns, + # control does not come to the program. + mnt_dir = str(tmpdir) + print("mnt_dir: '" + mnt_dir + "'") + create_tmpdir(mnt_dir) + + cmdline = [ pjoin(basename, 'test', 'test_write_cache'), + mnt_dir ] + if writeback: + cmdline.append('-owriteback_cache') + elif parse_kernel_version(platform.release()) >= version.parse('5.16'): + # Test that close(rofd) does not block waiting for pending writes. + # This test requires kernel commit a390ccb316be ("fuse: add FOPEN_NOFLUSH") + # so opt-in for this test from kernel 5.16. + cmdline.append('--delay_ms=200') + subprocess.check_call(cmdline, stdout=output_checker.fd, stderr=output_checker.fd) + + +names = [ 'notify_inval_inode', 'invalidate_path' ] +if fuse_proto >= (7,15): + names.append('notify_store_retrieve') +@pytest.mark.skipif(fuse_proto < (7,12), + reason='not supported by running kernel') +@pytest.mark.parametrize("name", names) +@pytest.mark.parametrize("notify", (True, False)) +def test_notify1(tmpdir, name, notify, output_checker): + logger = logging.getLogger(__name__) + mnt_dir = str(tmpdir) + logger.debug(f"Mount directory: {mnt_dir}") + create_tmpdir(mnt_dir) + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', name), + '-f', '--update-interval=1', mnt_dir ] + if not notify: + cmdline.append('--no-notify') + logger.debug(f"Command line: {' '.join(cmdline)}") + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + try: + wait_for_mount(mount_process, mnt_dir) + logger.debug("Mount completed") + filename = pjoin(mnt_dir, 'current_time') + logger.debug(f"Target filename: {filename}") + with open(filename, 'r') as fh: + read1 = fh.read() + logger.debug(f"First read: {read1}") + logger.debug("Sleeping for 2 seconds...") + safe_sleep(2) + logger.debug("Sleep completed") + with open(filename, 'r') as fh: + read2 = fh.read() + logger.debug(f"Second read: {read2}") + if notify: + logger.debug("Expecting reads to be different") + assert read1 != read2 + else: + logger.debug("Expecting reads to be the same") + assert read1 == read2 + logger.debug("Test completed successfully") + except: + logger.error(f"Failure in notify test: '{' '.join(cmdline)}'") + logger.exception("Exception details:") + cleanup(mount_process, mnt_dir) + raise + else: + logger.debug("Unmounting...") + try: + umount(mount_process, mnt_dir) + logger.debug("Umount disabled") + except: + logger.error(f"Failure in unmount: '{' '.join(cmdline)}'") + cleanup(mount_process, mnt_dir) + logger.debug("Unmount completed") + +@pytest.mark.skipif(fuse_proto < (7,12), + reason='not supported by running kernel') +@pytest.mark.parametrize("notify", (True, False)) +def test_notify_file_size(tmpdir, notify, output_checker): + logger = logging.getLogger(__name__) + mnt_dir = str(tmpdir) + logger.debug(f"Mount directory: {mnt_dir}") + create_tmpdir(mnt_dir) + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'invalidate_path'), + '-f', '--update-interval=1', mnt_dir ] + if not notify: + cmdline.append('--no-notify') + logger.debug(f"Command line: {' '.join(cmdline)}") + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + logger.debug(f"Mount process PID: {mount_process.pid}") + try: + wait_for_mount(mount_process, mnt_dir) + filename = pjoin(mnt_dir, 'growing') + size = os.path.getsize(filename) + logger.debug(f"Initial file size: {size}") + logger.debug("Sleeping for 2 seconds...") + safe_sleep(2) + logger.debug("Sleep completed") + new_size = os.path.getsize(filename) + logger.debug(f"New file size: {new_size}") + if notify: + assert new_size > size + else: + assert new_size == size + logger.debug("Test completed successfully") + except: + cleanup(mount_process, mnt_dir) + raise + else: + try: + umount(mount_process, mnt_dir) + except: + logger.error(f"Failure in unmount: '{' '.join(cmdline)}'") + cleanup(mount_process, mnt_dir) + logger.debug("Unmount completed") + +def test_signals(output_checker): + """Test for proper signal handling (issue #1182)""" + logger = logging.getLogger(__name__) + logger.debug("Testing signal handling") + cmdline = [ pjoin(basename, 'test', 'test_signals') ] + logger.debug(f"Command line: {' '.join(cmdline)}") + subprocess.run(cmdline, stdout=output_checker.fd, \ + stderr=output_checker.fd, timeout=10, check=True) + logger.debug("Signal handling test completed successfully") + diff --git a/test/test_custom_io.py b/test/test_custom_io.py new file mode 100644 index 0000000..737b939 --- /dev/null +++ b/test/test_custom_io.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + +if __name__ == '__main__': + import sys + + import pytest + sys.exit(pytest.main([__file__] + sys.argv[1:])) + +import os +import socket +import struct +import subprocess +import sys +import time +from os.path import join as pjoin + +import pytest + +from util import base_cmdline, basename + +FUSE_OP_INIT = 26 + +FUSE_MAJOR_VERSION = 7 +FUSE_MINOR_VERSION = 38 + +fuse_in_header_fmt = ' bytes: + buf = bytes() + while len(buf) < bufsize: + buf += sock.recv(bufsize - len(buf)) + return buf + + +def tst_init(sock: socket.socket): + unique_req = 10 + dummy_init_req_header = struct.pack( + fuse_in_header_fmt, struct.calcsize(fuse_in_header_fmt) + + struct.calcsize(fuse_init_in_fmt), FUSE_OP_INIT, unique_req, 0, 0, 0, + 0, 0) + dummy_init_req_payload = struct.pack( + fuse_init_in_fmt, FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION, 0, 0, 0) + dummy_init_req = dummy_init_req_header + dummy_init_req_payload + + sock.sendall(dummy_init_req) + + response_header = sock_recvall(sock, struct.calcsize(fuse_out_header_fmt)) + packet_len, _, unique_res = struct.unpack( + fuse_out_header_fmt, response_header) + assert unique_res == unique_req + + response_payload = sock_recvall(sock, packet_len - len(response_header)) + response_payload = struct.unpack(fuse_init_out_fmt, response_payload) + assert response_payload[0] == FUSE_MAJOR_VERSION + + +def test_hello_uds(output_checker): + cmdline = base_cmdline + [pjoin(basename, 'example', 'hello_ll_uds')] + print(cmdline) + uds_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + time.sleep(1) + + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.settimeout(1) + sock.connect("/tmp/libfuse-hello-ll.sock") + + tst_init(sock) + + sock.close() + uds_process.terminate() + try: + uds_process.wait(1) + except subprocess.TimeoutExpired: + uds_process.kill() + os.remove("/tmp/libfuse-hello-ll.sock") diff --git a/test/test_examples.py b/test/test_examples.py new file mode 100755 index 0000000..05efa09 --- /dev/null +++ b/test/test_examples.py @@ -0,0 +1,980 @@ +#!/usr/bin/env python3 + +if __name__ == '__main__': + import pytest + import sys + sys.exit(pytest.main([__file__] + sys.argv[1:])) + +import subprocess +import os +import sys +import py +import pytest +import stat +import shutil +import filecmp +import tempfile +import time +import errno +import sys +import platform +import re +from packaging import version +from tempfile import NamedTemporaryFile +from contextlib import contextmanager +from util import (wait_for_mount, umount, cleanup, base_cmdline, + safe_sleep, basename, fuse_test_marker, test_printcap, + fuse_proto, fuse_caps, powerset, parse_kernel_version) +from os.path import join as pjoin +import logging +from enum import Enum + +class InodeCheck(Enum): + EXACT = 1 + NONZERO = 2 + +pytestmark = fuse_test_marker() + +TEST_FILE = __file__ + +with open(TEST_FILE, 'rb') as fh: + TEST_DATA = fh.read() + +def name_generator(__ctr=[0]): + __ctr[0] += 1 + return 'testfile_%d' % __ctr[0] + +options = [] +if sys.platform == 'linux': + options.append('clone_fd') + +def invoke_directly(mnt_dir, name, options): + # Handle test/hello specially since it's not in example/ + if name.startswith('test/'): + path = pjoin(basename, name) + else: + path = pjoin(basename, 'example', name) + + cmdline = base_cmdline + [ path, '-f', mnt_dir, '-o', ','.join(options) ] + if name == 'hello_ll': + # supports single-threading only + cmdline.append('-s') + + return cmdline + +def invoke_mount_fuse(mnt_dir, name, options): + return base_cmdline + [ pjoin(basename, 'util', 'mount.fuse3'), + name, mnt_dir, '-o', ','.join(options) ] + +def invoke_mount_fuse_drop_privileges(mnt_dir, name, options): + if os.getuid() != 0: + pytest.skip('drop_privileges requires root, skipping.') + + return invoke_mount_fuse(mnt_dir, name, options + ('drop_privileges',)) + +class raii_tmpdir: + def __init__(self): + self.d = tempfile.mkdtemp() + + def __str__(self): + return str(self.d) + + def mkdir(self, path): + return py.path.local(str(self.d)).mkdir(path) + +@pytest.fixture +def short_tmpdir(): + return raii_tmpdir() + +def readdir_inode(dir): + cmd = base_cmdline + [ pjoin(basename, 'test', 'readdir_inode'), dir ] + with subprocess.Popen(cmd, stdout=subprocess.PIPE, + universal_newlines=True) as proc: + lines = proc.communicate()[0].splitlines() + lines.sort() + return lines + + +@pytest.mark.parametrize("cmdline_builder", (invoke_directly, invoke_mount_fuse, + invoke_mount_fuse_drop_privileges)) +@pytest.mark.parametrize("options", powerset(options)) +@pytest.mark.parametrize("name", ('hello', 'hello_ll', 'test/hello')) +def test_hello(tmpdir, name, options, cmdline_builder, output_checker): + logger = logging.getLogger(__name__) + mnt_dir = str(tmpdir) + logger.debug(f"Mount directory: {mnt_dir}") + cmdline = cmdline_builder(mnt_dir, name, options) + logger.debug(f"Command line: {' '.join(cmdline)}") + mount_process = subprocess.Popen( + cmdline, + stdout=output_checker.fd, stderr=output_checker.fd) + logger.debug(f"Mount process PID: {mount_process.pid}") + try: + logger.debug("Waiting for mount...") + wait_for_mount(mount_process, mnt_dir) + logger.debug("Mount completed") + assert os.listdir(mnt_dir) == [ 'hello' ] + logger.debug("Verified 'hello' file exists in mount directory") + filename = pjoin(mnt_dir, 'hello') + with open(filename, 'r') as fh: + assert fh.read() == 'Hello World!\n' + logger.debug("Verified contents of 'hello' file") + with pytest.raises(IOError) as exc_info: + open(filename, 'r+') + assert exc_info.value.errno == errno.EACCES + logger.debug("Verified EACCES error when trying to open file for writing") + with pytest.raises(IOError) as exc_info: + open(filename + 'does-not-exist', 'r+') + assert exc_info.value.errno == errno.ENOENT + logger.debug("Verified ENOENT error for non-existent file") + if name == 'hello_ll': + logger.debug("Testing xattr for hello_ll") + tst_xattr(mnt_dir) + path = os.path.join(mnt_dir, 'hello') + tst_xattr(path) + except: + logger.error("Exception occurred during test", exc_info=True) + cleanup(mount_process, mnt_dir) + raise + else: + logger.debug("Unmounting...") + umount(mount_process, mnt_dir) + logger.debug("Test completed successfully") + +@pytest.mark.parametrize("writeback", (False, True)) +@pytest.mark.parametrize("name", ('passthrough', 'passthrough_plus', + 'passthrough_fh', 'passthrough_ll', 'passthrough_zero_ino')) +@pytest.mark.parametrize("debug", (False, True)) +def test_passthrough(short_tmpdir, name, debug, output_checker, writeback): + # Avoid false positives from libfuse debug messages + if debug: + output_checker.register_output(r'^ unique: [0-9]+, error: -[0-9]+ .+$', + count=0) + + # test_syscalls prints "No error" under FreeBSD + output_checker.register_output(r"^ \d\d \[[^\]]+ message: 'No error: 0'\]", + count=0) + + mnt_dir = str(short_tmpdir.mkdir('mnt')) + src_dir = str(short_tmpdir.mkdir('src')) + + inode_check = InodeCheck.EXACT + if name == 'passthrough_plus': + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'passthrough'), + '--plus', '-f', mnt_dir ] + elif name == 'passthrough_zero_ino': + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'passthrough'), + '--plus', '--readdir-zero-inodes', '-f', mnt_dir ] + inode_check = InodeCheck.NONZERO + elif name == 'passthrough_ll': + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', name), + '-f', mnt_dir, '-o', 'timeout=0' ] + else: # passthrough and passthrough_fh + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', name), + '-f', mnt_dir ] + + # Set all timeouts to 0 for everything except passthrough_ll + # (this includes passthrough, passthrough_plus, and passthrough_fh) + if name != 'passthrough_ll': + cmdline.extend(['-o', 'entry_timeout=0,negative_timeout=0,attr_timeout=0,ac_attr_timeout=0']) + + if debug: + cmdline.append('-d') + + if writeback: + if name != 'passthrough_ll': + pytest.skip('example does not support writeback caching') + cmdline.append('-o') + cmdline.append('writeback') + + print(f"\nDebug: Command line: {' '.join(cmdline)}") + + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + try: + wait_for_mount(mount_process, mnt_dir) + work_dir = mnt_dir + src_dir + + tst_statvfs(work_dir) + tst_readdir(src_dir, work_dir, inode_check) + tst_readdir_big(src_dir, work_dir, inode_check) + tst_open_read(src_dir, work_dir) + tst_open_write(src_dir, work_dir) + tst_create(work_dir) + tst_passthrough(src_dir, work_dir, inode_check) + tst_append(src_dir, work_dir) + tst_seek(src_dir, work_dir) + tst_mkdir(work_dir) + tst_rmdir(work_dir, src_dir) + tst_unlink(work_dir, src_dir) + tst_symlink(work_dir) + if os.getuid() == 0: + tst_chown(work_dir) + + # Underlying fs may not have full nanosecond resolution + tst_utimens(work_dir, ns_tol=1000) + + if inode_check == InodeCheck.EXACT: + tst_link(work_dir) + tst_truncate_path(work_dir) + tst_truncate_fd(work_dir) + tst_open_unlink(work_dir) + + syscall_test_cmd = [ os.path.join(basename, 'test', 'test_syscalls'), + work_dir, ':' + src_dir ] + if writeback: + # When writeback caching is enabled, kernel has to open files for + # reading even when userspace opens with O_WDONLY. This fails if the + # filesystem process doesn't have special permission. + syscall_test_cmd.append('-53') + subprocess.check_call(syscall_test_cmd) + except: + cleanup(mount_process, mnt_dir) + raise + else: + umount(mount_process, mnt_dir) + +@pytest.mark.parametrize("cache", (False, True)) +def test_passthrough_hp(short_tmpdir, cache, output_checker): + mnt_dir = str(short_tmpdir.mkdir('mnt')) + src_dir = str(short_tmpdir.mkdir('src')) + + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'passthrough_hp'), + src_dir, mnt_dir ] + + cmdline.append('--foreground') + + if not cache: + cmdline.append('--nocache') + + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + try: + wait_for_mount(mount_process, mnt_dir) + + tst_statvfs(mnt_dir) + tst_readdir(src_dir, mnt_dir) + tst_readdir_big(src_dir, mnt_dir) + tst_open_read(src_dir, mnt_dir) + tst_open_write(src_dir, mnt_dir) + tst_create(mnt_dir) + if not cache: + tst_passthrough(src_dir, mnt_dir) + tst_append(src_dir, mnt_dir) + tst_seek(src_dir, mnt_dir) + tst_mkdir(mnt_dir) + if cache: + # if cache is enabled, no operations should go through + # src_dir as the cache will become stale. + tst_rmdir(mnt_dir) + tst_unlink(mnt_dir) + else: + tst_rmdir(mnt_dir, src_dir) + tst_unlink(mnt_dir, src_dir) + tst_symlink(mnt_dir) + if os.getuid() == 0: + tst_chown(mnt_dir) + + # Underlying fs may not have full nanosecond resolution + tst_utimens(mnt_dir, ns_tol=1000) + + tst_link(mnt_dir) + tst_truncate_path(mnt_dir) + tst_truncate_fd(mnt_dir) + tst_open_unlink(mnt_dir) + + # test_syscalls assumes that changes in source directory + # will be reflected immediately in mountpoint, so we + # can't use it. + if not cache: + syscall_test_cmd = [ os.path.join(basename, 'test', 'test_syscalls'), + mnt_dir, ':' + src_dir ] + # unlinked testfiles check fails without kernel fix + # "fuse: fix illegal access to inode with reused nodeid" + # so opt-in for this test from kernel 5.14 + if parse_kernel_version(platform.release()) >= version.parse('5.14'): + syscall_test_cmd.append('-u') + subprocess.check_call(syscall_test_cmd) + except: + cleanup(mount_process, mnt_dir) + raise + else: + umount(mount_process, mnt_dir) + + +@pytest.mark.skipif(fuse_proto < (7,11), + reason='not supported by running kernel') +def test_ioctl(tmpdir, output_checker): + progname = pjoin(basename, 'example', 'ioctl') + if not os.path.exists(progname): + pytest.skip('%s not built' % os.path.basename(progname)) + + # Check if binary is 32-bit + file_output = subprocess.check_output(['file', progname]).decode() + if 'ELF 32-bit' in file_output and platform.machine() == 'x86_64': + pytest.skip('ioctl test not supported for 32-bit binary on 64-bit system') + + mnt_dir = str(tmpdir) + testfile = pjoin(mnt_dir, 'fioc') + cmdline = base_cmdline + [progname, '-f', mnt_dir ] + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + try: + wait_for_mount(mount_process, mnt_dir) + + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'ioctl_client'), + testfile ] + assert subprocess.check_output(cmdline) == b'0\n' + with open(testfile, 'wb') as fh: + fh.write(b'foobar') + assert subprocess.check_output(cmdline) == b'6\n' + subprocess.check_call(cmdline + [ '3' ]) + with open(testfile, 'rb') as fh: + assert fh.read()== b'foo' + except: + cleanup(mount_process, mnt_dir) + raise + else: + umount(mount_process, mnt_dir) + +def test_poll(tmpdir, output_checker): + mnt_dir = str(tmpdir) + cmdline = base_cmdline + [pjoin(basename, 'example', 'poll'), + '-f', mnt_dir ] + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + try: + wait_for_mount(mount_process, mnt_dir) + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'poll_client') ] + subprocess.check_call(cmdline, cwd=mnt_dir) + except: + cleanup(mount_process, mnt_dir) + raise + else: + umount(mount_process, mnt_dir) + +def test_null(tmpdir, output_checker): + progname = pjoin(basename, 'example', 'null') + if not os.path.exists(progname): + pytest.skip('%s not built' % os.path.basename(progname)) + + mnt_file = str(tmpdir) + '/file' + with open(mnt_file, 'w') as fh: + fh.write('dummy') + cmdline = base_cmdline + [ progname, '-f', mnt_file ] + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + def test_fn(name): + return os.stat(name).st_size > 4000 + try: + wait_for_mount(mount_process, mnt_file, test_fn) + with open(mnt_file, 'rb') as fh: + assert fh.read(382) == b'\0' * 382 + with open(mnt_file, 'wb') as fh: + fh.write(b'whatever') + except: + cleanup(mount_process, mnt_file) + raise + else: + umount(mount_process, mnt_file) + + +@pytest.mark.skipif(fuse_proto < (7,12), + reason='not supported by running kernel') +@pytest.mark.parametrize("only_expire", ("invalidate_entries", + "expire_entries", "inc_epoch")) +@pytest.mark.parametrize("notify", (True, False)) +def test_notify_inval_entry(tmpdir, only_expire, notify, output_checker): + mnt_dir = str(tmpdir) + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'notify_inval_entry'), + '-f', '--update-interval=1', + '--timeout=5', mnt_dir ] + if not notify: + cmdline.append('--no-notify') + if only_expire == "expire_entries": + cmdline.append('--only-expire') + if "FUSE_CAP_EXPIRE_ONLY" not in fuse_caps: + pytest.skip('only-expire not supported by running kernel') + elif only_expire == "inc_epoch": + cmdline.append('--inc-epoch') + if fuse_proto < (7,44): + pytest.skip('inc-epoch not supported by running kernel') + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + try: + wait_for_mount(mount_process, mnt_dir) + fname = pjoin(mnt_dir, os.listdir(mnt_dir)[0]) + try: + os.stat(fname) + except FileNotFoundError: + # We may have hit a race condition and issued + # readdir just before the name changed + fname = pjoin(mnt_dir, os.listdir(mnt_dir)[0]) + os.stat(fname) + + safe_sleep(2) + if not notify: + os.stat(fname) + safe_sleep(5) + with pytest.raises(FileNotFoundError): + os.stat(fname) + except: + cleanup(mount_process, mnt_dir) + raise + else: + umount(mount_process, mnt_dir) + +@pytest.mark.parametrize("intended_user", ('root', 'non_root')) +def test_dev_auto_unmount(short_tmpdir, output_checker, intended_user): + """Check that root can mount with dev and auto_unmount + (but non-root cannot). + Split into root vs non-root, so that the output of pytest + makes clear what functionality is being tested.""" + if os.getuid() == 0 and intended_user == 'non_root': + pytest.skip('needs to run as non-root') + if os.getuid() != 0 and intended_user == 'root': + pytest.skip('needs to run as root') + mnt_dir = str(short_tmpdir.mkdir('mnt')) + src_dir = str('/dev') + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'passthrough_ll'), + '-o', f'source={src_dir},dev,auto_unmount', + '-f', mnt_dir ] + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + try: + wait_for_mount(mount_process, mnt_dir) + if os.getuid() == 0: + open(pjoin(mnt_dir, 'null')).close() + else: + with pytest.raises(PermissionError): + open(pjoin(mnt_dir, 'null')).close() + except: + cleanup(mount_process, mnt_dir) + raise + else: + umount(mount_process, mnt_dir) + +@pytest.mark.skipif(os.getuid() != 0, + reason='needs to run as root') +def test_cuse(output_checker): + progname = pjoin(basename, 'example', 'cuse') + if not os.path.exists(progname): + pytest.skip('%s not built' % os.path.basename(progname)) + + # Check if binary is 32-bit + file_output = subprocess.check_output(['file', progname]).decode() + if 'ELF 32-bit' in file_output and platform.machine() == 'x86_64': + pytest.skip('cuse test not supported for 32-bit binary on 64-bit system') + + # Valgrind warns about unknown ioctls, that's ok + output_checker.register_output(r'^==([0-9]+).+unhandled ioctl.+\n' + r'==\1== \s{3}.+\n' + r'==\1== \s{3}.+$', count=0) + + devname = 'cuse-test-%d' % os.getpid() + devpath = '/dev/%s' % devname + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'cuse'), + '-f', '--name=%s' % devname ] + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'cuse_client'), + devpath ] + try: + wait_for_mount(mount_process, devpath, + test_fn=os.path.exists) + assert subprocess.check_output(cmdline + ['s']) == b'0\n' + data = b'some test data' + off = 5 + proc = subprocess.Popen(cmdline + [ 'w', str(len(data)), str(off) ], + stdin=subprocess.PIPE) + proc.stdin.write(data) + proc.stdin.close() + assert proc.wait(timeout=10) == 0 + size = str(off + len(data)).encode() + b'\n' + assert subprocess.check_output(cmdline + ['s']) == size + out = subprocess.check_output( + cmdline + [ 'r', str(off + len(data) + 2), '0' ]) + assert out == (b'\0' * off) + data + finally: + mount_process.terminate() + +def test_release_unlink_race(tmpdir, output_checker): + """test case for Issue #746 + + If RELEASE and UNLINK opcodes are sent back to back, and fuse_fs_release() + and fuse_fs_rename() are slow to execute, UNLINK will run while RELEASE is + still executing. UNLINK will try to rename the file and, while the rename + is happening, the RELEASE will finish executing. As a result, RELEASE will + not detect in time that UNLINK has happened, and UNLINK will not detect in + time that RELEASE has happened. + + + NOTE: This is triggered only when nullpath_ok is set. + + If it is NOT SET then get_path_nullok() called by fuse_lib_release() will + call get_path_common() and lock the path, and then the fuse_lib_unlink() + will wait for the path to be unlocked before executing and thus synchronise + with fuse_lib_release(). + + If it is SET then get_path_nullok() will just set the path to null and + return without locking anything and thus allowing fuse_lib_unlink() to + eventually execute unimpeded while fuse_lib_release() is still running. + """ + + fuse_mountpoint = str(tmpdir) + + fuse_binary_command = base_cmdline + \ + [ pjoin(basename, 'test', 'release_unlink_race'), + "-f", fuse_mountpoint] + + fuse_process = subprocess.Popen(fuse_binary_command, + stdout=output_checker.fd, + stderr=output_checker.fd) + + try: + wait_for_mount(fuse_process, fuse_mountpoint) + + temp_dir = tempfile.TemporaryDirectory(dir="/tmp/") + temp_dir_path = temp_dir.name + + fuse_temp_file, fuse_temp_file_path = tempfile.mkstemp(dir=(fuse_mountpoint + temp_dir_path)) + + os.close(fuse_temp_file) + os.unlink(fuse_temp_file_path) + + # needed for slow CI/CD pipelines for unlink OP to complete processing + safe_sleep(3) + + assert os.listdir(temp_dir_path) == [] + + except: + temp_dir.cleanup() + cleanup(fuse_process, fuse_mountpoint) + raise + + else: + temp_dir.cleanup() + umount(fuse_process, fuse_mountpoint) + + +@contextmanager +def os_open(name, flags): + fd = os.open(name, flags) + try: + yield fd + finally: + os.close(fd) + +def os_create(name): + os.close(os.open(name, os.O_CREAT | os.O_RDWR)) + +def tst_unlink(mnt_dir, src_dir=None): + name = name_generator() + fullname = mnt_dir + "/" + name + srcname = fullname + if src_dir is not None: + srcname = pjoin(src_dir, name) + with open(srcname, 'wb') as fh: + fh.write(b'hello') + assert name in os.listdir(mnt_dir) + os.unlink(fullname) + with pytest.raises(OSError) as exc_info: + os.stat(fullname) + assert exc_info.value.errno == errno.ENOENT + assert name not in os.listdir(mnt_dir) + +def tst_mkdir(mnt_dir): + dirname = name_generator() + fullname = mnt_dir + "/" + dirname + os.mkdir(fullname) + fstat = os.stat(fullname) + assert stat.S_ISDIR(fstat.st_mode) + assert os.listdir(fullname) == [] + # Some filesystem (e.g. BTRFS) don't track st_nlink for directories + assert fstat.st_nlink in (1,2) + assert dirname in os.listdir(mnt_dir) + +def tst_rmdir(mnt_dir, src_dir=None): + name = name_generator() + fullname = mnt_dir + "/" + name + srcname = fullname + if src_dir is not None: + srcname = pjoin(src_dir, name) + os.mkdir(srcname) + assert name in os.listdir(mnt_dir) + os.rmdir(fullname) + with pytest.raises(OSError) as exc_info: + os.stat(fullname) + assert exc_info.value.errno == errno.ENOENT + assert name not in os.listdir(mnt_dir) + +def tst_symlink(mnt_dir): + linkname = name_generator() + fullname = mnt_dir + "/" + linkname + os.symlink("/imaginary/dest", fullname) + fstat = os.lstat(fullname) + assert stat.S_ISLNK(fstat.st_mode) + assert os.readlink(fullname) == "/imaginary/dest" + assert fstat.st_nlink == 1 + assert linkname in os.listdir(mnt_dir) + +def tst_create(mnt_dir): + name = name_generator() + fullname = pjoin(mnt_dir, name) + with pytest.raises(OSError) as exc_info: + os.stat(fullname) + assert exc_info.value.errno == errno.ENOENT + assert name not in os.listdir(mnt_dir) + + fd = os.open(fullname, os.O_CREAT | os.O_RDWR) + os.close(fd) + + assert name in os.listdir(mnt_dir) + fstat = os.lstat(fullname) + assert stat.S_ISREG(fstat.st_mode) + assert fstat.st_nlink == 1 + assert fstat.st_size == 0 + +def tst_chown(mnt_dir): + filename = pjoin(mnt_dir, name_generator()) + os.mkdir(filename) + fstat = os.lstat(filename) + uid = fstat.st_uid + gid = fstat.st_gid + + uid_new = uid + 1 + os.chown(filename, uid_new, -1) + fstat = os.lstat(filename) + assert fstat.st_uid == uid_new + assert fstat.st_gid == gid + + gid_new = gid + 1 + os.chown(filename, -1, gid_new) + fstat = os.lstat(filename) + assert fstat.st_uid == uid_new + assert fstat.st_gid == gid_new + +def tst_open_read(src_dir, mnt_dir): + name = name_generator() + with open(pjoin(src_dir, name), 'wb') as fh_out, \ + open(TEST_FILE, 'rb') as fh_in: + shutil.copyfileobj(fh_in, fh_out) + + assert filecmp.cmp(pjoin(mnt_dir, name), TEST_FILE, False) + +def tst_open_write(src_dir, mnt_dir): + name = name_generator() + os_create(pjoin(src_dir, name)) + fullname = pjoin(mnt_dir, name) + with open(fullname, 'wb') as fh_out, \ + open(TEST_FILE, 'rb') as fh_in: + shutil.copyfileobj(fh_in, fh_out) + + assert filecmp.cmp(fullname, TEST_FILE, False) + +def tst_append(src_dir, mnt_dir): + name = name_generator() + os_create(pjoin(src_dir, name)) + fullname = pjoin(mnt_dir, name) + with os_open(fullname, os.O_WRONLY) as fd: + os.write(fd, b'foo\n') + with os_open(fullname, os.O_WRONLY|os.O_APPEND) as fd: + os.write(fd, b'bar\n') + + with open(fullname, 'rb') as fh: + assert fh.read() == b'foo\nbar\n' + +def tst_seek(src_dir, mnt_dir): + name = name_generator() + os_create(pjoin(src_dir, name)) + fullname = pjoin(mnt_dir, name) + with os_open(fullname, os.O_WRONLY) as fd: + os.lseek(fd, 1, os.SEEK_SET) + os.write(fd, b'foobar\n') + with os_open(fullname, os.O_WRONLY) as fd: + os.lseek(fd, 4, os.SEEK_SET) + os.write(fd, b'com') + + with open(fullname, 'rb') as fh: + assert fh.read() == b'\0foocom\n' + +def tst_open_unlink(mnt_dir): + name = pjoin(mnt_dir, name_generator()) + data1 = b'foo' + data2 = b'bar' + fullname = pjoin(mnt_dir, name) + with open(fullname, 'wb+', buffering=0) as fh: + fh.write(data1) + os.unlink(fullname) + with pytest.raises(OSError) as exc_info: + os.stat(fullname) + assert exc_info.value.errno == errno.ENOENT + assert name not in os.listdir(mnt_dir) + fh.write(data2) + fh.seek(0) + assert fh.read() == data1+data2 + +def tst_statvfs(mnt_dir): + os.statvfs(mnt_dir) + +def tst_link(mnt_dir): + name1 = pjoin(mnt_dir, name_generator()) + name2 = pjoin(mnt_dir, name_generator()) + shutil.copyfile(TEST_FILE, name1) + assert filecmp.cmp(name1, TEST_FILE, False) + + fstat1 = os.lstat(name1) + assert fstat1.st_nlink == 1 + + os.link(name1, name2) + + fstat1 = os.lstat(name1) + fstat2 = os.lstat(name2) + assert fstat1 == fstat2 + assert fstat1.st_nlink == 2 + assert os.path.basename(name2) in os.listdir(mnt_dir) + assert filecmp.cmp(name1, name2, False) + + # Since RELEASE requests are asynchronous, it is possible that + # libfuse still considers the file to be open at this point + # and (since -o hard_remove is not used) renames it instead of + # deleting it. In that case, the following lstat() call will + # still report an st_nlink value of 2 (cf. issue #157). + os.unlink(name2) + + assert os.path.basename(name2) not in os.listdir(mnt_dir) + with pytest.raises(FileNotFoundError): + os.lstat(name2) + + # See above, we may have to wait until RELEASE has been + # received before the st_nlink value is correct. + maxwait = time.time() + 2 + fstat1 = os.lstat(name1) + while fstat1.st_nlink == 2 and time.time() < maxwait: + fstat1 = os.lstat(name1) + time.sleep(0.1) + assert fstat1.st_nlink == 1 + + os.unlink(name1) + +def tst_inodes_nonzero(lines): + inode_nums = [int(line.split()[0]) for line in lines] + assert all(i != 0 for i in inode_nums), inode_nums + +def tst_inode(inode_check, actual, expected): + if inode_check == InodeCheck.EXACT: + assert expected == actual + elif inode_check == InodeCheck.NONZERO: + assert actual != 0 + +def tst_readdir(src_dir, mnt_dir, inode_check=InodeCheck.EXACT): + newdir = name_generator() + + src_newdir = pjoin(src_dir, newdir) + mnt_newdir = pjoin(mnt_dir, newdir) + file_ = src_newdir + "/" + name_generator() + subdir = src_newdir + "/" + name_generator() + subfile = subdir + "/" + name_generator() + + os.mkdir(src_newdir) + shutil.copyfile(TEST_FILE, file_) + os.mkdir(subdir) + shutil.copyfile(TEST_FILE, subfile) + + listdir_is = os.listdir(mnt_newdir) + listdir_is.sort() + listdir_should = [ os.path.basename(file_), os.path.basename(subdir) ] + listdir_should.sort() + assert listdir_is == listdir_should + + inodes_is = readdir_inode(mnt_newdir) + if inode_check == InodeCheck.EXACT: + inodes_should = readdir_inode(src_newdir) + assert inodes_is == inodes_should + elif inode_check == InodeCheck.NONZERO: + tst_inodes_nonzero(inodes_is) + + os.unlink(file_) + os.unlink(subfile) + os.rmdir(subdir) + os.rmdir(src_newdir) + +def tst_readdir_big(src_dir, mnt_dir, inode_check=InodeCheck.EXACT): + # Add enough entries so that readdir needs to be called + # multiple times. + fnames = [] + for i in range(500): + fname = ('A rather long filename to make sure that we ' + 'fill up the buffer - ' * 3) + str(i) + with open(pjoin(src_dir, fname), 'w') as fh: + fh.write('File %d' % i) + fnames.append(fname) + + listdir_is = sorted(os.listdir(mnt_dir)) + listdir_should = sorted(os.listdir(src_dir)) + assert listdir_is == listdir_should + + inodes_is = readdir_inode(mnt_dir) + if inode_check == InodeCheck.EXACT: + inodes_should = readdir_inode(src_dir) + assert inodes_is == inodes_should + elif inode_check == InodeCheck.NONZERO: + tst_inodes_nonzero(inodes_is) + + for fname in fnames: + # A comment just to get a diff + stat_src = os.stat(pjoin(src_dir, fname)) + stat_mnt = os.stat(pjoin(mnt_dir, fname)) + tst_inode(inode_check, stat_mnt.st_ino, stat_src.st_ino) + assert stat_src.st_mtime == stat_mnt.st_mtime + assert stat_src.st_ctime == stat_mnt.st_ctime + assert stat_src.st_size == stat_mnt.st_size + os.unlink(pjoin(src_dir, fname)) + +def tst_truncate_path(mnt_dir): + assert len(TEST_DATA) > 1024 + + filename = pjoin(mnt_dir, name_generator()) + with open(filename, 'wb') as fh: + fh.write(TEST_DATA) + + fstat = os.stat(filename) + size = fstat.st_size + assert size == len(TEST_DATA) + + # Add zeros at the end + os.truncate(filename, size + 1024) + assert os.stat(filename).st_size == size + 1024 + with open(filename, 'rb') as fh: + assert fh.read(size) == TEST_DATA + assert fh.read(1025) == b'\0' * 1024 + + # Truncate data + os.truncate(filename, size - 1024) + assert os.stat(filename).st_size == size - 1024 + with open(filename, 'rb') as fh: + assert fh.read(size) == TEST_DATA[:size-1024] + + os.unlink(filename) + +def tst_truncate_fd(mnt_dir): + assert len(TEST_DATA) > 1024 + with NamedTemporaryFile('w+b', 0, dir=mnt_dir) as fh: + fd = fh.fileno() + fh.write(TEST_DATA) + fstat = os.fstat(fd) + size = fstat.st_size + assert size == len(TEST_DATA) + + # Add zeros at the end + os.ftruncate(fd, size + 1024) + assert os.fstat(fd).st_size == size + 1024 + fh.seek(0) + assert fh.read(size) == TEST_DATA + assert fh.read(1025) == b'\0' * 1024 + + # Truncate data + os.ftruncate(fd, size - 1024) + assert os.fstat(fd).st_size == size - 1024 + fh.seek(0) + assert fh.read(size) == TEST_DATA[:size-1024] + +def tst_utimens(mnt_dir, ns_tol=0): + filename = pjoin(mnt_dir, name_generator()) + os.mkdir(filename) + fstat = os.lstat(filename) + + atime = fstat.st_atime + 42.28 + mtime = fstat.st_mtime - 42.23 + if sys.version_info < (3,3): + os.utime(filename, (atime, mtime)) + else: + atime_ns = fstat.st_atime_ns + int(42.28*1e9) + mtime_ns = fstat.st_mtime_ns - int(42.23*1e9) + os.utime(filename, None, ns=(atime_ns, mtime_ns)) + + fstat = os.lstat(filename) + + assert abs(fstat.st_atime - atime) < 1 + assert abs(fstat.st_mtime - mtime) < 1 + if sys.version_info >= (3,3): + assert abs(fstat.st_atime_ns - atime_ns) <= ns_tol + assert abs(fstat.st_mtime_ns - mtime_ns) <= ns_tol + +def tst_passthrough(src_dir, mnt_dir, inode_check=InodeCheck.EXACT): + name = name_generator() + src_name = pjoin(src_dir, name) + mnt_name = pjoin(mnt_dir, name) + + print(f"\nDebug: Creating file {name}") + print(f"Debug: src_name={src_name}") + print(f"Debug: mnt_name={mnt_name}") + + # First test: write to source directory + assert name not in os.listdir(src_dir) + assert name not in os.listdir(mnt_dir) + with open(src_name, 'w') as fh: + fh.write('Hello, world') + + print(f"Debug: File written to src_name") + + start_time = time.time() + while time.time() - start_time < 10: # 10 second timeout + if name in os.listdir(mnt_dir): + break + print(f"Debug: Waiting for file to appear... ({time.time() - start_time:.1f}s)") + time.sleep(0.1) + else: + pytest.fail("File did not appear in mount directory within 10 seconds") + + assert name in os.listdir(src_dir) + assert name in os.listdir(mnt_dir) + + # Compare relevant stat attributes + src_stat = os.stat(src_name) + mnt_stat = os.stat(mnt_name) + assert src_stat.st_mode == mnt_stat.st_mode + tst_inode(inode_check, mnt_stat.st_ino, src_stat.st_ino) + assert src_stat.st_size == mnt_stat.st_size + assert src_stat.st_mtime == mnt_stat.st_mtime + + # Second test: write to mount directory + name = name_generator() + src_name = pjoin(src_dir, name) + mnt_name = pjoin(mnt_dir, name) + assert name not in os.listdir(src_dir) + assert name not in os.listdir(mnt_dir) + with open(mnt_name, 'w') as fh: + fh.write('Hello, world') + assert name in os.listdir(src_dir) + assert name in os.listdir(mnt_dir) + + # Compare relevant stat attributes + src_stat = os.stat(src_name) + mnt_stat = os.stat(mnt_name) + assert src_stat.st_mode == mnt_stat.st_mode + tst_inode(inode_check, mnt_stat.st_ino, src_stat.st_ino) + assert src_stat.st_size == mnt_stat.st_size + assert abs(src_stat.st_mtime - mnt_stat.st_mtime) < 0.01 + + +def tst_xattr(path): + os.setxattr(path, b'hello_ll_setxattr_name', b'hello_ll_setxattr_value') + assert os.getxattr(path, b'hello_ll_getxattr_name') == b'hello_ll_getxattr_value' + os.removexattr(path, b'hello_ll_removexattr_name') + + +# avoid warning about unused import +assert test_printcap diff --git a/test/test_setattr.c b/test/test_setattr.c new file mode 100644 index 0000000..ed0e93d --- /dev/null +++ b/test/test_setattr.c @@ -0,0 +1,194 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2016 Nikolaus Rath + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + + +#define FUSE_USE_VERSION 30 + +/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __linux__ +#include +#else +#include +#endif + +#define FILE_INO 2 +#define FILE_NAME "truncate_me" + +static int got_fh; +static mode_t file_mode = S_IFREG | 0644; + +static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) { + stbuf->st_ino = ino; + if (ino == FUSE_ROOT_ID) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } + + else if (ino == FILE_INO) { + stbuf->st_mode = file_mode; + stbuf->st_nlink = 1; + stbuf->st_size = 0; + } + + else + return -1; + + return 0; +} + +static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, + const char *name) { + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + + if (parent != FUSE_ROOT_ID) + goto err_out; + else if (strcmp(name, FILE_NAME) == 0) + e.ino = FILE_INO; + else + goto err_out; + + if (tfs_stat(e.ino, &e.attr) != 0) + goto err_out; + fuse_reply_entry(req, &e); + return; + +err_out: + fuse_reply_err(req, ENOENT); +} + +static void tfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) { + struct stat stbuf; + + (void) fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (tfs_stat(ino, &stbuf) != 0) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, 5); +} + +static void tfs_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) { + if (ino == FUSE_ROOT_ID) + fuse_reply_err(req, EISDIR); + else { + assert(ino == FILE_INO); + fi->fh = FILE_INO; + fuse_reply_open(req, fi); + } +} + +static void tfs_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int to_set, struct fuse_file_info *fi) { + if(ino != FILE_INO || + !(to_set & FUSE_SET_ATTR_MODE)) { + fuse_reply_err(req, EINVAL); + return; + } + + if(fi == NULL) + fprintf(stderr, "setattr with fi == NULL\n"); + else if (fi->fh != FILE_INO) + fprintf(stderr, "setattr with wrong fi->fh\n"); + else { + fprintf(stderr, "setattr ok\n"); + got_fh = 1; + file_mode = attr->st_mode; + } + + tfs_getattr(req, ino, fi); +} + +static struct fuse_lowlevel_ops tfs_oper = { + .lookup = tfs_lookup, + .getattr = tfs_getattr, + .open = tfs_open, + .setattr = tfs_setattr, +}; + +static void* run_fs(void *data) { + struct fuse_session *se = (struct fuse_session*) data; + assert(fuse_session_loop(se) == 0); + return NULL; +} + +static void test_fs(char *mountpoint) { + char fname[PATH_MAX]; + int fd; + + assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME, + mountpoint) > 0); + fd = open(fname, O_WRONLY); + if (fd == -1) { + perror(fname); + assert(0); + } + + assert(fchmod(fd, 0600) == 0); + close(fd); +} + +int main(int argc, char *argv[]) { + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts fuse_opts; + pthread_t fs_thread; + + assert(fuse_parse_cmdline(&args, &fuse_opts) == 0); +#ifndef __FreeBSD__ + assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0); +#endif + se = fuse_session_new(&args, &tfs_oper, + sizeof(tfs_oper), NULL); + assert (se != NULL); + assert(fuse_set_signal_handlers(se) == 0); + assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0); + + /* Start file-system thread */ + assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0); + + /* Do test */ + test_fs(fuse_opts.mountpoint); + + /* Stop file system */ + assert(pthread_cancel(fs_thread) == 0); + + fuse_session_unmount(se); + assert(got_fh == 1); + fuse_remove_signal_handlers(se); + fuse_session_destroy(se); + + printf("Test completed successfully.\n"); + return 0; +} + + +/** + * Local Variables: + * mode: c + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */ diff --git a/test/test_signals.c b/test/test_signals.c new file mode 100644 index 0000000..3ce7fbd --- /dev/null +++ b/test/test_signals.c @@ -0,0 +1,202 @@ +/* + * FUSE: Filesystem in Userspace + * Copyright (C) 2025 Bernd Schubert + * + * Test for signal handling in libfuse. + * + * This program can be distributed under the terms of the GNU LGPLv2. + * See the file GPL2.txt + */ + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17) + +#include "fuse_config.h" +#include "fuse_lowlevel.h" +#include "fuse_i.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void test_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + (void)parent; + (void)name; + /* Simulate slow lookup to test signal interruption */ + sleep(2); + fuse_reply_err(req, ENOENT); +} + +static void test_ll_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + (void)ino; + (void)fi; + /* Simulate slow getattr to test signal interruption */ + sleep(2); + fuse_reply_err(req, ENOENT); +} + +static const struct fuse_lowlevel_ops test_ll_ops = { + .lookup = test_ll_lookup, + .getattr = test_ll_getattr, +}; + +static void *signal_sender_thread(void *arg) +{ + (void)arg; + + usleep(2 * 1000 * 1000); + + /* Send SIGTERM to the process */ + kill(getpid(), SIGTERM); + return NULL; +} + +static void fork_child(void) +{ + struct fuse_args args = FUSE_ARGS_INIT(0, NULL); + struct fuse_session *se; + struct fuse_loop_config *loop_config; + pthread_t sig_thread; + char *mountpoint = NULL; + int ret = -1; + + /* Add the program name to arg[0] */ + if (fuse_opt_add_arg(&args, "test_signals")) { + fprintf(stderr, "Failed to add argument\n"); + goto out_free_mountpoint; + } + + /* Add debug flag to see more output */ + fuse_opt_add_arg(&args, "-d"); + + /* Create temporary mount point */ + mountpoint = strdup("/tmp/fuse_test_XXXXXX"); + if (!mountpoint || !mkdtemp(mountpoint)) { + fprintf(stderr, "Failed to create temp dir\n"); + goto out_free_args; + } + + /* Create session */ + se = fuse_session_new(&args, &test_ll_ops, sizeof(test_ll_ops), NULL); + if (!se) { + fprintf(stderr, "Failed to create FUSE session\n"); + goto out_free_mountpoint; + } + + /* Mount filesystem */ + if (fuse_session_mount(se, mountpoint)) { + fprintf(stderr, "Failed to mount FUSE filesystem\n"); + goto out_destroy_session; + } + + /* Create loop config */ + loop_config = fuse_loop_cfg_create(); + if (!loop_config) { + fprintf(stderr, "Failed to create loop config\n"); + goto out_unmount; + } + fuse_loop_cfg_set_clone_fd(loop_config, 0); + fuse_loop_cfg_set_max_threads(loop_config, 2); + + /* Set up signal handlers */ + if (fuse_set_signal_handlers(se)) { + fprintf(stderr, "Failed to set up signal handlers\n"); + goto out_destroy_config; + } + + /* Create thread that will send signals */ + if (pthread_create(&sig_thread, NULL, signal_sender_thread, NULL)) { + fprintf(stderr, "Failed to create signal sender thread\n"); + goto out_remove_handlers; + } + + /* Enter FUSE loop */ + ret = fuse_session_loop_mt_312(se, loop_config); + + printf("Debug: fuse_session_loop_mt_312 returned %d\n", ret); + printf("Debug: session exited state: %d\n", fuse_session_exited(se)); + printf("Debug: session status: %d\n", se->error); + + /* Check exit status before cleanup */ + int clean_exit = (fuse_session_exited(se) && se->error == SIGTERM); + + /* Clean up */ + pthread_join(sig_thread, NULL); + fuse_remove_signal_handlers(se); + fuse_session_unmount(se); + fuse_session_destroy(se); + fuse_loop_cfg_destroy(loop_config); + rmdir(mountpoint); + free(mountpoint); + fuse_opt_free_args(&args); + + /* Use saved exit status */ + if (clean_exit) { + printf("Debug: Clean shutdown via SIGTERM\n"); + exit(0); + } + printf("Debug: Exiting with status %d\n", ret != 0); + exit(ret != 0); + +out_remove_handlers: + fuse_remove_signal_handlers(se); +out_destroy_config: + fuse_loop_cfg_destroy(loop_config); +out_unmount: + fuse_session_unmount(se); +out_destroy_session: + fuse_session_destroy(se); +out_free_mountpoint: + rmdir(mountpoint); + free(mountpoint); +out_free_args: + fuse_opt_free_args(&args); + exit(1); +} + +static void run_test_in_child(void) +{ + pid_t child; + int status; + + child = fork(); + if (child == -1) { + perror("fork"); + exit(1); + } + + if (child == 0) + fork_child(); + + /* In parent process */ + if (waitpid(child, &status, 0) == -1) { + perror("waitpid"); + exit(1); + } + + /* Check if child exited due to SIGTERM - this is expected */ + if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) { + printf("Child process terminated by SIGTERM as expected\n"); + exit(0); + } + + /* For any other type of exit, maintain existing behavior */ + exit(WIFEXITED(status) ? WEXITSTATUS(status) : 1); +} + +int main(void) +{ + printf("Testing SIGTERM handling in libfuse\n"); + run_test_in_child(); + printf("SIGTERM handling test passed\n"); + return 0; +} diff --git a/test/test_syscalls.c b/test/test_syscalls.c new file mode 100644 index 0000000..61ee953 --- /dev/null +++ b/test/test_syscalls.c @@ -0,0 +1,2247 @@ +#define _GNU_SOURCE +#include "fuse_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef ALLPERMS +# define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)/* 07777 */ +#endif + + +static const char *basepath; +static const char *basepath_r; +static char testfile[1024]; +static char testfile2[1024]; +static char testdir[1024]; +static char testdir2[1024]; +static char testsock[1024]; +static char subfile[1280]; + +static char testfile_r[1024]; +static char testfile2_r[1024]; +static char testdir_r[1024]; +static char testdir2_r[1024]; +static char subfile_r[1280]; + +static char testname[256]; +static char testdata[] = "abcdefghijklmnopqrstuvwxyz"; +static char testdata2[] = "1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./"; +static const char *testdir_files[] = { "f1", "f2", NULL}; +static long seekdir_offsets[4]; +static char zerodata[4096]; +static int testdatalen = sizeof(testdata) - 1; +static int testdata2len = sizeof(testdata2) - 1; +static unsigned int testnum = 0; +static unsigned int select_test = 0; +static unsigned int skip_test = 0; +static unsigned int unlinked_test = 0; + +#define MAX_ENTRIES 1024 +#define MAX_TESTS 100 + +static struct test { + int fd; + struct stat stat; +} tests[MAX_TESTS]; + +static void test_perror(const char *func, const char *msg) +{ + fprintf(stderr, "%s %s() - %s: %s\n", testname, func, msg, + strerror(errno)); +} + +static void test_error(const char *func, const char *msg, ...) + __attribute__ ((format (printf, 2, 3))); + +static void __start_test(const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); + +static void test_error(const char *func, const char *msg, ...) +{ + va_list ap; + fprintf(stderr, "%s %s() - ", testname, func); + va_start(ap, msg); + vfprintf(stderr, msg, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static int is_dot_or_dotdot(const char *name) { + return name[0] == '.' && + (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); +} + +static void success(void) +{ + fprintf(stderr, "%s OK\n", testname); +} + +#define this_test (&tests[testnum-1]) +#define next_test (&tests[testnum]) + +static void __start_test(const char *fmt, ...) +{ + unsigned int n; + va_list ap; + n = sprintf(testname, "%3i [", testnum); + va_start(ap, fmt); + n += vsprintf(testname + n, fmt, ap); + va_end(ap); + sprintf(testname + n, "]"); + // Use dedicated testfile per test + sprintf(testfile, "%s/testfile.%d", basepath, testnum); + sprintf(testfile_r, "%s/testfile.%d", basepath_r, testnum); + if (testnum > MAX_TESTS) { + fprintf(stderr, "%s - too many tests\n", testname); + exit(1); + } + this_test->fd = -1; +} + +#define start_test(msg, args...) { \ + testnum++; \ + if ((select_test && testnum != select_test) || \ + (testnum == skip_test)) { \ + return 0; \ + } \ + __start_test(msg, ##args); \ +} + +#define PERROR(msg) test_perror(__FUNCTION__, msg) +#define ERROR(msg, args...) test_error(__FUNCTION__, msg, ##args) + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +static int st_check_size(struct stat *st, int len) +{ + if (st->st_size != len) { + ERROR("length %u instead of %u", (int) st->st_size, + (int) len); + return -1; + } + return 0; +} + +static int check_size(const char *path, int len) +{ + struct stat stbuf; + int res = stat(path, &stbuf); + if (res == -1) { + PERROR("stat"); + return -1; + } + return st_check_size(&stbuf, len); +} + +static int check_testfile_size(const char *path, int len) +{ + this_test->stat.st_size = len; + return check_size(path, len); +} + +static int st_check_type(struct stat *st, mode_t type) +{ + if ((st->st_mode & S_IFMT) != type) { + ERROR("type 0%o instead of 0%o", st->st_mode & S_IFMT, type); + return -1; + } + return 0; +} + +static int check_type(const char *path, mode_t type) +{ + struct stat stbuf; + int res = lstat(path, &stbuf); + if (res == -1) { + PERROR("lstat"); + return -1; + } + return st_check_type(&stbuf, type); +} + +static int st_check_mode(struct stat *st, mode_t mode) +{ + if ((st->st_mode & ALLPERMS) != mode) { + ERROR("mode 0%o instead of 0%o", st->st_mode & ALLPERMS, + mode); + return -1; + } + return 0; +} + +static int check_mode(const char *path, mode_t mode) +{ + struct stat stbuf; + int res = lstat(path, &stbuf); + if (res == -1) { + PERROR("lstat"); + return -1; + } + return st_check_mode(&stbuf, mode); +} + +static int check_testfile_mode(const char *path, mode_t mode) +{ + this_test->stat.st_mode &= ~ALLPERMS; + this_test->stat.st_mode |= mode; + return check_mode(path, mode); +} + +static int check_times(const char *path, time_t atime, time_t mtime) +{ + int err = 0; + struct stat stbuf; + int res = lstat(path, &stbuf); + if (res == -1) { + PERROR("lstat"); + return -1; + } + if (stbuf.st_atime != atime) { + ERROR("atime %li instead of %li", stbuf.st_atime, atime); + err--; + } + if (stbuf.st_mtime != mtime) { + ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime); + err--; + } + if (err) + return -1; + + return 0; +} + +#if 0 +static int fcheck_times(int fd, time_t atime, time_t mtime) +{ + int err = 0; + struct stat stbuf; + int res = fstat(fd, &stbuf); + if (res == -1) { + PERROR("fstat"); + return -1; + } + if (stbuf.st_atime != atime) { + ERROR("atime %li instead of %li", stbuf.st_atime, atime); + err--; + } + if (stbuf.st_mtime != mtime) { + ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime); + err--; + } + if (err) + return -1; + + return 0; +} +#endif + +static int st_check_nlink(struct stat *st, nlink_t nlink) +{ + if (st->st_nlink != nlink) { + ERROR("nlink %li instead of %li", (long) st->st_nlink, + (long) nlink); + return -1; + } + return 0; +} + +static int check_nlink(const char *path, nlink_t nlink) +{ + struct stat stbuf; + int res = lstat(path, &stbuf); + if (res == -1) { + PERROR("lstat"); + return -1; + } + return st_check_nlink(&stbuf, nlink); +} + +static int fcheck_stat(int fd, int flags, struct stat *st) +{ + struct stat stbuf; + int res = fstat(fd, &stbuf); + if (res == -1) { + if (flags & O_PATH) { + // With O_PATH fd, the server does not have to keep + // the inode alive so FUSE inode may be stale or bad + if (errno == ESTALE || errno == EIO || + errno == ENOENT || errno == EBADF) + return 0; + } + PERROR("fstat"); + return -1; + } + + int err = 0; + err += st_check_type(&stbuf, st->st_mode & S_IFMT); + err += st_check_mode(&stbuf, st->st_mode & ALLPERMS); + err += st_check_size(&stbuf, st->st_size); + err += st_check_nlink(&stbuf, st->st_nlink); + + return err; +} + +static int check_nonexist(const char *path) +{ + struct stat stbuf; + int res = lstat(path, &stbuf); + if (res == 0) { + ERROR("file should not exist"); + return -1; + } + if (errno != ENOENT) { + ERROR("file should not exist: %s", strerror(errno)); + return -1; + } + return 0; +} + +static int check_buffer(const char *buf, const char *data, unsigned len) +{ + if (memcmp(buf, data, len) != 0) { + ERROR("data mismatch"); + return -1; + } + return 0; +} + +static int check_data(const char *path, const char *data, int offset, + unsigned len) +{ + char buf[4096]; + int res; + int fd = open(path, O_RDONLY); + if (fd == -1) { + PERROR("open"); + return -1; + } + if (lseek(fd, offset, SEEK_SET) == (off_t) -1) { + PERROR("lseek"); + close(fd); + return -1; + } + while (len) { + int rdlen = len < sizeof(buf) ? len : sizeof(buf); + res = read(fd, buf, rdlen); + if (res == -1) { + PERROR("read"); + close(fd); + return -1; + } + if (res != rdlen) { + ERROR("short read: %u instead of %u", res, rdlen); + close(fd); + return -1; + } + if (check_buffer(buf, data, rdlen) != 0) { + close(fd); + return -1; + } + data += rdlen; + len -= rdlen; + } + res = close(fd); + if (res == -1) { + PERROR("close"); + return -1; + } + return 0; +} + +static int fcheck_data(int fd, const char *data, int offset, + unsigned len) +{ + char buf[4096]; + int res; + if (lseek(fd, offset, SEEK_SET) == (off_t) -1) { + PERROR("lseek"); + return -1; + } + while (len) { + int rdlen = len < sizeof(buf) ? len : sizeof(buf); + res = read(fd, buf, rdlen); + if (res == -1) { + PERROR("read"); + return -1; + } + if (res != rdlen) { + ERROR("short read: %u instead of %u", res, rdlen); + return -1; + } + if (check_buffer(buf, data, rdlen) != 0) { + return -1; + } + data += rdlen; + len -= rdlen; + } + return 0; +} + +static int check_dir_contents(const char *path, const char **contents) +{ + int i; + int res; + int err = 0; + int found[MAX_ENTRIES]; + const char *cont[MAX_ENTRIES]; + DIR *dp; + + for (i = 0; contents[i]; i++) { + assert(i < MAX_ENTRIES - 3); + found[i] = 0; + cont[i] = contents[i]; + } + cont[i] = NULL; + + dp = opendir(path); + if (dp == NULL) { + PERROR("opendir"); + return -1; + } + memset(found, 0, sizeof(found)); + while(1) { + struct dirent *de; + errno = 0; + de = readdir(dp); + if (de == NULL) { + if (errno) { + PERROR("readdir"); + closedir(dp); + return -1; + } + break; + } + if (is_dot_or_dotdot(de->d_name)) + continue; + for (i = 0; cont[i] != NULL; i++) { + assert(i < MAX_ENTRIES); + if (strcmp(cont[i], de->d_name) == 0) { + if (found[i]) { + ERROR("duplicate entry <%s>", + de->d_name); + err--; + } else + found[i] = 1; + break; + } + } + if (!cont[i]) { + ERROR("unexpected entry <%s>", de->d_name); + err --; + } + } + for (i = 0; cont[i] != NULL; i++) { + if (!found[i]) { + ERROR("missing entry <%s>", cont[i]); + err--; + } + } + res = closedir(dp); + if (res == -1) { + PERROR("closedir"); + return -1; + } + if (err) + return -1; + + return 0; +} + +static int create_file(const char *path, const char *data, int len) +{ + int res; + int fd; + + unlink(path); + fd = creat(path, 0644); + if (fd == -1) { + PERROR("creat"); + return -1; + } + if (len) { + res = write(fd, data, len); + if (res == -1) { + PERROR("write"); + close(fd); + return -1; + } + if (res != len) { + ERROR("write is short: %u instead of %u", res, len); + close(fd); + return -1; + } + } + res = close(fd); + if (res == -1) { + PERROR("close"); + return -1; + } + res = check_type(path, S_IFREG); + if (res == -1) + return -1; + res = check_mode(path, 0644); + if (res == -1) + return -1; + res = check_nlink(path, 1); + if (res == -1) + return -1; + res = check_size(path, len); + if (res == -1) + return -1; + + if (len) { + res = check_data(path, data, 0, len); + if (res == -1) + return -1; + } + + return 0; +} + +static int create_path_fd(const char *path, const char *data, int len) +{ + int path_fd; + int res; + + res = create_file(path, data, len); + if (res == -1) + return -1; + + path_fd = open(path, O_PATH); + if (path_fd == -1) + PERROR("open(O_PATH)"); + + return path_fd; +} + +// Can be called once per test +static int create_testfile(const char *path, const char *data, int len) +{ + struct test *t = this_test; + struct stat *st = &t->stat; + int res, fd; + + if (t->fd > 0) { + ERROR("testfile already created"); + return -1; + } + + fd = create_path_fd(path, data, len); + if (fd == -1) + return -1; + + t->fd = fd; + + res = fstat(fd, st); + if (res == -1) { + PERROR("fstat"); + return -1; + } + + return 0; +} + +static int check_unlinked_testfile(int fd) +{ + struct stat *st = &this_test->stat; + + st->st_nlink = 0; + return fcheck_stat(fd, O_PATH, st); +} + +// Check recorded testfiles after all tests completed +static int check_unlinked_testfiles(void) +{ + int fd; + int res, err = 0; + int num = testnum; + + if (!unlinked_test) + return 0; + + testnum = 0; + while (testnum < num) { + fd = next_test->fd; + start_test("check_unlinked_testfile"); + if (fd == -1) + continue; + + err += check_unlinked_testfile(fd); + res = close(fd); + if (res == -1) { + PERROR("close(test_fd)"); + err--; + } + } + + if (err) { + fprintf(stderr, "%i unlinked testfile checks failed\n", -err); + return 1; + } + + return err; +} + +static int cleanup_dir(const char *path, const char **dir_files, int quiet) +{ + int i; + int err = 0; + + for (i = 0; dir_files[i]; i++) { + int res; + char fpath[1280]; + sprintf(fpath, "%s/%s", path, dir_files[i]); + res = unlink(fpath); + if (res == -1 && !quiet) { + PERROR("unlink"); + err --; + } + } + if (err) + return -1; + + return 0; +} + +static int create_dir(const char *path, const char **dir_files) +{ + int res; + int i; + + rmdir(path); + res = mkdir(path, 0755); + if (res == -1) { + PERROR("mkdir"); + return -1; + } + res = check_type(path, S_IFDIR); + if (res == -1) + return -1; + res = check_mode(path, 0755); + if (res == -1) + return -1; + + for (i = 0; dir_files[i]; i++) { + char fpath[1280]; + sprintf(fpath, "%s/%s", path, dir_files[i]); + res = create_file(fpath, "", 0); + if (res == -1) { + cleanup_dir(path, dir_files, 1); + return -1; + } + } + res = check_dir_contents(path, dir_files); + if (res == -1) { + cleanup_dir(path, dir_files, 1); + return -1; + } + + return 0; +} + +static int test_truncate(int len) +{ + const char *data = testdata; + int datalen = testdatalen; + int res; + + start_test("truncate(%u)", (int) len); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + res = truncate(testfile, len); + if (res == -1) { + PERROR("truncate"); + return -1; + } + res = check_testfile_size(testfile, len); + if (res == -1) + return -1; + + if (len > 0) { + if (len <= datalen) { + res = check_data(testfile, data, 0, len); + if (res == -1) + return -1; + } else { + res = check_data(testfile, data, 0, datalen); + if (res == -1) + return -1; + res = check_data(testfile, zerodata, datalen, + len - datalen); + if (res == -1) + return -1; + } + } + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + + success(); + return 0; +} + +static int test_ftruncate(int len, int mode) +{ + const char *data = testdata; + int datalen = testdatalen; + int res; + int fd; + + start_test("ftruncate(%u) mode: 0%03o", len, mode); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + fd = open(testfile, O_WRONLY); + if (fd == -1) { + PERROR("open"); + return -1; + } + + res = fchmod(fd, mode); + if (res == -1) { + PERROR("fchmod"); + close(fd); + return -1; + } + res = check_testfile_mode(testfile, mode); + if (res == -1) { + close(fd); + return -1; + } + res = ftruncate(fd, len); + if (res == -1) { + PERROR("ftruncate"); + close(fd); + return -1; + } + close(fd); + res = check_testfile_size(testfile, len); + if (res == -1) + return -1; + + if (len > 0) { + if (len <= datalen) { + res = check_data(testfile, data, 0, len); + if (res == -1) + return -1; + } else { + res = check_data(testfile, data, 0, datalen); + if (res == -1) + return -1; + res = check_data(testfile, zerodata, datalen, + len - datalen); + if (res == -1) + return -1; + } + } + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + + success(); + return 0; +} + +static int test_seekdir(void) +{ + int i; + int res; + DIR *dp; + struct dirent *de = NULL; + + start_test("seekdir"); + res = create_dir(testdir, testdir_files); + if (res == -1) + return res; + + dp = opendir(testdir); + if (dp == NULL) { + PERROR("opendir"); + return -1; + } + + /* Remember dir offsets */ + for (i = 0; i < ARRAY_SIZE(seekdir_offsets); i++) { + seekdir_offsets[i] = telldir(dp); + errno = 0; + de = readdir(dp); + if (de == NULL) { + if (errno) { + PERROR("readdir"); + goto fail; + } + break; + } + } + + /* Walk until the end of directory */ + while (de) + de = readdir(dp); + + /* Start from the last valid dir offset and seek backwards */ + for (i--; i >= 0; i--) { + seekdir(dp, seekdir_offsets[i]); + de = readdir(dp); + if (de == NULL) { + ERROR("Unexpected end of directory after seekdir()"); + goto fail; + } + } + + closedir(dp); + res = cleanup_dir(testdir, testdir_files, 0); + if (!res) + success(); + return res; +fail: + closedir(dp); + cleanup_dir(testdir, testdir_files, 1); + return -1; +} + +#ifdef HAVE_COPY_FILE_RANGE +static int test_copy_file_range(void) +{ + const char *data = testdata; + int datalen = testdatalen; + int err = 0; + int res; + int fd_in, fd_out; + off_t pos_in = 0, pos_out = 0; + + start_test("copy_file_range"); + unlink(testfile); + fd_in = open(testfile, O_CREAT | O_RDWR, 0644); + if (fd_in == -1) { + PERROR("creat"); + return -1; + } + res = write(fd_in, data, datalen); + if (res == -1) { + PERROR("write"); + close(fd_in); + return -1; + } + if (res != datalen) { + ERROR("write is short: %u instead of %u", res, datalen); + close(fd_in); + return -1; + } + + unlink(testfile2); + fd_out = creat(testfile2, 0644); + if (fd_out == -1) { + PERROR("creat"); + close(fd_in); + return -1; + } + res = copy_file_range(fd_in, &pos_in, fd_out, &pos_out, datalen, 0); + if (res == -1) { + PERROR("copy_file_range"); + close(fd_in); + close(fd_out); + return -1; + } + if (res != datalen) { + ERROR("copy is short: %u instead of %u", res, datalen); + close(fd_in); + close(fd_out); + return -1; + } + + res = close(fd_in); + if (res == -1) { + PERROR("close"); + close(fd_out); + return -1; + } + res = close(fd_out); + if (res == -1) { + PERROR("close"); + return -1; + } + + err = check_data(testfile2, data, 0, datalen); + + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + if (err) + return -1; + + res = unlink(testfile2); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile2); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} +#else +static int test_copy_file_range(void) +{ + return 0; +} +#endif + +#ifdef HAVE_STATX +static int test_statx(void) +{ + struct statx sb; + char msg[] = "hi"; + size_t msg_size = sizeof(msg); + struct timespec tp; + int res; + + memset(&sb, 0, sizeof(sb)); + unlink(testfile); + + start_test("statx"); + + res = create_testfile(testfile, msg, msg_size); + if (res == -1) + return -1; + + res = statx(-1, testfile, AT_EMPTY_PATH, + STATX_BASIC_STATS | STATX_BTIME, &sb); + if (res == -1) + return -1; + + if (sb.stx_size != msg_size) + return -1; + + clock_gettime(CLOCK_REALTIME, &tp); + + if (sb.stx_btime.tv_sec > tp.tv_sec) + return -1; + + if (sb.stx_btime.tv_sec == tp.tv_sec && + sb.stx_btime.tv_nsec >= tp.tv_nsec) + return -1; + + unlink(testfile); + + success(); + return 0; +} +#else +static int test_statx(void) +{ + return 0; +} +#endif + +static int test_utime(void) +{ + struct utimbuf utm; + time_t atime = 987631200; + time_t mtime = 123116400; + int res; + + start_test("utime"); + res = create_testfile(testfile, NULL, 0); + if (res == -1) + return -1; + + utm.actime = atime; + utm.modtime = mtime; + res = utime(testfile, &utm); + if (res == -1) { + PERROR("utime"); + return -1; + } + res = check_times(testfile, atime, mtime); + if (res == -1) { + return -1; + } + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + + success(); + return 0; +} + +static int test_create(void) +{ + const char *data = testdata; + int datalen = testdatalen; + int err = 0; + int res; + int fd; + + start_test("create"); + unlink(testfile); + fd = creat(testfile, 0644); + if (fd == -1) { + PERROR("creat"); + return -1; + } + res = write(fd, data, datalen); + if (res == -1) { + PERROR("write"); + close(fd); + return -1; + } + if (res != datalen) { + ERROR("write is short: %u instead of %u", res, datalen); + close(fd); + return -1; + } + res = close(fd); + if (res == -1) { + PERROR("close"); + return -1; + } + res = check_type(testfile, S_IFREG); + if (res == -1) + return -1; + err += check_mode(testfile, 0644); + err += check_nlink(testfile, 1); + err += check_size(testfile, datalen); + err += check_data(testfile, data, 0, datalen); + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_create_unlink(void) +{ + const char *data = testdata; + int datalen = testdatalen; + int err = 0; + int res; + int fd; + + start_test("create+unlink"); + unlink(testfile); + fd = open(testfile, O_CREAT | O_RDWR | O_TRUNC, 0644); + if (fd == -1) { + PERROR("creat"); + return -1; + } + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + close(fd); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) { + close(fd); + return -1; + } + res = write(fd, data, datalen); + if (res == -1) { + PERROR("write"); + close(fd); + return -1; + } + if (res != datalen) { + ERROR("write is short: %u instead of %u", res, datalen); + close(fd); + return -1; + } + struct stat st = { + .st_mode = S_IFREG | 0644, + .st_size = datalen, + }; + err = fcheck_stat(fd, O_RDWR, &st); + err += fcheck_data(fd, data, 0, datalen); + res = close(fd); + if (res == -1) { + PERROR("close"); + err--; + } + if (err) + return -1; + + success(); + return 0; +} + +static int test_mknod(void) +{ + int err = 0; + int res; + + start_test("mknod"); + unlink(testfile); + res = mknod(testfile, 0644, 0); + if (res == -1) { + PERROR("mknod"); + return -1; + } + res = check_type(testfile, S_IFREG); + if (res == -1) + return -1; + err += check_mode(testfile, 0644); + err += check_nlink(testfile, 1); + err += check_size(testfile, 0); + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +#define test_open(exist, flags, mode) do_test_open(exist, flags, #flags, mode) + +static int do_test_open(int exist, int flags, const char *flags_str, int mode) +{ + char buf[4096]; + const char *data = testdata; + int datalen = testdatalen; + unsigned currlen = 0; + int err = 0; + int res; + int fd; + off_t off; + + start_test("open(%s, %s, 0%03o)", exist ? "+" : "-", flags_str, mode); + unlink(testfile); + if (exist) { + res = create_file(testfile_r, testdata2, testdata2len); + if (res == -1) + return -1; + + currlen = testdata2len; + } + + fd = open(testfile, flags, mode); + if ((flags & O_CREAT) && (flags & O_EXCL) && exist) { + if (fd != -1) { + ERROR("open should have failed"); + close(fd); + return -1; + } else if (errno == EEXIST) + goto succ; + } + if (!(flags & O_CREAT) && !exist) { + if (fd != -1) { + ERROR("open should have failed"); + close(fd); + return -1; + } else if (errno == ENOENT) + goto succ; + } + if (fd == -1) { + PERROR("open"); + return -1; + } + + if (flags & O_TRUNC) + currlen = 0; + + err += check_type(testfile, S_IFREG); + if (exist) + err += check_mode(testfile, 0644); + else + err += check_mode(testfile, mode); + err += check_nlink(testfile, 1); + err += check_size(testfile, currlen); + if (exist && !(flags & O_TRUNC) && (mode & S_IRUSR)) + err += check_data(testfile, testdata2, 0, testdata2len); + + res = write(fd, data, datalen); + if ((flags & O_ACCMODE) != O_RDONLY) { + if (res == -1) { + PERROR("write"); + err --; + } else if (res != datalen) { + ERROR("write is short: %u instead of %u", res, datalen); + err --; + } else { + if (datalen > (int) currlen) + currlen = datalen; + + err += check_size(testfile, currlen); + + if (mode & S_IRUSR) { + err += check_data(testfile, data, 0, datalen); + if (exist && !(flags & O_TRUNC) && + testdata2len > datalen) + err += check_data(testfile, + testdata2 + datalen, + datalen, + testdata2len - datalen); + } + } + } else { + if (res != -1) { + ERROR("write should have failed"); + err --; + } else if (errno != EBADF) { + PERROR("write"); + err --; + } + } + off = lseek(fd, SEEK_SET, 0); + if (off == (off_t) -1) { + PERROR("lseek"); + err--; + } else if (off != 0) { + ERROR("offset should have returned 0"); + err --; + } + res = read(fd, buf, sizeof(buf)); + if ((flags & O_ACCMODE) != O_WRONLY) { + if (res == -1) { + PERROR("read"); + err--; + } else { + int readsize = + currlen < sizeof(buf) ? currlen : sizeof(buf); + if (res != readsize) { + ERROR("read is short: %i instead of %u", + res, readsize); + err--; + } else { + if ((flags & O_ACCMODE) != O_RDONLY) { + err += check_buffer(buf, data, datalen); + if (exist && !(flags & O_TRUNC) && + testdata2len > datalen) + err += check_buffer(buf + datalen, + testdata2 + datalen, + testdata2len - datalen); + } else if (exist) + err += check_buffer(buf, testdata2, + testdata2len); + } + } + } else { + if (res != -1) { + ERROR("read should have failed"); + err --; + } else if (errno != EBADF) { + PERROR("read"); + err --; + } + } + + res = close(fd); + if (res == -1) { + PERROR("close"); + return -1; + } + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + res = check_nonexist(testfile_r); + if (res == -1) + return -1; + if (err) + return -1; + +succ: + success(); + return 0; +} + +#define test_open_acc(flags, mode, err) \ + do_test_open_acc(flags, #flags, mode, err) + +static int do_test_open_acc(int flags, const char *flags_str, int mode, int err) +{ + const char *data = testdata; + int datalen = testdatalen; + int res; + int fd; + + start_test("open_acc(%s) mode: 0%03o message: '%s'", flags_str, mode, + strerror(err)); + unlink(testfile); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + res = chmod(testfile, mode); + if (res == -1) { + PERROR("chmod"); + return -1; + } + + res = check_testfile_mode(testfile, mode); + if (res == -1) + return -1; + + fd = open(testfile, flags); + if (fd == -1) { + if (err != errno) { + PERROR("open"); + return -1; + } + } else { + if (err) { + ERROR("open should have failed"); + close(fd); + return -1; + } + close(fd); + } + + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + res = check_nonexist(testfile_r); + if (res == -1) + return -1; + + success(); + return 0; +} + +static int test_symlink(void) +{ + char buf[1024]; + const char *data = testdata; + int datalen = testdatalen; + int linklen = strlen(testfile); + int err = 0; + int res; + + start_test("symlink"); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + unlink(testfile2); + res = symlink(testfile, testfile2); + if (res == -1) { + PERROR("symlink"); + return -1; + } + res = check_type(testfile2, S_IFLNK); + if (res == -1) + return -1; + err += check_mode(testfile2, 0777); + err += check_nlink(testfile2, 1); + res = readlink(testfile2, buf, sizeof(buf)); + if (res == -1) { + PERROR("readlink"); + err--; + } + if (res != linklen) { + ERROR("short readlink: %u instead of %u", res, linklen); + err--; + } + if (memcmp(buf, testfile, linklen) != 0) { + ERROR("link mismatch"); + err--; + } + err += check_size(testfile2, datalen); + err += check_data(testfile2, data, 0, datalen); + res = unlink(testfile2); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile2); + if (res == -1) + return -1; + if (err) + return -1; + + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + + success(); + return 0; +} + +static int test_link(void) +{ + const char *data = testdata; + int datalen = testdatalen; + int err = 0; + int res; + + start_test("link"); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + unlink(testfile2); + res = link(testfile, testfile2); + if (res == -1) { + PERROR("link"); + return -1; + } + res = check_type(testfile2, S_IFREG); + if (res == -1) + return -1; + err += check_mode(testfile2, 0644); + err += check_nlink(testfile2, 2); + err += check_size(testfile2, datalen); + err += check_data(testfile2, data, 0, datalen); + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + + err += check_nlink(testfile2, 1); + res = unlink(testfile2); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile2); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_link2(void) +{ + const char *data = testdata; + int datalen = testdatalen; + int err = 0; + int res; + + start_test("link-unlink-link"); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + unlink(testfile2); + res = link(testfile, testfile2); + if (res == -1) { + PERROR("link"); + return -1; + } + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + res = link(testfile2, testfile); + if (res == -1) { + PERROR("link"); + } + res = check_type(testfile, S_IFREG); + if (res == -1) + return -1; + err += check_mode(testfile, 0644); + err += check_nlink(testfile, 2); + err += check_size(testfile, datalen); + err += check_data(testfile, data, 0, datalen); + + res = unlink(testfile2); + if (res == -1) { + PERROR("unlink"); + return -1; + } + err += check_nlink(testfile, 1); + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_rename_file(void) +{ + const char *data = testdata; + int datalen = testdatalen; + int err = 0; + int res; + + start_test("rename file"); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + unlink(testfile2); + res = rename(testfile, testfile2); + if (res == -1) { + PERROR("rename"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + res = check_type(testfile2, S_IFREG); + if (res == -1) + return -1; + err += check_mode(testfile2, 0644); + err += check_nlink(testfile2, 1); + err += check_size(testfile2, datalen); + err += check_data(testfile2, data, 0, datalen); + res = unlink(testfile2); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile2); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_rename_dir(void) +{ + int err = 0; + int res; + + start_test("rename dir"); + res = create_dir(testdir, testdir_files); + if (res == -1) + return -1; + + rmdir(testdir2); + res = rename(testdir, testdir2); + if (res == -1) { + PERROR("rename"); + cleanup_dir(testdir, testdir_files, 1); + return -1; + } + res = check_nonexist(testdir); + if (res == -1) { + cleanup_dir(testdir, testdir_files, 1); + return -1; + } + res = check_type(testdir2, S_IFDIR); + if (res == -1) { + cleanup_dir(testdir2, testdir_files, 1); + return -1; + } + err += check_mode(testdir2, 0755); + err += check_dir_contents(testdir2, testdir_files); + err += cleanup_dir(testdir2, testdir_files, 0); + res = rmdir(testdir2); + if (res == -1) { + PERROR("rmdir"); + return -1; + } + res = check_nonexist(testdir2); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_rename_dir_loop(void) +{ +#define PATH(p) (snprintf(path, sizeof path, "%s/%s", testdir, p), path) +#define PATH2(p) (snprintf(path2, sizeof path2, "%s/%s", testdir, p), path2) + + char path[1280], path2[1280]; + int err = 0; + int res; + + start_test("rename dir loop"); + + res = create_dir(testdir, testdir_files); + if (res == -1) + return -1; + + res = mkdir(PATH("a"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + res = rename(PATH("a"), PATH2("a")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + errno = 0; + res = rename(PATH("a"), PATH2("a/b")); + if (res == 0 || errno != EINVAL) { + PERROR("rename"); + goto fail; + } + + res = mkdir(PATH("a/b"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + res = mkdir(PATH("a/b/c"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + errno = 0; + res = rename(PATH("a"), PATH2("a/b/c")); + if (res == 0 || errno != EINVAL) { + PERROR("rename"); + goto fail; + } + + errno = 0; + res = rename(PATH("a"), PATH2("a/b/c/a")); + if (res == 0 || errno != EINVAL) { + PERROR("rename"); + goto fail; + } + + errno = 0; + res = rename(PATH("a/b/c"), PATH2("a")); + if (res == 0 || errno != ENOTEMPTY) { + PERROR("rename"); + goto fail; + } + + res = open(PATH("a/foo"), O_CREAT, 0644); + if (res == -1) { + PERROR("open"); + goto fail; + } + close(res); + + res = rename(PATH("a/foo"), PATH2("a/bar")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/bar"), PATH2("a/foo")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/foo"), PATH2("a/b/bar")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/b/bar"), PATH2("a/foo")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/foo"), PATH2("a/b/c/bar")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/b/c/bar"), PATH2("a/foo")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = open(PATH("a/bar"), O_CREAT, 0644); + if (res == -1) { + PERROR("open"); + goto fail; + } + close(res); + + res = rename(PATH("a/foo"), PATH2("a/bar")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + unlink(PATH("a/bar")); + + res = rename(PATH("a/b"), PATH2("a/d")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/d"), PATH2("a/b")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = mkdir(PATH("a/d"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + res = rename(PATH("a/b"), PATH2("a/d")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/d"), PATH2("a/b")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = mkdir(PATH("a/d"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + res = mkdir(PATH("a/d/e"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + errno = 0; + res = rename(PATH("a/b"), PATH2("a/d")); + if (res == 0 || (errno != ENOTEMPTY && errno != EEXIST)) { + PERROR("rename"); + goto fail; + } + + rmdir(PATH("a/d/e")); + rmdir(PATH("a/d")); + + rmdir(PATH("a/b/c")); + rmdir(PATH("a/b")); + rmdir(PATH("a")); + + err += cleanup_dir(testdir, testdir_files, 0); + res = rmdir(testdir); + if (res == -1) { + PERROR("rmdir"); + goto fail; + } + res = check_nonexist(testdir); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; + +fail: + unlink(PATH("a/bar")); + + rmdir(PATH("a/d/e")); + rmdir(PATH("a/d")); + + rmdir(PATH("a/b/c")); + rmdir(PATH("a/b")); + rmdir(PATH("a")); + + cleanup_dir(testdir, testdir_files, 1); + rmdir(testdir); + + return -1; + +#undef PATH2 +#undef PATH +} + +static int test_mkfifo(void) +{ + int res; + int err = 0; + + start_test("mkfifo"); + unlink(testfile); + res = mkfifo(testfile, 0644); + if (res == -1) { + PERROR("mkfifo"); + return -1; + } + res = check_type(testfile, S_IFIFO); + if (res == -1) + return -1; + err += check_mode(testfile, 0644); + err += check_nlink(testfile, 1); + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_mkdir(void) +{ + int res; + int err = 0; + const char *dir_contents[] = {NULL}; + + start_test("mkdir"); + rmdir(testdir); + res = mkdir(testdir, 0755); + if (res == -1) { + PERROR("mkdir"); + return -1; + } + res = check_type(testdir, S_IFDIR); + if (res == -1) + return -1; + err += check_mode(testdir, 0755); + /* Some file systems (like btrfs) don't track link + count for directories */ + //err += check_nlink(testdir, 2); + err += check_dir_contents(testdir, dir_contents); + res = rmdir(testdir); + if (res == -1) { + PERROR("rmdir"); + return -1; + } + res = check_nonexist(testdir); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_socket(void) +{ + struct sockaddr_un su; + int fd; + int res; + int err = 0; + const size_t test_sock_len = strlen(testsock) + 1; + + start_test("socket"); + if (test_sock_len > sizeof(su.sun_path)) { + fprintf(stderr, "Need to shorten mount point by %zu chars\n", + strlen(testsock) + 1 - sizeof(su.sun_path)); + return -1; + } + unlink(testsock); + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + PERROR("socket"); + return -1; + } + su.sun_family = AF_UNIX; + + strncpy(su.sun_path, testsock, test_sock_len); + su.sun_path[sizeof(su.sun_path) - 1] = '\0'; + res = bind(fd, (struct sockaddr*)&su, sizeof(su)); + if (res == -1) { + PERROR("bind"); + return -1; + } + + res = check_type(testsock, S_IFSOCK); + if (res == -1) { + close(fd); + return -1; + } + err += check_nlink(testsock, 1); + close(fd); + res = unlink(testsock); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testsock); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +#define test_create_ro_dir(flags) \ + do_test_create_ro_dir(flags, #flags) + +static int do_test_create_ro_dir(int flags, const char *flags_str) +{ + int res; + int err = 0; + int fd; + + start_test("open(%s) in read-only directory", flags_str); + rmdir(testdir); + res = mkdir(testdir, 0555); + if (res == -1) { + PERROR("mkdir"); + return -1; + } + fd = open(subfile, flags, 0644); + if (fd != -1) { + close(fd); + unlink(subfile); + ERROR("open should have failed"); + err--; + } else { + res = check_nonexist(subfile); + if (res == -1) + err--; + } + unlink(subfile); + res = rmdir(testdir); + if (res == -1) { + PERROR("rmdir"); + return -1; + } + res = check_nonexist(testdir); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +#ifndef __FreeBSD__ +/* this tests open with O_TMPFILE + note that this will only work with the fuse low level api + you will get ENOTSUP with the high level api */ +static int test_create_tmpfile(void) +{ + rmdir(testdir); + int res = mkdir(testdir, 0777); + if (res) + return -1; + + start_test("create tmpfile"); + + int fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR); + if(fd == -1) { + if (errno == ENOTSUP) { + /* don't bother if we're working on an old kernel + or on the high level API */ + return 0; + } + + PERROR("open O_TMPFILE | O_RDWR"); + return -1; + } + close(fd); + + fd = open(testdir, O_TMPFILE | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR); + if(fd == -1){ + PERROR("open with O_TMPFILE | O_WRONLY | O_EXCL"); + return -1; + }; + close(fd); + + fd = open(testdir, O_TMPFILE | O_RDONLY, S_IRUSR); + if (fd != -1) { + ERROR("open with O_TMPFILE | O_RDONLY succeeded"); + return -1; + } + + success(); + return 0; +} + +static int test_create_and_link_tmpfile(void) +{ + /* skip this test for now since the github runner will fail in the linkat call below */ + return 0; + + rmdir(testdir); + unlink(testfile); + + int res = mkdir(testdir, 0777); + if (res) + return -1; + + start_test("create and link tmpfile"); + + int fd = open(testdir, O_TMPFILE | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR); + if(fd == -1) { + if (errno == ENOTSUP) { + /* don't bother if we're working on an old kernel + or on the high level API */ + return 0; + } + PERROR("open with O_TMPFILE | O_RDWR | O_EXCL"); + return -1; + } + + if (!linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) { + ERROR("linkat succeeded on a tmpfile opened with O_EXCL"); + return -1; + } + close(fd); + + fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR); + if(fd == -1) { + PERROR("open O_TMPFILE"); + return -1; + } + + if (check_nonexist(testfile)) { + return -1; + } + + if (linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) { + PERROR("linkat tempfile"); + return -1; + } + close(fd); + + if (check_nlink(testfile, 1)) { + return -1; + } + unlink(testfile); + + success(); + return 0; +} +#endif + +int main(int argc, char *argv[]) +{ + int err = 0; + int a; + int is_root; + + umask(0); + if (argc < 2 || argc > 4) { + fprintf(stderr, "usage: %s testdir [:realdir] [[-]test#] [-u]\n", argv[0]); + return 1; + } + basepath = argv[1]; + basepath_r = basepath; + for (a = 2; a < argc; a++) { + char *endptr; + char *arg = argv[a]; + if (arg[0] == ':') { + basepath_r = arg + 1; + } else { + if (arg[0] == '-') { + arg++; + if (arg[0] == 'u') { + unlinked_test = 1; + endptr = arg + 1; + } else { + skip_test = strtoul(arg, &endptr, 10); + } + } else { + select_test = strtoul(arg, &endptr, 10); + } + if (arg[0] == '\0' || *endptr != '\0') { + fprintf(stderr, "invalid option: '%s'\n", argv[a]); + return 1; + } + } + } + assert(strlen(basepath) < 512); + assert(strlen(basepath_r) < 512); + if (basepath[0] != '/') { + fprintf(stderr, "testdir must be an absolute path\n"); + return 1; + } + + sprintf(testfile, "%s/testfile", basepath); + sprintf(testfile2, "%s/testfile2", basepath); + sprintf(testdir, "%s/testdir", basepath); + sprintf(testdir2, "%s/testdir2", basepath); + sprintf(subfile, "%s/subfile", testdir2); + sprintf(testsock, "%s/testsock", basepath); + + sprintf(testfile_r, "%s/testfile", basepath_r); + sprintf(testfile2_r, "%s/testfile2", basepath_r); + sprintf(testdir_r, "%s/testdir", basepath_r); + sprintf(testdir2_r, "%s/testdir2", basepath_r); + sprintf(subfile_r, "%s/subfile", testdir2_r); + + is_root = (geteuid() == 0); + + err += test_create(); + err += test_create_unlink(); + err += test_symlink(); + err += test_link(); + err += test_link2(); + err += test_mknod(); + err += test_mkfifo(); + err += test_mkdir(); + err += test_rename_file(); + err += test_rename_dir(); + err += test_rename_dir_loop(); + err += test_seekdir(); + err += test_socket(); + err += test_utime(); + err += test_truncate(0); + err += test_truncate(testdatalen / 2); + err += test_truncate(testdatalen); + err += test_truncate(testdatalen + 100); + err += test_ftruncate(0, 0600); + err += test_ftruncate(testdatalen / 2, 0600); + err += test_ftruncate(testdatalen, 0600); + err += test_ftruncate(testdatalen + 100, 0600); + err += test_ftruncate(0, 0400); + err += test_ftruncate(0, 0200); + err += test_ftruncate(0, 0000); + err += test_open(0, O_RDONLY, 0); + err += test_open(1, O_RDONLY, 0); + err += test_open(1, O_RDWR, 0); + err += test_open(1, O_WRONLY, 0); + err += test_open(0, O_RDWR | O_CREAT, 0600); + err += test_open(1, O_RDWR | O_CREAT, 0600); + err += test_open(0, O_RDWR | O_CREAT | O_TRUNC, 0600); + err += test_open(1, O_RDWR | O_CREAT | O_TRUNC, 0600); + err += test_open(0, O_RDONLY | O_CREAT, 0600); + err += test_open(0, O_RDONLY | O_CREAT, 0400); + err += test_open(0, O_RDONLY | O_CREAT, 0200); + err += test_open(0, O_RDONLY | O_CREAT, 0000); + err += test_open(0, O_WRONLY | O_CREAT, 0600); + err += test_open(0, O_WRONLY | O_CREAT, 0400); + err += test_open(0, O_WRONLY | O_CREAT, 0200); + err += test_open(0, O_WRONLY | O_CREAT, 0000); + err += test_open(0, O_RDWR | O_CREAT, 0400); + err += test_open(0, O_RDWR | O_CREAT, 0200); + err += test_open(0, O_RDWR | O_CREAT, 0000); + err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0600); + err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0600); + err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0000); + err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0000); + err += test_open_acc(O_RDONLY, 0600, 0); + err += test_open_acc(O_WRONLY, 0600, 0); + err += test_open_acc(O_RDWR, 0600, 0); + err += test_open_acc(O_RDONLY, 0400, 0); + err += test_open_acc(O_WRONLY, 0200, 0); + if(!is_root) { + err += test_open_acc(O_RDONLY | O_TRUNC, 0400, EACCES); + err += test_open_acc(O_WRONLY, 0400, EACCES); + err += test_open_acc(O_RDWR, 0400, EACCES); + err += test_open_acc(O_RDONLY, 0200, EACCES); + err += test_open_acc(O_RDWR, 0200, EACCES); + err += test_open_acc(O_RDONLY, 0000, EACCES); + err += test_open_acc(O_WRONLY, 0000, EACCES); + err += test_open_acc(O_RDWR, 0000, EACCES); + } + err += test_create_ro_dir(O_CREAT); + err += test_create_ro_dir(O_CREAT | O_EXCL); + err += test_create_ro_dir(O_CREAT | O_WRONLY); + err += test_create_ro_dir(O_CREAT | O_TRUNC); + err += test_copy_file_range(); + err += test_statx(); +#ifndef __FreeBSD__ + err += test_create_tmpfile(); + err += test_create_and_link_tmpfile(); +#endif + + unlink(testfile2); + unlink(testsock); + rmdir(testdir); + rmdir(testdir2); + + if (err) { + fprintf(stderr, "%i tests failed\n", -err); + return 1; + } + + return check_unlinked_testfiles(); +} diff --git a/test/test_want_conversion.c b/test/test_want_conversion.c new file mode 100644 index 0000000..db731ed --- /dev/null +++ b/test/test_want_conversion.c @@ -0,0 +1,181 @@ +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17) + +#include "util.h" +#include "fuse_i.h" +#include "fuse_lowlevel.h" +#include +#include +#include +#include +#include + +static void print_conn_info(const char *prefix, struct fuse_conn_info *conn) +{ + struct fuse_session *se = container_of(conn, struct fuse_session, conn); + + printf("%s: want=0x%" PRIx32 " want_ext=0x%" PRIx64 + " want_default=0x%" PRIx32 " want_ext_default=0x%" PRIx64 "\n", + prefix, conn->want, conn->want_ext, se->conn_want, + se->conn_want_ext); +} + +static void application_init_old_style(struct fuse_conn_info *conn) +{ + /* Simulate application init the old style */ + conn->want |= FUSE_CAP_ASYNC_READ; + conn->want &= ~FUSE_CAP_SPLICE_READ; + + /* + * Also use new style API, as that might happen through + * fuse_apply_conn_info_opts() + */ + fuse_set_feature_flag(conn, FUSE_CAP_IOCTL_DIR); +} + +static void application_init_new_style(struct fuse_conn_info *conn) +{ + /* Simulate application init the new style */ + fuse_set_feature_flag(conn, FUSE_CAP_ASYNC_READ); + fuse_set_feature_flag(conn, FUSE_CAP_IOCTL_DIR); + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ); +} + +static void test_fuse_fs_init(struct fuse_conn_info *conn, bool new_style) +{ + /* High-level init */ + fuse_set_feature_flag(conn, FUSE_CAP_EXPORT_SUPPORT); + + if (new_style) + application_init_new_style(conn); + else + application_init_old_style(conn); +} + +static void test_do_init(struct fuse_conn_info *conn, bool new_style) +{ + /* Initial setup */ + conn->capable_ext = FUSE_CAP_SPLICE_READ | FUSE_CAP_SPLICE_WRITE | + FUSE_CAP_SPLICE_MOVE | FUSE_CAP_POSIX_LOCKS | + FUSE_CAP_FLOCK_LOCKS | FUSE_CAP_EXPORT_SUPPORT | + FUSE_CAP_ASYNC_READ | FUSE_CAP_IOCTL_DIR; + conn->capable = fuse_lower_32_bits(conn->capable_ext); + + fuse_set_feature_flag(conn, FUSE_CAP_SPLICE_READ | + FUSE_CAP_SPLICE_WRITE | + FUSE_CAP_SPLICE_MOVE); + + print_conn_info("Initial state", conn); + + int rc; + + test_fuse_fs_init(conn, new_style); + print_conn_info("After init", conn); + + rc = fuse_convert_to_conn_want_ext(conn); + assert(rc == 0); + + /* Verify all expected flags are set */ + assert(!(conn->want_ext & FUSE_CAP_SPLICE_READ)); + assert(conn->want_ext & FUSE_CAP_SPLICE_WRITE); + assert(conn->want_ext & FUSE_CAP_SPLICE_MOVE); + assert(conn->want_ext & FUSE_CAP_EXPORT_SUPPORT); + assert(conn->want_ext & FUSE_CAP_ASYNC_READ); + assert(conn->want_ext & FUSE_CAP_IOCTL_DIR); + + /* Verify no other flags are set */ + assert(conn->want_ext == + (FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE | + FUSE_CAP_EXPORT_SUPPORT | FUSE_CAP_ASYNC_READ | + FUSE_CAP_IOCTL_DIR)); + + print_conn_info("After init", conn); +} + +static void test_want_conversion_basic(void) +{ + const struct fuse_lowlevel_ops ops = { 0 }; + struct fuse_args args = FUSE_ARGS_INIT(0, NULL); + struct fuse_session *se; + struct fuse_conn_info *conn; + + /* Add the program name to arg[0] */ + if (fuse_opt_add_arg(&args, "test_signals")) { + fprintf(stderr, "Failed to add argument\n"); + errx(1, "Failed to add argument"); + } + + + se = fuse_session_new(&args, &ops, sizeof(ops), NULL); + assert(se); + conn = &se->conn; + printf("\nTesting basic want conversion, old style:\n"); + test_do_init(conn, false); + fuse_session_destroy(se); + + se = fuse_session_new(&args, &ops, sizeof(ops), NULL); + assert(se); + conn = &se->conn; + printf("\nTesting basic want conversion, new style:\n"); + test_do_init(conn, true); + print_conn_info("After init", conn); + fuse_session_destroy(se); + + fuse_opt_free_args(&args); + +} + +static void test_want_conversion_conflict(void) +{ + struct fuse_conn_info conn = { 0 }; + int rc; + + printf("\nTesting want conversion conflict:\n"); + + /* Test conflicting values */ + /* Initialize like fuse_lowlevel.c does */ + conn.capable_ext = FUSE_CAP_SPLICE_READ | FUSE_CAP_SPLICE_WRITE | + FUSE_CAP_SPLICE_MOVE | FUSE_CAP_POSIX_LOCKS | + FUSE_CAP_FLOCK_LOCKS; + conn.capable = fuse_lower_32_bits(conn.capable_ext); + conn.want_ext = conn.capable_ext; + conn.want = fuse_lower_32_bits(conn.want_ext); + print_conn_info("Test conflict initial", &conn); + + /* Simulate application init modifying capabilities */ + conn.want_ext |= FUSE_CAP_ATOMIC_O_TRUNC; /* Add new capability */ + conn.want &= ~FUSE_CAP_SPLICE_READ; /* Remove a capability */ + + rc = fuse_convert_to_conn_want_ext(&conn); + assert(rc == -EINVAL); + print_conn_info("Test conflict after", &conn); + + printf("Want conversion conflict test passed\n"); +} + +static void test_want_conversion_high_bits(void) +{ + struct fuse_conn_info conn = { 0 }; + int rc; + + printf("\nTesting want conversion high bits preservation:\n"); + + /* Test high bits preservation */ + conn.want_ext = (1ULL << 33) | FUSE_CAP_ASYNC_READ; + conn.want = fuse_lower_32_bits(conn.want_ext); + print_conn_info("Test high bits initial", &conn); + + rc = fuse_convert_to_conn_want_ext(&conn); + assert(rc == 0); + assert(conn.want_ext == ((1ULL << 33) | FUSE_CAP_ASYNC_READ)); + print_conn_info("Test high bits after", &conn); + + printf("Want conversion high bits test passed\n"); +} + +int main(void) +{ + test_want_conversion_basic(); + test_want_conversion_conflict(); + test_want_conversion_high_bits(); + return 0; +} diff --git a/test/test_write_cache.c b/test/test_write_cache.c new file mode 100644 index 0000000..00db5a6 --- /dev/null +++ b/test/test_write_cache.c @@ -0,0 +1,323 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2016 Nikolaus Rath + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +#define FUSE_USE_VERSION 30 + +/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __linux__ +#include +#else +#include +#endif + +#define FILE_INO 2 +#define FILE_NAME "write_me" + +/* Command line parsing */ +struct options { + int writeback; + int data_size; + int delay_ms; +} options = { + .writeback = 0, + .data_size = 2048, + .delay_ms = 0, +}; + +#define WRITE_SYSCALLS 64 + +#define OPTION(t, p) { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("writeback_cache", writeback), + OPTION("--data-size=%d", data_size), OPTION("--delay_ms=%d", delay_ms), + FUSE_OPT_END +}; +static int got_write; +static atomic_int write_cnt; + +pthread_cond_t cond = PTHREAD_COND_INITIALIZER; +pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; +static int write_start, write_done; + +static void tfs_init(void *userdata, struct fuse_conn_info *conn) +{ + (void)userdata; + + if (options.writeback) { + assert(fuse_get_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE)); + fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); + } +} + +static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) +{ + stbuf->st_ino = ino; + if (ino == FUSE_ROOT_ID) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } + + else if (ino == FILE_INO) { + stbuf->st_mode = S_IFREG | 0222; + stbuf->st_nlink = 1; + stbuf->st_size = 0; + } + + else + return -1; + + return 0; +} + +static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e; + + memset(&e, 0, sizeof(e)); + + if (parent != FUSE_ROOT_ID) + goto err_out; + else if (strcmp(name, FILE_NAME) == 0) + e.ino = FILE_INO; + else + goto err_out; + + if (tfs_stat(e.ino, &e.attr) != 0) + goto err_out; + fuse_reply_entry(req, &e); + return; + +err_out: + fuse_reply_err(req, ENOENT); +} + +static void tfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct stat stbuf; + + (void)fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (tfs_stat(ino, &stbuf) != 0) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, 5); +} + +static void tfs_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + if (ino == FUSE_ROOT_ID) + fuse_reply_err(req, EISDIR); + else { + assert(ino == FILE_INO); + /* Test close(rofd) does not block waiting for pending writes */ + fi->noflush = !options.writeback && options.delay_ms && + (fi->flags & O_ACCMODE) == O_RDONLY; + fuse_reply_open(req, fi); + } +} + +static void tfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf, + size_t size, off_t off, struct fuse_file_info *fi) +{ + (void)fi; + (void)buf; + (void)off; + size_t expected; + + assert(ino == FILE_INO); + expected = options.data_size; + if (options.writeback) + expected *= 2; + + write_cnt++; + + if (size != expected && !options.writeback) + fprintf(stderr, "ERROR: Expected %zd bytes, got %zd\n!", + expected, size); + else + got_write = 1; + + /* Simulate waiting for pending writes */ + if (options.delay_ms) { + pthread_mutex_lock(&lock); + write_start = 1; + pthread_cond_signal(&cond); + pthread_mutex_unlock(&lock); + + usleep(options.delay_ms * 1000); + + pthread_mutex_lock(&lock); + write_done = 1; + pthread_cond_signal(&cond); + pthread_mutex_unlock(&lock); + } + + fuse_reply_write(req, size); +} + +static struct fuse_lowlevel_ops tfs_oper = { + .init = tfs_init, + .lookup = tfs_lookup, + .getattr = tfs_getattr, + .open = tfs_open, + .write = tfs_write, +}; + +static void *close_rofd(void *data) +{ + int rofd = (int)(long)data; + + /* Wait for first write to start */ + pthread_mutex_lock(&lock); + while (!write_start && !write_done) + pthread_cond_wait(&cond, &lock); + pthread_mutex_unlock(&lock); + + close(rofd); + printf("rofd closed. write_start: %d write_done: %d\n", write_start, + write_done); + + /* First write should not have been completed */ + if (write_done) + fprintf(stderr, "ERROR: close(rofd) blocked on write!\n"); + + return NULL; +} + +static void *run_fs(void *data) +{ + struct fuse_session *se = (struct fuse_session *)data; + + assert(fuse_session_loop(se) == 0); + return NULL; +} + +static void test_fs(char *mountpoint) +{ + char fname[PATH_MAX]; + char *buf; + const size_t iosize = options.data_size; + const size_t dsize = options.data_size * WRITE_SYSCALLS; + int fd, rofd; + pthread_t rofd_thread; + off_t off = 0; + + buf = malloc(dsize); + assert(buf != NULL); + assert((fd = open("/dev/urandom", O_RDONLY)) != -1); + assert(read(fd, buf, dsize) == dsize); + close(fd); + + assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME, mountpoint) > 0); + fd = open(fname, O_WRONLY); + if (fd == -1) { + perror(fname); + assert(0); + } + + if (options.delay_ms) { + /* Verify that close(rofd) does not block waiting for pending writes */ + rofd = open(fname, O_RDONLY); + assert(pthread_create(&rofd_thread, NULL, close_rofd, + (void *)(long)rofd) == 0); + /* Give close_rofd time to start */ + usleep(options.delay_ms * 1000); + } + + for (int cnt = 0; cnt < WRITE_SYSCALLS; cnt++) { + assert(pwrite(fd, buf + off, iosize, off) == iosize); + off += iosize; + assert(off <= dsize); + } + free(buf); + close(fd); + + if (options.delay_ms) { + printf("rwfd closed. write_start: %d write_done: %d\n", + write_start, write_done); + assert(pthread_join(rofd_thread, NULL) == 0); + } +} + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts fuse_opts; + pthread_t fs_thread; + + assert(fuse_opt_parse(&args, &options, option_spec, NULL) == 0); + assert(fuse_parse_cmdline(&args, &fuse_opts) == 0); +#ifndef __FreeBSD__ + assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0); +#endif + se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), NULL); + fuse_opt_free_args(&args); + assert(se != NULL); + assert(fuse_set_signal_handlers(se) == 0); + assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0); + + /* Start file-system thread */ + assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0); + + /* Write test data */ + test_fs(fuse_opts.mountpoint); + free(fuse_opts.mountpoint); + + /* Stop file system */ + fuse_session_exit(se); + fuse_session_unmount(se); + assert(pthread_join(fs_thread, NULL) == 0); + + assert(got_write == 1); + + /* + * when writeback cache is enabled, kernel side can merge requests, but + * memory pressure, system 'sync' might trigger data flushes before - flush + * might happen in between write syscalls - merging subpage writes into + * a single page and pages into large fuse requests might or might not work. + * Though we can expect that that at least some (but maybe all) write + * system calls can be merged. + */ + if (options.writeback) + assert(write_cnt < WRITE_SYSCALLS); + else + assert(write_cnt == WRITE_SYSCALLS); + + fuse_remove_signal_handlers(se); + fuse_session_destroy(se); + + printf("Test completed successfully.\n"); + return 0; +} + +/** + * Local Variables: + * mode: c + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */ diff --git a/test/util.py b/test/util.py new file mode 100644 index 0000000..125fd50 --- /dev/null +++ b/test/util.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python3 +import subprocess +import pytest +import os +import stat +import time +from os.path import join as pjoin +import sys +import re +import itertools +from packaging import version +import logging + +basename = pjoin(os.path.dirname(__file__), '..') + +def parse_kernel_version(release): + # Extract the first three numbers from the kernel version string + match = re.match(r'^(\d+\.\d+\.\d+)', release) + if match: + return version.parse(match.group(1)) + return version.parse('0') + +def get_printcap(): + cmdline = base_cmdline + [ pjoin(basename, 'example', 'printcap') ] + proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE, + universal_newlines=True) + (stdout, _) = proc.communicate(30) + assert proc.returncode == 0 + + proto = None + caps = set() + for line in stdout.split('\n'): + if line.startswith('\t'): + caps.add(line.strip()) + continue + + hit = re.match(r'Protocol version: (\d+)\.(\d+)$', line) + if hit: + proto = (int(hit.group(1)), int(hit.group(2))) + + return (proto, caps) + +def test_printcap(): + get_printcap() + +def wait_for_mount(mount_process, mnt_dir, + test_fn=os.path.ismount): + elapsed = 0 + while elapsed < 30: + if test_fn(mnt_dir): + return True + if mount_process.poll() is not None: + if test_fn(mnt_dir): + return True + pytest.fail('file system process terminated prematurely') + time.sleep(0.1) + elapsed += 0.1 + pytest.fail("mountpoint failed to come up") + +def cleanup(mount_process, mnt_dir): + # Don't bother trying Valgrind if things already went wrong + + if 'bsd' in sys.platform or 'dragonfly' in sys.platform: + cmd = [ 'umount', '-f', mnt_dir ] + else: + cmd = [pjoin(basename, 'util', 'fusermount3'), + '-z', '-u', mnt_dir] + subprocess.call(cmd, stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT) + mount_process.terminate() + try: + mount_process.wait(1) + except subprocess.TimeoutExpired: + mount_process.kill() + +def umount(mount_process, mnt_dir): + logger = logging.getLogger(__name__) + logger.debug(f"Unmounting {mnt_dir}") + + if 'bsd' in sys.platform or 'dragonfly' in sys.platform: + cmdline = [ 'umount', mnt_dir ] + logger.debug("Using BSD-style umount command") + else: + logger.debug("Using fusermount3 for unmounting") + # fusermount3 will be setuid root, so we can only trace it with + # valgrind if we're root + if os.getuid() == 0: + cmdline = base_cmdline + logger.debug("Running as root, using valgrind if configured") + else: + cmdline = [] + logger.debug("Not running as root, skipping valgrind for fusermount3") + cmdline = cmdline + [ pjoin(basename, 'util', 'fusermount3'), + '-z', '-u', mnt_dir ] + + logger.debug(f"Unmount command: {' '.join(cmdline)}") + try: + result = subprocess.run(cmdline, capture_output=True, text=True, check=True) + if result.stdout: + logger.debug(f"Unmount command stdout: {result.stdout}") + if result.stderr: + logger.debug(f"Unmount command stderr: {result.stderr}") + except subprocess.CalledProcessError as e: + logger.error(f"Unmount command failed with return code {e.returncode}\nStdout: {e.stdout}\nStderr: {e.stderr}") + raise + + if not os.path.ismount(mnt_dir): + logger.debug(f"{mnt_dir} is no longer a mount point") + else: + logger.warning(f"{mnt_dir} is still a mount point after unmount command") + + # Give mount process a little while to terminate. Popen.wait(timeout) + # was only added in 3.3... + elapsed = 0 + while elapsed < 30: + code = mount_process.poll() + if code is not None: + if code == 0: + return + logger.error(f"File system process terminated with code {code}") + pytest.fail(f'file system process terminated with code {code}') + time.sleep(0.1) + elapsed += 0.1 + logger.error("Mount process did not terminate within 30 seconds") + pytest.fail('mount process did not terminate') + + +def safe_sleep(secs): + '''Like time.sleep(), but sleep for at least *secs* + + `time.sleep` may sleep less than the given period if a signal is + received. This function ensures that we sleep for at least the + desired time. + ''' + + now = time.time() + end = now + secs + while now < end: + time.sleep(end - now) + now = time.time() + +def fuse_test_marker(): + '''Return a pytest.marker that indicates FUSE availability + + If system/user/environment does not support FUSE, return + a `pytest.mark.skip` object with more details. If FUSE is + supported, return `pytest.mark.uses_fuse()`. + ''' + + skip = lambda x: pytest.mark.skip(reason=x) + + if 'bsd' in sys.platform or 'dragonfly' in sys.platform: + return pytest.mark.uses_fuse() + + with subprocess.Popen(['which', 'fusermount3'], stdout=subprocess.PIPE, + universal_newlines=True) as which: + fusermount_path = which.communicate()[0].strip() + + if not fusermount_path or which.returncode != 0: + return skip("Can't find fusermount executable") + + if not os.path.exists('/dev/fuse'): + return skip("FUSE kernel module does not seem to be loaded") + + if os.getuid() == 0: + return pytest.mark.uses_fuse() + + mode = os.stat(fusermount_path).st_mode + if mode & stat.S_ISUID == 0: + return skip('fusermount executable not setuid, and we are not root.') + + try: + fd = os.open('/dev/fuse', os.O_RDWR) + except OSError as exc: + return skip('Unable to open /dev/fuse: %s' % exc.strerror) + else: + os.close(fd) + + return pytest.mark.uses_fuse() + +def powerset(iterable): + s = list(iterable) + return itertools.chain.from_iterable( + itertools.combinations(s, r) for r in range(len(s)+1)) + +def create_tmpdir(mnt_dir): + if not os.path.exists(mnt_dir): + print("makedirs: '" + mnt_dir + "'") + os.makedirs(mnt_dir) + else: + print("mnt_dir exists: '" + mnt_dir + "'") + +# Use valgrind if requested +if os.environ.get('TEST_WITH_VALGRIND', 'no').lower().strip() \ + not in ('no', 'false', '0'): + base_cmdline = [ 'valgrind', '-q', '--' ] +else: + base_cmdline = [] + +# Try to use local fusermount3 +os.environ['PATH'] = '%s:%s' % (pjoin(basename, 'util'), os.environ['PATH']) +# Put example binaries on PATH +os.environ['PATH'] = '%s:%s' % (pjoin(basename, 'example'), os.environ['PATH']) + +try: + (fuse_proto, fuse_caps) = get_printcap() +except: + # Rely on test to raise error + fuse_proto = (0,0) + fuse_caps = set() + diff --git a/test/wrong_command.c b/test/wrong_command.c new file mode 100644 index 0000000..8b563c9 --- /dev/null +++ b/test/wrong_command.c @@ -0,0 +1,16 @@ +#include + +int main(void) { +#ifdef MESON_IS_SUBPROJECT + fprintf(stderr, "libfuse tests were skipped because it's a meson subproject.\n" + "If you wish to run them try:\n" + "'cd /subprojects/libfuse && meson . build && cd build && python3 -m pytest test/' instead"); + return 77; /* report as a skipped test */ +#else + fprintf(stderr, "\x1B[31m\e[1m" + "This is not the command you are looking for.\n" + "You probably want to run 'python3 -m pytest test/' instead" + "\e[0m\n"); + return 1; +#endif +} diff --git a/tsan_suppressions.txt b/tsan_suppressions.txt new file mode 100644 index 0000000..3470d15 --- /dev/null +++ b/tsan_suppressions.txt @@ -0,0 +1,5 @@ +# Use with +# TSAN_OPTIONS="suppressions=tsan_suppressions.txt" ./myprogram + +# False positive +race:pthread_setcancelstate diff --git a/util/fuse.conf b/util/fuse.conf new file mode 100644 index 0000000..ab048e0 --- /dev/null +++ b/util/fuse.conf @@ -0,0 +1,17 @@ +# The file /etc/fuse.conf allows for the following parameters: +# +# user_allow_other - Using the allow_other mount option works fine as root, but +# in order to have it work as a regular user, you need to set user_allow_other +# in /etc/fuse.conf as well. This option allows non-root users to use the +# allow_other option. You need allow_other if you want users other than the +# owner of a mounted fuse to access it. This option must appear on a line by +# itself. There is no value; just the presence of the option activates it. + +#user_allow_other + + +# mount_max = n - this option sets the maximum number of mounts. +# It must be typed exactly as shown (with a single space before and after the +# equals sign). + +#mount_max = 1000 diff --git a/util/fusermount.c b/util/fusermount.c new file mode 100644 index 0000000..f17b44f --- /dev/null +++ b/util/fusermount.c @@ -0,0 +1,1783 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ +/* This program does the mounting and unmounting of FUSE filesystems */ + +#define _GNU_SOURCE /* for clone,strchrnul and close_range */ +#include "fuse_config.h" +#include "mount_util.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fuse_mount_compat.h" + +#include +#include +#include +#include +#include +#include + +#if defined HAVE_CLOSE_RANGE && defined linux +#include +#endif + +#if defined HAVE_LISTMOUNT +#include +#include +#include +#endif + +#define FUSE_COMMFD_ENV "_FUSE_COMMFD" +#define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE" + +#define FUSE_DEV "/dev/fuse" + +static const char *progname; + +static int user_allow_other = 0; +static int mount_max = 1000; + +static int auto_unmount = 0; + +#ifdef GETMNTENT_NEEDS_UNESCAPING +// Older versions of musl libc don't unescape entries in /etc/mtab + +// unescapes octal sequences like \040 in-place +// That's ok, because unescaping can not extend the length of the string. +static void unescape(char *buf) { + char *src = buf; + char *dest = buf; + while (1) { + char *next_src = strchrnul(src, '\\'); + int offset = next_src - src; + memmove(dest, src, offset); + src = next_src; + dest += offset; + + if(*src == '\0') { + *dest = *src; + return; + } + src++; + + if('0' <= src[0] && src[0] < '2' && + '0' <= src[1] && src[1] < '8' && + '0' <= src[2] && src[2] < '8') { + *dest++ = (src[0] - '0') << 6 + | (src[1] - '0') << 3 + | (src[2] - '0') << 0; + src += 3; + } else if (src[0] == '\\') { + *dest++ = '\\'; + src += 1; + } else { + *dest++ = '\\'; + } + } +} + +static struct mntent *GETMNTENT(FILE *stream) +{ + struct mntent *entp = getmntent(stream); + if(entp != NULL) { + unescape(entp->mnt_fsname); + unescape(entp->mnt_dir); + unescape(entp->mnt_type); + unescape(entp->mnt_opts); + } + return entp; +} +#else +#define GETMNTENT getmntent +#endif // GETMNTENT_NEEDS_UNESCAPING + +/* + * Take a ',' separated option string and extract "x-" options + */ +static int extract_x_options(const char *original, char **non_x_opts, + char **x_opts) +{ + size_t orig_len; + const char *opt, *opt_end; + + orig_len = strlen(original) + 1; + + *non_x_opts = calloc(1, orig_len); + *x_opts = calloc(1, orig_len); + + size_t non_x_opts_len = orig_len; + size_t x_opts_len = orig_len; + + if (*non_x_opts == NULL || *x_opts == NULL) { + fprintf(stderr, "%s: Failed to allocate %zuB.\n", + __func__, orig_len); + return -ENOMEM; + } + + for (opt = original; opt < original + orig_len; opt = opt_end + 1) { + char *opt_buf; + + opt_end = strchr(opt, ','); + if (opt_end == NULL) + opt_end = original + orig_len; + + size_t opt_len = opt_end - opt; + size_t opt_len_left = orig_len - (opt - original); + size_t buf_len; + bool is_x_opts; + + if (strncmp(opt, "x-", MIN(2, opt_len_left)) == 0) { + buf_len = x_opts_len; + is_x_opts = true; + opt_buf = *x_opts; + } else { + buf_len = non_x_opts_len; + is_x_opts = false; + opt_buf = *non_x_opts; + } + + if (buf_len < orig_len) { + strncat(opt_buf, ",", 2); + buf_len -= 1; + } + + /* omits ',' */ + if ((ssize_t)(buf_len - opt_len) < 0) { + /* This would be a bug */ + fprintf(stderr, "%s: no buf space left in copy, orig='%s'\n", + __func__, original); + return -EIO; + } + + strncat(opt_buf, opt, opt_end - opt); + buf_len -= opt_len; + + if (is_x_opts) + x_opts_len = buf_len; + else + non_x_opts_len = buf_len; + } + + return 0; +} + +static const char *get_user_name(void) +{ + struct passwd *pw = getpwuid(getuid()); + if (pw != NULL && pw->pw_name != NULL) + return pw->pw_name; + else { + fprintf(stderr, "%s: could not determine username\n", progname); + return NULL; + } +} + +static uid_t oldfsuid; +static gid_t oldfsgid; + +static void drop_privs(void) +{ + if (getuid() != 0) { + oldfsuid = setfsuid(getuid()); + oldfsgid = setfsgid(getgid()); + } +} + +static void restore_privs(void) +{ + if (getuid() != 0) { + setfsuid(oldfsuid); + setfsgid(oldfsgid); + } +} + +#ifndef IGNORE_MTAB +/* + * Make sure that /etc/mtab is checked and updated atomically + */ +static int lock_umount(void) +{ + const char *mtab_lock = _PATH_MOUNTED ".fuselock"; + int mtablock; + int res; + struct stat mtab_stat; + + /* /etc/mtab could be a symlink to /proc/mounts */ + if (lstat(_PATH_MOUNTED, &mtab_stat) == 0 && S_ISLNK(mtab_stat.st_mode)) + return -1; + + mtablock = open(mtab_lock, O_RDWR | O_CREAT, 0600); + if (mtablock == -1) { + fprintf(stderr, "%s: unable to open fuse lock file: %s\n", + progname, strerror(errno)); + return -1; + } + res = lockf(mtablock, F_LOCK, 0); + if (res < 0) { + fprintf(stderr, "%s: error getting lock: %s\n", progname, + strerror(errno)); + close(mtablock); + return -1; + } + + return mtablock; +} + +static void unlock_umount(int mtablock) +{ + if (mtablock >= 0) { + int res; + + res = lockf(mtablock, F_ULOCK, 0); + if (res < 0) { + fprintf(stderr, "%s: error releasing lock: %s\n", + progname, strerror(errno)); + } + close(mtablock); + } +} + +static int add_mount(const char *source, const char *mnt, const char *type, + const char *opts) +{ + return fuse_mnt_add_mount(progname, source, mnt, type, opts); +} + +static int may_unmount(const char *mnt, int quiet) +{ + struct mntent *entp; + FILE *fp; + const char *user = NULL; + char uidstr[32]; + unsigned uidlen = 0; + int found; + const char *mtab = _PATH_MOUNTED; + + user = get_user_name(); + if (user == NULL) + return -1; + + fp = setmntent(mtab, "r"); + if (fp == NULL) { + fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab, + strerror(errno)); + return -1; + } + + uidlen = sprintf(uidstr, "%u", getuid()); + + found = 0; + while ((entp = GETMNTENT(fp)) != NULL) { + if (!found && strcmp(entp->mnt_dir, mnt) == 0 && + (strcmp(entp->mnt_type, "fuse") == 0 || + strcmp(entp->mnt_type, "fuseblk") == 0 || + strncmp(entp->mnt_type, "fuse.", 5) == 0 || + strncmp(entp->mnt_type, "fuseblk.", 8) == 0)) { + char *p = strstr(entp->mnt_opts, "user="); + if (p && + (p == entp->mnt_opts || *(p-1) == ',') && + strcmp(p + 5, user) == 0) { + found = 1; + break; + } + /* /etc/mtab is a link pointing to + /proc/mounts: */ + else if ((p = + strstr(entp->mnt_opts, "user_id=")) && + (p == entp->mnt_opts || + *(p-1) == ',') && + strncmp(p + 8, uidstr, uidlen) == 0 && + (*(p+8+uidlen) == ',' || + *(p+8+uidlen) == '\0')) { + found = 1; + break; + } + } + } + endmntent(fp); + + if (!found) { + if (!quiet) + fprintf(stderr, + "%s: entry for %s not found in %s\n", + progname, mnt, mtab); + return -1; + } + + return 0; +} +#endif + +/* + * Check whether the file specified in "fusermount3 -u" is really a + * mountpoint and not a symlink. This is necessary otherwise the user + * could move the mountpoint away and replace it with a symlink + * pointing to an arbitrary mount, thereby tricking fusermount3 into + * unmounting that (umount(2) will follow symlinks). + * + * This is the child process running in a separate mount namespace, so + * we don't mess with the global namespace and if the process is + * killed for any reason, mounts are automatically cleaned up. + * + * First make sure nothing is propagated back into the parent + * namespace by marking all mounts "private". + * + * Then bind mount parent onto a stable base where the user can't move + * it around. + * + * Finally check /proc/mounts for an entry matching the requested + * mountpoint. If it's found then we are OK, and the user can't move + * it around within the parent directory as rename() will return + * EBUSY. Be careful to ignore any mounts that existed before the + * bind. + */ +static int check_is_mount_child(void *p) +{ + const char **a = p; + const char *last = a[0]; + const char *mnt = a[1]; + const char *type = a[2]; + int res; + const char *procmounts = "/proc/mounts"; + int found; + FILE *fp; + struct mntent *entp; + int count; + + res = mount("", "/", "", MS_PRIVATE | MS_REC, NULL); + if (res == -1) { + fprintf(stderr, "%s: failed to mark mounts private: %s\n", + progname, strerror(errno)); + return 1; + } + + fp = setmntent(procmounts, "r"); + if (fp == NULL) { + fprintf(stderr, "%s: failed to open %s: %s\n", progname, + procmounts, strerror(errno)); + return 1; + } + + count = 0; + while (GETMNTENT(fp) != NULL) + count++; + endmntent(fp); + + fp = setmntent(procmounts, "r"); + if (fp == NULL) { + fprintf(stderr, "%s: failed to open %s: %s\n", progname, + procmounts, strerror(errno)); + return 1; + } + + res = mount(".", "/", "", MS_BIND | MS_REC, NULL); + if (res == -1) { + fprintf(stderr, "%s: failed to bind parent to /: %s\n", + progname, strerror(errno)); + return 1; + } + + found = 0; + while ((entp = GETMNTENT(fp)) != NULL) { + if (count > 0) { + count--; + continue; + } + if (entp->mnt_dir[0] == '/' && + strcmp(entp->mnt_dir + 1, last) == 0 && + (!type || strcmp(entp->mnt_type, type) == 0)) { + found = 1; + break; + } + } + endmntent(fp); + + if (!found) { + fprintf(stderr, "%s: %s not mounted\n", progname, mnt); + return 1; + } + + return 0; +} + +static pid_t clone_newns(void *a) +{ + char buf[131072]; + char *stack = buf + (sizeof(buf) / 2 - ((size_t) buf & 15)); + +#ifdef __ia64__ + extern int __clone2(int (*fn)(void *), + void *child_stack_base, size_t stack_size, + int flags, void *arg, pid_t *ptid, + void *tls, pid_t *ctid); + + return __clone2(check_is_mount_child, stack, sizeof(buf) / 2, + CLONE_NEWNS, a, NULL, NULL, NULL); +#else + return clone(check_is_mount_child, stack, CLONE_NEWNS, a); +#endif +} + +static int check_is_mount(const char *last, const char *mnt, const char *type) +{ + pid_t pid, p; + int status; + const char *a[3] = { last, mnt, type }; + + pid = clone_newns((void *) a); + if (pid == (pid_t) -1) { + fprintf(stderr, "%s: failed to clone namespace: %s\n", + progname, strerror(errno)); + return -1; + } + p = waitpid(pid, &status, __WCLONE); + if (p == (pid_t) -1) { + fprintf(stderr, "%s: waitpid failed: %s\n", + progname, strerror(errno)); + return -1; + } + if (!WIFEXITED(status)) { + fprintf(stderr, "%s: child terminated abnormally (status %i)\n", + progname, status); + return -1; + } + if (WEXITSTATUS(status) != 0) + return -1; + + return 0; +} + +static int chdir_to_parent(char *copy, const char **lastp) +{ + char *tmp; + const char *parent; + char buf[65536]; + int res; + + tmp = strrchr(copy, '/'); + if (tmp == NULL || tmp[1] == '\0') { + fprintf(stderr, "%s: internal error: invalid abs path: <%s>\n", + progname, copy); + return -1; + } + if (tmp != copy) { + *tmp = '\0'; + parent = copy; + *lastp = tmp + 1; + } else if (tmp[1] != '\0') { + *lastp = tmp + 1; + parent = "/"; + } else { + *lastp = "."; + parent = "/"; + } + + res = chdir(parent); + if (res == -1) { + fprintf(stderr, "%s: failed to chdir to %s: %s\n", + progname, parent, strerror(errno)); + return -1; + } + + if (getcwd(buf, sizeof(buf)) == NULL) { + fprintf(stderr, "%s: failed to obtain current directory: %s\n", + progname, strerror(errno)); + return -1; + } + if (strcmp(buf, parent) != 0) { + fprintf(stderr, "%s: mountpoint moved (%s -> %s)\n", progname, + parent, buf); + return -1; + + } + + return 0; +} + +#ifndef IGNORE_MTAB +static int unmount_fuse_locked(const char *mnt, int quiet, int lazy) +{ + int res; + char *copy; + const char *last; + int umount_flags = (lazy ? UMOUNT_DETACH : 0) | UMOUNT_NOFOLLOW; + + if (getuid() != 0) { + res = may_unmount(mnt, quiet); + if (res == -1) + return -1; + } + + copy = strdup(mnt); + if (copy == NULL) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return -1; + } + + drop_privs(); + res = chdir_to_parent(copy, &last); + if (res == -1) { + restore_privs(); + goto out; + } + + res = umount2(last, umount_flags); + restore_privs(); + if (res == -1 && !quiet) { + fprintf(stderr, "%s: failed to unmount %s: %s\n", + progname, mnt, strerror(errno)); + } + +out: + free(copy); + if (res == -1) + return -1; + + res = chdir("/"); + if (res == -1) { + fprintf(stderr, "%s: failed to chdir to '/'\n", progname); + return -1; + } + + return fuse_mnt_remove_mount(progname, mnt); +} + +static int unmount_fuse(const char *mnt, int quiet, int lazy) +{ + int res; + int mtablock = lock_umount(); + + res = unmount_fuse_locked(mnt, quiet, lazy); + unlock_umount(mtablock); + + return res; +} + +static int count_fuse_fs_mtab(void) +{ + struct mntent *entp; + int count = 0; + const char *mtab = _PATH_MOUNTED; + FILE *fp = setmntent(mtab, "r"); + if (fp == NULL) { + fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab, + strerror(errno)); + return -1; + } + while ((entp = GETMNTENT(fp)) != NULL) { + if (strcmp(entp->mnt_type, "fuse") == 0 || + strncmp(entp->mnt_type, "fuse.", 5) == 0) + count ++; + } + endmntent(fp); + return count; +} + +#ifdef HAVE_LISTMOUNT +static int count_fuse_fs_ls_mnt(void) +{ + #define SMBUF_SIZE 1024 + #define MNT_ID_LEN 128 + + int fuse_count = 0; + int n_mounts = 0; + int ret = 0; + uint64_t mnt_ids[MNT_ID_LEN]; + unsigned char smbuf[SMBUF_SIZE]; + struct mnt_id_req req = { + .size = sizeof(struct mnt_id_req), + }; + struct statmount *sm; + + for (;;) { + req.mnt_id = LSMT_ROOT; + + n_mounts = syscall(SYS_listmount, &req, &mnt_ids, MNT_ID_LEN, 0); + if (n_mounts == -1) { + if (errno != ENOSYS) { + fprintf(stderr, "%s: failed to list mounts: %s\n", progname, + strerror(errno)); + } + return -1; + } + + for (int i = 0; i < n_mounts; i++) { + req.mnt_id = mnt_ids[i]; + req.param = STATMOUNT_FS_TYPE; + ret = syscall(SYS_statmount, &req, &smbuf, SMBUF_SIZE, 0); + if (ret) { + if (errno == ENOENT) + continue; + + fprintf(stderr, "%s: failed to stat mount %lld: %s\n", progname, + req.mnt_id, strerror(errno)); + return -1; + } + + sm = (struct statmount *)smbuf; + if (sm->mask & STATMOUNT_FS_TYPE && + strcmp(&sm->str[sm->fs_type], "fuse") == 0) + fuse_count++; + } + + if (n_mounts < MNT_ID_LEN) + break; + req.param = mnt_ids[MNT_ID_LEN - 1]; + } + return fuse_count; +} + +static int count_fuse_fs(void) +{ + int count = count_fuse_fs_ls_mnt(); + + return count >= 0 ? count : count_fuse_fs_mtab(); +} +#else +static int count_fuse_fs(void) +{ + return count_fuse_fs_mtab(); +} +#endif + +#else /* IGNORE_MTAB */ +static int count_fuse_fs(void) +{ + return 0; +} + +static int add_mount(const char *source, const char *mnt, const char *type, + const char *opts) +{ + (void) source; + (void) mnt; + (void) type; + (void) opts; + return 0; +} + +static int unmount_fuse(const char *mnt, int quiet, int lazy) +{ + (void) quiet; + return fuse_mnt_umount(progname, mnt, mnt, lazy); +} +#endif /* IGNORE_MTAB */ + +static void strip_line(char *line) +{ + char *s = strchr(line, '#'); + if (s != NULL) + s[0] = '\0'; + for (s = line + strlen(line) - 1; + s >= line && isspace((unsigned char) *s); s--); + s[1] = '\0'; + for (s = line; isspace((unsigned char) *s); s++); + if (s != line) + memmove(line, s, strlen(s)+1); +} + +static void parse_line(char *line, int linenum) +{ + int tmp; + if (strcmp(line, "user_allow_other") == 0) + user_allow_other = 1; + else if (sscanf(line, "mount_max = %i", &tmp) == 1) + mount_max = tmp; + else if(line[0]) + fprintf(stderr, + "%s: unknown parameter in %s at line %i: '%s'\n", + progname, FUSE_CONF, linenum, line); +} + +static void read_conf(void) +{ + FILE *fp = fopen(FUSE_CONF, "r"); + if (fp != NULL) { + int linenum = 1; + char line[256]; + int isnewline = 1; + while (fgets(line, sizeof(line), fp) != NULL) { + if (isnewline) { + if (line[strlen(line)-1] == '\n') { + strip_line(line); + parse_line(line, linenum); + } else { + isnewline = 0; + } + } else if(line[strlen(line)-1] == '\n') { + fprintf(stderr, "%s: reading %s: line %i too long\n", progname, FUSE_CONF, linenum); + + isnewline = 1; + } + if (isnewline) + linenum ++; + } + if (!isnewline) { + fprintf(stderr, "%s: reading %s: missing newline at end of file\n", progname, FUSE_CONF); + + } + if (ferror(fp)) { + fprintf(stderr, "%s: reading %s: read failed\n", progname, FUSE_CONF); + exit(1); + } + fclose(fp); + } else if (errno != ENOENT) { + bool fatal = (errno != EACCES && errno != ELOOP && + errno != ENAMETOOLONG && errno != ENOTDIR && + errno != EOVERFLOW); + fprintf(stderr, "%s: failed to open %s: %s\n", + progname, FUSE_CONF, strerror(errno)); + if (fatal) + exit(1); + } +} + +static int begins_with(const char *s, const char *beg) +{ + if (strncmp(s, beg, strlen(beg)) == 0) + return 1; + else + return 0; +} + +struct mount_flags { + const char *opt; + unsigned long flag; + int on; + int safe; +}; + +static struct mount_flags mount_flags[] = { + {"rw", MS_RDONLY, 0, 1}, + {"ro", MS_RDONLY, 1, 1}, + {"suid", MS_NOSUID, 0, 0}, + {"nosuid", MS_NOSUID, 1, 1}, + {"dev", MS_NODEV, 0, 0}, + {"nodev", MS_NODEV, 1, 1}, + {"exec", MS_NOEXEC, 0, 1}, + {"noexec", MS_NOEXEC, 1, 1}, + {"async", MS_SYNCHRONOUS, 0, 1}, + {"sync", MS_SYNCHRONOUS, 1, 1}, + {"atime", MS_NOATIME, 0, 1}, + {"noatime", MS_NOATIME, 1, 1}, + {"diratime", MS_NODIRATIME, 0, 1}, + {"nodiratime", MS_NODIRATIME, 1, 1}, + {"lazytime", MS_LAZYTIME, 1, 1}, + {"nolazytime", MS_LAZYTIME, 0, 1}, + {"relatime", MS_RELATIME, 1, 1}, + {"norelatime", MS_RELATIME, 0, 1}, + {"strictatime", MS_STRICTATIME, 1, 1}, + {"nostrictatime", MS_STRICTATIME, 0, 1}, + {"dirsync", MS_DIRSYNC, 1, 1}, + {"symfollow", MS_NOSYMFOLLOW, 0, 1}, + {"nosymfollow", MS_NOSYMFOLLOW, 1, 1}, + {NULL, 0, 0, 0} +}; + +static int find_mount_flag(const char *s, unsigned len, int *on, int *flag) +{ + int i; + + for (i = 0; mount_flags[i].opt != NULL; i++) { + const char *opt = mount_flags[i].opt; + if (strlen(opt) == len && strncmp(opt, s, len) == 0) { + *on = mount_flags[i].on; + *flag = mount_flags[i].flag; + if (!mount_flags[i].safe && getuid() != 0) { + *flag = 0; + fprintf(stderr, + "%s: unsafe option %s ignored\n", + progname, opt); + } + return 1; + } + } + return 0; +} + +static int add_option(char **optsp, const char *opt, unsigned expand) +{ + char *newopts; + if (*optsp == NULL) + newopts = strdup(opt); + else { + unsigned oldsize = strlen(*optsp); + unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1; + newopts = (char *) realloc(*optsp, newsize); + if (newopts) + sprintf(newopts + oldsize, ",%s", opt); + } + if (newopts == NULL) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return -1; + } + *optsp = newopts; + return 0; +} + +static int get_mnt_opts(int flags, char *opts, char **mnt_optsp) +{ + int i; + int l; + + if (!(flags & MS_RDONLY) && add_option(mnt_optsp, "rw", 0) == -1) + return -1; + + for (i = 0; mount_flags[i].opt != NULL; i++) { + if (mount_flags[i].on && (flags & mount_flags[i].flag) && + add_option(mnt_optsp, mount_flags[i].opt, 0) == -1) + return -1; + } + + if (add_option(mnt_optsp, opts, 0) == -1) + return -1; + /* remove comma from end of opts*/ + l = strlen(*mnt_optsp); + if ((*mnt_optsp)[l-1] == ',') + (*mnt_optsp)[l-1] = '\0'; + if (getuid() != 0) { + const char *user = get_user_name(); + if (user == NULL) + return -1; + + if (add_option(mnt_optsp, "user=", strlen(user)) == -1) + return -1; + strcat(*mnt_optsp, user); + } + return 0; +} + +static int opt_eq(const char *s, unsigned len, const char *opt) +{ + if(strlen(opt) == len && strncmp(s, opt, len) == 0) + return 1; + else + return 0; +} + +static int get_string_opt(const char *s, unsigned len, const char *opt, + char **val) +{ + int i; + unsigned opt_len = strlen(opt); + char *d; + + if (*val) + free(*val); + *val = (char *) malloc(len - opt_len + 1); + if (!*val) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return 0; + } + + d = *val; + s += opt_len; + len -= opt_len; + for (i = 0; i < len; i++) { + if (s[i] == '\\' && i + 1 < len) + i++; + *d++ = s[i]; + } + *d = '\0'; + return 1; +} + +/* The kernel silently truncates the "data" argument to PAGE_SIZE-1 characters. + * This can be dangerous if it e.g. truncates the option "group_id=1000" to + * "group_id=1". + * This wrapper detects this case and bails out with an error. + */ +static int mount_notrunc(const char *source, const char *target, + const char *filesystemtype, unsigned long mountflags, + const char *data) { + if (strlen(data) > sysconf(_SC_PAGESIZE) - 1) { + fprintf(stderr, "%s: mount options too long\n", progname); + errno = EINVAL; + return -1; + } + return mount(source, target, filesystemtype, mountflags, data); +} + + +static int do_mount(const char *mnt, const char **typep, mode_t rootmode, + int fd, const char *opts, const char *dev, char **sourcep, + char **mnt_optsp) +{ + int res; + int flags = MS_NOSUID | MS_NODEV; + char *optbuf; + char *mnt_opts = NULL; + const char *s; + char *d; + char *fsname = NULL; + char *subtype = NULL; + char *source = NULL; + char *type = NULL; + int blkdev = 0; + + optbuf = (char *) malloc(strlen(opts) + 128); + if (!optbuf) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return -1; + } + + for (s = opts, d = optbuf; *s;) { + unsigned len; + const char *fsname_str = "fsname="; + const char *subtype_str = "subtype="; + bool escape_ok = begins_with(s, fsname_str) || + begins_with(s, subtype_str); + for (len = 0; s[len]; len++) { + if (escape_ok && s[len] == '\\' && s[len + 1]) + len++; + else if (s[len] == ',') + break; + } + if (begins_with(s, fsname_str)) { + if (!get_string_opt(s, len, fsname_str, &fsname)) + goto err; + } else if (begins_with(s, subtype_str)) { + if (!get_string_opt(s, len, subtype_str, &subtype)) + goto err; + } else if (opt_eq(s, len, "blkdev")) { + if (getuid() != 0) { + fprintf(stderr, + "%s: option blkdev is privileged\n", + progname); + goto err; + } + blkdev = 1; + } else if (opt_eq(s, len, "auto_unmount")) { + auto_unmount = 1; + } else if (!opt_eq(s, len, "nonempty") && + !begins_with(s, "fd=") && + !begins_with(s, "rootmode=") && + !begins_with(s, "user_id=") && + !begins_with(s, "group_id=")) { + int on; + int flag; + int skip_option = 0; + if (opt_eq(s, len, "large_read")) { + struct utsname utsname; + unsigned kmaj, kmin; + res = uname(&utsname); + if (res == 0 && + sscanf(utsname.release, "%u.%u", + &kmaj, &kmin) == 2 && + (kmaj > 2 || (kmaj == 2 && kmin > 4))) { + fprintf(stderr, "%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n", progname, kmaj, kmin); + skip_option = 1; + } + } + if (getuid() != 0 && !user_allow_other && + (opt_eq(s, len, "allow_other") || + opt_eq(s, len, "allow_root"))) { + fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n", progname, len, s, FUSE_CONF); + goto err; + } + if (!skip_option) { + if (find_mount_flag(s, len, &on, &flag)) { + if (on) + flags |= flag; + else + flags &= ~flag; + } else if (opt_eq(s, len, "default_permissions") || + opt_eq(s, len, "allow_other") || + begins_with(s, "max_read=") || + begins_with(s, "blksize=")) { + memcpy(d, s, len); + d += len; + *d++ = ','; + } else { + fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, s); + exit(1); + } + } + } + s += len; + if (*s) + s++; + } + *d = '\0'; + res = get_mnt_opts(flags, optbuf, &mnt_opts); + if (res == -1) + goto err; + + sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u", + fd, rootmode, getuid(), getgid()); + + source = malloc((fsname ? strlen(fsname) : 0) + + (subtype ? strlen(subtype) : 0) + strlen(dev) + 32); + + type = malloc((subtype ? strlen(subtype) : 0) + 32); + if (!type || !source) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + goto err; + } + + if (subtype) + sprintf(type, "%s.%s", blkdev ? "fuseblk" : "fuse", subtype); + else + strcpy(type, blkdev ? "fuseblk" : "fuse"); + + if (fsname) + strcpy(source, fsname); + else + strcpy(source, subtype ? subtype : dev); + + res = mount_notrunc(source, mnt, type, flags, optbuf); + if (res == -1 && errno == ENODEV && subtype) { + /* Probably missing subtype support */ + strcpy(type, blkdev ? "fuseblk" : "fuse"); + if (fsname) { + if (!blkdev) + sprintf(source, "%s#%s", subtype, fsname); + } else { + strcpy(source, type); + } + + res = mount_notrunc(source, mnt, type, flags, optbuf); + } + if (res == -1 && errno == EINVAL) { + /* It could be an old version not supporting group_id */ + sprintf(d, "fd=%i,rootmode=%o,user_id=%u", + fd, rootmode, getuid()); + res = mount_notrunc(source, mnt, type, flags, optbuf); + } + if (res == -1) { + int errno_save = errno; + if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk()) + fprintf(stderr, "%s: 'fuseblk' support missing\n", + progname); + else + fprintf(stderr, "%s: mount failed: %s\n", progname, + strerror(errno_save)); + goto err; + } + *sourcep = source; + *typep = type; + *mnt_optsp = mnt_opts; + free(fsname); + free(optbuf); + + return 0; + +err: + free(fsname); + free(subtype); + free(source); + free(type); + free(mnt_opts); + free(optbuf); + return -1; +} + +static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd) +{ + int res; + const char *mnt = *mntp; + const char *origmnt = mnt; + struct statfs fs_buf; + size_t i; + + res = lstat(mnt, stbuf); + if (res == -1) { + fprintf(stderr, "%s: failed to access mountpoint %s: %s\n", + progname, mnt, strerror(errno)); + return -1; + } + + /* No permission checking is done for root */ + if (getuid() == 0) + return 0; + + if (S_ISDIR(stbuf->st_mode)) { + res = chdir(mnt); + if (res == -1) { + fprintf(stderr, + "%s: failed to chdir to mountpoint: %s\n", + progname, strerror(errno)); + return -1; + } + mnt = *mntp = "."; + res = lstat(mnt, stbuf); + if (res == -1) { + fprintf(stderr, + "%s: failed to access mountpoint %s: %s\n", + progname, origmnt, strerror(errno)); + return -1; + } + + if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) { + fprintf(stderr, "%s: mountpoint %s not owned by user\n", + progname, origmnt); + return -1; + } + + res = access(mnt, W_OK); + if (res == -1) { + fprintf(stderr, "%s: user has no write access to mountpoint %s\n", + progname, origmnt); + return -1; + } + } else if (S_ISREG(stbuf->st_mode)) { + static char procfile[256]; + *mountpoint_fd = open(mnt, O_WRONLY); + if (*mountpoint_fd == -1) { + fprintf(stderr, "%s: failed to open %s: %s\n", + progname, mnt, strerror(errno)); + return -1; + } + res = fstat(*mountpoint_fd, stbuf); + if (res == -1) { + fprintf(stderr, + "%s: failed to access mountpoint %s: %s\n", + progname, mnt, strerror(errno)); + return -1; + } + if (!S_ISREG(stbuf->st_mode)) { + fprintf(stderr, + "%s: mountpoint %s is no longer a regular file\n", + progname, mnt); + return -1; + } + + sprintf(procfile, "/proc/self/fd/%i", *mountpoint_fd); + *mntp = procfile; + } else { + fprintf(stderr, + "%s: mountpoint %s is not a directory or a regular file\n", + progname, mnt); + return -1; + } + + /* Do not permit mounting over anything in procfs - it has a couple + * places to which we have "write access" without being supposed to be + * able to just put anything we want there. + * Luckily, without allow_other, we can't get other users to actually + * use any fake information we try to put there anyway. + * Use a whitelist to be safe. */ + if (statfs(*mntp, &fs_buf)) { + fprintf(stderr, "%s: failed to access mountpoint %s: %s\n", + progname, mnt, strerror(errno)); + return -1; + } + + /* Define permitted filesystems for the mount target. This was + * originally the same list as used by the ecryptfs mount helper + * (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225) + * but got expanded as we found more filesystems that needed to be + * overlaid. */ + typeof(fs_buf.f_type) f_type_whitelist[] = { + 0x61756673 /* AUFS_SUPER_MAGIC */, + 0x00000187 /* AUTOFS_SUPER_MAGIC */, + 0xCA451A4E /* BCACHEFS_STATFS_MAGIC */, + 0x9123683E /* BTRFS_SUPER_MAGIC */, + 0x00C36400 /* CEPH_SUPER_MAGIC */, + 0xFF534D42 /* CIFS_MAGIC_NUMBER */, + 0x0000F15F /* ECRYPTFS_SUPER_MAGIC */, + 0X2011BAB0 /* EXFAT_SUPER_MAGIC */, + 0x0000EF53 /* EXT[234]_SUPER_MAGIC */, + 0xF2F52010 /* F2FS_SUPER_MAGIC */, + 0x65735546 /* FUSE_SUPER_MAGIC */, + 0x01161970 /* GFS2_MAGIC */, + 0x47504653 /* GPFS_SUPER_MAGIC */, + 0x0000482b /* HFSPLUS_SUPER_MAGIC */, + 0x000072B6 /* JFFS2_SUPER_MAGIC */, + 0x3153464A /* JFS_SUPER_MAGIC */, + 0x0BD00BD0 /* LL_SUPER_MAGIC */, + 0X00004D44 /* MSDOS_SUPER_MAGIC */, + 0x0000564C /* NCP_SUPER_MAGIC */, + 0x00006969 /* NFS_SUPER_MAGIC */, + 0x00003434 /* NILFS_SUPER_MAGIC */, + 0x5346544E /* NTFS_SB_MAGIC */, + 0x7366746E /* NTFS3_SUPER_MAGIC */, + 0x5346414f /* OPENAFS_SUPER_MAGIC */, + 0x794C7630 /* OVERLAYFS_SUPER_MAGIC */, + 0xAAD7AAEA /* PANFS_SUPER_MAGIC */, + 0x52654973 /* REISERFS_SUPER_MAGIC */, + 0xFE534D42 /* SMB2_SUPER_MAGIC */, + 0x73717368 /* SQUASHFS_MAGIC */, + 0x01021994 /* TMPFS_MAGIC */, + 0x24051905 /* UBIFS_SUPER_MAGIC */, + 0x18031977 /* WEKAFS_SUPER_MAGIC */, +#if __SIZEOF_LONG__ > 4 + 0x736675005346544e /* UFSD */, +#endif + 0x58465342 /* XFS_SB_MAGIC */, + 0x2FC12FC1 /* ZFS_SUPER_MAGIC */, + 0x858458f6 /* RAMFS_MAGIC */, + }; + for (i = 0; i < sizeof(f_type_whitelist)/sizeof(f_type_whitelist[0]); i++) { + if (f_type_whitelist[i] == fs_buf.f_type) + return 0; + } + + fprintf(stderr, "%s: mounting over filesystem type %#010lx is forbidden\n", + progname, (unsigned long)fs_buf.f_type); + return -1; +} + +static int open_fuse_device(const char *dev) +{ + int fd; + + drop_privs(); + fd = open(dev, O_RDWR); + if (fd == -1) { + if (errno == ENODEV || errno == ENOENT)/* check for ENOENT too, for the udev case */ + fprintf(stderr, + "%s: fuse device %s not found. Kernel module not loaded?\n", + progname, dev); + else + fprintf(stderr, + "%s: failed to open %s: %s\n", progname, dev, strerror(errno)); + } + restore_privs(); + return fd; +} + +static int mount_fuse(const char *mnt, const char *opts, const char **type) +{ + int res; + int fd; + const char *dev = getenv(FUSE_KERN_DEVICE_ENV) ?: FUSE_DEV; + struct stat stbuf; + char *source = NULL; + char *mnt_opts = NULL; + const char *real_mnt = mnt; + int mountpoint_fd = -1; + char *do_mount_opts = NULL; + char *x_opts = NULL; + + fd = open_fuse_device(dev); + if (fd == -1) + return -1; + + drop_privs(); + read_conf(); + + if (getuid() != 0 && mount_max != -1) { + int mount_count = count_fuse_fs(); + if (mount_count >= mount_max) { + fprintf(stderr, "%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n", progname, FUSE_CONF); + goto fail_close_fd; + } + } + + // Extract any options starting with "x-" + res= extract_x_options(opts, &do_mount_opts, &x_opts); + if (res) + goto fail_close_fd; + + res = check_perm(&real_mnt, &stbuf, &mountpoint_fd); + restore_privs(); + if (res != -1) + res = do_mount(real_mnt, type, stbuf.st_mode & S_IFMT, + fd, do_mount_opts, dev, &source, &mnt_opts); + + if (mountpoint_fd != -1) + close(mountpoint_fd); + + if (res == -1) + goto fail_close_fd; + + res = chdir("/"); + if (res == -1) { + fprintf(stderr, "%s: failed to chdir to '/'\n", progname); + goto fail_close_fd; + } + + if (geteuid() == 0) { + if (x_opts && strlen(x_opts) > 0) { + /* + * Add back the options starting with "x-" to opts from + * do_mount. +2 for ',' and '\0' + */ + size_t mnt_opts_len = strlen(mnt_opts); + size_t x_mnt_opts_len = mnt_opts_len+ + strlen(x_opts) + 2; + char *x_mnt_opts = calloc(1, x_mnt_opts_len); + + if (mnt_opts_len) { + strcpy(x_mnt_opts, mnt_opts); + strncat(x_mnt_opts, ",", 2); + } + + strncat(x_mnt_opts, x_opts, + x_mnt_opts_len - mnt_opts_len - 2); + + free(mnt_opts); + mnt_opts = x_mnt_opts; + } + + res = add_mount(source, mnt, *type, mnt_opts); + if (res == -1) { + /* Can't clean up mount in a non-racy way */ + goto fail_close_fd; + } + } + +out_free: + free(source); + free(mnt_opts); + free(x_opts); + free(do_mount_opts); + + return fd; + +fail_close_fd: + close(fd); + fd = -1; + goto out_free; +} + +static int send_fd(int sock_fd, int fd) +{ + int retval; + struct msghdr msg; + struct cmsghdr *p_cmsg; + struct iovec vec; + size_t cmsgbuf[CMSG_SPACE(sizeof(fd)) / sizeof(size_t)]; + int *p_fds; + char sendchar = 0; + + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + p_cmsg = CMSG_FIRSTHDR(&msg); + p_cmsg->cmsg_level = SOL_SOCKET; + p_cmsg->cmsg_type = SCM_RIGHTS; + p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); + p_fds = (int *) CMSG_DATA(p_cmsg); + *p_fds = fd; + msg.msg_controllen = p_cmsg->cmsg_len; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + /* "To pass file descriptors or credentials you need to send/read at + * least one byte" (man 7 unix) */ + vec.iov_base = &sendchar; + vec.iov_len = sizeof(sendchar); + while ((retval = sendmsg(sock_fd, &msg, 0)) == -1 && errno == EINTR); + if (retval != 1) { + perror("sending file descriptor"); + return -1; + } + return 0; +} + +/* Helper for should_auto_unmount + * + * fusermount typically has the s-bit set - initial open of `mnt` was as root + * and got EACCESS as 'allow_other' was not specified. + * Try opening `mnt` again with uid and guid of the calling process. + */ +static int recheck_ENOTCONN_as_owner(const char *mnt) +{ + int pid = fork(); + if(pid == -1) { + perror("fuse: recheck_ENOTCONN_as_owner can't fork"); + _exit(EXIT_FAILURE); + } else if(pid == 0) { + uid_t uid = getuid(); + gid_t gid = getgid(); + if(setresgid(gid, gid, gid) == -1) { + perror("fuse: can't set resgid"); + _exit(EXIT_FAILURE); + } + if(setresuid(uid, uid, uid) == -1) { + perror("fuse: can't set resuid"); + _exit(EXIT_FAILURE); + } + + int fd = open(mnt, O_RDONLY); + if(fd == -1 && errno == ENOTCONN) + _exit(EXIT_SUCCESS); + else + _exit(EXIT_FAILURE); + } else { + int status; + int res = waitpid(pid, &status, 0); + if (res == -1) { + perror("fuse: waiting for child failed"); + _exit(EXIT_FAILURE); + } + return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS; + } +} + +/* The parent fuse process has died: decide whether to auto_unmount. + * + * In the normal case (umount or fusermount -u), the filesystem + * has already been unmounted. If we simply unmount again we can + * cause problems with stacked mounts (e.g. autofs). + * + * So we unmount here only in abnormal case where fuse process has + * died without unmount happening. To detect this, we first look in + * the mount table to make sure the mountpoint is still mounted and + * has proper type. If so, we then see if opening the mount dir is + * returning 'Transport endpoint is not connected'. + * + * The order of these is important, because if autofs is in use, + * opening the dir to check for ENOTCONN will cause a new mount + * in the normal case where filesystem has been unmounted cleanly. + */ +static int should_auto_unmount(const char *mnt, const char *type) +{ + char *copy; + const char *last; + int result = 0; + int fd; + + copy = strdup(mnt); + if (copy == NULL) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return 0; + } + + if (chdir_to_parent(copy, &last) == -1) + goto out; + if (check_is_mount(last, mnt, type) == -1) + goto out; + + fd = open(mnt, O_RDONLY); + + if (fd != -1) { + close(fd); + } else { + switch(errno) { + case ENOTCONN: + result = 1; + break; + case EACCES: + result = recheck_ENOTCONN_as_owner(mnt); + break; + default: + result = 0; + break; + } + } +out: + free(copy); + return result; +} + +static void usage(void) +{ + printf("%s: [options] mountpoint\n" + "Options:\n" + " -h print help\n" + " -V print version\n" + " -o opt[,opt...] mount options\n" + " -u unmount\n" + " -q quiet\n" + " -z lazy unmount\n", + progname); + exit(1); +} + +static void show_version(void) +{ + printf("fusermount3 version: %s\n", PACKAGE_VERSION); + exit(0); +} + +static void close_range_loop(int min_fd, int max_fd, int cfd) +{ + for (int fd = min_fd; fd <= max_fd; fd++) + if (fd != cfd) + close(fd); +} + +/* + * Close all inherited fds that are not needed + * Ideally these wouldn't come up at all, applications should better + * use FD_CLOEXEC / O_CLOEXEC + */ +static int close_inherited_fds(int cfd) +{ + int rc = -1; + int nullfd; + + /* We can't even report an error */ + if (cfd <= STDERR_FILENO) + return -EINVAL; + +#ifdef HAVE_CLOSE_RANGE + if (cfd < STDERR_FILENO + 2) { + close_range_loop(STDERR_FILENO + 1, cfd - 1, cfd); + } else { + rc = close_range(STDERR_FILENO + 1, cfd - 1, 0); + if (rc < 0) + goto fallback; + } + + /* Close high range */ + rc = close_range(cfd + 1, ~0U, 0); +#else + goto fallback; /* make use of fallback to avoid compiler warnings */ +#endif + +fallback: + if (rc < 0) { + int max_fd = sysconf(_SC_OPEN_MAX) - 1; + + close_range_loop(STDERR_FILENO + 1, max_fd, cfd); + } + + nullfd = open("/dev/null", O_RDWR); + if (nullfd < 0) { + perror("fusermount: cannot open /dev/null"); + return -errno; + } + + /* Redirect stdin, stdout, stderr to /dev/null */ + dup2(nullfd, STDIN_FILENO); + dup2(nullfd, STDOUT_FILENO); + dup2(nullfd, STDERR_FILENO); + if (nullfd > STDERR_FILENO) + close(nullfd); + + return 0; +} + +int main(int argc, char *argv[]) +{ + sigset_t sigset; + int ch; + int fd; + int res; + char *origmnt; + char *mnt; + static int unmount = 0; + static int lazy = 0; + static int quiet = 0; + char *commfd = NULL; + long cfd; + const char *opts = ""; + const char *type = NULL; + int setup_auto_unmount_only = 0; + + static const struct option long_opts[] = { + {"unmount", no_argument, NULL, 'u'}, + {"lazy", no_argument, NULL, 'z'}, + {"quiet", no_argument, NULL, 'q'}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {"options", required_argument, NULL, 'o'}, + // Note: auto-unmount and comm-fd don't have short versions. + // They'ne meant for internal use by mount.c + {"auto-unmount", no_argument, NULL, 'U'}, + {"comm-fd", required_argument, NULL, 'c'}, + {0, 0, 0, 0}}; + + progname = strdup(argc > 0 ? argv[0] : "fusermount"); + if (progname == NULL) { + fprintf(stderr, "%s: failed to allocate memory\n", argv[0]); + exit(1); + } + + while ((ch = getopt_long(argc, argv, "hVo:uzq", long_opts, + NULL)) != -1) { + switch (ch) { + case 'h': + usage(); + break; + + case 'V': + show_version(); + break; + + case 'o': + opts = optarg; + break; + + case 'u': + unmount = 1; + break; + case 'U': + unmount = 1; + auto_unmount = 1; + setup_auto_unmount_only = 1; + break; + case 'c': + commfd = optarg; + break; + case 'z': + lazy = 1; + break; + + case 'q': + quiet = 1; + break; + + default: + exit(1); + } + } + + if (lazy && !unmount) { + fprintf(stderr, "%s: -z can only be used with -u\n", progname); + exit(1); + } + + if (optind >= argc) { + fprintf(stderr, "%s: missing mountpoint argument\n", progname); + exit(1); + } else if (argc > optind + 1) { + fprintf(stderr, "%s: extra arguments after the mountpoint\n", + progname); + exit(1); + } + + origmnt = argv[optind]; + + drop_privs(); + mnt = fuse_mnt_resolve_path(progname, origmnt); + if (mnt != NULL) { + res = chdir("/"); + if (res == -1) { + fprintf(stderr, "%s: failed to chdir to '/'\n", progname); + goto err_out; + } + } + restore_privs(); + if (mnt == NULL) + exit(1); + + umask(033); + if (!setup_auto_unmount_only && unmount) + goto do_unmount; + + if(commfd == NULL) + commfd = getenv(FUSE_COMMFD_ENV); + if (commfd == NULL) { + fprintf(stderr, "%s: old style mounting not supported\n", + progname); + goto err_out; + } + + res = libfuse_strtol(commfd, &cfd); + if (res) { + fprintf(stderr, + "%s: invalid _FUSE_COMMFD: %s\n", + progname, commfd); + goto err_out; + + } + + { + struct stat statbuf; + fstat(cfd, &statbuf); + if(!S_ISSOCK(statbuf.st_mode)) { + fprintf(stderr, + "%s: file descriptor %li is not a socket, can't send fuse fd\n", + progname, cfd); + goto err_out; + } + } + + if (setup_auto_unmount_only) + goto wait_for_auto_unmount; + + fd = mount_fuse(mnt, opts, &type); + if (fd == -1) + goto err_out; + + res = send_fd(cfd, fd); + if (res != 0) { + umount2(mnt, MNT_DETACH); /* lazy umount */ + goto err_out; + } + close(fd); + + if (!auto_unmount) { + free(mnt); + free((void*) type); + return 0; + } + +wait_for_auto_unmount: + /* Become a daemon and wait for the parent to exit or die. + ie For the control socket to get closed. + Btw, we don't want to use daemon() function here because + it forks and messes with the file descriptors. */ + + res = close_inherited_fds(cfd); + if (res < 0) + exit(EXIT_FAILURE); + + setsid(); + res = chdir("/"); + if (res == -1) { + fprintf(stderr, "%s: failed to chdir to '/'\n", progname); + goto err_out; + } + + sigfillset(&sigset); + sigprocmask(SIG_BLOCK, &sigset, NULL); + + lazy = 1; + quiet = 1; + + while (1) { + unsigned char buf[16]; + int n = recv(cfd, buf, sizeof(buf), 0); + if (!n) + break; + + if (n < 0) { + if (errno == EINTR) + continue; + break; + } + } + + if (!should_auto_unmount(mnt, type)) { + goto success_out; + } + +do_unmount: + if (geteuid() == 0) + res = unmount_fuse(mnt, quiet, lazy); + else { + res = umount2(mnt, lazy ? UMOUNT_DETACH : 0); + if (res == -1 && !quiet) + fprintf(stderr, + "%s: failed to unmount %s: %s\n", + progname, mnt, strerror(errno)); + } + if (res == -1) + goto err_out; + +success_out: + free((void*) type); + free(mnt); + return 0; + +err_out: + free((void*) type); + free(mnt); + exit(1); +} diff --git a/util/init_script b/util/init_script new file mode 100755 index 0000000..a4b8e7b --- /dev/null +++ b/util/init_script @@ -0,0 +1,92 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: fuse +# Required-Start: +# Should-Start: udev +# Required-Stop: +# Default-Start: S +# Default-Stop: +# Short-Description: Start and stop fuse. +# Description: Load the fuse module and mount the fuse control +# filesystem. +### END INIT INFO + +set -e + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +MOUNTPOINT=/sys/fs/fuse/connections + +# Gracefully exit if the package has been removed. +which fusermount3 &>/dev/null || exit 5 + +# Define LSB log_* functions. +. /lib/lsb/init-functions + +case "$1" in + start|restart|force-reload) + if ! grep -qw fuse /proc/filesystems; then + echo -n "Loading fuse module" + if ! modprobe fuse >/dev/null 2>&1; then + echo " failed!" + exit 1 + else + echo "." + fi + else + echo "Fuse filesystem already available." + fi + if grep -qw fusectl /proc/filesystems && \ + ! grep -qw $MOUNTPOINT /proc/mounts; then + echo -n "Mounting fuse control filesystem" + if ! mount -t fusectl fusectl $MOUNTPOINT >/dev/null 2>&1; then + echo " failed!" + exit 1 + else + echo "." + fi + else + echo "Fuse control filesystem already available." + fi + ;; + stop) + if ! grep -qw fuse /proc/filesystems; then + echo "Fuse filesystem not loaded." + exit 7 + fi + if grep -qw $MOUNTPOINT /proc/mounts; then + echo -n "Unmounting fuse control filesystem" + if ! umount $MOUNTPOINT >/dev/null 2>&1; then + echo " failed!" + else + echo "." + fi + else + echo "Fuse control filesystem not mounted." + fi + if grep -qw "^fuse" /proc/modules; then + echo -n "Unloading fuse module" + if ! rmmod fuse >/dev/null 2>&1; then + echo " failed!" + else + echo "." + fi + else + echo "Fuse module not loaded." + fi + ;; + status) + echo -n "Checking fuse filesystem" + if ! grep -qw fuse /proc/filesystems; then + echo " not available." + exit 3 + else + echo " ok." + fi + ;; + *) + echo "Usage: $0 {start|stop|restart|force-reload|status}" + exit 1 + ;; +esac + +exit 0 diff --git a/util/install_helper.sh b/util/install_helper.sh new file mode 100755 index 0000000..76f2b47 --- /dev/null +++ b/util/install_helper.sh @@ -0,0 +1,55 @@ +#!/bin/sh +# +# Don't call this script. It is used internally by the Meson +# build system. Thank you for your cooperation. +# + +set -e + +sysconfdir="$1" +bindir="$2" +udevrulesdir="$3" +useroot="$4" +initscriptdir="$5" + +# Both sysconfdir and bindir are absolute paths (since they are joined +# with --prefix in meson.build), but need to be interpreted relative +# to DESTDIR (if specified). + +if [ -z "${DESTDIR}" ]; then + # Prevent warnings about uninitialized variable + DESTDIR="" +else + # Get rid of duplicate slash + DESTDIR="${DESTDIR%/}" +fi + +install -D -m 644 "${MESON_SOURCE_ROOT}/util/fuse.conf" \ + "${DESTDIR}${sysconfdir}/fuse.conf" + +if $useroot; then + chown root:root "${DESTDIR}${bindir}/fusermount3" + chmod u+s "${DESTDIR}${bindir}/fusermount3" + + if test ! -e "${DESTDIR}/dev/fuse"; then + mkdir -p "${DESTDIR}/dev" + mknod "${DESTDIR}/dev/fuse" -m 0666 c 10 229 + fi +fi + +if [ "${udevrulesdir}" != "" ]; then + install -D -m 644 "${MESON_SOURCE_ROOT}/util/udev.rules" \ + "${DESTDIR}${udevrulesdir}/99-fuse3.rules" +fi + +if [ "$initscriptdir" != "" ]; then + install -D -m 755 "${MESON_SOURCE_ROOT}/util/init_script" \ + "${DESTDIR}${initscriptdir}/fuse3" + + if test -x /usr/sbin/update-rc.d && test -z "${DESTDIR}"; then + /usr/sbin/update-rc.d fuse3 start 34 S . start 41 0 6 . || /bin/true + else + echo "== FURTHER ACTION REQUIRED ==" + echo "Make sure that your init system will start the ${DESTDIR}${initscriptdir}/init.d/fuse3 init script" + fi +fi diff --git a/util/meson.build b/util/meson.build new file mode 100644 index 0000000..0e4b1cc --- /dev/null +++ b/util/meson.build @@ -0,0 +1,34 @@ +fuseconf_path = join_paths(get_option('prefix'), get_option('sysconfdir'), 'fuse.conf') + +executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/util.c'], + include_directories: include_dirs, + install: true, + install_dir: get_option('bindir'), + c_args: '-DFUSE_CONF="@0@"'.format(fuseconf_path)) + +executable('mount.fuse3', ['mount.fuse.c'], + include_directories: include_dirs, + link_with: [ libfuse ], + install: true, + install_dir: get_option('sbindir'), + c_args: '-DFUSE_USE_VERSION=317') + + +udevrulesdir = get_option('udevrulesdir') +if udevrulesdir == '' + udev = dependency('udev', required: false) + if udev.found() + udevrulesdir = join_paths(udev.get_variable(pkgconfig: 'udevdir'), 'rules.d') + endif +endif + +if udevrulesdir == '' + warning('could not determine udevdir, udev.rules will not be installed') +endif + +meson.add_install_script('install_helper.sh', + join_paths(get_option('prefix'), get_option('sysconfdir')), + join_paths(get_option('prefix'), get_option('bindir')), + udevrulesdir, + '@0@'.format(get_option('useroot')), + get_option('initscriptdir')) diff --git a/util/mount.fuse.c b/util/mount.fuse.c new file mode 100644 index 0000000..f1a90fe --- /dev/null +++ b/util/mount.fuse.c @@ -0,0 +1,454 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +#include "fuse_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef linux +#include +#include +#include +#include +/* for 2.6 kernels */ +#if !defined(SECBIT_KEEP_CAPS) && defined(SECURE_KEEP_CAPS) +#define SECBIT_KEEP_CAPS (issecure_mask(SECURE_KEEP_CAPS)) +#endif +#if !defined(SECBIT_KEEP_CAPS_LOCKED) && defined(SECURE_KEEP_CAPS_LOCKED) +#define SECBIT_KEEP_CAPS_LOCKED (issecure_mask(SECURE_KEEP_CAPS_LOCKED)) +#endif +#if !defined(SECBIT_NO_SETUID_FIXUP) && defined(SECURE_NO_SETUID_FIXUP) +#define SECBIT_NO_SETUID_FIXUP (issecure_mask(SECURE_NO_SETUID_FIXUP)) +#endif +#if !defined(SECBIT_NO_SETUID_FIXUP_LOCKED) && defined(SECURE_NO_SETUID_FIXUP_LOCKED) +#define SECBIT_NO_SETUID_FIXUP_LOCKED (issecure_mask(SECURE_NO_SETUID_FIXUP_LOCKED)) +#endif +#if !defined(SECBIT_NOROOT) && defined(SECURE_NOROOT) +#define SECBIT_NOROOT (issecure_mask(SECURE_NOROOT)) +#endif +#if !defined(SECBIT_NOROOT_LOCKED) && defined(SECURE_NOROOT_LOCKED) +#define SECBIT_NOROOT_LOCKED (issecure_mask(SECURE_NOROOT_LOCKED)) +#endif +#endif +/* linux < 3.5 */ +#ifndef PR_SET_NO_NEW_PRIVS +#define PR_SET_NO_NEW_PRIVS 38 +#endif + +#include "fuse.h" + +static char *progname; + +static char *xstrdup(const char *s) +{ + char *t = strdup(s); + if (!t) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + exit(1); + } + return t; +} + +static void *xrealloc(void *oldptr, size_t size) +{ + void *ptr = realloc(oldptr, size); + if (!ptr) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + exit(1); + } + return ptr; +} + +static void add_arg(char **cmdp, const char *opt) +{ + size_t optlen = strlen(opt); + size_t cmdlen = *cmdp ? strlen(*cmdp) : 0; + if (optlen >= (SIZE_MAX - cmdlen - 4)/4) { + fprintf(stderr, "%s: argument too long\n", progname); + exit(1); + } + char *cmd = xrealloc(*cmdp, cmdlen + optlen * 4 + 4); + char *s; + s = cmd + cmdlen; + if (*cmdp) + *s++ = ' '; + + *s++ = '\''; + for (; *opt; opt++) { + if (*opt == '\'') { + *s++ = '\''; + *s++ = '\\'; + *s++ = '\''; + *s++ = '\''; + } else + *s++ = *opt; + } + *s++ = '\''; + *s = '\0'; + *cmdp = cmd; +} + +static char *add_option(const char *opt, char *options) +{ + int oldlen = options ? strlen(options) : 0; + + options = xrealloc(options, oldlen + 1 + strlen(opt) + 1); + if (!oldlen) + strcpy(options, opt); + else { + strcat(options, ","); + strcat(options, opt); + } + return options; +} + +static int prepare_fuse_fd(const char *mountpoint, const char* subtype, + const char *options) +{ + int fuse_fd = -1; + int flags = -1; + int subtype_len = strlen(subtype) + 9; + char* options_copy = xrealloc(NULL, subtype_len); + + snprintf(options_copy, subtype_len, "subtype=%s", subtype); + options_copy = add_option(options, options_copy); + fuse_fd = fuse_open_channel(mountpoint, options_copy); + if (fuse_fd == -1) { + exit(1); + } + + flags = fcntl(fuse_fd, F_GETFD); + if (flags == -1 || fcntl(fuse_fd, F_SETFD, flags & ~FD_CLOEXEC) == 1) { + fprintf(stderr, "%s: Failed to clear CLOEXEC: %s\n", + progname, strerror(errno)); + exit(1); + } + + return fuse_fd; +} + +#ifdef linux +static uint64_t get_capabilities(void) +{ + /* + * This invokes the capset syscall directly to avoid the libcap + * dependency, which isn't really justified just for this. + */ + struct __user_cap_header_struct header = { + .version = _LINUX_CAPABILITY_VERSION_3, + .pid = 0, + }; + struct __user_cap_data_struct data[2]; + memset(data, 0, sizeof(data)); + if (syscall(SYS_capget, &header, data) == -1) { + fprintf(stderr, "%s: Failed to get capabilities: %s\n", + progname, strerror(errno)); + exit(1); + } + + return data[0].effective | ((uint64_t) data[1].effective << 32); +} + +static void set_capabilities(uint64_t caps) +{ + /* + * This invokes the capset syscall directly to avoid the libcap + * dependency, which isn't really justified just for this. + */ + struct __user_cap_header_struct header = { + .version = _LINUX_CAPABILITY_VERSION_3, + .pid = 0, + }; + struct __user_cap_data_struct data[2]; + memset(data, 0, sizeof(data)); + data[0].effective = data[0].permitted = caps; + data[1].effective = data[1].permitted = caps >> 32; + if (syscall(SYS_capset, &header, data) == -1) { + fprintf(stderr, "%s: Failed to set capabilities: %s\n", + progname, strerror(errno)); + exit(1); + } +} + +static void drop_and_lock_capabilities(void) +{ + /* Set and lock securebits. */ + if (prctl(PR_SET_SECUREBITS, + SECBIT_KEEP_CAPS_LOCKED | + SECBIT_NO_SETUID_FIXUP | + SECBIT_NO_SETUID_FIXUP_LOCKED | + SECBIT_NOROOT | + SECBIT_NOROOT_LOCKED) == -1) { + fprintf(stderr, "%s: Failed to set securebits %s\n", + progname, strerror(errno)); + exit(1); + } + + /* Clear the capability bounding set. */ + int cap; + for (cap = 0; ; cap++) { + int cap_status = prctl(PR_CAPBSET_READ, cap); + if (cap_status == 0) { + continue; + } + if (cap_status == -1 && errno == EINVAL) { + break; + } + + if (cap_status != 1) { + fprintf(stderr, + "%s: Failed to get capability %u: %s\n", + progname, cap, strerror(errno)); + exit(1); + } + if (prctl(PR_CAPBSET_DROP, cap) == -1) { + fprintf(stderr, + "%s: Failed to drop capability %u: %s\n", + progname, cap, strerror(errno)); + } + } + + /* Drop capabilities. */ + set_capabilities(0); + + /* Prevent re-acquisition of privileges. */ + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) { + fprintf(stderr, "%s: Failed to set no_new_privs: %s\n", + progname, strerror(errno)); + exit(1); + } +} +#endif + +int main(int argc, char *argv[]) +{ + char *type = NULL; + char *source; + char *dup_source = NULL; + const char *mountpoint; + char *basename; + char *options = NULL; + char *command = NULL; + char *setuid_name = NULL; + int i; + int dev = 1; + int suid = 1; + int pass_fuse_fd = 0; + int fuse_fd = 0; + int drop_privileges = 0; + char *dev_fd_mountpoint = NULL; + + progname = argv[0]; + basename = strrchr(argv[0], '/'); + if (basename) + basename++; + else + basename = argv[0]; + + if (strncmp(basename, "mount.fuse.", 11) == 0) + type = basename + 11; + if (strncmp(basename, "mount.fuseblk.", 14) == 0) + type = basename + 14; + + if (type && !type[0]) + type = NULL; + + if (argc < 3) { + fprintf(stderr, + "usage: %s %s destination [-t type] [-o opt[,opts...]]\n", + progname, type ? "source" : "type#[source]"); + exit(1); + } + + source = argv[1]; + if (!source[0]) + source = NULL; + + mountpoint = argv[2]; + + for (i = 3; i < argc; i++) { + if (strcmp(argv[i], "-v") == 0) { + continue; + } else if (strcmp(argv[i], "-t") == 0) { + i++; + + if (i == argc) { + fprintf(stderr, + "%s: missing argument to option '-t'\n", + progname); + exit(1); + } + type = argv[i]; + if (strncmp(type, "fuse.", 5) == 0) + type += 5; + else if (strncmp(type, "fuseblk.", 8) == 0) + type += 8; + + if (!type[0]) { + fprintf(stderr, + "%s: empty type given as argument to option '-t'\n", + progname); + exit(1); + } + } else if (strcmp(argv[i], "-o") == 0) { + char *opts; + char *opt; + i++; + if (i == argc) + break; + + opts = xstrdup(argv[i]); + opt = strtok(opts, ","); + while (opt) { + int j; + int ignore = 0; + const char *ignore_opts[] = { "", + "user", + "nofail", + "nouser", + "users", + "auto", + "noauto", + "_netdev", + NULL}; + if (strncmp(opt, "setuid=", 7) == 0) { + setuid_name = xstrdup(opt + 7); + ignore = 1; + } else if (strcmp(opt, + "drop_privileges") == 0) { + pass_fuse_fd = 1; + drop_privileges = 1; + ignore = 1; + } + for (j = 0; ignore_opts[j]; j++) + if (strcmp(opt, ignore_opts[j]) == 0) + ignore = 1; + + if (!ignore) { + if (strcmp(opt, "nodev") == 0) + dev = 0; + else if (strcmp(opt, "nosuid") == 0) + suid = 0; + + options = add_option(opt, options); + } + opt = strtok(NULL, ","); + } + free(opts); + } + } + + if (drop_privileges) { + uint64_t required_caps = CAP_TO_MASK(CAP_SETPCAP) | + CAP_TO_MASK(CAP_SYS_ADMIN); + if ((get_capabilities() & required_caps) != required_caps) { + fprintf(stderr, "%s: drop_privileges was requested, which launches the FUSE file system fully unprivileged. In order to do so %s must be run with privileges, please invoke with CAP_SYS_ADMIN and CAP_SETPCAP (e.g. as root).\n", + progname, progname); + exit(1); + } + } + + if (dev) + options = add_option("dev", options); + if (suid) + options = add_option("suid", options); + + if (!type) { + if (source) { + dup_source = xstrdup(source); + type = dup_source; + source = strchr(type, '#'); + if (source) + *source++ = '\0'; + if (!type[0]) { + fprintf(stderr, "%s: empty filesystem type\n", + progname); + exit(1); + } + } else { + fprintf(stderr, "%s: empty source\n", progname); + exit(1); + } + } + + if (setuid_name && setuid_name[0]) { +#ifdef linux + if (drop_privileges) { + /* + * Make securebits more permissive before calling + * setuid(). Specifically, if SECBIT_KEEP_CAPS and + * SECBIT_NO_SETUID_FIXUP weren't set, setuid() would + * have the side effect of dropping all capabilities, + * and we need to retain CAP_SETPCAP in order to drop + * all privileges before exec(). + */ + if (prctl(PR_SET_SECUREBITS, + SECBIT_KEEP_CAPS | + SECBIT_NO_SETUID_FIXUP) == -1) { + fprintf(stderr, + "%s: Failed to set securebits %s\n", + progname, strerror(errno)); + exit(1); + } + } +#endif + + struct passwd *pwd = getpwnam(setuid_name); + if (!pwd || setgid(pwd->pw_gid) == -1 || setuid(pwd->pw_uid) == -1) { + fprintf(stderr, "%s: Failed to setuid to %s: %s\n", + progname, setuid_name, strerror(errno)); + exit(1); + } + } else if (!getenv("HOME")) { + /* Hack to make filesystems work in the boot environment */ + setenv("HOME", "/root", 0); + } + + if (pass_fuse_fd) { + fuse_fd = prepare_fuse_fd(mountpoint, type, options); + dev_fd_mountpoint = xrealloc(NULL, 20); + snprintf(dev_fd_mountpoint, 20, "/dev/fd/%u", fuse_fd); + mountpoint = dev_fd_mountpoint; + } + +#ifdef linux + if (drop_privileges) { + drop_and_lock_capabilities(); + } +#endif + add_arg(&command, type); + if (source) + add_arg(&command, source); + add_arg(&command, mountpoint); + if (options) { + add_arg(&command, "-o"); + add_arg(&command, options); + } + + free(options); + free(dev_fd_mountpoint); + free(dup_source); + free(setuid_name); + + execl("/bin/sh", "/bin/sh", "-c", command, NULL); + fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname, + strerror(errno)); + + if (pass_fuse_fd) + close(fuse_fd); + free(command); + return 1; +} diff --git a/util/parse-backtrace.sh b/util/parse-backtrace.sh new file mode 100755 index 0000000..3db96f8 --- /dev/null +++ b/util/parse-backtrace.sh @@ -0,0 +1,117 @@ +#!/bin/bash + +tmpfile=`mktemp backtrace.XXX` + +PROGRAM_PATH='' +TRACE_TYPE=glibc + +print_help() +{ + echo "Usage: " + echo " " `basename $0`" [-s] [-p ] -t <\"full trace\">" + echo "Options: " + echo " -t '' - The entire trace should be put into quotes" + echo " for this option" + echo " -p path/to/filename - optional path to the binary, typically" + echo " autodetected" + echo + exit 1 +} + +if [ -z "$1" -o "$1" = "-h" -o "$1" = "--help" ]; then + rm -f $tmpfile + print_help +fi + +while getopts "hf:t:p:" opt; do + case $opt in + h) + print_help + ;; + f) + PROGRAM_PATH="$OPTARG" + ;; + t) + TRACE="$OPTARG" + ;; + p) + PROGRAM_PATH="$OPTARG" + ;; + *) + print_help + ;; + esac +done + + +# use addr2line +parse_glibc_trace() +{ + local trace="$1" + local filename="$2" + local tmpname="" + local symbol="" + + IFS=$'\n' + for line in ${trace}; do + + #echo "Line: '$line" + + # remove C2 A0 (non breaking space, as inserted by windows) + line=$(echo $line | sed 's/\xC2\xA0/ /g') + + line=`echo $line | egrep "\[" | egrep "\]"` + [ -n "$line" ] || continue + + # cut off additional syslog part - beginning of line to ':' + line=$(echo $line line | sed -e 's/.*://') + + # parse lines like + # /usr/lib/libfuse3.so.3(+0x1c0ef) [0x7fca6061c0ef] + + filename=$(echo $line | awk '{print $1}' | sed -e 's/(.*$//') + if [ -z "${filename}" ]; then + echo "Failed to get filename path for line: \"$line\"" + return + fi + + if [[ $filename != /* ]]; then + if [ -n "${PROGRAM_PATH}" ]; then + filename="${PROGRAM_PATH}" + else + tmpname="$(which $filename)" + if [ $? -ne 0 ]; then + echo "Failed to get path for '$filename'" + continue + fi + filename="${tmpname}" + fi + fi + + # for plain glibc backtrace_symbols the symbol is also in column1, + # within the brackets () + symbol=$(echo $line | awk '{print $1}' | sed -e 's/^.*(//' | sed -e 's/).*//') + if [ -z "${symbol}" ]; then + echo "Failed to get symbol for line: \"$line\"" + continue + fi + + addr2line -a -p -s -C -f -i -e ${filename} ${symbol} + done +} + +if [ -z "$TRACE" ]; then + echo "Missing backtrace option!" + echo + print_help +fi + + +# For now only glibc backtrace_symbols traces are supported +if [ $TRACE_TYPE = "glibc" ]; then + parse_glibc_trace "$TRACE" "${PROGRAM_PATH}" +else + echo "Unknown tracetype: '${TRACE_TYPE}'" +fi + +rm -f $tmpfile diff --git a/util/udev.rules b/util/udev.rules new file mode 100644 index 0000000..9585111 --- /dev/null +++ b/util/udev.rules @@ -0,0 +1 @@ +KERNEL=="fuse", MODE="0666" diff --git a/xfstests/README.md b/xfstests/README.md new file mode 100644 index 0000000..deda553 --- /dev/null +++ b/xfstests/README.md @@ -0,0 +1,18 @@ +To test FUSE with xfstests¹: + +1. copy the `mount.fuse.passthrough` file into + `/sbin` and edit the `PASSTHROUGH_PATH`, `SCRATCH_SOURCE` and `TEST_SOURCE` variables as needed. + +2. Make sure that the `SCRATCH_SOURCE` and `TEST_SOURCE` directories +exist. + +3. Copy `local.config` into your xfstests directory + +Tests can then be run with e.g.: + +```sh +# make +# sudo ./check -fuse -b +``` + +¹https://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git/about/ diff --git a/xfstests/local.config b/xfstests/local.config new file mode 100644 index 0000000..c34ebb8 --- /dev/null +++ b/xfstests/local.config @@ -0,0 +1,21 @@ +export TEST_DEV=source:/mnt/src/test +export TEST_DIR=/mnt/test + +export SCRATCH_DEV=source:/mnt/src/scratch +export SCRATCH_MNT=/mnt/scratch + +export FSTYP=fuse +export FUSE_SUBTYP=.passthrough +export MOUNT_OPTIONS="" +export TEST_FS_MOUNT_OPTS="" + +# extra binary options, such as '--direct-io' or '--nopassthrough', etc +export EXTRA_BIN_OPTIONS="" + +# If PASSTHROUGH_PATH is unset, the mount helper is going to look +# for the binary '${FUSE_SUBTYP}', though omitting the leading dot '.'. +# Example: +# with FUSE_SUBTYP=".passthrough", the mount helper is called +# 'mount.fuse.passthrough' and that would try to run +# 'passthrough'. +# export PASSTHROUGH_PATH=/example/passthrough_hp diff --git a/xfstests/mount.fuse.passthrough b/xfstests/mount.fuse.passthrough new file mode 100755 index 0000000..94c45e5 --- /dev/null +++ b/xfstests/mount.fuse.passthrough @@ -0,0 +1,40 @@ +#!/bin/bash + +ulimit -n 1048576 + +dev="$1" +shift +mnt="$1" +shift +# -o +shift +mntopts="$1" +shift + +# source can be provided as NFS style device (e.g. TEST_DEV=source:/${TEST_SOURCE}) +# and/or it can already be inside mount options (passthrough_ll style) +if ( echo "$mntopts" | grep -q "source=" ) ; then + # Don't pass source as position argument + source= +elif [[ "$dev" == "source:"* ]]; then + source="${dev#"source:"}" +else + >&2 echo "passthrough source is undefined, aborting!" +fi + +if ( echo "$mntopts" | grep -q remount ) ; then + exec mount -i "$dev" "$mnt" -o "$mntopts" +fi + +# set default to SUBTYPE (extracted from this script name) +# example: +# Copy or link this script to /sbin/mount.fuse.passthrough_hp +# If xfstests local.config does not set PASSTHROUGH_PATH, +# PASSTHROUGH_PATH will be set to 'passthrough_hp' and exec below +# will look that up from $PATH + +[ -n "$PASSTHROUGH_PATH" ] || PASSTHROUGH_PATH=${0#*mount.fuse.} + +#echo "EXTRA_BIN_OPTIONS='${EXTRA_BIN_OPTIONS}'" + +exec "$PASSTHROUGH_PATH" ${EXTRA_BIN_OPTIONS} -o fsname=$dev,allow_other,dev $source "$mnt" -o "$mntopts" "$@" -- 2.30.2

    m6Gd17E*N9mBF&{{=qy}-B` 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�?i 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 z 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